閱讀859 返回首頁    go 阿裏雲 go 技術社區[雲棲]


9種排序算法總結

排序算法可以說是計算機專業學生要學習的最基礎的算法,但其實也是最重要的,現在大部分互聯網公司筆試麵試也都會涉及到排序算法的知識。除了了解思想之外,還應該動手寫一寫,分析一些具體思路、時間複雜度、空間複雜度和穩定性等。

我們麵試討論小分隊也簡單討論了一下排序算法,為了加深記憶,我自己也動手寫了一些代碼(Linux平台寫的,自己測試是通過了),並做一些分析(由於水平較水,代碼可能有誤!)。

9種排序算法分別為:選擇排序、冒泡排序、插入排序、希爾排序、歸並排序、堆排序、快速排序、計數排序、基數排序!

1. 選擇排序

基本思想:從第一個位置開始依次選擇該位置的元素,第i次掃描就可以選出第i小的元素,思想很簡單,現在用的較少。

特點:平均時間複雜度O(n^2),最壞時間複雜度O(n^2),額外空間O(1),不穩定排序(舉例:序列5 8 5 2 9, 第一遍選擇第1個元素5會和2交換,原序列中2個5的相對前後順序就被破壞了),n較小時較好!

代碼:

void select_sort(int *a, int n)
{
    for(inti = 1; i <= n; i++) {
        intmin_pos = i;
        for(intj = i+1; j <= n; j++)
            if(a[j] < a[min_pos])
                min_pos = j;
                                                                                                                                                                                                                  
        if(min_pos != i) {
            inttemp = a[i];
            a[i] = a[min_pos];
            a[min_pos] = temp;
        }   
    }   
}

2. 冒泡排序

基本思想:顧名思義,每一趟都通過相鄰元素兩兩比較,通過交換將較小的元素往前移動,一趟下來就可以將最小的元素(氣泡)移動到最前麵。一般會加一個標誌flag,若一趟掃描沒有任何元素交換,則說明序列已經有序,flag為false,直接退出。

特點:平均時間複雜度O(n^2),最壞時間複雜度O(n^2),額外空間O(1),穩定排序(因為比較和交換都是兩相鄰元素,相等時不交換),n較小時較好!

代碼:

void bubble_sort(int *a, int n)
{
    boolflag =false;
    for(inti = 1; i <= n; i++) {
        for(intj = n; j > i; j--)
            if(a[j] < a[j-1]) {
                inttemp = a[j];
                a[j] = a[j-1];
                a[j-1] = temp;
                flag =true;
            }
        if(!flag)
            return;
    }
}

3. 插入排序

基本思想:假定一個已排好序的序列和一個元素,隻需將該元素從序列末尾向前比較,找到第一個小於它的序列元素,排在其之後即可。思想類似於玩撲克牌時整理牌麵。

特點:平均時間複雜度O(n^2),最壞時間複雜度O(n^2),額外空間O(1),穩定排序(比較元素和序列時,找到序列中相等元素的話,排在其之後),序列大部分已排好序時(時間複雜度可提升至O(n))較好!

代碼:

void insert_sort(int *a, int n)
{
    inttemp;
    for(inti =2; i <= n; i++) {
        intj = i - 1;
        temp = a[i];
        while(j >= 1) {
            if(a[j] > temp) {
                a[j+1] = a[j];
                j--;
            }else
                break;
        }
        a[j+1] = temp;
    }
}

4. 希爾排序

基本思想:插入排序的升級版(根據其特點:序列大部分已排好序時效率很高),將數據分為不同的組,先對每一組進行排序,然後對所有元素進行一次排序(即最後步長必須為1),步長的選擇是遞減的,比如5、3、1,現在一般使用D.E.Knuth分組方法(n很大是,用h(n+1)=3h(n)+1來分組,即1、4、13......)。

特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n^s)(1<s<2),額外空間O(1),不穩定排序(相等元素在不同組裏,交換後相對順序可能改變)!

代碼:

void shell_sort(int *a, int n)
{   //我這裏步長為5、3、1,僅為舉例
    for(intgap = 5; gap > 0; gap -= 2)
        for(inti = gap + 1; i <=n; i++) {
            intj = i - gap;
            inttemp = a[i];
            while(j >= 1) {
                if(a[j] > temp) {
                    a[j + gap] = a[j];
                    j -= gap;
                }else
                    break;
            }
            a[j+gap] = temp;
        }
}

5. 歸並排序

基本思想:分治的思想,就是用遞歸先將序列分解成隻剩一個元素的子序列,然後逐漸向上進行合並,每次合並過程就是將兩個內部已排序的子序列進行合並排序,隻需O(n)時間。

特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n*logn),額外空間O(n)(另外需要一個數組),穩定排序,當n較大時較好(當也不能太大,用了遞歸就要考慮棧溢出)!

代碼:

int b[MAX] = {0};
                                                                                                                               
void merge(int *a,intlow, int mid, inthigh)
{
    inti = low, j = mid + 1;//左邊和右邊的初始位置
    intk = i;
    while(i <= mid && j <= high) {
        if(a[i] <= a[j]) {
            b[k++] = a[i];
            i++;
        }else{
            b[k++] = a[j];
            j++;
        }
    }
    while(i <= mid){
        b[k++] = a[i++];
    }
    while(j <= high){
        b[k++] = a[j++];
    }
                                                                                                                               
    for(intx = 1, i = low; x <= high-low+1; x++, i++)
        a[i] = b[i];
}
                                                                                                                               
voidmerge_sort(int*a,int low, int high)
{
    intmid;
    if(low < high) {
        mid = (low + high) / 2;
        merge_sort(a, low, mid);
        merge_sort(a, mid+1, high);
        merge(a, low, mid, high);
    }
}

6. 堆排序

基本思想:利用最大堆的性質——父節點擁有最大值,所以不斷的將堆的根節點與最後節點交換,減小堆長度,然後再恢複堆性質,堆排序主要就是建立最大堆和不斷恢複堆性質兩個過程。堆排序不需要用到遞歸,所以適合海量數據處理,同時堆還可以用於優先級隊列。

特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n*logn),額外空間O(1),不穩定排序(涉及根節點與最後節點的交換,可能會破壞兩相等元素的相對位置!),當n較大時較好(海量數據)!

代碼:

void max_heapify(int *a, int p, int n)
{
    intleft = 2 * p;
    intright = 2 * p + 1;
    intlarge = p; 
    if(left <= n && a[left] > a[p])
        large = left;
    if(right <= n && a[right] > a[large])
        large = right;
                                                                                                                
    if(large != p) {
        inttemp = a[p];
        a[p] = a[large];
        a[large] = temp;
        max_heapify(a, large, n);
    }
}
                                                                                                    
voidheap_sort(int*a,int n)
{
    //build_max_heap
    for(inti = n/2; i > 0; i--)
        max_heapify(a, i, n);
                                                                                                    
    inttemp;
    while(n > 1){
        temp = a[n];
        a[n] = a[1];
        a[1] = temp;
                                                                                                            
        --n;
        max_heapify(a, 1, n);
    }
}

7. 快速排序

基本思想:快排是目前使用最多的排序算法,每次都是先選擇一個位置的元素(可以為序列的最左或最右位置)作為中間值,將比其小的元素放在其左邊,比其大的元素放在右邊,然後遞歸對其左邊和右邊的子序列進行相同操作,直到子序列為單個元素。

特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n^2)(序列基本有序時,退化為冒泡排序),額外空間O(logn),不穩定排序(舉例:序列為 5 3 3 4 3 8 9 10 11, 現在中樞元素5和3(第5個元素,下標從1開始計)交換就會把元素3的穩定性打亂),當n較大時較好(當也不能太大,用了遞歸就要考慮棧溢出)!

代碼:

void quick_sort(int *a, int p, int r)
{
    if(p < r) {
        inttemp;
        intx = a[r];
        inti = p - 1;
        for(intj = p; j < r; j++)
            if(a[j] < x) {
                i++;
                temp = a[j];
                a[j] = a[i];
                a[i] = temp;
            }
                                                                                
        temp = a[i+1];
        a[i+1] = a[r];
        a[r] = temp;
        quick_sort(a, p, i);
        quick_sort(a, i+2, r);
    }
}

8. 計數排序

基本思想:假定輸入是有一個小範圍內的整數構成的(比如年齡等),利用額外的數組去記錄元素應該排列的位置,思想比較簡單,看代碼即可了解。

特點:在一定限製下時間複雜度為O(n),額外空間O(n)(需要兩個數組),穩定排序!

代碼:

int b[MAX] = {0};
int c[MAX] = {0};
                                                           
void counting_sort(int *a, int n)
{
                                                           
    for(inti=1; i <= n; i++)
        c[a[i]]++;   //c[i]包含等於i的元素個數
                                                           
    for(inti=1; i < MAX; i++)
        c[i] += c[i-1];//c[i]包含小於等於i的元素個數
                                                           
    for(inti = n; i>0; i--){
        b[c[a[i]]] = a[i];
        c[a[i]]--;
    }
    for(inti = 1; i <=n; i++)
        a[i] = b[i];
}

9. 基數排序

基本思想:隻適用於整數排序,確定序列中元素的最大位數d,隻要進行d次循環,從低位開始根據相應位置的數進行排序。(我的代碼中具體排序是參考了計數排序,數據結構中還可以用鏈式相關的方法)。

特點:在一定限製下時間複雜度為O(n),額外空間O(n)(需要兩個數組),穩定排序!

代碼:

int b[MAX] = {0};
int counter[10] = {0};
int get_value(int v, int d) //獲取第d位上的值
{
    for(inti = 1; i < d; i++) 
        v = v/10;
    returnv%10;
                                      
}
//隻能排序d位的十進製數
voidradix_sort(int*a,int n, int d)
{
    intx;
    for(intk = 1; k <= d; k++) {
        for(inti = 0; i < 10; i++)
            counter[i] = 0;//注意,一定要清零
        for(inti = 1; i <= n; i++) {
            x = get_value(a[i], k);
            counter[x]++;
        }
                                              
        for(inti = 1; i < 10; i++)
            counter[i] += counter[i-1];
        for(inti = n; i > 0; i--) {
            x = get_value(a[i], k);
            b[counter[x]] = a[i];
            counter[x]--;
        }
        for(inti = 1; i <= n; i++)
            a[i] = b[i];
    }
}

排序總結

穩定性:選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法,而冒泡排序、插入排序、歸並排序和基數排序是穩定的排序算法。

快速排序算法使用最廣泛,大數據量時適合使用快速排序、歸並排序和堆排序,需要O(n)時間複雜度時(當然要考慮數值範圍的限製),可以考慮使用計數排序、基數排序、桶排序(上麵未介紹,思想很簡單,假設數據分布均勻!)等。

最後是我用來測試排序算法的main函數,非常簡單!

#include <iostream>
usingnamespacestd;
                            
constintMAX = 255;
                        
int main ()
{
    intn;
    inta[MAX];
    cin >> n;
    for(inti = 1; i <= n; i++)
        cin >> a[i];
                        
    cout <<"Before sort:";
    for(inti = 1; i <= n; i++)
        cout << a[i] <<" ";
    cout << endl;
    //radix_sort(a, n, 2);
    //select_sort(a, n);
    //insert_sort(a, n);
    //bubble_sort(a, n);
    //quick_sort(a, 1, n);
    //heap_sort(a, n);
    //merge_sort(a, 1, n);
    //counting_sort(a, n);
    //shell_sort(a, n);
    cout <<"Sort:";
    for(inti = 1; i <= n; i++)
        cout << a[i] <<" ";
    cout << endl;
    return0;
}

排序算法基本上就總結如上,告誡自己,不能死記硬背!要理解思想,同時要注意實現上的一些技巧!

最後更新:2017-04-03 20:51:30

  上一篇:go 樂嘉,你快回來
  下一篇:go 平衡二叉樹(AVL)