複習資料結構:排序演算法(五)——快速排序的各種版本

bigface1234fdfg發表於2015-02-24

    

    之前已經比較熟悉快排的基本思想了,其實現的方式也有很多種。下面我們羅列一些常見的實現方式:


版本一:演算法導論上的單向掃描,選取最後一個元素作為主元


#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]三者之中的那個第二大的元素為主元時,便能盡最大限度保證快速排序演算法不會出現ON^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


相關文章