快速排序逐行代碼精析

快速排序逐行代碼精析

1.定義

快速排序有三要素:

  • 分區算法【如何將一個無序的數組分區,纔是快速排序的關鍵】
  • 遞歸【分治法解決問題】
  • 合併問題【其實不用合併數組,因爲數組在遞歸的時候就已經完全有序了】

2.算法思想

  • 利用分治的方法【表現形式是遞歸】將一個數組由一個樞軸元素分成兩部分。樞軸的左部分比樞軸元素小,樞軸元素的右部分比樞軸元素大。 【正確的描述應該是:樞軸左邊的元素不比樞軸大,樞軸右邊的元素不比樞軸小。】
  • 取某無序數組區間的第一個元素【下標爲 0 (不一定是0,樞軸元素下標是low,只有第一次遞歸時,纔是0)開始計算】爲樞軸元素,記爲pivotKey
  • 初始時:low = 0,high = array.length-1【是否減一,則要看數組的存儲是從0開始,還是1開始。】;
  • high開始依次向左依次遍歷數組,如果low<high && array[high] > pivotKey,則執行high--;反之,退出while循環;
  • low開始依次向右遍歷數組,如果low<high && array[low] < pivotKey,則執行low++;反之,退出while循環;
  • 判斷low是否小於等於high,若是,則將array[low]array[high]交換;否則,不交換。
  • low==high時,一輪排序結束,返回當前樞軸元素所在的下標。
  • 樞軸元素下標左部分和右部分的無序數組再次進行排序。直至全部完成。

3.代碼

3.1 僞代碼

因爲僞代碼好理解,方便記憶,所以先給出文字版的僞代碼,然後再給出具體代碼。

  • 僞代碼
#include<iostream>
using namespace std;

int arr[maxN];//初始數組 

//0.待排序的區間是 [low,high] 
void quickSort(int low,int high){
	/*step 1.臨界返回條件 		
		01.如果只有一個元素(low==high),就不用排序了,直接返回就可以了。 
		02.如果區間low甚至比high還要大,肯定也是要返回的。出現這種情況的原因是:
			每次排序只將一個元素正確的放到位置上。如果對於原本就有序的序列,在將high變成i-1時,就可能比low小了
		(同樣的道理,i+1可能比high大)
	*/
	if(low >= high)
		return ;
	
	/*
	step 2.移動元素,做區間的劃分
	   01.選樞軸元素,做左右區間的劃分
	   02.【重要】不能污染low,high,因爲後面還要遞歸調用。所以賦值爲i,j即可。 
	   03.【重要】使用三個while循環,每次循環成立的條件都必須有 i<=j。
	   同時需要注意,左側不小於時停止while循環,右側不大於時停止while循環。
	   在代碼中的體現就是,while循環中的條件沒有等於號。
	   04.只有在i <= j時,才交換兩個數 (等於號可以不要)
	*/
	
	/*
	step 3.遞歸對左右子區間排序 
	*/	
	quickSort(low,i-1);
	quickSort(i+1,high);
}
  • 具體代碼模板【記下來!】
#include<iostream>
using namespace std;
const int maxN = 100;
int arr[maxN];//初始數組 
void quickSort(int low,int high){
	if(low >= high)
		return ;

	int pivotKey = arr[low];	
	int i = low,j = high;
	while( i < j) {		
		while(arr[i] < pivotKey && i<=j ) {
			i++;
		}
		while(arr[j] > pivotKey && i<=j ){
			j--;
		}
		if(i<j){			
			int temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;		
		} 		
	}
	quickSort(low,i-1);
	quickSort(i+1,high);
}


int main(){
	int n;
	cin >> n;
	for(int i = 0;i< n;i++)
		cin >> arr[i];
	quickSort(0,n-1);
	
	for(int i = 0;i<n;i++)
		cout << arr[i]<<" ";
	cout <<"\n";
}

沉痛的面試教訓

  • 【20200621】 我大概在20200518號面試了一家公司,當時面試官讓我手寫快排,那真是一個字的尬!我寫成出了shit一樣的代碼。這樣的事情發生不止一次了,在我記憶裏至少有兩個面試官問過我快排的問題了,而我答得都不是很好。希望自己基礎知識能夠牢牢掌握。

#include <iostream>
using namespace std;
const int N = 5;
int arr[N] ={3,4,1,2,5};

void quickSort(int left, int right){
  if(left > right) 
      return ;
  int mid = (left+right)/2;
  int tl = left,tr = right;//temp 
  while(tl <= tr){
    //1!!!!
    while(arr[tl] < arr[mid] )
      tl++;//不一定執行
    
    while(arr[tr] > arr[mid] )
      tr--;
    
    if(tr >= tl){//swap
      int temp;
      temp = arr[tl];      
      arr[tl] = arr[tr];
      arr[tr]=temp;
    }
  }
  quickSort(left,mid);
  quickSort(mid+1,right);
}
  

int main()
{
    quickSort(0,4);
    for(int i = 0;i< 5;i++)
      cout << arr[i] << " ";
    return 0;
}

上面這段程序至少存在如下幾個問題:
bug1
快排是根據樞軸值將區間分成兩個子區間。

int mid = (left+right)/2;
……
while(arr[tl] < arr[mid])
	tl++;
if(tr >= tl){
	//交換操作...
}

這段代碼裏會不會出現:本身的arr[mid] 被換到了另外一個位置? 那麼問題就顯現出來:我們根據某個值劃分區間,結果這個值是個變量!!。這樣就無法把樞軸元素放到正確的位置上了。

bug2
程序會陷入死循環。

while(tl <= tr){
  while(arr[tl] < arr[mid] )
    tl++;//不一定執行
  
  while(arr[tr] > arr[mid] )
    tr--;
  
  if(tr >= tl){//swap
    int temp;
    temp = arr[tl];      
    arr[tl] = arr[tr];
    arr[tr]=temp;
  }
}

這段while 代碼會讓程序陷入死循環。假設現在有如下區間
1 4 3,樞軸元素取1,tl = 0, tr = 2。 按照上面這個代碼,會得到tl=tr=1處的死循環,原因就在於 while(tl<=tr) 這個條件

bug3
對與分好的區間,我們接着分其子區間即可。

quickSort(left,mid-1);
quickSort(mid+1,right);

但是上面的代碼則暴露了一個問題:我並不是真正瞭解快速排序。我竟然把左區間的右端點寫成了mid-1天理難容!! 經過上面這個while循環,我們已經將區間根據樞軸值 pivot 分成了左右兩個部分,這個while終止的條件也一定是 tl=tr,即arr[tl]的值就是樞軸變量的值。 那麼我們的區間應該分成:

quickSort(left,tl-1);
quickSort(tl+1,right);

至此,運行一下修改後的快排,貌似可以得到正確答案了。

#include <iostream>
using namespace std;
const int N = 5;
int arr[N] ={3,4,1,2,5};
//int arr[N] ={3,4,1,2,1};

void quickSort(int left, int right){
    if(left > right)
        return ;
    int mid = (left+right)/2;
    int tl = left,tr = right;//temp
    int pivot = arr[mid];//隨機一下=>取中間的值
    while(tl < tr){
        while(arr[tl] < pivot )
            tl++;//不一定執行
        while(arr[tr] > pivot )
            tr--;

        if(tr >= tl){//swap
            int temp;
            temp = arr[tl];
            arr[tl] = arr[tr];
            arr[tr]=temp;
        }
    }
    quickSort(left,tl-1);
    quickSort(tl+1,right);
}

int main()
{
    quickSort(0,4);
    for(int i = 0;i< 5;i++)
        cout << arr[i] << " ";
    return 0;
}

但是如果將arr[]數組替換成 int arr[N] ={5,4,1,2,1};,則發現程序又進入了死循環!!又是爲什麼呢?

看看下面這組測試數據,其就會得到一個死循環的結果:
在這裏插入圖片描述
這裏死循環的原因在於有相同的數字1,爲什麼有相同的數字就導致死循環呢? 因爲我們的第二重while循環存在問題。

while(tl<tr)
{
	...
	if(){
		... // 程序就是在這裏出現了問題!! 
	}
}

上面程序的問題就是:在相等的情況下,如果只做交換,那麼“指針”位置相當於沒有移動,所以就導致指針不再往希望方向移動,基於此,可以把代碼修改爲如下

while(tl<tr)
{
	...
	if(){
		... // 程序就是在這裏出現了問題!! 
		tr--; 
		tl++; //往後移動一下
	}
}

最後再給出一份沒有bug的代碼:

==========
// Created by lawson on 20-6-21.
#include<iostream>
using namespace std;
int arr[5] = {3,4,1,2,1};
//int arr[5] = {4,3,1,2,5};
//int arr[3] = {1,2,1};

int partition(int left,int right){
    int pivot = arr[left];//01.使用區間左端點作爲樞軸
    //02.遞歸邊界是left < right => 說明最後在退出while循環時會得到:left=right
    while(left < right){
        /*03.思考爲什麼去掉等於號就不行了?
         原因是:會導致死循環。
         如果不在while()循環中越過相同值,那麼就應該在交換值的時候越過它;[上面那份代碼]
         如果在while()循環中越過相同值,那麼後面在交換指的時候就可以不用執行right-- 和 left++了【本代碼】

         04.必須保證先從右往左,因爲arr[left]已經保存放到pivot中了,可以覆蓋;
         但是arr[right]卻是不能覆蓋的
         * */
        while(left < right && arr[right]>=pivot) //從右往左找第一個比它小的數
            right--;
        arr[left] = arr[right];
        /*
         01.程序運行到這裏的時候,說明 right 這個位置的數已經可以被覆蓋了,因爲它已經把數給arr[left]了,
            所以後面可以開始對 left 進行一個 while 操作。直到找到一個 “合適的位置” 將left 賦值到right 中。 此時left 要麼小於right ,要麼等於right。無論在哪種條件下都是滿足交換條件的。
         */

        while(left < right && arr[left]<=pivot)//從左往右找第一個比它大的數
            left++;
        arr[right] = arr[left]; // => 會默認保證right > left
    }
    arr[left] = pivot; //將pivot 的值放在一個正確的位置上
    return left;//這個位置就是一個後面區間的分界點
}

void quickSort(int left,int right){
    if(right <= left)
        return ;
    int mid = partition(left,right);
    quickSort(left,mid-1);
    quickSort(mid+1,right);
}

int main(){
    int n = 5;
    quickSort(0,n-1);
    for(int i = 0;i< n;i++){
        cout << arr[i]<<" ";
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章