經常使用排序算法總結(性能+代碼)

雖是讀書筆記,可是如轉載請註明出處http://segmentfault.com/blog/exploring/
..拒絕伸手複製黨java

想更一進步的支持我,請掃描下方的二維碼,你懂的~git

圖片描述

1. 插入排序

1.1 性能分析

時間複雜度O(n^2), 空間複雜度O(1)
排序時間與輸入有關:輸入的元素個數;元素已排序的程度。
最佳狀況,輸入數組是已經排好序的數組,運行時間是n的線性函數; 最壞狀況,輸入數組是逆序,運行時間是n的二次函數。算法

1.2 核心代碼

javapublic void sort(){
        int temp;
        for(int i = 1; i<arraytoSort.length; i++){
            for(int j = i-1; j>=0; j--){
                if( arraytoSort[j+1] < arraytoSort[j] ){
                    temp = arraytoSort[j+1];
                    arraytoSort[j+1] = arraytoSort[j];
                    arraytoSort[j] = temp;
                }   
            }   
        }
    }

2.選擇排序

2.1 性能分析

時間複雜度O(n^2), 空間複雜度O(1)
排序時間與輸入無關,最佳狀況,最壞狀況都是如此, 不穩定 如 {5,5,2}segmentfault

2.2核心代碼

javapublic void sort(){ 
        for(int i = 0; i<arraytoSort.length-1; i++){
            int min = i;
            int temp;
            //find min
            for(int j = i+1; j<arraytoSort.length ;j++){
                if(arraytoSort[j] <arraytoSort[min]){
                    min = j;
                    }
            }
            //swap the min with the ith element
            temp = arraytoSort[min];
            arraytoSort[min] = arraytoSort[i];
            arraytoSort[i] = temp;
        }
    }

3. 歸併排序

3.1 性能分析

時間複雜度 O(nlogn), 空間複雜度O(n) +O(logn)
排序時間與輸入無關,最佳狀況,最壞狀況都是如此, 穩定。api

原理:數組

能夠將數組分紅二組。依次類推,當分出來的小組只有一個數據時,能夠認爲這個小組組內已經達到了有序,而後再合併相鄰的二個小組就能夠了。這樣經過先遞歸的分解數列,再合併數列就完成了歸併排序數據結構

歸併排序的時間複雜度,合併耗費O(n)時間,而由徹底二叉樹的深度可知,整個歸併排序須要進行log_2n次,所以,總的時間複雜度爲 O(nlogn),並且這是歸併排序算法中最好、最壞、平均的時間性能。函數

因爲歸併排序在歸併過程當中須要與原始記錄序列一樣數量的存儲空間存放歸併結果 以及 遞歸時深度爲 log_2n 的棧空間,所以空間複雜度爲O(n+logn)性能

另外,對代碼進行仔細研究,發現 Merge 函數中有if (L[i]<R[j]) 語句,這就說明它須要兩兩比較,不存在跳躍,所以歸併排序是一種穩定的排序算法。大數據

也就是說,歸併排序是一種比較佔用內存,但卻效率高且穩定的算法。

3.2 核心代碼

javapublic int merge( int p, int q, int r ){
        int count = 0;
        int[] right = assignlist(p,q); 
        //賦值左半部分數組(賦值就是用for循環,還有一個哨兵:最後一個元素設置爲maxvalue)
        int[] left = assignlist(q+1,r); //賦值有半部分數組
        int i = 0;
        int j = 0;

        for(int k = p; k<=r; k++){
            if(right[i] <= left[j]){    
                arraytoSort[k] = right[i];
                i++;    
            }
            else if(right[i] > left[j]){
                arraytoSort[k] = left[j];
                j++;
                count = count + (q - p + 1) -i;
            }
        }
        return count;
    }

  void MergeSort(int arry[],int start,int end)
 {
    if(start<end)
    {
        int mid=(start+end)/2;//數組重點
        MergeSort(arry,start,mid);//遞歸調用,排序前半段arry[start...mid]
        MergeSort(arry,mid+1,end);//遞歸調用,排序後半段arry[mid+1,end]
        MergeArry(arry,start,mid,end);//歸併上述兩段有序數組。
    }
}

3.3 延伸

求逆序對

4冒泡排序

4.1 性能分析

時間複雜度O(n^2), 空間複雜度O(1), 穩定,由於存在兩兩比較,不存在跳躍。
排序時間與輸入無關,最好,最差,平均都是O(n^2)

4.2 核心代碼

javafor(int i=0;i<arraytoSort.length-1;i++){    
            for(int j=arraytoSort.length-1;j>=i+1;j--){
                int temp;
                if(arraytoSort[j]<arraytoSort[j-1])
                {
                    temp = arraytoSort[j];
                    arraytoSort[j] = arraytoSort[j-1];
                    arraytoSort[j-1] = temp;
                }
            }
        }

4.3 延伸

冒泡排序缺陷:

  1. 在排序過程當中,執行完當前的第i趟排序後,可能數據已所有排序完備,可是程序沒法判斷是否完成排序,會繼續執行剩下的(n-1-i)趟排序。解決方法: 設置一個flag位, 若是一趟無元素交換,則 flag = 0; 之後不再進入第二層循環。
  2. 當排序的數據比較多時排序的時間會明顯延長,由於會比較 n*(n-1)/2次。

5. 堆排序

5.1 性能分析

時間複雜度 O(nlogn), 空間複雜度O(1). 從這一點就能夠看出,堆排序在時間上相似歸併,可是它又是一種原地排序,時間複雜度小於歸併的O(n+logn)
排序時間與輸入無關,最好,最差,平均都是O(nlogn). 不穩定

堆排序藉助了堆這個數據結構,堆相似二叉樹,又具備堆積的性質(子節點的關鍵值總小於(大於)父節點)
堆排序包括兩個主要操做:

  1. 保持堆的性質heapify(A,i)
    時間複雜度O(logn)
  2. 建堆 buildmaxheap(A)
    時間複雜度O(n)線性時間建堆

對於大數據的處理: 若是對100億條數據選擇Topk數據,選擇快速排序好仍是堆排序好? 答案是隻能用堆排序。 堆排序只須要維護一個k大小的空間,即在內存開闢k大小的空間。而快速排序須要開闢能存儲100億條數據的空間,which is impossible.

5.2 核心代碼

javapublic class HeapSort {
    public void buildheap(int array[]){
        int length = array.length;
        int heapsize = length;
        int nonleaf = length / 2 - 1;
        for(int i = nonleaf; i>=0;i--){
            heapify(array,i,heapsize);
        }
    }

    public void heapify(int array[], int i,int heapsize){
        int smallest = i;
        int left = 2*i+1;
        int right = 2*i+2;
        if(left<heapsize){
            if(array[i]>array[left]){
                smallest = left;
            }
            else smallest = i;
        }
        if(right<heapsize){
            if(array[smallest]>array[right]){
                smallest = right;
            }
        }
        if(smallest != i){
            int temp;
            temp = array[i];
            array[i] = array[smallest];
            array[smallest] = temp;
            heapify(array,smallest,heapsize);
        }
    }

    public void heapsort(int array[]){
        int heapsize = array.length;
        buildheap(array);

        for(int i=0;i<array.length-1;i++){
            // swap the first and the last
            int temp;
            temp = array[0];
            array[0] = array[heapsize-1];
            array[heapsize-1] = temp;
            // heapify the array
            heapsize = heapsize - 1;
            heapify(array,0,heapsize);

        }   
    }

5.3 延伸

堆這種數據結構的很好的應用是 優先級隊列,如做業調度。

6 快速排序

6.1 性能分析

時間複雜度 O(nlogn) 空間複雜度O(logn) 不穩定 【兩個時間複雜度O(nlogn) 的排序算法都不穩定】

時間複雜度:
最壞O(n^2)劃分不均勻時候 逆序and排好序都是最壞狀況
最好O(n) 當劃分均勻
partition的時間複雜度: O(n)一共須要lognpartition

空間複雜度:遞歸形成的棧空間的使用,最好狀況,遞歸樹的深度logn 空間複雜的logn,最壞狀況,須要進行n‐1 遞歸調用,其空間複雜度爲 O(n),平均狀況,空間複雜度也爲O(log2n)
因爲關鍵字的比較和交換是跳躍進行的,所以,快速排序是一種不穩定的排序方法。

快速排序的每一輪就是將這一輪的基準數歸位,直到全部的數都歸爲爲止,排序結束。(相似冒泡).
partition是返回一個基準值的index, index 左邊都小於該index的數,右邊都大於該index的數。

快速排序之所比較快,由於相比冒泡排序,每次交換是跳躍式的。每次排序的時候設置一個基準點,將小於等於基準點的數所有放到基準點的左邊,將大於等於基準點的數所有放到基準點的右邊。這樣在每次交換的時候就不會像冒泡排序同樣每次只能在相鄰的數之間進行交換,交換的距離就大的多了。所以總的比較和交換次數就少了,速度天然就提升了。固然在最壞的狀況下,仍多是相鄰的兩個數進行了交換。所以快速排序的最差時間複雜度和冒泡排序是同樣的都是 O(n^2),它的平均時間複雜度爲 O(nlogn)。其實快速排序是基於 「二分」 的思想。

6.2 核心代碼

javapublic class Quicksort {
       public int partition(int A[], int begin, int end){
             int i = begin;
             int j = end;
             int q;
             int pivot = begin;
             int pivotnumber = A[pivot];
             while(i!=j){
                   int temp;
                   while(A[j]>pivotnumber && i<j){
                        j--;

                  }
                   while(A[i]<=pivotnumber && i<j)
                  {
                        i++;
                  }
                  temp = A[i];
                  A[i] = A[j];
                  A[j] = temp;
            }

             if(i == j){
                   int temp;
                  temp =A[pivot];
                  A[pivot] = A[i];
                  A[i] = temp;      
            }
             return i;
      }
       public void sort(int A[], int begin,int end){
             if(begin<end){
                   int q;
                  q = partition(A,begin, end);
                  sort(A,begin, q-1);
                  sort(A,q+1,end);
            }     
      }
       public static void main(String[] args) {
             int array[] = {8,7,1,6,5,4,3,2};
            Quicksort s = new Quicksort();
            s.sort(array, 0, 7);
             for(int i=0;i<array.length;i++){
                  System. out.println("output " + array[i]);
            }
      }
}

非比較排序: ,計數排序,基數排序,桶排序,時間複雜度可以達到O(n). 這些排序爲了達到不比較的目的,對數據作了一些基本假設(限制)。如計數排序假設數據都[0,n] 範圍內,且範圍較小;基數排序假設數據都[0,n] 範圍內;也是桶排序假設數據均勻獨立的分佈。

並且,非比較排序的空間要求比較高,用空間換取時間吧。當咱們的待排序數組具有一些基數排序與桶排序要求的特性,且空間上又比較富裕時,桶排序與基數排序不失爲最佳選擇。

7. 計數排序

咱們但願能線性的時間複雜度排序,若是一個一個比較,顯然是不實際的,書上也在決策樹模型中論證了,比較排序的狀況爲nlogn 的複雜度。既然不能一個一個比較,咱們想到一個辦法,就是若是在排序的時候就知道他的位置,那不就是掃描一遍,把他放入他應該的位置不就能夠了。 要知道他的位置,咱們只須要知道有多少不大於他不就能夠了嗎?

7.1 性能分析

最好,最壞,平均的時間複雜度O(n+k), 天了嚕, 線性時間完成排序,且穩定。

優勢:不須要比較函數,利用地址偏移,對範圍固定在[0,k]的整數排序的最佳選擇。是排序字節串最快的排序算法。

缺點:因爲用來計數的數組的長度取決於待排序數組中數據的範圍(等於待排序數組的最大值與最小值的差加上1),這使得計數排序對於數據範圍很大的數組,須要大量時間和內存。

7.2 核心代碼

javapublic int[] countsort(int A[]){
        int[] B = new int[A.length]; //to store result after sorting
        int k = max(A);
        int [] C = new int[k+1]; // to store temp
        for(int i=0;i<A.length;i++){    
            C[A[i]] = C[A[i]] + 1;
        }
        // 小於等於A[i]的數的有多少個, 存入數組C
        for(int i=1;i<C.length;i++){
            C[i] = C[i] + C[i-1];
        }
        //逆序輸出確保穩定-相同元素相對順序不變
        for(int i=A.length-1;i>=0;i--){

            B[C[A[i]]-1] = A[i]; 
            C[A[i]] = C[A[i]]-1;
        }
        return B;
    }

7.3 擴展

請給出一個算法,使之對給定的介於 0k 之間的 n個整數進行預處理,並能在O(1) 時間內回答出輸入的整數中有多少個落在 [a...b] 區間內。你給出的算法的預處理時間爲O(n+k)

分析:就是用計數排序中的預處理方法,得到數組 C[0...k],使得C[i]爲不大於 i的元素的個數。這樣落入 [a...b] 區間內的元素個數有 C[b]-C[a-1]

計數排序的重要性質是他是穩定的。通常而言,僅當衛星數據隨着被排序的元素一塊兒移動時,穩定性才顯得比較重要。而這也是計數排序做爲基數排序的子過程的重要緣由

8 基數排序

爲何要用基數排序 ?

計數排序和桶排序都只是在研究一個關鍵字的排序,如今咱們來討論有多個關鍵字的排序問題。

假設咱們有一些二元組(a,b),要對它們進行以a 爲首要關鍵字,b的次要關鍵字的排序。咱們能夠先把它們先按照首要關鍵字排序,分紅首要關鍵字相同的若干堆。而後,在按照次要關鍵值分別對每一堆進行單獨排序。最後再把這些堆串連到一塊兒,使首要關鍵字較小的一堆排在上面。按這種方式的基數排序稱爲 MSD(Most Significant Dight) 排序

第二種方式是從最低有效關鍵字開始排序,稱爲 LSD(Least Significant Dight)排序 。首先對全部的數據按照次要關鍵字排序,而後對全部的數據按照首要關鍵字排序。要注意的是,使用的排序算法必須是穩定的,不然就會取消前一次排序的結果。因爲不須要分堆對每堆單獨排序,LSD 方法每每比 MSD 簡單而開銷小。下文介紹的方法所有是基於 LSD 的。

一般,基數排序要用到計數排序或者桶排序。使用計數排序時,須要的是Order數組。使用桶排序時,能夠用鏈表的方法直接求出排序後的順序。

這裏寫圖片描述

8.1 性能分析

時間複雜度O(n) (其實是O(d(n+k)) d是位數)

8.2 核心代碼

cRADIX-SORT(A,d)
    for i = 1 to d
        do use a stable sort to sort array A on digit i

8.3擴展

問題:對[0,n^2-1]n 個整數進行線性時間排序。
思路 : 把整數轉換爲n進制再排序,每一個數有兩位,每位的取值範圍是[0..n-1],再進行基數排序

http://blog.csdn.net/mishifangxiangdefeng/article/details/7685839

問題: 給定一個字符串數組,其中不一樣的串包含的字符數可能不一樣,但全部串中總的字符個數爲 n。說明如何在 O(n) 時間內對該數組進行排序

9. 桶排序

桶排序的思想近乎完全的分治思想。

桶排序假設待排序的一組數均勻獨立的分佈在一個範圍中,並將這一範圍劃分紅幾個子範圍(桶)。

而後基於某種映射函數f ,將待排序列的關鍵字 k 映射到第i個桶中 (即桶數組B 的下標i) ,那麼該關鍵字k 就做爲 B[i]中的元素 (每一個桶B[i]都是一組大小爲N/M 的序列 )。

接着將各個桶中的數據有序的合併起來 : 對每一個桶B[i] 中的全部元素進行比較排序 (可使用快排)。而後依次枚舉輸出 B[0]....B[M] 中的所有內容便是一個有序序列。

補充: 映射函數通常是 f = array[i] / k; k^2 = n; n是全部元素個數

桶排序示意圖

9.1 性能分析

平均時間複雜度爲線性的 O(n+C) 最優情形下,桶排序的時間複雜度爲O(n)
桶排序的空間複雜度一般是比較高的,額外開銷爲O(n+m)(由於要維護 M 個數組的引用)。
就是桶越多,時間效率就越高,而桶越多,空間卻就越大,因而可知時間和空間是一個矛盾的兩個方面。

算法穩定性 : 桶排序的穩定性依賴於桶內排序。若是咱們使用了快排,顯然,算法是不穩定的。
一個講bucket排序很是好的文章
桶排序利用函數的映射關係,減小了幾乎全部的比較工做。實際上,桶排序的 f(k) 值的計算,其做用就至關於快排中劃分,已經把大量數據分割成了基本有序的數據塊 (桶)。而後只須要對桶中的少許數據作先進的比較排序便可。

對 N 個關鍵字進行桶排序的時間複雜度分爲兩個部分:
(1) 循環計算每一個關鍵字的桶映射函數,這個時間複雜度是 O(n)
(2) 利用先進的比較排序算法對每一個桶內的全部數據進行排序,其時間複雜度爲 ∑ O(ni*logni) 。其中 ni 爲第 i個桶的數據量。

很顯然,第 (2) 部分是桶排序性能好壞的決定因素。這就是一個時間代價和空間代價的權衡問題了。

9.2 核心代碼

9.3擴展

海量數據應用

in summary

圖片描述

關於穩定性:

  • 選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法,
  • 冒泡排序、插入排序、歸併排序和基數排序是穩定的排序算法。
  • 經常使用時間複雜度的大小關係:O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
相關文章
相關標籤/搜索