1 分而治之(divided and conquer,D&C)算法
第一個🌰:如何將一塊地均勻地分紅方塊,並確保分出的方塊是最大的呢?數組
使用D&C策略(並不是解決問題的算法,而是一種解決問題的思路)!D&C解決問題的兩個步驟:less
①找出基線條件,儘量的簡單ide
②不斷講問題分界,或者說縮小規模,使其知足基線條件函數
首先基線條件:一個條邊的長度是另外一條邊的兩倍。50m*25m性能
再找遞歸條件,這就要用到D&C策略了,先找出可容納的最大方塊,對餘下方塊使用一樣的算法。ui
你能夠從這塊地中劃出兩個640 m×640 m的方塊,同時餘下一小塊地640m*400m,適用於這小塊地的最大方塊,也是適用於整塊地的最大方塊(涉及歐幾里得算法)。對這塊地實用一樣的辦法,變成400m*240m,而後變爲240m*160m,最後變爲160m*80,達到了基線條件。spa
第二個🌰:給定一個數字數組。須要加總並返回code
用循環很容易完成:blog
def sum(arr): total = 0 for x in arr: total += x return total print(sum([1, 2, 3, 4]))
遞歸如何處理?
①找出基線條件:若是數組中只有一個數或者沒有數,那麼加總很好算
②每次遞歸調用都必須離空數組更近一步。
與
二者之間是等效的,可是右邊的數組更短,縮小了問題的規模。
sum()函數的遞歸運行過程:
提示:編寫涉及數組的遞歸函數時,基線條件一般是數組爲空或只包含一個元素。陷入困境時,請檢查基線條件是否是這樣的。
練習
4.1 請編寫前述 sum 函數的代碼。
def sum(list): if list == []: return 0 return list[0] + sum(list[1:]) print(sum([1,2,3,4,5,6,7]))
4.2 編寫一個遞歸函數來計算列表包含的元素數。
def sum(list): if list == []: return 0 return list[0] + sum(list[1:]) print(sum([1,2,3,4,5,6,7]))
4.3 找出列表中最大的數字。
def max(list): if len(list) == 2: return list[0] if list[0] > list[1] else list[1] sub_max = max(list[1:]) #這裏若是list很大,棧的空間就會很大,由於max()函數一直在運行,只到list被切分紅長度爲2 return list[0] if list[0] > sub_max else sub_max print(max([1,2,3,4,5,6,7,8,9]))
4.4 還記得第1章介紹的二分查找嗎?它也是一種分而治之算法。你能找出二分查找算法的基線條件和遞歸條件嗎?
基線條件:數組只包含一個元素。
遞歸條件:把數組分紅兩半,將其中一半丟棄,並對另外一半執行二分查找。
2 快速排序
基線條件:數組爲空或者只有一個元素,這樣就不須要排序了
兩個元素的數組進行元素比較便可。三個元素呢?使用D&C,將數組分解,直到知足基線條件。
快速排序的工做原理:
①從數組中選擇一個元素,這個元素被稱爲基準值(pivot)
②找出比基準值小的元素以及比基準值大的元素,這被稱爲分區(partitioning),數組變爲:
一個由全部小於基準值的數字組成的數組;基準值;一個由全部大於基準值的數組組成的子數組。
如今要對子數組進行排序,對於包含兩個元素的數組(左邊的子數組)以及空數組(右邊的子數組),快速排序知道如何將它們排序,所以只要對這兩個子數組進行快速排序,再合併結果,就能獲得一個有序數組!
對三個元素的數組進行排序:
①選擇基準值。
②將數組分紅兩個子數組:小於基準值的元素和大於基準值的元素。
③對這兩個子數組進行快速排序。
包含四個元素呢?一樣的作法,找出一個基準值,若是一個子數組有三個元素,對三個元素遞歸調用快速排序。那麼五個元素一樣也能夠。
代碼表示:
def quicksort(array): if len(array) < 2: return array else: pivot = array[0] #將數組的第一個元素定爲基準線 less = [i for i in array[1:] if i <= pivot] #遍歷數組剩下元素,若是小於它,放入less列表 greater = [i for i in array[1:] if i > pivot] #遍歷數組剩下元素,若是大於它,放入greater列表 return quicksort(less) + [pivot] + quicksort(greater) #對less和greater遞歸,最後返回排序數組 print quicksort([10, 5, 2, 3])
3 再談大O表示法
快速排序的狀況比較棘手,在最糟狀況下,其運行時間爲O(n 2 )。
與選擇排序同樣慢!但這是最糟狀況。在平均狀況下,快速排序的運行時間爲O(n log n)。
比較合併排序和快速排序:
有時候,常量的影響可能很大,對快速查找和合並查找來講就是如此。快速查找的常量比合並查找小,所以若是它們的運行時間都爲O(n log n),快速查找的速度將更快。實際上,快速查找的速度確實更快,由於相對於趕上最糟狀況,它趕上平均狀況的可能性要大得多。
4 平均狀況和最糟狀況
快速排序的性能高度依賴你選擇的基準值。假設你總將第一個元素用做基準值,那麼棧長爲O(n),若是你總將中間的元素用做基準值,那麼棧長爲O(log n)。
實際上,在調用棧的每層都涉及O(n)個元素。
所以,完成每層所需的時間都是O(n)
在第二張圖中,層數爲O(log n)(用技術術語說,調用棧的高度爲O(log n)),每層須要的時間爲O(n)。所以整個算法須要的時間爲O(n) * O(log n) = O(n log n)。這就是最佳狀況。在最糟狀況下,有O(n)層,所以該算法的運行時間爲O(n) * O(n) = O(n **2 )。
這裏要告訴你的是,最佳狀況也是平均狀況。只要你每次都隨機地選擇一個數組元素做爲基準值,快速排序的平均運行時間就將爲O(n log n)。
練習
使用大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 )。
5 小結D&C將問題逐步分解。使用D&C處理列表時,基線條件極可能是空數組或只包含一個元素的數組。實現快速排序時,請隨機地選擇用做基準值的元素。快速排序的平均運行時間爲O(n log n)。大O表示法中的常量有時候事關重大,這就是快速排序比合並排序快的緣由所在。比較簡單查找和二分查找時,常量幾乎可有可無,由於列表很長時,O(log n)的速度比O(n)快得多。