快速排序是Hoare於1962年提出的一種二叉樹結構的交換排序方法。
其基本思想爲:任取待排序元素序列中 的某元素作爲基準值,按照該排序碼將待排序集合分割成兩子序列,左子序列中所有元素均小於基準值,右子序列中所有元素均大於基準值,然後最左右子序列重複該過程,直到所有元素都排列在相應位置上爲止。
如何尋找基準值?
一般情況下會選擇排序序列的最後一個元素作爲基準值,但是如果待排序列的最後一個元素是最值或者接近於最值的情況下,會使得排序後的左右序列元素個數相差過大,降低了效率,所以我們一般採用三數取中法:找出序列的開始節點、結束節點以及序列的中的節點取中間值作爲基準值。
將區間按照基準值劃分爲左右兩半部分的常見方式有:
hoare版本 :
1、首先設定基準值
假設我們找到了基準值是最後一個值,此時我們設定其位置爲 pivot ,同時設定兩個指針 left 、right 分別指向待排序列的左右兩端;
2、然後開始移動 left(++) 和 right(--)
當 left 所指向的數字比 pivot 所指向的數字大時,停止移動
當 right 所指向的數字比 pivot 所指向的數字小時,停止移動
當 left 和 right 都停止移動後,交換兩個指針所指向的數字;
3、繼續移動,當 left 和 right 想遇時停止移動,這裏我們假設 left 先移動,那麼兩者的可以相遇的情況一定是 左右都已經排好了,並且此時 left 和 right 所指向的數字一定比 pvoit 所指向的數字大!!!
此時只需要將,left 和 right 所指向的數組和 pvoit 所指向的數字進行交換即可;
4、此時以及成功將待排序列分爲左右兩部分 (以 6 爲中心),之後只需要對左右序列進行以上同的工作就可以了;
用遞歸實現是非常簡單的,主要是要完成一次排序後,不斷對左右生於的子序列進行遞歸即可。
那麼,非遞歸又怎麼辦?其實也很簡單,利用棧(stack)的結構,來模擬進行遞歸就可以了,函數的遞歸調用本來也就是在函數調用棧中進行的~
代碼(C++)如下所示:
template <class T>
void Swap(T* a, T* b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
int partition(int* data, int left, int right)
{
int pivot = right;
while (left < right) {
while (left < right && data[left] <= data[pivot])
++left;
while (left < right && data[right] >= data[pivot])
--right;
Swap(&data[left], &data[right]);
}
Swap(&data[left], &data[pivot]);
return pivot;
}
//遞歸
void quickSortRecusive(int* data, int left, int right){
if (data == NULL || left < 0 || right <= 0 || left > right)
return;
int pivot = partition(data,left,right);
if (pivot > left)
quickSortRecusive(data, left, pivot - 1);
if (pivot < right)
quickSortRecusive(data, pivot + 1, right);
}
//非遞歸實現
void quickSortNorecusive(int* data, int left, int right)
{
if (data == NULL || left < 0 || right <= 0 || left > right)
return;
stack<int> s;
int l, r;
s.push(right);
s.push(left);
while (!s.empty()) {
l = s.top();
s.pop();
r = s.top();
s.pop();
if (l < r) {
int povit = partition(data, l, r);
if (povit > l) {
s.push(povit - 1);
s.push(l);
}
if (r > povit) {
s.push(r);
s.push(povit + 1);
}
}
}
}
挖坑法 :
挖坑法顧名思義,我們需要把選擇的基準值的位置固定下來,就像於是挖了一個坑。
1、首先需要定一個指針 index 指向選定的基準值而且記住這個值 pivot,並且需要定義兩個指針 left 和 right 分別指向 代拍序列的首段和尾端;
2、移動指針及判斷,假設我們打算先移動 right,首先要判斷 right 所指向的值 和 index 所指向的 pivot 相比較
如果 right 的值小於 index 的值,那就把right 所指向的值,填到 left 中,並且將 index 指向right,left 向右移動;
3、然後比較 left 和 index 所指向的值:
如果 left 的值 大於 index 的值,就將 left 的值 填入 indx 中,nidex 指向 left,right 左移;
4、繼續移動,當 left 和 right 相遇的時候,只需要將開始記錄的 Pivot 的值,填入到當前 index 和 left right 共同指向的位置即可;
代碼(C++)如下:
int partitionW(int* arr,int left,int right) {
int pivot = arr[left];
int index = left;
while (left < right) {
while (right != index) {
if (arr[right] < pivot) {
arr[index] = arr[right];
index = right;
++left;
}
else
--right;
}
while (left != index) {
if (arr[left] > pivot) {
arr[index] = arr[left];
index = left;
--right;
}
else
++left;
}
}
arr[index] = pivot;
return index;
}
//遞歸
void quickSortRecusiveW(int* data,int left,int right) {
if (data == NULL || left < 0 || right <= 0 || left >right)
return;
int pos = partitionW(data, left, right);
//左右
quickSortRecusiveW(data, left, pos - 1);
quickSortRecusiveW(data, pos + 1, right);
}
//非遞歸
void quickSortNorecusiveW(int* data, int left, int right) {
if (data == NULL || left < 0 || right <= 0 || left > right)
return;
stack<int> s;
int l, r;
s.push(right);
s.push(left);
while (!s.empty()) {
l = s.top();
s.pop();
r = s.top();
s.pop();
if (l < r) {
int pos = partitionW(data, l, r);
if (l < pos) {
s.push(pos - 1);
s.push(l);
}
if (r > pos) {
s.push(r);
s.push(pos + 1);
}
}
}
}
前後指針版本:
1、首先我們選擇基準值設爲 piovt ,並且設定兩個指針 left 和 right 分別指向待排序列的初始位置和結尾位置。
2、首先移動 right 指針,如果 right 所指向的值小於 piovt ,則停止移動。
然後移動 left 指針,如果 left 所指向的值大於 piovt,則停止移動;
3、交換 left 的值和 right 的值;
4、繼續上面的動作,直到 left 和 right 指向同一個值,此時只需要將 pivot 的值和這個交換即可;
int partitionZ(int* arr, int left, int right) {
int index = left;
int pivot = arr[left];
while (left < right) {
while (left < right) {
if (arr[right] < pivot)
break;
--right;
}
while (left < right) {
if (arr[left] > pivot)
break;
++left;
}
if(left < right)
swap(arr[left], arr[right]);
}
swap(arr[left], arr[index]);
return left;
}
//遞歸
void quickSortRecusiveZ(int* arr, int left, int right) {
if (arr == NULL || left < 0 || right <= 0 || left > right)
return;
int pos = partitionZ(arr, left, right);
if(left < pos)
quickSortRecusiveZ(arr, left, pos - 1);
if(right > pos)
quickSortRecusiveZ(arr, pos + 1, right);
}
//非遞歸
void quickSortNorecusiveZ(int* arr, int left, int right) {
if (arr == NULL || left < 0 || right <= 0 || left > right)
return;
stack<int> s;
s.push(right);
s.push(left);
int l, r;
while (!s.empty()) {
l = s.top();
s.pop();
r = s.top();
s.pop();
if(l < r){
int pos = partitionZ(arr, l, r);
if (l < pos) {
s.push(pos - 1 );
s.push(l);
}
if (r > pos) {
s.push(r);
s.push(l + 1);
}
}
}
}