1. 程式人生 > >細致分析快速排序!

細致分析快速排序!

一次 quick 移動 blog 一個 -1 升序 幫助 剛才

快速排序是當下面試最容易考的題之一。初學算法基礎的夥伴一定飽受它的折磨,因為快速排序的思想讓初學者有一些發懵。
我寫下這篇文章談一談自己對快速排序的理解,希望能夠幫助初學者理解一下快速排序算法的流程。

快速排序的思想:
我要探討的思想呢,不是教科書上的,而是我自己按容易理解的方法進行了改編。
請大家跟著我的思路:

宏觀的來看:
1 我們拿出第一個數當作基準數,把所有比他小的數放在他左邊,所有比他大的數放在右邊
2 對mid左側所有的數當成一個新的數列 進行上述排序
3 對mid右側所有的數當成一個新的數列 進行上述排序

一個大數列,一點一點被我們分的越來越小,最後找到一個數做mid排完之後,左側就剩下一個數,右側就剩下一個數,就排完了


具體怎麽實現找到基準數位置的呢??
我們拿到一個數列有n個數, 我們假設面前擺了n個水桶,每個水桶裏面有一個數,現在數是亂序的,我們要不移動水桶,而把數取出來排序。
我們把n個水桶從左到右編號分別為0到n-1
ok!!!請大家跟著我的步驟哦~不要掉隊!!
1 把0號水桶的數拿在手裏, 這個數,我們把它當作基準數mid
2 拿一個名字叫做high的水瓶子當作標記,從最右側n-1號桶開始往左找,找到第一個比基準數mid小的數的時候,我們把這個數取出來,放在0號位置,把high放進這個水桶裏,標記一下現在這個水桶空了。
3 拿另一個名字為low的水瓶子當作標記,從剛才把high所在桶取出的數扔進的那個桶的下一個桶(也就是1號)開始向右找,找到第一個比mid大的數,我們把low水瓶扔進去把數取出來,把這個數扔進high水瓶所在的桶裏。

4 從剛剛取出high的水桶的左邊一個水桶開始向左找,找到第一個比基準數mid小的數,把high瓶子扔進去把數取出來,然後把數扔進low瓶子所在的桶裏,把low瓶子取出來
5 從剛取出low的桶右邊第一個桶開始向右找,找到第一個比基準數mid小的數,把low扔進桶,把數取出來放進high瓶子所在桶裏,把high瓶子取出來
6 從剛取出high的桶左邊第一個桶向左找,找到第一個比基準數mid大的數,把high扔進去,把數取出來放到low所在的桶裏,把low拿出來
7 從剛取出low的桶右側第一個桶向右找,找到第一個比基準數mid小的數,把low扔進去,把數取出來放進high瓶子所在桶裏,把high取出來

.......
我們發現從2到7 一直在做相同的工作,右側的high水瓶子在一點一點向左推進,左側的low水瓶子在不斷向右推進
最後 low和high兩個水平碰面了,我們把mid基準數放到這個碰面的水桶裏
此時,mid左側的數全都比mid小
mid右側的數全都比mid大

然後,對mid 左側所有的數當作一個新的數列,進行剛剛的排序
再對mid右側所有的數當作一個新的數列,進行剛剛的操作

這樣一直重復下去,數列就被排好了。

我們簡化一下剛剛的步驟:
1 把第一個數當作基準數mid拿出來,0號位置空了
2 從右側向左找第一個比基準數小的數記錄位置high,把數拿出來放到mid所在位置上,high位置空了
3 mid右側第一個向右找第一個比基準數大的數記錄位置low,把數拿出來放到high空位上, 這時候左邊low位置空了
4 從high左邊第一個數向左找,找到第一個比mid小的數high重新記錄位置,把數取出來放到low位置上,high位置空了
5 從low右側第一個開始向右找,找到第一個比mid大的數low重新記錄位置,把數取出來放到high位置上,low位置空了
....之後我們一直重復
low一點點向右,high一點點向左
當low和high碰頭的時候,我們把mid放在碰頭位置上
這時候形成的局面就是,mid左側的數全都比mid小,mid右側的數全都比mid大
然後我們把mid左側所有數當成新的數列進行上述排序
把mid右側所有數當成新的數列進行上述排序

這樣一直重復下去,每次數列都被裁剪成更小的數列,一直到我們拿出第一個數做基準數,我發現右側沒有數左側也沒有數,這個數列排序結束了


但是我們剛剛忽略了一個嚴重的問題!!!!我們把比mid小的數放在左側,把比mid大的數放在右側了!那和mid相等的呢????
1 如果在兩邊遇到mid相等的數不操作,結果就是一個升序列插入了mid 並且mid不挨著
2 如果我們在兩側遇到mid相等的數,都移動到另一側,最後 還是和1 發生同樣的結果
所以怎麽辦!!??
其實我們只要規定好,如果左邊遇到和mid相等的就移動到右面,而右面遇到mid相等的數,我們不移動
或者 在右面遇到和mid相等的數移到左邊,左邊遇到和mid相等的數,不移動
這兩種方式都可以! 我們保證和mid相同的數在一側就可以,這樣排序到最後與mid相等的數就會挨著了!

時間復雜度:
最壞時間復雜度: O(n^2) 我們假設拿到一個正序,每次拿到的基準數比較之後都沒移動,所有的數都參與了比較,那就相當於每個數都跟其他所有數比了一次
最優時間復雜度: O(n log n)
我們粗略理解成每次把數列分成兩半的話,一共會分多少次呢?就看長度n除以2 要除x次後n為1,x=log n
每次分開的數列n個數每個都跟基準數比了一次 所以粗略看成 是 n log n
穩定性: 不穩定
從我們剛才探討的,碰到和基準數相等的問題上就能看出快速排序不穩定



我們上代碼!!!
在python中這樣實現
 1 #快速排序 傳入列表、開始位置和結束位置
 2 def quick_sort( li , start , end ):
 3     # 如果start和end碰頭了,說明要我排的這個子數列就剩下一個數了,就不用排序了
 4     if not start < end :
 5         return
 6 
 7     mid = li[start] #拿出第一個數當作基準數mid
 8     low = start   #low來標記左側從基準數始找比mid大的數的位置
 9     high = end  #high來標記右側end向左找比mid小的數的位置
10 
11     # 我們要進行循環,只要low和high沒有碰頭就一直進行,當low和high相等說明碰頭了
12     while low < high :
13         #從high開始向左,找到第一個比mid小或者等於mid的數,標記位置,(如果high的數比mid大,我們就左移high)
14         # 並且我們要確定找到之前,如果low和high碰頭了,也不找了
15         while low < high and li[high] > mid :
16             high -= 1
17         #跳出while後,high所在的下標就是找到的右側比mid小的數
18         #把找到的數放到左側的空位 low 標記了這個空位
19         li[low] = li[high]
20         # 從low開始向右,找到第一個比mid大的數,標記位置,(如果low的數小於等於mid,我們就右移low)
21         # 並且我們要確定找到之前,如果low和high碰頭了,也不找了
22         while low < high and li[low] <= mid :
23             low += 1
24         #跳出while循環後low所在的下標就是左側比mid大的數所在位置
25         # 我們把找到的數放在右側空位上,high標記了這個空位
26         li[high] = li[low]
27         #以上我們完成了一次 從右側找到一個小數移到左側,從左側找到一個大數移動到右側
28     #當這個while跳出來之後相當於low和high碰頭了,我們把mid所在位置放在這個空位
29     li[low] = mid
30     #這個時候mid左側看的數都比mid小,mid右側的數都比mid大
31 
32     #然後我們對mid左側所有數進行上述的排序
33     quick_sort( li , start, low-1 )
34     #我們mid右側所有數進行上述排序
35     quick_sort( li , low +1 , end )
36 
37 
38 
39 #ok我們實踐一下
40 if __name__ == __main__:
41     li = [5,4,3,2,1]
42     quick_sort(li , 0 , len(li) -1 )
43     print(li)

如果後續有時間,我會再補一個c語言實現的快排上來!畢竟會python的夥伴太少了

希望對朋友們有所幫助!歡迎大家發現錯誤與我交流

細致分析快速排序!