七大排序演算法(5)------快速排序(遞迴和非遞迴)
在本文中使用到的升序,降序,交換函式的程式碼見:這篇部落格
快速排序(遞迴實現)
快速排序的基本思想是在待排序序列中找到一個基準值(一般取待排序序列的最後一個元素),然後將該基準值放置在一個合適的位置,使得在基準值之前的元素都小於等於基準值,基準值之後的元素都大於等於基準值。
然後再對基準值之前的序列使用上述方法進行排序尋找基準值位置,對基準值之後的序列使用上述方法進行排序尋找基準值位置。所以這是一個遞迴的過程。
根據這個合適的位置尋找的方式可以分為兩種方法來實現。一種是交換法,一種是挖坑法。
1. 交換法實現快速排序
排序思路如下:
所以將上述的過程1封裝為一個函式,該函式的實現就是不斷的交換的過程,因此及該方法也成為交換法:
(1)首先對待排序序列使用該函式,使基準值之前的元素小於等於基準值,基準值之後的元素大於等於基準值,同時返回基準值所在的下標位置left;
(2)然後對left之前的序列進行(1)~(3)的過程;
(3)再對left之後的序列進行(1)~(3)的過程;
所以,上述的(1)~(3)可以作為遞迴函式的過程,當待排序序列的元素個數為小於等於1時,遞迴函式返回,不用再進行(1)~(3)的過程。
注意:
1. 如果選取的基準值為最後一個元素,則right指標先走,left指標後走。(無論升降序)
2. 如果選取的基準值為第一個元素,此時left指標就要先走,right指標後走(無論升降序)。
總之,無論是升序排序,先走的指標為與基準值對立的位置。
程式碼實現如下:
void QuickSort(int arr[],int size)
{
if(arr == NULL || size <= 1)
{
return;
}
int left = 0;
int right = size;
_QuickSort(arr,left,right);
}
//遞迴函式 void _QuickSort(int arr[],int left,int right) { if(arr == NULL || right - left <= 1) { return; } //先找一個基準值,這裡設定陣列最後一個元素基準值 //找到放置基準值的位置,使得在該位置之前的陣列元素小於等於基準值 //在該位置之後的陣列元素大於等於基準值 int mid = Pattern2(arr,left,right); _QuickSort(arr,left,mid); _QuickSort(arr,mid + 1,right); }
交換函式為:
//交換法
int Pattern1(int arr[],int left,int right)
{
//該函式用於給基準值找一個合適的位置,
//使得在基準值之前的元素都小於等於基準值
//在基準值之後的元素都大於等於基準值
//所以要先從前往後遍歷使陣列前面的元素小於等於基準值
//從後往前遍歷使後面的元素大於等於基準值
//當兩個遍歷索引相遇時,一定找到了小於基準值和大於據準值元素的分界點
//此時將分界點處的值替換為基準值即可保證上述的要求
//如果陣列只有一個元素,則基準值直接放置在起始位置即可
if(right - left <= 1)
{
return left;
}
//從起始位置開始往後遍歷尋找大於基準值的陣列元素
int left_index = left;
int right_index = right - 1;
//從末尾位置開始往前遍歷尋找小於基準值的陣列元素
//將基準值設定為所在區間的最後一個元素值
int basic_value = arr[right - 1];
while(left_index < right_index)
{
//如果從前往後找到大於基準值的元素,就停下來
while(left_index < right_index && arr[left_index] <= basic_value)
{
left_index++;
}
//如果從後往前找到小於基準值的元素,就停下來
while(left_index < right_index && arr[right_index] >= basic_value)
{
right_index--;
}
//交換停下來的兩個值
Swap(&arr[left_index],&arr[right_index]);
}
Swap(&arr[left_index],&arr[right - 1]);
return left_index;
}
2. 挖坑法實現快速排序
該方法在實現是:
(1)先將基準值之前的元素小於等於基準值,之後的元素大於等於基準值。
(2)然後對基準值之前的序列呼叫遞迴函式
(3)最後對基準值之後的序列呼叫遞迴函式。
只是在(1)的實現上與上述的交換法不同,如下圖:
所以上述過程可以實現為:
//挖坑法
int Pattern2(int arr[],int left,int right)
{
if(right - left <= 1)
{
return left;
}
int left_index = 0;
int right_index = right - 1;
//基準值為帶排序序列的最後一個元素
int basic_value = arr[right - 1];
//當left_index和right_index沒有相遇時
while(left_index < right_index)
{
//如果沒有相遇且left_index遇到小於等於基準值的元素,則left_index後移
while(left_index < right_index && arr[left_index] <= basic_value)
{
left_index++;
}
//如果沒有相遇且left_index遇到大於基準值的元素
if(arr[left_index] > basic_value)
{
//將該元素賦值到坑所在位置
arr[right_index] = arr[left_index];
}
//同理
while(left_index < right_index && arr[right_index] >= basic_value)
{
right_index--;
}
if(arr[right_index] < basic_value)
{
arr[left_index] = arr[right_index];
}
}
//當left_index與right_index相遇時,一定位於坑所在的位置
//此時,將基準值賦值到該處即可
arr[left_index] = basic_value;
//返回基準值放置的位置
return left_index;}
}
非遞迴實現快速排序
非遞迴的實現與上述思路相同。都是相對整個序列進行交換法或挖坑法進行處理,然後對基準值之前的序列進行相同的處理,在對基準值之後的序列進行處理。
因為在對基準值之前或之後的序列進行處理時,還可能再分為兩部分的序列,所以這裡藉助棧來儲存待處理序列的首尾下標:
(1)將整個待排序序列首尾下標入棧;
(2)取棧頂元素,如果棧頂元素為空,說明待排序序列處理完了,此時直接返回;
如果不為空,則該棧頂元素必為帶排序序列的尾座標。出棧後,再取棧頂元素,該棧頂元素必為待排序序列的首座標,然後 再出棧,如果待排序序列的元素個數小於等於1,此時不用再進行下面的處理,重新回到(2)進行新一輪的處理;
(3)將上述的兩個首尾座標利用交換法或挖坑法進行處理,得到基準值的座標;
(4)將基準值之前的序列首尾座標分別入棧;
(5)將基準值之後的序列首尾座標分別入棧;
(6)重複(2)~(5)的操作。
以下有關的棧的操作實現見:這篇部落格
根據上述思路,程式碼實現如下:
void QuickSortByLoop(int arr[],int size)
{
if(arr == NULL || size <= 1)
{
return;
}
SeqStack stack;//定義棧
InitSeqStack(&stack);//初始化棧
//入棧整個待排序序列的首位座標
SeqStackPush(&stack,0);
SeqStackPush(&stack,size);
int left;
int right;
while(1)
{
//取棧頂元素作為帶排序序列的尾座標
int ret = SeqStackTop(&stack,&right);
if(ret == -1)//棧定為空,帶排序序列處理完
{
return;
}
//出棧後再取棧頂元素
SeqStackPop(&stack);
//此時的棧定元素必為帶排序序列的首座標
SeqStackTop(&stack,&left);
//出棧棧頂元素
SeqStackPop(&stack);
//如果帶排序序列元素個數小於等於1,則該序列必然已經排好序
//且不能在分為更小的序列,也就不需要再進行排序再進行入棧了
if(right - left <= 1)
{
continue;
}
//對帶排序序列進行排序處理
int mid = Pattern1(arr,left,right);
//將帶排序序列分為兩部分分別入棧,進行處理
SeqStackPush(&stack,left);
SeqStackPush(&stack,mid);
SeqStackPush(&stack,mid + 1);
SeqStackPush(&stack,right);
}
return;
}
快速排序的時間複雜度為:O(nlogn)(平均時間複雜度),O(n^2)(最壞時間複雜度)
最快空間複雜度:O(n)
穩定性:不穩定