【排序】八大排序算法簡介及它們各自的特色總結

概述:

    通常使用的八大排序算法是:插入排序、選擇排序、冒泡排序、希爾排序、歸併排序、快速排序、堆排序、基數排序,每一個方法有其適合的使用場景,能夠根據具體數據進行選擇.算法

    幾個概念:shell

    內部排序:排序期間元素所有存放在內存中的排序;api

    外部排序:排序期間元素沒法所有存放在內存中,必須在排序過程當中根據要求不斷地進行內外存之間移動地排序;數組

    (這八種排序算法中除了多路歸併排序是外部排序,其餘都是內部排序)微信

    穩定性:指的是通過排序後,值相同的元素保持原來順序中的相對位置不變.函數

    各算法時間複雜度、空間複雜度、所需輔助空間與穩定性以下:
性能

 

實現方法(這裏都是進行從小到大的排序):

1.冒泡排序:

冒泡排序思想很簡單,就是對每一個下標i,取j從0到n-1-i(n是數組長度)進行遍歷,若是兩個相鄰的元素s[j]>s[j+1],就交換,這樣每次最大的元素已經移動到了後面正確的位置.ui

void bubbleSort(vector<int> &s){
    for (int i = 0; i < s.size(); i++){
        for (int j = 0; j < s.size() - 1 - i; j++){
            if (s[j] > s[j + 1])  swap(s[j], s[j + 1]);
        }
    }
}

冒泡排序的特色:穩定,每次排序後,後面的元素確定是已經排好序的,因此每次排序後能夠肯定一個元素在其最終的位置上,冒泡排序比較次數:n(n-1)/2,移動次數:3n(n-1)/2.spa

2.插入排序:

插入排序又分爲簡單插入排序和折半插入排序;簡單插入排序思想是每趟排序把元素插入到已排好序的數組中,折半插入排序是改進的插入排序,因爲前半部分爲已排好序的數列,這樣咱們不用按順序依次尋找插入點,能夠採用折半查找的方法來加快尋找插入點的速度.code

簡單插入排序:

void insertSort(vector<int> &s){
    if (s.size() > 1){
        for (int i = 1; i < s.size(); i++){
            for (int j = i; j > 0 && s[j] < s[j - 1]; j--){
                swap(s[j], s[j - 1]);
            }
        }
    }
}

折半插入排序:

void binaryInsertSort(vector<int> &s){
    int low, high, m, temp, i, j;
    for (i = 1; i < s.size(); i++){
        //採用折半查找方法,尋找應該插入的位置
        low = 0;
        high = i - 1;
        while (low <= high){
            m = (low + high) / 2;
            if (s[m] > s[i])  high = m - 1;
            else  low = m + 1;
        }
        //統一移動元素,將該元素插入到正確的位置
        temp = s[i];
        for (j = i; j > high + 1; j--){
            s[j] = s[j - 1];
        }
        s[high + 1] = temp;
    }
}

插入排序的特色:穩定,最壞狀況下比較n*(n-1)/2次,最好狀況下比較n-1次

3.簡單選擇排序:

選擇排序思想是對每一個下標i,從i後面的元素中選擇最小的那個和s[i]交換.

void selectSort(vector<int> &s){
    if(s.size()>1){
        for(int i=0;i<s.size();i++){
            int min=i;
            for(int j=i+1;j<s.size();j++){
                if(s[j]<s[min])  min=j;
            }
            swap(s[i], s[min]);
        }
    }
}

選擇排序的特色:不穩定,每趟排序後前面的元素確定是已經排好序的了,每次排序後能夠肯定一個元素會在其最終位置上.

4.快速排序:

快速排序是內排序中平均性能較好的排序,思想是每趟排序時選取一個數據(一般用數組的第一個數)做爲關鍵數據,而後將全部比它小的數都放到它的左邊,全部比它大的數都放到它的右邊.

void quickSort(vector<int> &s, int low, int high){
    if (low >= high)  return;
    int l = low, r = high, val = s[low];
    while (l < r){
        while (l < r&&s[r] >= val)  r--;
        if (l < r)  s[l++] = s[r];
        while (l < r&&s[l] <= val)  l++;
        if (l < r)  s[r--] = s[l];
    }
    s[l] = val;
    quickSort(s, low, l - 1);
    quickSort(s, r + 1, high);
}

快速排序的特色:

1.不穩定;
2.快速排序過程當中不會產生有序子序列,但每一趟排序後都有一個元素放在其最終位置上;
3.每次選擇的關鍵值能夠把數組分爲兩個子數組的時候,快速排序算法的速度最快,當數組已是正序或逆序時速度最慢;
4.遞歸次數與每次劃分後獲得的分區的處理順序無關;
5.對n個關鍵字進行快速排序,最大遞歸深度爲n,最小遞歸深度爲log2n;

上面的快速排序算法是遞歸算法,非遞歸算法使用棧來實現:

//進行區域的劃分
int partition(vector<int> &s, int low, int height){
    int val = s[low];
    while (low < height){
        while (low < height&&s[height] >= val)
            height--;
        s[low] = s[height];
        while (low < height&&s[low] <= val)
            low++;
        s[height] = s[low];
    }
    s[low] = val;
    return low;
}
//排序
void quickSortNonRecursive(vector<int> &s, int low, int height){
    stack<int> p;
    if (low < height){
        int mid = partition(s, low, height);
        if (mid - 1 > low){
            p.push(low);
            p.push(mid - 1);
        }
        if (mid + 1 < height){
            p.push(mid + 1);
            p.push(height);
        }
        while (!p.empty()){
            int qHeight = p.top();
            p.pop();
            int pLow = p.top();
            p.pop();
            int pqMid = partition(s, pLow, qHeight);
            if (pqMid - 1 > pLow){
                p.push(pLow);
                p.push(pqMid - 1);
            }
            if (pqMid + 1 < qHeight){
                p.push(pqMid + 1);
                p.push(qHeight);
            }
        }
    }
}

5.希爾排序:

希爾排序是基於插入排序的一種排序算法,思想是對長度爲n的數組s,每趟排序基於間隔h分紅幾組,對每組數據使用插入排序方法進行排序,而後減少h的值,這樣剛開始時候雖然分組比較多,但每組數據不多,h減少後每組數據多但基本有序,而插入排序對已經基本有序的數組排序效率較高.

void shellSort(vector<int> &s){
    int n = s.size(), h = 1;
    while (h < n / 3)  h = 3 * h + 1;
    while (h >= 1){
        for (int i = h; i < n; i++){
            for (int j = i; j >= h && s[j] < s[j - h]; j -= h){
                swap(s[j], s[j - h]);
            }
        }
        h /= 3;
    }
}

希爾排序的特色:不穩定,每次排序後不能保證有一個元素在最終位置上

6.歸併排序:

歸併排序的思想是將兩個有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每一個子序列是有序的。而後再把有序子序列合併爲總體有序序列。即先劃分爲兩個部分,最後進行合併。

 

const int maxn=500000,INF=0x3f3f3f3f;
int L[maxn/2+2],R[maxn/2+2];
void merge(vector<int> &a,int n,int left,int mid,int right){
    int n1=mid-left,n2=right-mid;
    for(int i=0;i<n1;i++)  L[i]=a[left+i];
    for(int i=0;i<n2;i++)  R[i]=a[mid+i];
    L[n1]=R[n2]=INF;
    int i=0,j=0;
    for(int k=left;k<right;k++){
        if(L[i]<=R[j])  a[k]=L[i++];
        else  a[k]=R[j++];
    }
}
void mergesort(vector<int> &a,int n,int left,int right){
    if(left+1<right){
        int mid=(left+right)/2;
        mergesort(a,n,left,mid);
        mergesort(a,n,mid,right);
        merge(a,n,left,mid,right);
    }
}

 

歸併排序特色:穩定,能夠用在順序存儲和鏈式存儲的結構,時間複雜度在最好和最壞狀況下都是O(nlogn)

7.堆排序:

堆排序是基於選擇排序的一種排序算法,堆是一個近似徹底二叉樹的結構,且知足子結點的鍵值或索引老是小於(或者大於)它的父節點。這裏採用最大堆方式:位於堆頂的元素老是整棵樹的最大值,每一個子節點的值都比父節點小,堆要時刻保持這樣的結構,因此一旦堆裏面的數據發生變化,要對堆從新進行一次構建。

void max_heapify(vector<int> &arr, int start, int end) {
    //創建父節點指標和子節點指標
    int dad = start;
    int son = dad * 2 + 1;
    while (son <= end) { //若子節點指標在範圍內才作比較
        if (son + 1 <= end && arr[son] < arr[son + 1]) //先比較兩個子節點大小,選擇最大的
            son++;
        if (arr[dad] > arr[son]) //若是父節點大於子節點表明調整完畢,直接跳出函數
            return;
        else { //不然交換父子內容再繼續子節點和孫節點比較
            swap(arr[dad], arr[son]);
            dad = son;
            son = dad * 2 + 1;
        }
    }
}

void heap_sort(vector<int> &arr, int len) {
    //初始化,i從最後一個父節點開始調整
    for (int i = len / 2 - 1; i >= 0; i--)
        max_heapify(arr, i, len - 1);
    //先將第一個元素和已經排好的元素前一位作交換,再重新調整(剛調整的元素以前的元素),直到排序完畢
    for (int i = len - 1; i > 0; i--) {
        swap(arr[0], arr[i]);
        max_heapify(arr, 0, i - 1);
    }
}

堆排序特色:不穩定,最壞,最好,平均時間複雜度均爲O(nlogn)

8.基數排序:

基數排序是一種非比較型整數排序算法,其原理是將數據按位數切割成不一樣的數字,而後按每一個位數分別比較,在相似對百萬級的電話號碼進行排序的問題上,使用基數排序效率較高

//尋找數組中最大數的位數做爲基數排序循環次數
int KeySize(vector<int> &s, int n){
    int key = 1;
    for (int i = 0; i < n; i++){
        int temp = 1;
        int r = 10;
        while (s[i] / r > 0){
            temp++;
            r *= 10;
        }
        key = (temp > key) ? temp : key;
    }
    return key;
}

//基數排序
void RadixSort(vector<int> &s, int n){
    int key = KeySize(s, n);
    int bucket[10][10] = { 0 };
    int order[10] = { 0 };
    for (int r = 1; key > 0; key--, r *= 10){
        for (int i = 0; i < n; i++){
            int lsd = (s[i] / r) % 10;
            bucket[lsd][order[lsd]++] = s[i];
        }
        int k = 0;
        for (int i = 0; i < 10; i++){
            if (order[i] != 0){
                for (int j = 0; j < order[i]; j++)
                    s[k++] = bucket[i][j];
            }
            order[i] = 0;
        }
    }
}

基數排序特色:穩定,時間複雜度爲O (nlog(r)m),其中r爲所採起的基數,而m爲堆數,在某些時候,基數排序法的效率高於其它的穩定性排序法。

 

部分參考:百度百科、微信公衆號五分鐘學算法原創文章《這或許是東半球分析十大排序算法最好的一篇文章》—— https://mp.weixin.qq.com/s/Qf416rfT4pwURpW3aDHuCg

相關文章
相關標籤/搜索