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”的常见错误。