快速排序算法及其優化

快速排序的的基本思想:
      設數組a中存放了n個數據元素,low爲數組的低端下標,high爲數組的高端下標,從數組a中任取一個 元素(一般選取a[ow])做爲標準,調整數組a中各個元素的位置,使排在標準元素前面的元素的關鍵字均小於標準元素的關鍵字,排在標準元素後面元素的關鍵字均大於或等於標準元素的關鍵字。一次結束後,把標準元素放在適合的位置,而且把以標準元素爲中心劃分了兩個子數組,前面數組的數都小於標準元素,後面的數組都大於標準元素。而後分別對這兩個自數組進行相同的遞歸快速排序。遞歸算法的出口是high>lowios

快速排序至關與冒泡排序的升級版
       1.選擇基準:在待排序列中,按照某種方式挑出一個元素,做爲 "基準"(pivot)算法

       2.分割操做:以該基準在序列中的實際位置,把序列分紅兩個子序列。此時,在基準左邊的元素都比該基準小,在基準右邊的元素都比基準大數組

      3.遞歸地對兩個序列進行快速排序,直到序列爲空或者只有一個元素。dom

挖坑填數,進行快速排序:
1.i =L; j = R; 將基準數挖出造成第一個坑a[i]。函數

2.j--由後向前找比它小的數,找到後挖出此數填前一個坑a[i]中。優化

3.i++由前向後找比它大的數,找到後也挖出此數填到前一個坑a[j]中。ui

4.再重複執行2,3二步,直到i==j,將基準數填入a[i]中spa


快速排序的過程圖:code

快速排序的完整代碼(遞歸):blog

#include<iostream>
using namespace std;
int partition(int *arr,int left,int right)
{
	int temp=arr[left];
	while(left<right)//直達left和right重合的時候,才找到合適的位置
	{
		//先從後往前找比基準小的
		while(left<right  &&  arr[right]>=temp)//當right的值大於temp的值的時候才執行
		        //等號必定得寫,由於可能會出現,保存的temp元素和數據中的元素同樣的,不寫會出現死循環的現象
		{
			right--;
		}
		arr[left]=arr[right];//當right的值小於temp的值的時候執行
		//從前日後找,找比基準大的
		while(left<right  && arr[left] <=temp)//當left的值小於temp的值的時候執行
		{
			left++;
		}
		arr[right]=arr[left];//當left的值大於temp的時候執行
	}
	arr[left]=temp;//此時的left和right在同一個位置,此時爲合適的位置,把temp的值給left
	return left;//此時返回的值是temp合適的位置,即小於它的在它的左邊,大於它的在它的右邊
}
 void quick(int *arr,int left,int right)
{
	if(left<right)
	{
		int pivot=partition(arr,left,right);
		quick(arr,left,pivot-1);
		quick(arr,pivot+1,right);
	}
}
void quick_sort(int *arr,int len)
{
	quick(arr,0,len-1);
}
int main()
{
	int arr[]={9,5,7,10,45,12};
	int len=sizeof(arr)/sizeof(arr[0]);
	quick_sort(arr,len);
	for(int k=0;k<len;++k)
	{
		cout<<arr[k]<<"  ";
	}
	cout<<endl;
}

快速排序的完整代碼(非遞歸):

#include<stack>
#include<iostream>
using namespace std;
 
int partition(int *arr,int left,int right)
{
	int temp=arr[left];//基準
	while(left<right)
	{
		//先從後往前找比基準小的
		while(left<right && temp<=arr[right])
		{
			right--;
		}
		arr[left]=arr[right];
		//從前日後找比基準大的
		while(left<right  && temp>=arr[left])
		{
			left++;
		}
		arr[right]=arr[left];
	}
	arr[left]=temp;
	return left;
}
//其實就是用棧保存每個待排序子串的首尾元素的下標
void q_sort(int *arr,int left,int right )
{
	stack<int>  st;
	int pos=0;
    st.push (left);
	st.push (right);
	while(!st.empty())
	{
		right=st.top();
		st.pop();
		left=st.top();
		st.pop ();//5,6,8,2,9,4,1,3,45,89,65
		pos=partition(arr,left,right);
		if(pos+1<right)//先入基準右邊的,若是基準右邊只有一個元素的時候,就不用入了
		{
			st.push (pos+1);
			st.push (right);
		}
		if(pos-1>left)//再入基準左邊的,若是基準左邊只有一個元素的時候,就不用入了
		{   
			st.push (left);
			st.push (pos-1);
			
		}
	}
}
 
void quick_sort(int *arr,int len)
{
	q_sort(arr,0,len-1);
}
int main()
{
	int arr[]={5,6,8,2,9,4,1,3,45,89,65};
	int len=sizeof(arr)/sizeof(arr[0]);
	quick_sort(arr,len);
	for(int i=0;i<len;i++)
	{
		cout<<arr[i]<<"  ";
	}
	cout<<endl;
}

  

快速排序的各類優化:
優化一:三數取中法,解決數據基本有序的(就是找到數組中最小下標,最大下標,中間下標的數字,進行比較,把中間大的數組放在最左邊)

代碼:

//*************************************************************************
void swap(int *arr,int left,int right)
{
  int temp;
  temp=arr[left];
  arr[left]=arr[right];
  arr[right]=temp;	
}
//***************************************************************************
int partition(int *arr,int left,int right)
{

  //***************************
  //e.g:9,1,5,8,3,7,4,6,2
  int m=left+(right-left)/2;//找到中間的數字的下標
  if(arr[left]>arr[right])//最左大於最右的時候,交換左右
  {
    swap(arr,left,right);
  }
  if(arr[m]>arr[right])//若是中間的>right ,交換
  {
    swap(arr,m,right);
  }
  if(arr[m]>arr[left])//若是中間的>left,交換
  {
    swap(arr,m,right);
  }
  //通過交換以後low變爲3
  //****************************
  int temp=arr[left];//基準
  while(left<right)//知道left和right重合的時候,才找到合適的位置
  { //從後向前找到比小的數字
    while(left<right && arr[right]>=temp)//當right的值大於temp的值的時候才執行
    {
      right--;
    }
    arr[left]=arr[right];//當right的值小於temp的值的時候執行
    while(left<right && arr[left] <= temp)//從前日後找到比基準大的數字
    {
      left++;
    }
    arr[right]=arr[left];//當left的值大於temp的時候執行
  }
  arr[left]=temp;//此時的left和right在同一個位置,此時爲合適的位置,把temp的值給left
  return left;//此時返回的值是temp合適的位置,即小於它的在它的左邊,大於它的在它的右邊
}

  


優化二:

隨機選取基準

引入的緣由:在待排序列是部分有序時,固定選取樞軸使快排效率底下,要緩解這種狀況,就引入了隨機選取樞軸

思想:取待排序列中任意一個元素做爲基準

/*隨機選擇樞軸的位置,區間在low和high之間*/  
int SelectPivotRandom(int arr[],int low,int high)  
{  
  //產生樞軸的位置  
   srand((unsigned)time(NULL));  
  int pivotPos = rand()%(high - low) + low;   
  //把樞軸位置的元素和low位置元素互換,此時能夠和普通的快排同樣調用劃分函數  
  swap(arr[pivotPos],arr[low]);  
   return arr[low];  
}

優化三:優化小數組的交換,就是爲了解決大才小用問題

緣由:對於很小和部分有序的數組,快排不如插排好。當待排序序列的長度分割到必定大小後,繼續分割的效率比插入排序要差,此時可使用插排而不是快排

截止範圍:待排序序列長度N = 10,雖然在5~20之間任一截止範圍都有可能產生相似的結果,這種作法也避免了一些有害的退化情形。

#define  max_len  10
void quick(int *arr,int left,int right)
{
    int length=right-left;
    if(length>max_len )
    {
        int pivot=partition(arr,left,right);
        quick(arr,left,pivot-1);
        quick(arr,pivot+1,right);
    }
    else
    {
    //用插入排序
    }
}

 

優化四:

 在一次分割結束後,能夠把與Key相等的元素聚在一塊兒,繼續下次分割時,不用再對與key相等元素分割

舉例:

待排序序列 1 4 6 7 6 6 7 6 8 6

三數取中選取樞軸:下標爲4的數6

轉換後,待分割序列:6 4 6 7 1 6 7 6 8 6

             樞軸key:6

本次劃分後,未對與key元素相等處理的結果:1 4 6 6 7 6 7 6 8 6

下次的兩個子序列爲:1 4 6 和 7 6 7 6 8 6

本次劃分後,對與key元素相等處理的結果:1 4 6 6 6 6 6 7 8 7

下次的兩個子序列爲:1 4 和 7 8 7

通過對比,咱們能夠看出,在一次劃分後,把與key相等的元素聚在一塊兒,能減小迭代次數,效率會提升很多

具體過程:在處理過程當中,會有兩個步驟

第一步,在劃分過程當中,把與key相等元素放入數組的兩端

第二步,劃分結束後,把與key相等的元素移到樞軸周圍

舉例:

待排序序列 1 4 6 7 6 6 7 6 8 6

三數取中選取樞軸:下標爲4的數6

轉換後,待分割序列:6 4 6 7 1 6 7 6 8 6

             樞軸key:6

第一步,在劃分過程當中,把與key相等元素放入數組的兩端 

結果爲:6 4 1 6(樞軸) 7 8 7 6 6 6

此時,與6相等的元素全放入在兩端了

第二步,劃分結束後,把與key相等的元素移到樞軸周圍

結果爲:1 4 6 6(樞軸)  6 6 6 7 8 7

此時,與6相等的元素全移到樞軸周圍了

以後,在1 4 和 7 8 7兩個子序列進行快排

代碼:

void QSort(int arr[],int low,int high)  
{  
    int first = low;  
   int last = high;  
  
   int left = low;  
    int right = high;  
 
   int leftLen = 0;  //用來統計左邊與key相等的元素的個數
   int rightLen = 0;  //統計右邊與key相等的元素的個數
  
    if (high - low + 1 < 10)  
    {  
        InsertSort(arr,low,high);  //數據量少,就用插入排序
        return;  
    }  
      
    //一次分割  
   int key = SelectPivotMedianOfThree(arr,low,high);//使用三數取中法選擇樞軸  
         
    while(low < high)  
    {  
       while(high > low && arr[high] >= key)  
        {  
         if (arr[high] == key)//處理相等元素  
           {  
               swap(arr[right],arr[high]); //把右邊與key元素相等的彙集的右端
                right--;  
                rightLen++;  
            }  
            high--;  
        }  
        arr[low] = arr[high];  
        while(high > low && arr[low] <= key)  
       {  
            if (arr[low] == key)  //把左邊與key元素相等的彙集數組的左端
           {  
                swap(arr[left],arr[low]);  
                left++;  
                leftLen++;  
            }  
            low++;  
        }  
       arr[high] = arr[low];  
    }  
    arr[low] = key;  
 
    //一次快排結束  
    //把與樞軸key相同的元素移到樞軸最終位置周圍  
    int i = low - 1;  //軸的左邊
    int j = first;  
    while(j < left && arr[i] != key)  
   {  
        swap(arr[i],arr[j]);  //此時,把數組左端與key相等的數據換到key的左邊
        i--;  
        j++;  
    }  
    i = low + 1;  //軸的右邊
    j = last;  
    while(j > right && arr[i] != key)  
    {  
       swap(arr[i],arr[j]);  //此時,把數組右端與key相等的數據換到,key右邊
       i++;  
       j--;  
   }  
    partition(arr,first,low - 1 - leftLen);  
    partition(arr,low + 1 + rightLen,last);  
}
相關文章
相關標籤/搜索