交換排序 快速排序

2021-06-21 23:57:45 字數 4746 閱讀 7510

快速排序(quick sort)也是一種交換排序,它在排序中採取了分治策略。

從待排序列中選取一元素作為軸值(也叫主元)。

將序列中的剩餘元素以該軸值為基準,分為左右兩部分。左部分元素不大於軸值,右部分元素不小於軸值。軸值最終位於兩部分的分割處。

對左右兩部分重複進行這樣的分割,直至無可分割。

從快速排序的演算法思想可以看出,這是一遞迴的過程。

要想徹底弄懂快速排序,得解決兩個問題:

如何選擇軸值?(軸值不同,對排序有影響嗎?)

如何分割?

問題一:軸值的選取?

軸值的重要性在於:經過分割應使序列盡量分為長度相等的兩個部分,這樣分治法才會起作用。若是軸值正好為序列的最值,分割後,元素統統跑到一邊兒去了,分治法就無效了。演算法效率無法提高。-看別人寫快排的時候,注意他軸值的選取哦。

問題二:如何分割?

這涉及到具體的技巧和策略。在稍後的**中我們一一介紹。

直接選取第乙個元素或最後乙個元素為軸值。這也是國內眾多教材中的寫法。

舉個例子:

原序列   4   8   12   1   9   6

下標     0   1   2    3   4   5   

軸值 pivot=4

初始化   i                    j

i            j           i不動,移動j,while(i=pivot)j--; 

移動元素 

1   8   12   1   9   6

i        j           j不動,移動i,while(i

移動元素 1   8   12   8   9   6

i,j                   再次移動j,i和j相遇,結束

最後一步 1   4   12   8   9   6   pivot歸位

軸值4的左右兩部分接著分割……

我想你一定看懂了,並且這軸值4,真的沒選好,因為分割後左部分只有乙個元素。

有人稱上面的做法是:挖坑填數。這種描述真的很形象。簡單解釋下:首先取首元素為軸值,用變數pivot儲存軸值,這就是挖了乙個坑。此時,a[0]就是乙個坑。接著移動j,把合適位置的j填入a[0],於是a[j]成了新的坑。舊的坑被填上,新的坑就出現。直到i和j相遇,這最後乙個坑,被pivot填上。至此完成了第一趟分割……

看懂了,就動手敲**吧!

void quicksort(int a, int n)  //快速排序,版本一

a[i] = pivot; //把軸值放到分割處

quicksort(a, i);

quicksort(a + i + 1, n - i -1);

}}

現在想想以最後乙個元素為軸值的**了,先別急著看,先動動手哦!**如下:

void quicksort(int a, int n)

a[i] = pivot; //把軸值放到分割處

quicksort(a, i);

quicksort(a + i + 1, n - i - 1);

}}

為了讓軸值pivot不至於無效(不讓pivot出現最值的情況)。我們可以使用一些策略來改進pivot的選取。

策略一:

隨機選取序列中一元素為軸值。

int selectpivot(int a, int low, int high)

選取首尾元素不就是該策略的一種特例!

策略二:

隨機選取三數,取中位數。

int selectpivot(int a, int low, int high)

它的一種特例就是,選取原序列首、

尾、中間三數,取它們的中位數。

目前看來基本常用的就這兩種策略。

不過我得吐槽一句:如果原序列中的元素本身就是隨機存放的,也就是說,各個元素出現在各個位置的概率一樣。那麼特別地選取首尾元素和隨機選取又有什麼區別呢?不知大家怎麼看?

還得補充一句:隨機選取軸值後,記得要把它和首或尾的元素交換哦。至於為什麼?***!

這也是《演算法導論》上的版本。它的普遍做法是選取尾元素為pivot。重點是使用了乙個分割函式:partition()。

偽**與如下:

partition(a, low, high)

1. pivot <- a[high]    //選取尾元素為軸值

2. i <- low-1          //把low-1賦值給i,下同

3. for j <- low to high-1    //j的變化範圍[low, high-1]

4.      do if a[j] <= pivot

5.            then i <- i+1

6.            exchange a[i]<->a[j]

7. exchange a[i+1} <

-> a[high]

8. return i+1;    //返回的是分割的位置

然後,對整個陣列進行遞迴排序:

quicksort(a, low, high)

1  if low < high

2  then q <- partition(a, low, high)  //對元素進行分割就在這裡

3  quicksort(a, low, q - 1)

4  quicksort(a, q + 1, high)

如果你不習慣於看偽**,我來舉個例子:(還是上面的序列)

原序列   4   8   12   1   9   6

下標  -1 0   1   2    3   4   5   軸值pivot是6

初始化 i j                        a[j]=a[0]=4<6,下一步先 i++;再swap(a[i],a[j]);隨後j++;

交換     4   8   12   1   9   6

i   j                    接著移動j

i            j           a[j]=a[3]=1<6,下一步…

交換     4   1   12   8   9   6

i            j       

i                j   

交換     4   1   6    8   9   12  最後一步 swap(a[i+1], a[high]);或者是 swap(a[i+1], a[j]);

所以最後返回的是 i+1

用大白話講講上面的排序過程:用兩個指標i,j,它們初始化為i=-1;j=0,接著讓j不斷地自增,遇到a[j]>pivot就與i交換,直到j指向末尾。

更直白的話:從頭開始遍歷原序列,遇到小於軸值的就交換到序列前面。

看懂了,就寫**了…

int partition(int a, int low, int high)

swap(a[++i], a[high]); //主元歸位

return i; //上面一步已經 ++i,所以這裡不用 i+1

}void quicksort(int a, int low, int high)

}void quicksort(int a, int n)

題外話:看到有的api設計是這樣的:quicksort(int a, int low, int high)。居然讓使用者多寫乙個0!如此不為使用者考慮。應越簡潔越好。排序只給陣列名和陣列大小,即可。

對上面的流程再思考:看到初始化i=-1;你不覺得奇怪嗎?為什麼i一定要從-1開始,仔細了解了i的作用,你會發現i本可以從0開始。這種做法的partition()方法是這樣的:

int partition(int a, int low, int high)

swap(a[i], a[high]); //主元歸位

return i;

}

再思考:為什麼j不能指向high?若是更改if(a[j]

此時的partition()是這樣的:

int partition(int a, int low, int high)

return i-1; //這裡為什麼是i-1,得想明白?

}

至於有時候把quicksort()和partition()寫成乙個函式,那是再簡單不過的事情,你肯定會的。

上面用的都是遞迴的方法,把遞迴轉化非遞迴總是不簡單的,也總讓人興奮。這個版本就是快速排序的非遞迴寫法;

void quicksort(int a, int low, int high)

if (mid + 1 < high)

//只要棧不為空,說明仍有可分割的部分

while(!s.empty())

if (mid + 1 < h)

} }}

這個非遞迴的寫法是很有意思的,很需要技巧。仔細想想,你能明白的。

快速排序號稱快速搞定,時間複雜度是o(nlogn)。基本上是最優的排序方法。它的寫法不外乎以上三種,大同小異。看到這裡。你一定徹底了解了它。以上寫法,都經過了本人測試,不知道你的測試是否和我一樣?

若是有所幫助,頂乙個哦!

本專欄的目錄

所有內容的目錄

交換排序 快速排序

簡述 快速排序可以說算是針對氣泡排序的一種優化,氣泡排序是順序交換,這樣交換次數順序增長。如果我們做跳躍的交換,那麼可以使得交換次數也是跳躍性,會有所降低 演算法思想 找出乙個樞軸,用於做比較交換,並記下後用。一般用第乙個 用第 一 中間 最後三個取中後來效果會更好 定乙個高位 high和底位 lo...

交換排序之快速排序

1.基本思想 假設要排序的陣列是array 0 array n 1 首先任意選取乙個資料 通常選用第乙個資料 作為關鍵資料,然後將所有比它的數都放到它前面,所有比它大的數都放到它後面,這個過程稱為一趟快速排序。一趟快速排序的演算法是 1 設定兩個變數i j,排序開始的時候i 0,j n 1 2 以第...

交換排序 2 快速排序

快速排序 快速排序的基本演算法思想是 設待排序的元素序列個數為 n,存放在陣列 a中,令第乙個元素為樞軸元素,即將 a 0 作為參考元素,令 pivot a 0 初始時,令 i 0 j a.legth 1 然後按以下方法操作 1 從序列的 j位置往前,依次將陣列的中的元素與樞軸元素相比較,如果當前元...