快速排序是 O(nlog^n ),但如果分割點不在列表中間附近,可能會降級到O(n^2 ) 。它不需要額外的空間。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import random
def partition(li, left, right):
"""
歸位函數
"""
tmp = li[left] # 先把最左側的數拿出來,然后開始查找其該在的位置
while left < right:
while left < right and li[right] >= tmp: # 如果降序排列修改li[right] <= tmp
right -= 1 # 從右邊找比6小的數,填充到6的位置
li[left] = li[right]
while left < right and li[left] <= tmp: # 如果降序排列修改li[right] >= tmp
left += 1 # 從左邊找比6大的數字放在右邊的空位
li[right] = li[left]
li[left] = tmp # 當跳出循環條件的時候說明找到了,并且把拿出來的6在放進去
return left
def _quick_sort(li, left, right):
# 快速排序的兩個關鍵點:歸位,遞歸
if left < right: # 至少有兩個元素,才能進行遞歸
mid = partition(li, left, right) # 找到歸位的位置
_quick_sort(li, left, mid - 1) # 遞歸,右邊的-1
_quick_sort(li, mid + 1, right) # 遞歸,左邊的+1
def quick_sort(li):
return _quick_sort(li, 0, len(li) - 1)
li = list(range(100000))
random.shuffle(li)
quick_sort(li)
現在開始解析
分別從初始序列“6 1 2 7 9 3 4 5 10 8”兩端開始“探測”。先從右往左找一個小于6的數,再從左往右找一個大于6的數,然后交換他們。這里可以用兩個變量i和j,分別指向序列最左邊和最右邊。我們為這兩個變量起個好聽的名字“哨兵i”和“哨兵j”。剛開始的時候讓哨兵i指向序列的最左邊(即i=1),指向數字6。讓哨兵j指向序列的最右邊(即j=10),指向數字8。
首先哨兵j開始出動。因為此處設置的基準數是最左邊的數,所以需要讓哨兵j先出動,這一點非常重要(請自己想一想為什么)。哨兵j一步一步地向左挪動(即j--),直到找到一個小于6的數停下來。接下來哨兵i再一步一步向右挪動(即i++),直到找到一個數大于6的數停下來。最后哨兵j停在了數字5面前,哨兵i停在了數字7面前。
現在交換哨兵i和哨兵j所指向的元素的值。交換之后的序列如下。
到此,第一次交換結束。接下來開始哨兵j繼續向左挪動(再友情提醒,每次必須是哨兵j先出發)。他發現了4(比基準數6要小,滿足要求)之后停了下來。哨兵i也繼續向右挪動的,他發現了9(比基準數6要大,滿足要求)之后停了下來。此時再次進行交換,交換之后的序列如下。
6 1 2 5 4 3 9 7 10 8
第二次交換結束,“探測”繼續。哨兵j繼續向左挪動,他發現了3(比基準數6要小,滿足要求)之后又停了下來。哨兵i繼續向右移動,糟啦!此時哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。說明此時“探測”結束。我們將基準數6和3進行交換。交換之后的序列如下。
3 1 2 5 4 6 9 7 10 8
到此第一輪“探測”真正結束。此時以基準數6為分界點,6左邊的數都小于等于6,6右邊的數都大于等于6。回顧一下剛才的過程,其實哨兵j的使命就是要找小于基準數的數,而哨兵i的使命就是要找大于基準數的數,直到i和j碰頭為止。