續論秋招中的排序(排序法彙總-------上篇)(因爲篇幅過大),下面咱們繼續學習。html
待續學習
(原創,轉發須註明原處)ui
五、快速排序spa
快速排序在面試中常常被問到(包括各類改進思路),此排序算法能夠說是用得最爲普遍的排序算法,是對冒泡排序的一種改進,每次交換是跳躍式的。其比較易於實現,同時它能夠處理多種不一樣的輸入數據,許多狀況下所消耗的資源也較其餘排序算法少。理想狀況下,其只使用一個小的輔助棧,對N個數據項排序的平均時間複雜度爲O(NlogN),而且它內部的循環很小,缺點就是不穩定的排序算法,在最壞的狀況下時間複雜度爲O(N^2)。其實過程很簡單,想一想就明白了,因此不想再細說,圖解比語言更明確更精準,下面給出一輪劃分的圖解,接着遞歸兩邊的部分便可,圖以下:3d
即劃分後,key=32的最終位置的左邊的全部數據值都小於(或等於)key,右邊的全部數據值都大於(或等於)key。指針
代碼以下:code
int partion(int a[], int left, int right)//劃分操做
{ int lleft = left, rright = right, key = a[lleft]; while (lleft < rright) { while (lleft < rright && a[rright] >= key)//找右邊第一個小於key的
rright--; a[lleft] = a[rright]; while (lleft < rright && a[lleft] <= key)//找左邊第一個大於key的
lleft++; a[rright] = a[lleft]; } a[lleft] = key; return lleft;//key的最終位置
}
遞歸:htm
void Qsort(int a[], int left, int right) { if (left >= right)//遞歸結束條件 return; int index = partion(a, left, right); Qsort(a, left, index - 1);//遞歸key左邊區域 Qsort(a, index + 1, right);//遞歸key右邊區域 }
非遞歸:
void qsort(int a[], int left, int right) { std::stack<int> st; if (left < right) { int mid = partion(a, left, right); if (left < mid - 1) //將左邊邊界入棧 { st.push(left); st.push(mid - 1); } if (mid + 1 < right)//將右邊邊界入棧 { st.push(mid + 1); st.push(right); } while (!st.empty()) //若是棧不爲空,即排序未結束 { int q = st.top(); st.pop(); int p = st.top(); st.pop(); mid = partion(a, p, q);//繼續劃分 if (p < mid - 1) { st.push(p); st.push(mid - 1); } if (mid + 1 < q) { st.push(mid + 1); st.push(q); } } } }
在最壞的狀況下,若是要排序的序列是有序的,則快速排序將退化爲冒泡排序,時間複雜度變爲O(N^2);在最好的狀況下,每次劃分過程都剛好把文件分割成兩個大小徹底相等的部分,則時間複雜度爲O(NlogN)。
改進算法:
上面咱們也提到了,在最壞的狀況下,若是待排序列已經有序了,則快排會變得很是低效。下面將會介紹改進的方法:
改進選取的參考樞紐元素:一、選取隨機數做爲樞軸。可是隨機數的生成自己是一種代價,根本減小不了算法其他部分的平均運行時間。二、使用左端,右端和中心的中值作爲樞軸元(即三者取中)。三、每次選取數據集中的中位數作樞軸。
改進代碼:
void exchange(int*a, int *b) { int tmp; tmp = *a; *a = *b; *b = tmp; } void compexch(int* a, int* b) { if (*a > *b) exchange(a, b); } int partion(int a[], int left, int right)//劃分操做 { int lleft = left + 1, rright = right - 1; int key; //三者取中法 int mid = (left + right) / 2; compexch(&a[left], &a[mid]); compexch(&a[left], &a[right]); compexch(&a[mid], &a[right]); key = a[mid]; while (lleft < rright) { while (lleft < rright && a[rright] >= key)//找右邊第一個小於key的 rright--; a[lleft] = a[rright]; while (lleft < rright && a[lleft] <= key)//找左邊第一個大於key的 lleft++; a[rright] = a[lleft]; } a[lleft] = key; return lleft;//key的最終位置 } void quicksort(int a[], int left, int right) //小區域排序採用插入排序方法(對於有序序列的排序效率較高O(N)),三者取中 { if (left >= right)//遞歸結束條件 return; if (right - left <= 5)//當區域段長度小於5時,改用插入排序法 { insertion(a, right-left+1); return; } int i; i = partion(a, left, right); quicksort(a, left, i - 1); quicksort(a, i + 1, right); }
下面咱們繼續考慮另外方面的改進,當待排序的序列中有大量的重複元素時,標準的快排又變得極其低效(哎呀,問題怎麼這麼多啊,煩不煩啊。。。。)。嘿嘿,固然,有解決的方法了,最直觀的想法是將序列劃分爲三部分(三路劃分)而再也不是兩部分了,即比劃分元素小的部分、比劃分元素大的部分和與劃分元素相等的部分。
三路劃分僅僅在標準的快排下稍做改動:遍歷時將遇到的左邊區域中的與劃分元素相等的元素放到序列的最左邊,將遇到的右邊區域中的與劃分元素相等的元素放到序列的最右邊。繼而,當兩個掃描的指針相遇時,序列中與劃分元素相等的元素的位置就精肯定位了。對於重複的元素的額外工做量只與所找到的重複的元素的個數呈線性相關;即便在沒有重複的元素的狀況下,在方法沒有額外的開銷,效果也很好。
代碼:
void quicksort(int a[], int left, int right) //小區域排序採用插入排序方法,三者取中,三路劃分 { if (left >= right) return; int lleft = left - 1, rright = right , k, p=left-1, q=right; int key; if (right - left <= 1) { insertion(a, right-left+1); return; //遞歸返回條件 } int mid = (left + right) / 2; compexch(&a[left], &a[mid]); compexch(&a[left], &a[right]); compexch(&a[mid], &a[right]); key = a[right]; while(true) { while (a[++lleft]< key); while (key< a[--rright]) { if (rright == left) { break; } } if (lleft >= rright) { break; } exchange(&a[lleft], &a[rright]); if (a[lleft]== key) { p++; exchange(&a[p], &a[lleft]); } if (a[rright]== key) { q--; exchange(&a[q], &a[rright]); } } exchange(&a[lleft], &a[right]); lleft = lleft - 1; rright = lleft + 1; for (k = left; k <= p; k++, lleft--) { exchange(&a[k], &a[lleft]); } for (k = right - 1; k >= q; k--, rright++) { exchange(&a[k], &a[rright]); } quicksort(a, left, lleft); quicksort(a, rright, right); }