快速排序的的基本思想:
設數組a中存放了n個數據元素,low為數組的低端下標,high為數組的高端下標,從數組a中任取一個 元素(通常選取a[ow])作為標准,調整數組a中各個元素的位置,使排在標准元素前面的元素的關鍵字均小於標准元素的關鍵字,排在標准元素后面元素的關鍵字均大於或等於標准元素的關鍵字。一次結束后,把標准元素放在適合的位置,並且把以標准元素為中心划分了兩個子數組,前面數組的數都小於標准元素,后面的數組都大於標准元素。然后分別對這兩個自數組進行相同的遞歸快速排序。遞歸算法的出口是high>low
快速排序相當與冒泡排序的升級版
1.選擇基准:在待排序列中,按照某種方式挑出一個元素,作為 "基准"(pivot)
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 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);
}
