十種基本排序算法


一、

//直接插入排序
//思路:先取一個有序的隊列,然后將其他數字一個一個和這個有序數列排序
//穩定
//時間復雜度  最好情況:O(n) 最壞情況O(n²)
//空間復雜度 O(n)

/**
* 直接插入排序
* @author TMAC-J
*
*/
public class InsertSort {

private int[] array;

public InsertSort(int[] array) {
this.array = array;
}
/**
* 按從小到大的順序排列
*/
public void calculate(){
for(int i = 1;i<array.length;i++){
for(int j = 0;j<i;j++){
if(array[i]<array[j]){
int temp = array[i];
//向后移動
for(int k = i-1;k>=j;k--){
array[k+1] = array[k];
}
array[j] = temp;
}
}
}
}

public static void main(String[] args) {
int[] a = {10,9,8,7,6,5,4,3,2,1};
InsertSort insertSort = new InsertSort(a);
insertSort.calculate();
for(int i = 0;i<a.length;i++){
System.out.println(a[i]);
}
}

}

二、插入排序-希爾排序

  1. 選擇一個增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列個數k,對序列進行k 趟排序;
  3. 每趟排序,根據對應的增量ti,將待排序列分割成若干長度為m 的子序列,分別對各子表進行直接插入排序。僅增量因子為1 時,整個序列作為一個表來處理,表長度即為整個序列的長度
    void print(int a[], int n ,int i){  
        cout<<i <<":";  
        for(int j= 0; j<8; j++){  
            cout<<a[j] <<" ";  
        }  
        cout<<endl;  
    }  
    /** 
     * 直接插入排序的一般形式 
     * 
     * @param int dk 縮小增量,如果是直接插入排序,dk=1 
     * 
     */  
      
    void ShellInsertSort(int a[], int n, int dk)  
    {  
        for(int i= dk; i<n; ++i){  
            if(a[i] < a[i-dk]){          //若第i個元素大於i-1元素,直接插入。小於的話,移動有序表后插入  
                int j = i-dk;     
                int x = a[i];           //復制為哨兵,即存儲待排序元素  
                a[i] = a[i-dk];         //首先后移一個元素  
                while(x < a[j]){     //查找在有序表的插入位置  
                    a[j+dk] = a[j];  
                    j -= dk;             //元素后移  
                }  
                a[j+dk] = x;            //插入到正確位置  
            }  
            print(a, n,i );  
        }  
          
    }  
      
    /** 
     * 先按增量d(n/2,n為要排序數的個數進行希爾排序 
     * 
     */  
    void shellSort(int a[], int n){  
      
        int dk = n/2;  
        while( dk >= 1  ){  
            ShellInsertSort(a, n, dk);  
            dk = dk/2;  
        }  
    }  
    int main(){  
        int a[8] = {3,1,5,7,2,4,9,6};  
        //ShellInsertSort(a,8,1); //直接插入排序  
        shellSort(a,8);           //希爾插入排序  
        print(a,8,8);  
    }  

     三、冒泡排序

  4. //冒泡排序
    //思路:兩兩互相排序,把大的放后面,每一輪最后面的都是最大的,就好像冒泡一下
    //時間復雜度  O(n²)
    //空間復雜度 O(1)
    public class BubbleSort {
        static int[] arr = { 5, 3, 2, 4, 7 };
    
        public static void main(String[] args) {
            BubbleSort bs = new BubbleSort();
            bs.betterSort1();
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }
        }
    
        public void sort() {
            int temp = 0;
            for (int i = 0; i < arr.length - 1; i++) {
                for (int j = 1; j < arr.length; j++) {
                    if (arr[j - 1] > arr[j]) {
                        temp = arr[j - 1];
                        arr[j - 1] = arr[j];
                        arr[j] = temp;
                    }
                }
            }
        }
    //改良版冒泡排序
        public void betterSort1() {
            int pos = arr.length;
            while (pos > 0) {
                int temp = 0;
                for (int j = 1; j < pos; j++) {
                    if (arr[j - 1] > arr[j]) {
                        temp = arr[j - 1];
                        arr[j - 1] = arr[j];
                        arr[j] = temp;
                    }
                }
                pos--;
            }
        }
        //兩頭冒泡法
        public void Bubble_2 ( int r[], int n){  
            int low = 0;   
            int high= n -1; //設置變量的初始值  
            int tmp,j;  
            while (low < high) {  
                for (j= low; j< high; ++j) //正向冒泡,找到最大者  
                    if (r[j]> r[j+1]) {  
                        tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;  
                    }   
                --high;                 //修改high值, 前移一位  
                for ( j=high; j>low; --j) //反向冒泡,找到最小者  
                    if (r[j]<r[j-1]) {  
                        tmp = r[j]; r[j]=r[j-1];r[j-1]=tmp;  
                    }  
                ++low;                  //修改low值,后移一位  
            }   
        }
    }

    四、簡單選擇排序

//簡單選擇排序
//思路:在要排序的一組數中,選出最小(或者最大)的一個數與第1個位置的數交換;然后在剩下的數當中再找最小(或者最大)的與第2個位置的數交換,依次類推,直到第n-1個元素(倒數第二個數)和第n個元素(最后一個數)比較為止。
//O(n^2)
//空間復雜度 O(1)
public class SelectSort {
    static int[] arr={3,1,5,7,2,10,9};
    public static void main(String[] args){
        SelectSort ss=new SelectSort();
        ss.sort();
        for(int i=0;i<arr.length;i++){
            System.out.println(arr[i]);
        }
        
    }
    public void sort(){
        for(int i=0;i<arr.length;i++){
            int temp=0;
            for(int j=i+1;j<arr.length;j++){
                if(arr[i]>arr[j]){
                    temp=arr[i];
                    arr[i]=arr[j];
                    arr[j]=temp;
                }
            }
        }
    }
}

五、歸並算法

import java.util.Arrays;

public class MergeSort {  
    /** 
     * 歸並排序 
     * 簡介:將兩個(或兩個以上)有序表合並成一個新的有序表 即把待排序序列分為若干個子序列,每個子序列是有序的。然后再把有序子序列合並為整體有序序列 
     * 時間復雜度為O(nlgn) 
     * 穩定排序方式 
     * @param nums 待排序數組 
     * @return 輸出有序數組 
     */  
    public static int[] sort(int[] nums, int low, int high) {  
        int mid = (low + high) / 2;  
        if (low < high) {  
            // 左邊  
            sort(nums, low, mid);  
            // 右邊  
            sort(nums, mid + 1, high);  
            // 左右歸並  
            merge(nums, low, mid, high);  
        }  
        return nums;  
    }  
  
    public static void merge(int[] nums, int low, int mid, int high) {  
        int[] temp = new int[high - low + 1];  
        int i = low;// 左指針  
        int j = mid + 1;// 右指針  
        int k = 0;  
  
        // 把較小的數先移到新數組中  
        while (i <= mid && j <= high) {  
            if (nums[i] < nums[j]) {  
                temp[k++] = nums[i++];  
            } else {  
                temp[k++] = nums[j++];  
            }  
        }  
  
        // 把左邊剩余的數移入數組  
        while (i <= mid) {  
            temp[k++] = nums[i++];  
        }  
  
        // 把右邊邊剩余的數移入數組  
        while (j <= high) {  
            temp[k++] = nums[j++];  
        }  
  
        // 把新數組中的數覆蓋nums數組  
        for (int k2 = 0; k2 < temp.length; k2++) {  
            nums[k2 + low] = temp[k2];  
        }  
    }  
  
      
    // 歸並排序的實現  
    public static void main(String[] args) {  
  
        int[] nums = { 2, 7, 8, 3, 1, 6, 9, 0, 5, 4 };  
  
        MergeSort.sort(nums, 0, nums.length-1);  
        System.out.println(Arrays.toString(nums));  
    }  
}  

 六、快速排序

 
         
//快速排序
//思路:一般選取第一個數作為基數,然后從后往前比較,如果比基數小,就和基數交換,在從左往右比較,如果比基數大,就交換,直到i=j為止,此時,把這個數組按基數為分界,分成兩組,在進行上述計算
//空間復雜度 O(1)
//時間復雜度O(n^2)

public
class Quick { /**主方法*/ public static void main(String[] args) { //聲明數組 int[] nums = {27, 8, 100, 9, 23, 41, 65, 19,3,6, 0, 1, 2, 4, 5}; //應用快速排序方法 quickSort(nums, 0, nums.length-1); //顯示排序后的數組 for(int i = 0; i < nums.length; ++i) { System.out.print(nums[i] + ","); } System.out.println(""); } /**快速排序方法*/ public static void quickSort(int[] a, int lo0, int hi0) { int lo = lo0; int hi = hi0; if (lo >= hi) return; //確定指針方向的邏輯變量 boolean transfer=true; while (lo != hi) { if (a[lo] > a[hi]) { //交換數字 int temp = a[lo]; a[lo] = a[hi]; a[hi] = temp; //決定下標移動,還是上標移動 transfer = (transfer == true) ? false : true; } //將指針向前或者向后移動 if(transfer) hi--; else lo++; //顯示每一次指針移動的數組數字的變化 /*for(int i = 0; i < a.length; ++i) { System.out.print(a[i] + ","); } System.out.print(" (lo,hi) = " + "(" + lo + "," + hi + ")"); System.out.println("");*/ } //將數組分開兩半,確定每個數字的正確位置 lo--; hi++; quickSort(a, lo0, lo); quickSort(a, hi, hi0); } }

 七、堆排序

//堆排序
//思路:初始時把要排序的n個數的序列看作是一棵順序存儲的二叉樹(一維數組存儲二叉樹),調整它們的存儲序,使之成為一個堆,將堆頂元素輸出,得到n 個元素中最小(或最大)的元素,這時堆的根節點的數最小(或者最大)。然后對前面(n-1)個元素重新調整使之成為堆,輸出堆頂元素,得到n 個元素中次小(或次大)的元素。依此類推,直到只有兩個節點的堆,並對它們作交換,最后得到有n個節點的有序序列。稱這個過程為堆排序。
//時間復雜度  O(nlgn )
//空間復雜度 O(1)
  
public class HeapSort {  
  
    private static int heapSize;  
  
    public static void main(String[] args) { 
        int[] a={1,4,2,7,9,3};
        heapSort(a);  
        for (int i : a)  
            System.out.print(i + " ");  
    }  
  
    private static void heapSort(int[] a) {  
        heapSize = a.length;  
        buildMaxHeap(a);  
        for (int i = a.length - 1; i >= 1; i--) {  
            swap(a, i, 0);  
            heapSize = heapSize - 1;  
            maxHeapify(a, 0);  
        }  
    }  
  
    private static void swap(int[] a, int i, int j) {  
        int temp = a[i];  
        a[i] = a[j];  
        a[j] = temp;  
    }  
  
    private static void buildMaxHeap(int[] a) {  
        for (int i = a.length / 2; i >= 0; i--) {  
            maxHeapify(a, i);  
        }  
    }  
  
    private static void maxHeapify(int[] a, int i) {  
        int l = left(i);  
        int r = right(i);  
        int largest = i;  
        if (l < heapSize && a[l] > a[i])  
            largest = l;  
        else  
            largest = i;  
        if (r < heapSize && a[r] > a[largest])  
            largest = r;  
        if (largest != i) {  
            swap(a, i, largest);  
            maxHeapify(a, largest);  
        }  
    }  
  
    private static int left(int i) {  
        return 2 * i;  
    }  
  
    private static int right(int i) {  
        return 2 * i + 1;  
    }  
  
}  

八、計數排序

計數排序的基本思想是對於給定的輸入序列中的每一個元素x,確定該序列中值小於x的元素的個數(此處並非比較各元素的大小,而是通過對元素值的計數和計數值的累加來確定)。一旦有了這個信息,就可以將x直接存放到最終的輸出序列的正確位置上。例如,如果輸入序列中只有17個元素的值小於x的值,則x可以直接存放在輸出序列的第18個位置上。當然,如果有多個元素具有相同的值時,我們不能將這些元素放在輸出序列的同一個位置上,因此,上述方案還要作適當的修改。犧牲空間換取時間的方法,在某個整數范圍內,快於任何算法

public class CountSort {  
  
        public static void main(String[] args) throws Exception {  
            int[] array = { 9, 8, 7, 6, 5, 4, 3, 2, 6, 1, 0 };  
  
  
            countSort(array, 9);  
            for(int i=0;i<array.length;i++){
                System.out.println(array[i]);
            }
        }  
  
        public static void countSort(int[] array, int range) throws Exception {  
            if (range <= 0) {  
                throw new Exception("range can't be negative or zero.");  
            }  
  
            if (array.length <= 1) {  
                return;  
            }  
  
            int[] countArray = new int[range + 1];  
            for (int i = 0; i < array.length; i++) {  
                int value = array[i];  
                if (value < 0 || value > range) {  
                    throw new Exception("array element overflow range.");  
                }  
                countArray[value] += 1;  
            }  
  
            for (int i = 1; i < countArray.length; i++) {  
                countArray[i] += countArray[i - 1];  
            }  
  
            int[] temp = new int[array.length];  
            for (int i = array.length - 1; i >= 0; i--) {  
                int value = array[i];  
                int position = countArray[value] - 1;  
  
                temp[position] = value;  
                countArray[value] -= 1;  
            }  
  
            for (int i = 0; i < array.length; i++) {  
                array[i] = temp[i];  
            }  
        }  
    }  

九、桶排序

  

//桶排序
//思路:是將陣列分到有限數量的桶子里。每個桶子再個別排序(有可能再使用別的排序算法或是以遞回方式繼續使用桶排序進行排序)。桶排序是鴿巢排序的一種歸納結果。當要被排序的陣列內的數值是均勻分配的時候,桶排序使用線性時間(Θ(n))。但桶排序並不是 比較排序,他不受到 O(n log n) 下限的影響。
//         簡單來說,就是把數據分組,放在一個個的桶中,然后對每個桶里面的在進行排序。  
// 例如要對大小為[1..1000]范圍內的n個整數A[1..n]排序  
//
// 首先,可以把桶設為大小為10的范圍,具體而言,設集合B[1]存儲[1..10]的整數,集合B[2]存儲   (10..20]的整數,……集合B[i]存儲(   (i-1)*10,   i*10]的整數,i   =   1,2,..100。總共有  100個桶。  
//
//  然后,對A[1..n]從頭到尾掃描一遍,把每個A[i]放入對應的桶B[j]中。  再對這100個桶中每個桶里的數字排序,這時可用冒泡,選擇,乃至快排,一般來說任  何排序法都可以。
//
//  最后,依次輸出每個桶里面的數字,且每個桶中的數字從小到大輸出,這  樣就得到所有數字排好序的一個序列了。  
//當均勻分布時,空間復雜度接近為0(n)
//空間復雜度O(N+M)
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;

public class BucketSort {

    public static void bucketSort(double array[]) {
        int length = array.length;
        ArrayList arrList[] = new ArrayList[length];
        /*
         *  每個桶是一個list,存放落在此桶上的元素
         *  上次的基數排序我采用的是計數排序實現的,其實也可以用下面的方法,有興趣的讀者不妨一試(我認為太復雜)
         *  不過效率估計不高(采用了動態數組)
         */
        //划分桶並填元素
        for (int i = 0; i < length; i++) {
            //0.7到0.79放在第8個桶里,編號7;第一個桶放0到0.09
            int temp = (int) Math.floor(10 * array[i]);
            if (null == arrList[temp])
                arrList[temp] = new ArrayList();
            arrList[temp].add(array[i]);
        }
        // 對每個桶中的數進行插入排序
        for (int i = 0; i < length; i++) {
            if (null != arrList[i]) {
                //此處排序方法不定,不過越快越好,除了三大線性排序外,都沒有Collections
                //和Arrays里的sort好,因為這是調優后的快拍
                //Arrays里也有,在基數排序里用過copyOf和fill方法
                Collections.sort(arrList[i]);
            }
                
        }
        //輸出類似鴿巢排序
        int count = 0;
        for (int i = 0; i < length; i++) {
            if (null != arrList[i]) {
                Iterator iter = arrList[i].iterator();
                while (iter.hasNext()) {
                    Double d = (Double) iter.next();
                    array[count] = d;
                    count++;
                }
            }
        }
    }

    /*
     * 每個元素滿足0<=array[i]<1,貌似還要長度相同,
     * 若是相同小數位(digit),則可以把小數搞為整數,最后再除以10^digit
     *  可以Random.nextInt(101)/100
     */
    public static void main(String[] args) {
        double array[] = { 0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21, 0.12,
                0.23, 0.68 };
        bucketSort(array);
        for (int i = 0; i < array.length; i++)
            System.out.print(array[i] + " ");
        System.out.println();
    }
}

十、基數排序

基數排序(radix sort)屬於“分配式排序”(distribution sort),又稱“桶子法”(bucket sort)或bin sort,顧名思義,它是透過鍵值的部份資訊,將要排序的元素分配至某些“桶”中,藉以達到排序的作用,基數排序法是屬於穩定性的排序,其時間復雜度為O (nlog(r)m),其中r為所采取的基數,而m為堆數,在某些時候,基數排序法的效率高於其它的穩定性排序法。

第一步

以LSD為例,假設原來有一串數值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根據個位數的數值,在走訪數值時將它們分配至編號0到9的桶子中:
0
1 81
2 22
3 73 93 43
4 14
5 55 65
6
7
8 28
9 39

第二步

接下來將這些桶子中的數值重新串接起來,成為以下的數列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再進行一次分配,這次是根據十位數來分配:
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93

第三步

接下來將這些桶子中的數值重新串接起來,成為以下的數列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
這時候整個數列已經排序完畢;如果排序的對象有三位數以上,則持續進行以上的動作直至最高位數為止。
LSD的基數排序適用於位數小的數列,如果位數多的話,使用MSD的效率會比較好。MSD的方式與LSD相反,是由高位數為基底開始進行分配,但在分配之后並不馬上合並回一個 數組中,而是在每個“桶子”中建立“子桶”,將每個桶子中的數值按照下一數位的值分配到“子桶”中。在進行完最低位數的分配后再合並回單一的 數組中。
import java.util.Arrays;  
  
public class RadixSort {  
  
    public static void main(String[] args) {  
        int[] data = new int[] { 1100, 192, 221, 12, 23 };  
        print(data);  
        radixSort(data, 10, 4);  
        System.out.println("排序后的數組:");  
        print(data);  
    }  
  
    public static void radixSort(int[] data, int radix, int d) {  
        // 緩存數組  
        int[] tmp = new int[data.length];  
        // buckets用於記錄待排序元素的信息  
        // buckets數組定義了max-min個桶  
        int[] buckets = new int[radix];  
  
        for (int i = 0, rate = 1; i < d; i++) {  
  
            // 重置count數組,開始統計下一個關鍵字  
            Arrays.fill(buckets, 0);  
            // 將data中的元素完全復制到tmp數組中  
            System.arraycopy(data, 0, tmp, 0, data.length);  
  
            // 計算每個待排序數據的子關鍵字  
            for (int j = 0; j < data.length; j++) {  
                int subKey = (tmp[j] / rate) % radix;  
                buckets[subKey]++;  
            }  
  
            for (int j = 1; j < radix; j++) {  
                buckets[j] = buckets[j] + buckets[j - 1];  
            }  
  
            // 按子關鍵字對指定的數據進行排序  
            for (int m = data.length - 1; m >= 0; m--) {  
                int subKey = (tmp[m] / rate) % radix;  
                data[--buckets[subKey]] = tmp[m];  
            }  
            rate *= radix;  
        }  
  
    }  
  
    public static void print(int[] data) {  
        for (int i = 0; i < data.length; i++) {  
            System.out.print(data[i] + "\t");  
        }  
        System.out.println();  
    }  
  
}  

總結

各種排序的穩定性,時間復雜度和空間復雜度總結:

 我們比較時間復雜度函數的情況:

 

                             時間復雜度函數O(n)的增長情況

所以對n較大的排序記錄。一般的選擇都是時間復雜度為O(nlog2n)的排序方法。

 

時間復雜度來說:

(1)平方階(O(n2))排序
  各類簡單排序:直接插入、直接選擇和冒泡排序;
 (2)線性對數階(O(nlog2n))排序
  快速排序、堆排序和歸並排序;
 (3)O(n1+§))排序,§是介於0和1之間的常數。

       希爾排序
(4)線性階(O(n))排序
  基數排序,此外還有桶、箱排序。

說明:

當原表有序或基本有序時,直接插入排序和冒泡排序將大大減少比較次數和移動記錄的次數,時間復雜度可降至O(n);

而快速排序則相反,當原表基本有序時,將蛻化為冒泡排序,時間復雜度提高為O(n2);

原表是否有序,對簡單選擇排序、堆排序、歸並排序和基數排序的時間復雜度影響不大。

 

穩定性:

排序算法的穩定性:若待排序的序列中,存在多個具有相同關鍵字的記錄,經過排序, 這些記錄的相對次序保持不變,則稱該算法是穩定的;若經排序后,記錄的相對 次序發生了改變,則稱該算法是不穩定的。 
     穩定性的好處:排序算法如果是穩定的,那么從一個鍵上排序,然后再從另一個鍵上排序,第一個鍵排序的結果可以為第二個鍵排序所用。基數排序就是這樣,先按低位排序,逐次按高位排序,低位相同的元素其順序再高位也相同時是不會改變的。另外,如果排序算法穩定,可以避免多余的比較;

穩定的排序算法:冒泡排序、插入排序、歸並排序和基數排序

不是穩定的排序算法:選擇排序、快速排序、希爾排序、堆排序

 

選擇排序算法准則:

每種排序算法都各有優缺點。因此,在實用時需根據不同情況適當選用,甚至可以將多種方法結合起來使用。

選擇排序算法的依據

影響排序的因素有很多,平均時間復雜度低的算法並不一定就是最優的。相反,有時平均時間復雜度高的算法可能更適合某些特殊情況。同時,選擇算法時還得考慮它的可讀性,以利於軟件的維護。一般而言,需要考慮的因素有以下四點:

1.待排序的記錄數目n的大小;

2.記錄本身數據量的大小,也就是記錄中除關鍵字外的其他信息量的大小;

3.關鍵字的結構及其分布情況;

4.對排序穩定性的要求。

設待排序元素的個數為n.

1)當n較大,則應采用時間復雜度為O(nlog2n)的排序方法:快速排序、堆排序或歸並排序序。

   快速排序:是目前基於比較的內部排序中被認為是最好的方法,當待排序的關鍵字是隨機分布時,快速排序的平均時間最短;
       堆排序 :  如果內存空間允許且要求穩定性的,

       歸並排序:它有一定數量的數據移動,所以我們可能過與插入排序組合,先獲得一定長度的序列,然后再合並,在效率上將有所提高。

2)  當n較大,內存空間允許,且要求穩定性 =》歸並排序

3)當n較小,可采用直接插入或直接選擇排序。

    直接插入排序:當元素分布有序,直接插入排序將大大減少比較次數和移動記錄的次數。

    直接選擇排序 :元素分布有序,如果不要求穩定性,選擇直接選擇排序

5)一般不使用或不直接使用傳統的冒泡排序。

6)基數排序
它是一種穩定的排序算法,但有一定的局限性:
  1、關鍵字可分解。
  2、記錄的關鍵字位數較少,如果密集更好
  3、如果是數字時,最好是無符號的,否則將增加相應的映射復雜度,可先將其正負分開排序。

 

注:更多資料可參考:http://blog.csdn.net/hguisu/article/details/7776068


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM