MIT一直是免費公開課的傳播者和引領者,旨在為全世界各地的人們提供免費可在線觀看的大學課程。將精英大學的課程搬到網上,會造福整個人類。
這一分類博文將跟隨MIT的6006課程《Introduction to Algorithms》,實現課程中所講到的算法。
首先講到的是算法思想,如何通過將復雜問題,高緯度問題簡單化。一種好的思路是通過將大的問題,復雜的問題划分成子問題,通過子問題的解決,從而解決復雜問題。最經典的思想就是“DIvide &
Conquer”以及“Recursive”,分而治之和遞歸的思想。
問題一##
Peak Finding:在任何一個一維數組中,均存在至少一個peak,當然,數組長度大於0。若a[k] >= a[k - 1]且a[k] >= a[k + 1](k > 0且k < n - 2),則說a[k]為peak。a[0]為peak若a[0] >= a[1], a[n - 1]為peak若a[n - 1] >= a[n - 2]。
有了定義之后如何在一個數組中找到一個peak呢?如果我們從第一個元素開始遍歷整個數組,當然可以找到一個peak。每當遍歷到一個元素的時候,我們同時比較其左右鄰居,若都比其鄰居大或相等,則為peak。我們知道,遍歷整個數組的時間復雜度為\(O(n)\)。既然學習的是算法導論,就會選擇更高效的算法實現。
采用分而治之的思想,我們可以使用二分查找法,如下圖:
我們先找中間的元素,若恰好找到,則返回。若中間的元素比其左鄰居小,那么我們只從0到2/n - 1的范圍查找peak。若中間的元素的元素比其又鄰居小,那么我們只從2/n + 1到n - 1的范圍查找。
代碼實現###
def find_1d_peak(arr):
return find_1d_peak_util(arr, 0, len(arr) - 1)
def find_1d_peak_util(arr, low, high):
mid = (low + high) // 2
if mid > 0 and arr[mid] < arr[mid - 1]:
return find_1d_peak_util(arr, low, mid - 1)
elif mid < len(arr) - 1 and arr[mid] < arr[mid + 1]:
return find_1d_peak_util(arr, mid + 1, high)
return mid
test_1d = [1, 2, 3, 1]
print(find_1d_peak(test_1d))
邏輯很簡單,采用折半查找和遞歸的方式,這樣整個的時間復雜度可以降為\(O(\log_2n)\)。
問題二##
依然是Peak Finding,這次我們要在二維數組(矩陣)中查找一個peak。定義:若a[i][i]同時>=(a[i - 1][j], a[i + 1][j], a[i][j - 1], a[i][j + 1]),既同時大於其上下左右四個鄰居,則其為一個peak,邊界元素若缺少某些鄰居,也依然為peak。
思路:問題一為一維,采用的是二分查找和遞歸的思想。問題二依然可以用此思想,只不過是增加了一個緯度而已。我們依然是二分,只不過這次是按列二分。首先在中間列里找到一個最大值。既j = m / 2,找到第j列中的最大值為martix[i][j]。比較martix[i][j]其左右鄰居martix[i][j - 1]和martix[i][j + 1]。若同時大於等於其左右鄰居,則為peak(在該列中最大,則也同時大於等於其上下鄰居)。若martix[i][j]<=martix[i][j - 1],則在列為0到j - 1的位置查找,繼續二分。若martix[i][j]<=martix[i][j + 1],則在列為j + 1到m - 1的位置查找,繼續二分。
代碼實現##
def find_2d_peak(martix):
return find_2d_peak_util(martix, 0, len(martix[0]) - 1)
def find_2d_peak_util(martix, low, high):
mid = (low + high) // 2
max_row = 0
max_value_in_row = martix[max_row][mid]
for i in range(1, len(martix)):
if martix[i][mid] > max_value_in_row:
max_value_in_row = martix[i][mid]
max_row = i
if mid > 0 and max_value_in_row < martix[max_row][mid - 1]:
return find_2d_peak_util(martix, low, mid - 1)
elif mid < len(martix[0]) - 1 and max_value_in_row < martix[max_row][mid + 1]:
return find_2d_peak_util(martix, mid + 1, high)
return [max_row, mid]
test_2d = [[10,8,10, 10], [14, 13, 12, 11], [15, 9, 11, 21], [16, 17, 19, 20]]
print(find_2d_peak(test_2d))
思路和問題一完全一致,只不過增加了一個維度而已。若n行m列的矩陣,則其時間復雜度為\(O(n\log_2m)\)。若m = n,則為\(O(nlog_2n)\)。注意每一列查找最大值的時間復雜度為\(O(n)\)。
總結##
該問題是比較典型的二分查找和遞歸算法,通過將大的問題划分為子問題,通過求解子問題來實現復雜問題的解決。值得注意的是在二個問題中都要注意邊界的控制,否則容易出現“index out of range”的常見錯誤。