通常使用的八大排序算法是:插入排序、選擇排序、冒泡排序、希爾排序、歸併排序、快速排序、堆排序、基數排序,每一個方法有其適合的使用場景,能夠根據具體數據進行選擇.算法
幾個概念:shell
內部排序:排序期間元素所有存放在內存中的排序;api
外部排序:排序期間元素沒法所有存放在內存中,必須在排序過程當中根據要求不斷地進行內外存之間移動地排序;數組
(這八種排序算法中除了多路歸併排序是外部排序,其餘都是內部排序)微信
穩定性:指的是通過排序後,值相同的元素保持原來順序中的相對位置不變.函數
各算法時間複雜度、空間複雜度、所需輔助空間與穩定性以下:
性能
冒泡排序思想很簡單,就是對每一個下標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
插入排序又分爲簡單插入排序和折半插入排序;簡單插入排序思想是每趟排序把元素插入到已排好序的數組中,折半插入排序是改進的插入排序,因爲前半部分爲已排好序的數列,這樣咱們不用按順序依次尋找插入點,能夠採用折半查找的方法來加快尋找插入點的速度.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次
選擇排序思想是對每一個下標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]); } } }
選擇排序的特色:不穩定,每趟排序後前面的元素確定是已經排好序的了,每次排序後能夠肯定一個元素會在其最終位置上.
快速排序是內排序中平均性能較好的排序,思想是每趟排序時選取一個數據(一般用數組的第一個數)做爲關鍵數據,而後將全部比它小的數都放到它的左邊,全部比它大的數都放到它的右邊.
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); } } } }
希爾排序是基於插入排序的一種排序算法,思想是對長度爲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; } }
希爾排序的特色:不穩定,每次排序後不能保證有一個元素在最終位置上
歸併排序的思想是將兩個有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每一個子序列是有序的。而後再把有序子序列合併爲總體有序序列。即先劃分爲兩個部分,最後進行合併。
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)
堆排序是基於選擇排序的一種排序算法,堆是一個近似徹底二叉樹的結構,且知足子結點的鍵值或索引老是小於(或者大於)它的父節點。這裏採用最大堆方式:位於堆頂的元素老是整棵樹的最大值,每一個子節點的值都比父節點小,堆要時刻保持這樣的結構,因此一旦堆裏面的數據發生變化,要對堆從新進行一次構建。
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)
基數排序是一種非比較型整數排序算法,其原理是將數據按位數切割成不一樣的數字,而後按每一個位數分別比較,在相似對百萬級的電話號碼進行排序的問題上,使用基數排序效率較高
//尋找數組中最大數的位數做爲基數排序循環次數 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