前言算法
複習各類排序算法,並記錄下。數組
正文數據結構
1.冒泡排序app
冒泡能夠說是最簡單的排序算法,它的排序過程就是每次遍歷數組將最大的那個數往前頂,就好像氣泡上浮同樣。優化
過程能夠參考以下圖網站
參考代碼ui
void bubbleSort(vector<int>& num) { for(int i = num.size()- 1;i > 0;i--) { for(int j = 1;j <= i;j++) { if(num[j] < num[j-1]) swap(num[j],num[j-1]); } } }
複雜度分析spa
冒泡的最壞狀況下的時間複雜度爲:O(n2),平均複雜度:O(n2) 。.net
優化blog
咱們能夠稍微優化下冒泡排序,增長一個標識來肯定一個數組是否有序,若是是那麼能夠提早終止排序,參考代碼以下
//提早終止的冒泡排序 bool PreStopBubble(vector<int>& num,const int n) { bool isSwapped = false; for(int i = 1;i < n;i++) { if(num[i - 1] > num[i]) { isSwapped = true; cout << "Swapped!" << endl; swap(num[i - 1],num[i]); } } return isSwapped; } void PreStopBubbleSort(vector<int>&num) { for(int i = num.size();i > 0 && PreStopBubble(num,i);i--); //若是某一次冒泡過程未發生交換,那麼說明數組已經有序,因此提早終止 }
這種情形下,冒泡排序的最好複雜度能夠達到O(n)。
2.選擇排序
選擇排序和冒泡排序的思路相似,都是找到最大(或者最小)的數,只不過選擇排序須要額外的空間來存儲一次遍歷過程當中最大(或者最小)的數字和它的位置,而後將它與首位(或者末位)交換。
過程能夠參考以下圖
注:圖中的選擇排序每次是選擇的最小值,參考代碼每次是選取最大值。
參考代碼
void SelectionSort(vector<int>& num) { for(int i = num.size();i > 0 ;i--) { int tmp = num[0],index = 0; for(int j = 1;j < i;j++) { if(num[j] > tmp) { tmp = num[j]; index = j; } } swap(num[index],num[i - 1]); } }
複雜度分析
選擇排序的最壞狀況下的時間複雜度爲:O(n2),平均複雜度:O(n2) 。可是每每選擇排序效率優於冒泡排序(若是不是提早終止的冒泡),由於每一次遍歷選擇排序只須要發生一次交換,而冒泡排序可能發生了屢次交換,然而,這是選擇排序犧牲了額外空間來存儲最值得來的。
3.計數排序
計數排序,也叫桶排序。這種排序方法適合已知必定範圍內的數字排序,每個數字都有一個對應的桶,一次遍歷將數組的數字放入到對應的桶中進行統計,而後遍歷每一個桶將其輸出便可。
過程能夠參考以下圖
參考代碼
void CountSort(vector<int>& num,const int maxnum) { vector<int> countBox(maxnum + 1,0); for(int i =0;i < num.size();i++) { countBox[num[i]]++; } int j = 0; for(int i = 0;i < num.size();i++) { if(countBox[j]-- > 0) num[i] = j; else { j++;i--; } } }
複雜度分析
計數排序的平均時間複雜度爲:O(n)。可是它的空間複雜度不好。這是一種犧牲空間換取時間的排序算法。
4.歸併排序
有序數組的合併
首先,咱們要知道如何將兩個有序數組合並。方法很簡單,從頭開始比較兩個數組的數字,用一個額外的數組tmp存儲一次比較時的較小數字,而後較小者所在數組的索引向後+1繼續和以前另外一個數組的較大者比較。若是其中一個數組遍歷到末尾,那麼把另外一個數組的剩餘元素依次添加到tmp數組末尾便可,這樣tmp就是一個合併後的有序數組。
分治
而對於一個無序數組,咱們能夠將它劃分爲兩個無序子數組,子數組又能夠不停劃分,直到當一個子數組只有一個數時,這時這個子數組確定是有序的,那麼咱們就能夠將一個個有序子數組合併成更大的有序子數組,直到最終合併成一個有序數組,這就是歸併排序的思想。
排序過程能夠參考以下圖
參考代碼
void MergeArray(vector<int>& num,int left,int right,int mid,vector<int>& tmp) { //合併兩個有序子數組 int l = left,lm = mid; int mr = mid + 1,r = right; int k = 0; while(l <= lm && mr <= r) { if(num[l] < num[mr]) tmp[k++] = num[l++]; else tmp[k++] = num[mr++]; } while(l <= lm) tmp[k++] = num[l++]; while(mr <= r) tmp[k++] = num[mr++]; for(int i = 0;i < k;i++) { num[left + i] = tmp[i]; } } void MergeSort(vector<int>& num,int left,int right,vector<int>& tmp) { if(left >= right) return; int mid = (left + right)/2; //遞歸+分治 MergeSort(num,left,mid,tmp); MergeSort(num,mid + 1,right,tmp); MergeArray(num,left,right,mid,tmp); } void MergeSort_begin(vector<int>& num) //歸併排序入口 { if(num.empty()) return ; vector<int> tmp(num.size()); MergeSort(num,0,num.size() - 1,tmp); }
複雜度分析
歸併排序的最壞時間複雜度爲:O(nlogn),平均複雜度爲:O(nlogn)。
5.快速排序
快速排序的思想從本質來講與歸併排序相似,也是分治。只不過快速排序是在數組中選定了一個軸點,比軸點小的數字劃分到左邊,比軸點大的劃分到右邊,這樣一個數組就被劃分紅了兩部分,而後在這兩部分基礎上繼續選擇一個軸點劃分,如此直到沒法劃分爲止。
排序過程能夠參考以下圖
參考代碼
注:這裏是選取每一個數組最左邊的數字爲軸點。
int qs_partition(vector<int>& num,int left,int right) { int pivot = num[left]; int l = left,r =right; while(l < r) { while(l < r && num[r] >= pivot) r--; if(l < r) num[l++] = num[r]; while(l < r && num[l] < pivot) l++; if(l < r) num[r--] = num[l]; } num[l] = pivot; return l; } void quickSort(vector<int>& num,int left,int right) { if(left > right) return; int k = qs_partition(num,left,right); quickSort(num,left,k - 1); quickSort(num,k + 1,right); }
上述的爲遞歸的快速排序,若是數據量很是大可能會致使棧內存爆掉,因此能夠用一個棧來實現非遞歸的快速排序。
非遞歸的快速排序參考代碼
int qs_partition(vector<int>& num,int left,int right) { int pivot = num[left]; int l = left,r =right; while(l < r) { while(l < r && num[r] >= pivot) r--; if(l < r) num[l++] = num[r]; while(l < r && num[l] < pivot) l++; if(l < r) num[r--] = num[l]; } num[l] = pivot; return l; } void stack_quickSort(vector<int>& num) { int left = 0,right = num.size() - 1; if(left > right) return; stack<int> stk; int l,r; stk.push(right); stk.push(left); while(!stk.empty()) { l = stk.top();stk.pop(); r = stk.top();stk.pop(); if(l < r) { int k = qs_partition(num,l,r); stk.push(k - 1);stk.push(l); stk.push(r);stk.push(k + 1); } } }
複雜度分析
快速排序的最壞時間複雜度爲:O(n2),即軸點的左側或者右側沒有數字。最好的狀況是左右兩側數字大體相同,平均複雜度爲:O(nlogn)。
補充
快速排序的軸點選擇對於該排序算法的效率有很大的影響,最普通的選取最左或者最右的數字做爲軸點的方法其實不太穩定,經常使用的選取軸點的方法是隨機取值或者三值取中。
三值取中:顧名思義,在最左、最右、中間三個位置選取三個數字,而後在三個數字中選取值居於中間的那個數字做爲軸點。
參考資料
《數據結構、算法與應用——C++描述》 做者:【美】 薩特吉·薩尼 機械工業出版社