《算法圖解》學習筆記習題和代碼(第四章 快速排序)Python3

目錄

第四章 快速排序

4.1 分而治之 D&C

循環實現數組裏的元素相加(code)

遞歸函數實現數組裏的元素相加(code)

練習1

4.2 快速排序 

快速排序代碼

4.3 再談大 O 表示法 

4.3.1 比較合併排序和快速排序

4.3.2 平均情況和最糟情況

練習2

4.4 小結 


第四章 快速排序

快速排序——一種常用的優雅的排序算法。快速排序使用分而治之的策略。

分而治之(divide and conquer,D&C)——一種著名的遞歸式問題解決方法。 

4.1 分而治之 D&C

有一塊土地,你要將這塊地均勻地分成方塊,且分出的方塊要儘可能大

D&C解決問題的過程包括兩個步驟:
(1) 找出基線條件,這種條件必須儘可能簡單。 
(2) 不斷將問題分解(或者說縮小規模),直到符合基線條件。  

在這個問題中,基線條件:最容易處理的情況是,一條邊的長度是另一條邊的整數倍。如果一邊長25 m,另一邊長50 m,那麼可使用的最大方塊爲 25 m×25 m。

遞歸條件:每次遞歸調用都必須縮小問題的規模。 首先找出這塊地可容納的最大方塊。 

對餘下的那一小塊地使用相同的算法。、

 

因此,對於最初的那片土地,適用的最大方塊爲80 m× 80 m。 

“適用於這小塊地的最大方塊,也是適用於整塊地的最大方塊。”具體原理參見歐幾里得算法:輾轉相除,找到兩個正整數的最大公約數。歐幾里得算法百度百科

D&C的工作原理: 
(1) 找出簡單的基線條件; 
(2) 確定如何縮小問題的規模,使其符合基線條件。

動手練習:

給定一個數字數組。 將這些數字相加,並返回結果。使用循環可以很方便完成任務。

循環實現數組裏的元素相加(code)

#用循環實現數組裏的數相加
def sum(arr):
    total = 0
    for i in arr:
        total += i
    return total
print(sum([5,4,3,2]))

#OUT:  14

遞歸函數實現數組裏的元素相加(code)

#用遞歸實現
def sum(arr):
    b = arr.pop()
    if arr == []:     #基線條件
        return b
    else:
        return b+sum(arr)      #遞歸
print(sum([5,4,3,2]))

#OUT:  14

涉及數組的遞歸函數時,基線條件通常是數組爲空或只包含一個元素。

練習1

4.1 請編寫前述sum函數的代碼。 

答:如上
4.2 編寫一個遞歸函數來計算列表包含的元素數。

答:如上 
4.3 找出列表中最大的數字。 

答:可以用循環,遞歸的方法或者調用python的函數。(選擇排序代碼就不放了)

#1.循環 
def findlargest(arr):
    large = arr[0]
    for i in range(len(arr)):
        if arr[i]>=large:
            large = arr[i]
    return large
print(findlargest([1,2,3,4,5]))
#OUT:  5

#2.遞歸    https://blog.csdn.net/Sukiyou_xixi/article/details/95099292
def find_max(arr):
    tmp = arr.pop(0)
    if len(arr) == 0:
        return tmp
    max = find_max(arr)
    if max > tmp:
        return max
    else:
        return tmp

print(find_max([15, 10, 90, 200, 20]))
#OUT:  200

#3.python 內置函數
a = [1,2,3,4]
print(max(a))
#OUT:  4


4.4 還記得第1章介紹的二分查找嗎?它也是一種分而治之算法。你能找出二分查找算法的基線條件和遞歸條件嗎? 

答:基線條件是查找的數正好是想要的數。遞歸條件就是,在符合條件那邊繼續折半查找。

4.2 快速排序 

快速排序也是一種常見的排序算法,比選擇排序快。

快速排序也採用了D&C,分而治之的策略:找基線條件,分解問題成基線條件去解決。遞歸的思想。

快速排序是怎麼進行的?

第一步找基線條件

如果一個數組只有一個數,或者爲空,則排序就原樣返回,不需要排序。那麼排序的基線條件就是數組爲空或只包含一個元素。

 如果數組有很多數,我們進行分解和歸納。

如果數組有兩個數:

如果有三個數 :要使用D&C,分解數組,直到滿足基線條件。

 排序前,確定基準值(pivot),我們採用數組第一個元素作爲基準值,根據基準值進行分區。所有小於基準值的元素放在它左邊,大的放右邊,

分區完,你有一個由所有小於基準值的數字組成的子數組;基準值;一個由所有大於基準值的數組組成的子數組。

 接着對左右兩個數組再進行以上操作:以基準值再分左右區,直至返回一個元素。

其實 任何元素用作基準值都可行。你能對含有一個、兩個、三個、四個元素的數組進行排序,同理以此類推,快速排序對任何長度的數組都管用。這裏涉及到一個歸納證明的知識點。

快速排序代碼

def quicksort(arr):
    if len(arr)<2:     #基線條件
        return arr
    else:
        pivot = arr[0]   #基準值
        less = [i for i in arr[1:] if i<=pivot]
        greater = [i for i in arr[1:] if i>pivot]
        return quicksort(less)+[pivot]+quicksort(greater)
print(quicksort([10,5,2,3]))

OUT:[2, 3, 5, 10]

4.3 再談大 O 表示法 

快速排序的獨特之處在於,其速度取決於選擇的基準值。討論快速排序的大O表示之前,回顧一下一些查找和排序算法的常見的大O運行時間。

還有一種名爲合併排序(merge sort)的排序算法,其運行時間爲O(n log n),比選擇排序快得多。

快速排序速度與基準值的選取有關,平均情況下快速排序的運行時間爲O(n log n),最糟情況下,其運行時間爲O(n^2)。

4.3.1 比較合併排序和快速排序

比如二分查找和簡單查找:

暫且認爲簡單查找一次時間常量(算法所需的固定時間量)爲10毫秒,二分查找時間常量是1秒。看起來好像是簡單查找快,但如果要在包含40億個元素的列表中查找:

 還是二分查找快的多。如果兩個算法的大O表示不同,時間常量無關緊要。

但有時候,常量的影響可能很大。比如對快速查找和合並查找而言,快速查找的常量比合並查找小,因此如果它們的運行時間都爲O(n log n),快速查找的速度將更快。實際上,快速查找的速度確實更快,因爲相對於遇上最糟情況,它遇上平均情況的可能性要大得多。

什麼是平均情況?最糟情況?

4.3.2 平均情況和最糟情況

使用快速排序,考慮兩種基準值情況。

1.基準值每次都選擇列表的第一個元素,且處理的數組是有序的。

在此過程中,分區時,其中一個子數組始終爲空,這導致調用棧非常長。這是最糟情況,棧長爲O(n)

2.也是上面的有序數組,但每次都選取數組的中間元素作爲基準值。

這樣每次都將數組分成兩半,所以不需要那麼多遞歸調用。你很快就到達了基線條件,因此調用棧短得多。  這是最佳情況,棧長爲O(logn)。

看第一種情況棧的第一層。你將一個元素用作基準值,並將其他的元素劃分到兩個子數組中。這涉及數組中的全部8個元素,因此該操作的時間爲O(n)。實際上,在調用棧的每層都涉及O(n)個元素。

第二種情況,以中間元素作爲基準,對半劃分,每次也將涉及O(n)個元素。 

因此,完成每層所需的時間都爲O(n)。 

第一種情況,有O(n)層,因此該算法的運行時間爲O(n) * O(n) = O(n^2),最糟情況。

 

第二種情況,層數爲O(log n)(用技術術語說,調用棧的高度爲O(log n)),而每層需要的時間爲O(n)。因此整個算法需要的時間爲O(n) * O(log n) = O(n log n)。這就是最佳情況。 

最佳情況也是平均情況。只要你每次都隨機地選擇一個數組元素作爲基準值,快速排序的平均運行時間就將爲O(n log n)。快速排序是最快的排序算法之一,也是D&C典範。 

練習2

使用大O表示法時,下面各種操作都需要多長時間? 
4.5 打印數組中每個元素的值。 

答:O(n)
4.6 將數組中每個元素的值都乘以2。

答:O(n) 
4.7 只將數組中第一個元素的值乘以2。

答:O(1)常量

4.8 根據數組包含的元素創建一個乘法表,即如果數組爲[2, 3, 7, 8, 10],首先將每個元素 都乘以2,再將每個元素都乘以3,然後將每個元素都乘以7,以此類推。 

答:O(n^2)

4.4 小結 

D&C將問題逐步分解。使用D&C處理列表時,基線條件很可能是空數組或只包含一個元素的數組。
實現快速排序時,請隨機地選擇用作基準值的元素。快速排序的平均運行時間爲O(n log n)。
大O表示法中的常量有時候事關重大,這就是快速排序比合並排序快的原因所在。
比較簡單查找和二分查找時,常量幾乎無關緊要,因爲列表很長時,O(log n)的速度比O(n)快得多。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章