快速排序及快速排序的優化(完整版)
快速排序的的基本思想:
設陣列a中存放了n個數據元素,low為陣列的低端下標,high為陣列的高階下標,從陣列a中任取一個 元素(通常選取a[ow])作為標準,調整陣列a中各個元素的位置,使排在標準元素前面的元素的關鍵字均小於標準元素的關鍵字,排在標準元素後面元素的關鍵字均大於或等於標準元素的關鍵字。一次結束後,把標準元素放在適合的位置,並且把以標準元素為中心劃分了兩個子陣列,前面陣列的數都小於標準元素,後面的陣列都大於標準元素。然後分別對這兩個自陣列進行相同的遞迴快速排序。遞迴演算法的出口是high>low
快速排序相當與氣泡排序的升級版
1.選擇基準:在待排序列中,按照某種方式挑出一個元素,作為
2.分割操作:以該基準在序列中的實際位置,把序列分成兩個子序列。此時,在基準左邊的元素都比該基準小,在基準右邊的元素都比基準大
3.遞迴地對兩個序列進行快速排序,直到序列為空或者只有一個元素。
挖坑填數,進行快速排序:
1.i =L; j = R; 將基準數挖出形成第一個坑a[i]。
2.j--由後向前找比它小的數,找到後挖出此數填前一個坑a[i]中。
3.i++由前向後找比它大的數,找到後也挖出此數填到前一個坑a[j]中。
4.再重複執行2,3二步,直到i==j,將基準數填入a[i]中
快速排序的過程圖:
快速排序的完整程式碼(遞迴):
#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 66(樞軸) 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);
}