排序算法之快速排序(Quicksort)解析


一.快速排序算法的優點,為什么稱之為快排?

Quicksort是對歸並排序算法的優化,繼承了歸並排序的優點,同樣應用了分治思想。

所謂的分治思想就是對一個問題“分而治之”,用分治思想來解決問題需要兩個步驟:

1.如何“分”?(如何縮小問題的規模)

2.如何“治”?(如何解決子問題)

快排的前身是歸並,而正是因為歸並存在不可忽視的缺點,才產生了快排。歸並的最大問題是需要額外的存儲空間,並且由於合並過程不確定,致使每個元素在序列中的最終位置上不可預知的。針對這一點,快速排序提出了新的思路:把更多的時間用在“分”上,而把較少的時間用在“治”上。從而解決了額外存儲空間的問題,並提升了算法效率。

快排之所以被稱為“快”排,是因為它在平均時間上說最快的,主要原因是硬件方面的,每趟快排需要指定一個“支點”(也就是作為分界點的值),一趟中涉及的所有比較都是與這個“支點”來進行比較的,那么我們可以把這個“支點”放在寄存器里,如此這般,效率自然大大提高。除此之外,快排的高效率與分治思想也是分不開的。

二.算法思想

按照快排的思想,對一已知序列排序有如下步驟:

1.指定“支點”

注意,是“指定”,並沒有明確的約束條件,也就是說這個支點是任意一個元素,一般我們選擇兩種支點:當前序列首元,或者隨機選取

兩種方式各有優劣,前者勝在簡單,但可能影響算法效率

快排中,支點的最終位置越靠近中間位置效率越高,讀起來可能有點怪怪的,注意支點是一個值(具體元素),而不是字面意思的位置,當支點在最終序列中的位置靠前或者靠后時算法效率都不高(類似於“最壞情況”)

因此,后者在一定程度上減少了最壞情況的發生次數,但隨機選取需要耗費額外的時間

所以在具體應用中一般采用第一種方式來指定“支點”,也就是直接把當前序列的首元作為“支點”

2.進行一趟快排

快排中,一趟操作的最終目的是把“支點”放到它應該去的地方,舉個例子,已知序列{7, -1, 5, 23, 100, 101},那么第一趟快排的結果是{_, _, 7, _, _, _}

可以看到,首元(支點)已經去了它該去的地方(在最終的結果序列中,7就在中間位置,沒錯吧)

3.對子序列進行快排

第2步不僅確定了7的最終位置,還把原序列自然地划分為兩個子序列{_, _}和{_, _, _},這里用"_"代替具體的數值,因為我們也不知道第2步的結果具體是什么,除非真正地做一趟快排,當然,在這里不必要,下面會有針對具體例子的詳細解釋

很自然的我們想到了對子序列進行同樣的操作,然后對子序列的子序列再進行同樣的操作...遞歸

當所有的子序列長度都為1的時候,排序結束

三.具體實例

現有一序列{9, 0, 8, 10, -5, 2, 13, 7},我們用快速排序算法來對其排序

首先,聲明一些特殊的記號,便於描述

a, 數字后面跟的大寫字母表示指針,例如2.5P表示指針P指向元素2.5所在的位置

b, @表示垃圾數字,也就是說,當前位置是幾都無所謂,不必糾結於此,后面會有具體解釋

c, _表示該位的元素與上一行一樣(_表示不變)

-------

P.S.想要真正弄明白的話,現在拿出紙和筆吧,光靠眼睛是絕對不夠的

下面正式開始一趟快排的過程解析

【1】9L  0  8  10  -5  2  13  7H

【2】7  0L  _  __  __  _  __  @H

【3】_  _  8L  __  __  _  __  __

【4】_  _  _  10L  __  _  __  __

【5】_  _  _  @L  __  _  13H  10

【6】_  _  _  __  __  2H  13  __

【7】_  _  _  2  -5L  @H  __  __

【8】_  _  _  _  -5  @HL  __  __

【9】_  _  _  _  __  9HL  __  __

解釋:

1.第一行是初始狀態,快排需要兩個指針L和H(表示低位Low,高位High),一個臨時變量temp

初始時,低位指針L指向首元9,高位指針H指向尾元7,temp=首元9(temp就是所謂的”支點“)

2.進行如下操作:(先不要問為什么)

比較*H與temp,若*H大,則向前移動H繼續比較,若*H小,則*L = *H,*H = @(H指向的值變成垃圾數字了),向后移動L

因為7 < 9,所以把L指向的9變成7,把H指向的7變成垃圾數字,向后移動L指針,得到第二行的結果

3.進行如下操作:(先不要問為什么)

比較*L與temp,若*L小,則向后移動L繼續比較,若*L大,則*H = *L,*L = @(L指向的值變成垃圾數字了),向前移動H

因為0 < 9,所以向后移動L,得到第三行的結果

4.因為8 < 9,同上

5.因為10 > 9,所以把H指向的垃圾數字@變成10,把L指向的10變成垃圾數字,向前移動H指針,得到第5行的結果

6.因為13 > 9,所以向前移動H指針,得到第6行的結果

7.因為2 < 9,所以把L指向的垃圾數字@變成2,把H指向的2變成垃圾數字,並向后移動L指針,得到第7行的結果

8.因為-5 < 9,所以向后移動L指針得到第8行的結果

9.進行如下操作:(先不要問為什么)

若L = H,則*L = *H = temp,一趟快排結束

因為L指針與H指針重合了,所以把L指向的垃圾數字@變成temp的值9,一趟結束

至此,我們確定了支點9的最終位置,給定序列也被自然的分為兩個子序列{7, 0, 8, 2, -5}和{13, 10},對子序列進行相同的操作,最終能夠得到有序序列

-------

下面來解釋上面提到的三組操作

簡單的說,上面的三組操作上為了找出temp的最終位置,每一步都保證L前面都比temp小,H后面都比temp大。所以,H與L重合的位置上的元素只能是temp的值了

上面提到的三組操作可以簡化成下面的幾條規則,便於記憶:

1.L指向的值小則L移動,反之賦值並移動指針

2.H指向的值大則H移動,反之同上

3.若HL重合,則賦值temp

4.H,L輪流與temp比較,規則是賦值一次后算一輪結束(所以一開始也可以從L與temp開始比較,下一輪H與temp比,再下一輪...)

P.S.至於怎么移動,自然是低位指針只能向高位移動,反之亦然。至於賦值后移動哪個指針,當然是另一個指針(非當前指針)了

四.總結

排序算法的應用都需要結合具體環境來考慮,例如若給定序列部分有序,自然是折半插入算法最快...

快速排序也並不是最好的,它的”快“是建立在綜合考慮的基礎上,具體情況則不一定

快速排序也不是萬能的,例如當給定序列規模很小時,選擇排序就要比快排好很多

另外,常見的排序算法有:

1.桶排序/箱排序(Bucketsort)

2.基數排序(Radixsort)

3.插入排序(Insertsort)

4.選擇排序(Selectsort)

5.歸並排序(Mergesort)

6.快速排序(Quicksort)

7.堆排序(Heapsort)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM