複習資料結構:排序演算法(五)——快速排序的各種版本
之前已經比較熟悉快排的基本思想了,其實現的方式也有很多種。下面我們羅列一些常見的實現方式:
版本一:演算法導論上的單向掃描,選取最後一個元素作為主元
#include<iostream>
using namespace std;
int partition(int data[], int low, int high)
{
int pivot = data[high]; // let the last atom be the pivot
int i = low - 1; // mark the pivot's true index: i+1
for(int j = low; j < high; j++) // 陣列是從low ~ high, 這裡就要到high-1截止
{
if(data[j] <= pivot)
{
i++; // 說明pivot的位置要後移一位
swap(data[i], data[j]); // 較小的數放前面,由於pivot是i+1, 所以把data[j]放在i的位置
}
}
swap(data[i+1], data[j]); // 把主元放在合適的位置i+1,記住不是pivot,而是data[j]
return i+1; // return the true index of pivot, that is, i+1
}
void quickSort(int data[], int low, int high)
{
if(low < high)
{
int k = partition(data, low, high);
quickSort(data, low, k-1);
quickSort(data, k+1, high);
}
}
int main()
{
int data[] = {2, 6, 3, 8, 1, 4};
quickSort(data, 0, 5);
for(int i = 0; i < 6; i++)
cout<<data[i]<<' ';
cout<<endl;
return 0;
}
版本二:雙向掃描,選取第一個元素作為主元
#include<iostream>
using namespace std;
void print(int a[], int n){
for(int j= 0; j<n; j++){
cout<<a[j] <<" ";
}
cout<<endl;
}
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
// 雙向掃描法
int partition(int a[], int low, int high)
{
int privotKey = a[low]; //基準元素
while(low < high){ //從表的兩端交替地向中間掃描
while(low < high && a[high] >= privotKey) --high; //從high 所指位置向前搜尋,至多到low+1 位置。將比基準元素小的交換到低端
swap(&a[low], &a[high]);
while(low < high && a[low] <= privotKey ) ++low;
swap(&a[low], &a[high]);
}
print(a,10);
return low;
}
void quickSort(int a[], int low, int high){
if(low < high){
int privotLoc = partition(a, low, high); //將表一分為二
quickSort(a, low, privotLoc -1); //遞迴對低子表遞迴排序
quickSort(a, privotLoc + 1, high); //遞迴對高子表遞迴排序
}
}
int main(){
int a[10] = {3,1,5,7,2,4,9,6,10,8};
cout<<"初始值:";
print(a,10);
quickSort(a,0,9);
cout<<"結果:";
print(a,10);
return 0;
}
版本三:隨機選取一個元素作為主元
//隨機選擇主元的快排主要步驟是先隨機選擇主元的位置,然後把該主元與第一個元素調換,之後的步驟與拿第一個元素當做主元的演算法一樣
#include<iostream>
using namespace std;
//交換兩個元素值,我們們換一種方式,採取引用“&”
void swap(int& a , int& b)
{
int temp = a;
a = b;
b = temp;
}
//返回屬於[lo,hi)的隨機整數
int rand(int lo,int hi)
{
int size = hi-lo+1;
return lo+ rand()%size;
}
//分割,換一種方式,採取指標a指向陣列中第一個元素
int RandPartition(int* data, int lo , int hi)
{
//普通的分割方法和隨機化分割方法的區別就在於下面三行
swap(data[rand(lo,hi)], data[lo]);
int key = data[lo];
int i = lo;
for(int j=lo+1; j<=hi; j++)
{
if(data[j]<=key)
{
i = i+1;
swap(data[i], data[j]);
}
}
swap(data[i],data[lo]);
return i;
}
//逐步分割排序
void RandQuickSortMid(int* data, int lo, int hi)
{
if(lo<hi)
{
int k = RandPartition(data,lo,hi);
RandQuickSortMid(data,lo,k-1);
RandQuickSortMid(data,k+1,hi);
}
}
int main()
{
const int N = 100; //此就是上文說所的“極限”測試。為了保證程式的準確無誤,你也可以讓N=10000。
int *data = new int[N];
for(int i =0; i<N; i++)
data[i] = rand(); //同樣,隨機化的版本,採取隨機輸入。
for(i=0; i<N; i++)
cout<<data[i]<<" ";
RandQuickSortMid(data,0,N-1);
cout<<endl;
for(i=0; i<N; i++)
cout<<data[i]<<" ";
cout<<endl;
return 0;
}
版本四:三數取中作為主元
上面的程式版本,其中演算法導論上採取單向掃描中,是以最後一個元素為樞紐元素,即主元,而在Hoare版本及其幾個變形中,都是以第一個元素、或中間元素為主元,最後,上述給的快速排序演算法的隨機化版本,則是以序列中任一一個元素作為主元。
那麼,樞紐元素的選取,即主元元素的選取是否決定快速排序最終的效率列?
答案是肯定的,當我們採取data[lo],data[mid],data[hi]三者之中的那個第二大的元素為主元時,便能盡最大限度保證快速排序演算法不會出現O(N^2)的最壞情況。這就是所謂的三數取中分割方法。當然,針對的還是那個Partition過程。
//三數取中分割方法
int RandPartition(int* a, int p , int q)
{
//三數取中方法的關鍵就在於下述六行,可以得到a[p], a[m], a[q]三個數中的中位數
int m=(p+q)/2;
if(a[p]<a[m])
swap(a[p],a[m]);
if(a[q]<a[m])
swap(a[q],a[m]);
if(a[q]<a[p])
swap(a[q],a[p]);
int key = a[p];
int i = p;
for(int j = p+1; j <= q; j++)
{
if(a[j] <= key)
{
i = i+1;
if(i != j)
swap(a[i], a[j]);
}
}
swap(a[i],a[p]);
return i;
}
void QuickSort(int data[], int lo, int hi)
{
if (lo<hi)
{
int k = RandPartition(data, lo, hi);
QuickSort(data, lo, k-1);
QuickSort(data, k+1, hi);
}
}
版本五:非遞迴版本
//遞迴的本質是什麼?對了,就是藉助棧,進棧,出棧來實現的。
if( (key-lo)<(key-hi) )
{
st.push(key+1);
st.push(hi);
hi=key-1;
}
else
{
st.push(lo);
st.push(key-1);
lo=key+1;
}
}
if(st.empty())
return;
hi=st.top();
st.pop();
lo=st.top();
st.pop();
}while(1);
}
參考:
http://blog.csdn.net/hguisu/article/details/7776068
http://blog.csdn.net/xiazdong/article/details/8462393
http://blog.csdn.net/v_JULY_v/article/details/6262915
相關文章
- 複習資料結構:排序演算法(六)——堆排序資料結構排序演算法
- 複習資料結構:排序演算法(七)——桶排序資料結構排序演算法
- 複習資料結構:排序演算法(八)——基排序資料結構排序演算法
- 【資料結構】 各種排序演算法的實現資料結構排序演算法
- 複習資料結構:排序演算法(二)——氣泡排序資料結構排序演算法
- 複習資料結構:排序演算法(四)——歸併排序資料結構排序演算法
- 複習資料結構:排序(一)——插入排序資料結構排序
- 複習資料結構:排序(三)——選擇排序資料結構排序
- 資料結構-各種排序演算法效率對比圖資料結構排序演算法
- 【資料結構與演算法】內部排序總結(附各種排序演算法原始碼)資料結構演算法排序原始碼
- 【資料結構】快速排序資料結構排序
- 資料結構與演算法——快速排序資料結構演算法排序
- 資料結構 快速排序 C Swift 極簡版本資料結構排序Swift
- 資料結構與演算法之快速排序資料結構演算法排序
- 資料結構和演算法(Golang實現)(25)排序演算法-快速排序資料結構演算法Golang排序
- 資料結構和演算法面試題系列—排序演算法之快速排序資料結構演算法面試題排序
- python演算法與資料結構-快速排序(36)Python演算法資料結構排序
- 淺談演算法和資料結構(4):快速排序演算法資料結構排序
- 資料結構與演算法 排序演算法 快速排序【詳細步驟圖解】資料結構演算法排序圖解
- 資料結構與演算法——排序演算法-氣泡排序資料結構演算法排序
- 資料結構與演算法——排序演算法-選擇排序資料結構演算法排序
- 資料結構與演算法——排序演算法-歸併排序資料結構演算法排序
- 資料結構與演算法——排序演算法-基數排序資料結構演算法排序
- 三種快速排序演算法以及快速排序的優化排序演算法優化
- 資料結構與演算法——排序資料結構演算法排序
- C語言版資料結構及演算法_快速排序C語言資料結構演算法排序
- 資料結構 桶排序 基數排序MSD c++ swift 版本資料結構排序C++Swift
- 【資料結構與演算法】內部排序之五:計數排序、基數排序和桶排序(含完整原始碼)資料結構演算法排序原始碼
- 【Java資料結構與演算法】第八章 快速排序、歸併排序和基數排序Java資料結構演算法排序
- 【資料結構與演算法】非比較排序(計數排序、桶排序、基數排序)資料結構演算法排序
- 資料結構之---C語言實現快速排序(多個版本)資料結構C語言排序
- 【資料結構與演算法】高階排序(希爾排序、歸併排序、快速排序)完整思路,並用程式碼封裝排序函式資料結構演算法排序封裝函式
- 從零開始學資料結構和演算法 (五) 分治法 (二分查詢、快速排序、歸併排序)資料結構演算法排序
- C#資料結構與演算法5-C# 快速排序C#資料結構演算法排序
- 演算法導論學習之五:快速排序演算法排序
- 資料結構與演算法(八):排序資料結構演算法排序
- 資料結構與演算法——堆排序資料結構演算法排序
- 【資料結構與演算法】堆排序資料結構演算法排序