python快速排序的原理 理解快速排序演算法

2021-10-11 00:07:54 字數 2978 閱讀 3071

快速排序的時間複雜度為o(nlogn),空間複雜度為o(n)。根據@張小牛 的文章快速排序(quick sort)詳解,證明最優的排序演算法,其時間複雜度可為o(nlogn),對應的空間複雜度可為o(n)。快速排序可實現理論最優效率,這可能是快速排序比較重要的原因吧。

我們基於python學習寫一下快速排序吧。

先給定乙個長度為10的列表data = [5, 4, 7, 8, 2, 7, 8, 5, 6, 3],如下:

初始列表

有了乙個列表,看起來會直觀多了吧,但是我們想放著不管。快速排序的主旨很簡單:找乙個標桿數,稱為x,然後根據x把陣列的數分堆,小於x的全放左邊,大於x的全放右邊,就可以啦。對於實際情況呢,我們還需要考慮等於x的情況,我將其與小於歸為一起,即陣列排列後,形成「小於等於x」 + 「大於x」兩部分。

就是說,快速排序的主要步驟就是:找x + 跟x比大小排列?

你可能會疑惑,只是按「比x大或比x小」排列陣列,怎麼能得到完整的排序呢。一次排列幾乎不可能排好,但我們可以將排了一次的陣列上,切分為「小於等於x」和「大於x」兩塊,再對這兩塊分別再找標桿數x'和x'',接著再分別排序。最後組合再一起,就得到了排列了兩次的陣列,其順序肯定更接近完美序列。那麼我們繼續這麼「切分→找標桿數→排列」操作下去呢?在此例中,由於每一分堆總小於右側的分堆,而大於左側的分堆,同時每個分堆內部已排好序,因此整個序列排序完成。

以上這種操作叫做「遞迴」,可以對陣列不斷地切分並採用同樣的排列模式進行排列,直到遞迴條件不再滿足,則停止遞迴。在這裡,我們選擇切分後陣列的長度大小,作為遞迴的條件,細節在後面詳述。

我們不妨先試著試驗一下,找x,然後將陣列跟x比大小排列。我沒有研究哪乙個當標桿好,不如就選第乙個數字吧。

選擇第乙個數字為「標桿數」

下面我們就要依據「標桿數」,也就是數字「5」(其序數為0),對其餘部分進行分堆了。我們想分為「<=5」與「>5」的兩部分,並使前者位於左側,後者位於右側,操作步驟如下:

1. 命名左側序數為 i,初始 i = 1;命名右側序數為 j,初始 j = len(data)-1(即最後一位)。

初始化i、j

2. 讓j開始移動並進行判斷:

若j所在的數字<=5,則讓i開始向右移動,直到i所在的數字》5,接著交換data中i, j所對應的數字,即:

data[i], data[j] = data[j], data[i]

若j所在數字》5,則忽略,繼續向左移動。

3. 當 j == i 時,意味著交換結束,列表除了首位的「標桿數」,其餘部分分為<=5和》5兩堆,那麼我們還應該把「5」放到這兩堆中間,讓列表看上去更有序。即:

data[0], data[j] = date[j], data[0]

注意:如果j此時不在列表中間呢,比如由於資料特殊,j最終停在在首、尾處呢?

不能交換

可以交換

考慮到這一點,我們就可以意識到,要做的是把一開始找的標桿放到應有的位置上,即最後乙個<=5的數的位置。因此,我們在交換前加乙個判斷:

if data[j] <= data[0]:

data[0], data[j] = data[j], data[0]

4. 結束操作,返回此時的data。

容易看到,由於我讓j的移動佔主動性,j先找到乙個<=5的數後,i才能開始行動找》2的數。那麼當j、i會,請問j最終會停在<=5還是》5的數字上呢?

答案是:對於這裡給出的data,j總是會停在<=5的數字上,或者說對於len(data)>5的data,j與i總是相遇在最右的「<=標桿數」的位置上(仔細想一想~)。

這樣做是為了便於data[j]與data[0]交換,所以讓j先行;如果讓i先行,那麼相反的,i、j通常相遇在最左的「>標桿數」的位置上,這樣不利於data[i]與data[0]交換。

對於len(data) == 1 or len(data) == 2的兩種例外情況,留給讀者思考。偽**如下:

if len(data) == 2:

if data[j] <= data[0]:

data[0], data[j] = data[j], data[0]

return quicksort(data[left_part]) + quicksort(data[right_part])

if len(data) == 1:

return data

以上部分講的是單次排序的(囉嗦)細節,整個快速排序是若干次單次排序的遞迴,下面講解一下遞迴部分:

首先簡化模型,我們把不直觀的「數字比大小」轉換為直觀的「圖形排序」,將data中的「標桿數5」及<=5的數替換為「☻」,將》5的數替換為「█」,則有:

接著,用上述的i,j排序規則操作一遍之後,得到:

是不是清晰許多?

進行遞迴,我們要做的就是把分大小排序的data拆分為兩個data,分界線即為「標桿數」,然後分別對兩個拆分data排序,直至抵達遞迴的回歸條件(len(data) <= 1即終止)。

對示例列表進行快速排序的原理如下:

完整**如下:

def quicksort(data): #快速排序

stone = data[0]

i = 1

j = len(data)-1

if len(data) > 1: #分為len(data) >2和len(data) == 2兩種情況,可合併

while j > i:

if data[j] <= stone:

if data[i] > stone:

data[j], data[i] = data[i], data[j]

else:

i += 1

else:

j -= 1

if data[j] <= stone: #當len(data) == 2時只執行此部分

data[0], data[j] = data[j], data[0]

return quicksort(data[:j]) + quicksort(data[j:])

else: #回歸條件,len(data) <= 1

return data

完結撒花~

以上 .

CSS 原理理解

網頁製作最初,html規定了 normal document stream 標準文件流 來規範元素在網頁中的顯示法則 標準文件流中元素分兩種 塊內元素,行內元素。行內元素的特點 span標籤 豎直margin中的塌陷現象,上下緊密排列的元素的外邊距並不是兩個元素外邊距之和,而是選取那個最大的外邊距作...

Spring IOC原理理解

ioc,inversion of control,控制倒轉。這是spring的核心,貫穿始終。所謂ioc,對於spring框架來說,就是由spring來負責控制物件的生命週期和物件間的關係。di,dependency injection,依賴注入。在系統執行中,動態的向某個物件提供它所需要的其他物件...

ROI Align 原理理解

對背景問題的理解 之前一直在想乙個問題 乙個label在原圖上標記出乙個包含目標的區域。這個框在特徵提取後,大小被縮小到了什麼程度?如果這個label框本身就不大,那麼經過幾層池化之後,是不是在最後的feature map上都沒有乙個位置,能夠對應到這個區域?目標在特徵提取過程中,由於這種深度結構導...