分治法:
分治算法 这个博客介绍了分治的基本内容
首先回答分治法的基本思想:在解决一个问题的时候,可以把这个问题分成子问题,子问题的求解方式和原问题基本相同,这样可以不断划分,直到问题能够以最小的形式解决,然后将子问题的结果合并起来就是原问题的解决方法。
分治法的适用情况:1:问题规模足够小的时候能够解决。 2:该问题可以划分为规模较小的若干问题。 3:子问题的解合并是原来问题的解。 4:各个子问题相互独立。
分治法的执行过程:1:问题拆分。 2:子问题求解。 3:合并。
算法复杂性分析:复杂性是一个递归公式,$T(n) = aT(frac{n}{b}) + f(n)$ b为子问题相对于原问题的规模,a为子问题个数,f(n)表示剩余程序的复杂度。
若f(n)复杂度为$O(n^d)$,这个时候公式变为了$T(n) = aT(frac{n}{b}) + O(n^d)$ ,一般用下面的主方法进行分析。
$$ T(x)=left{ egin{aligned} O(n^d) & & a< b^d\ O(n^d log n) & & a = b^d \ O(n^{log_b a}) & & a>b^d end{aligned} ight. $$
下面是一些分治法的例子:
归并排序
若将一个数组排序,1:需要将其拆分为左右两部分 2:左右两部分都需要进行排序 3:合并的时候使用外排的方法进行合并。
def merge_sort(array): """归并排序 使用分治法的思想来进行排序, 使用递归的方法来进行实现 时间复杂度:使用master公式 a=2, b=2。除去递归,其它问题的复杂度,也就是merge的复杂度o(n), 所以,整体的复杂度为O(n*logn) 空间复杂度:归并排序每次递归需要用到一个辅助表,长度与待排序的表相等, 虽然递归次数是O(log2n),但每次递归都会释放掉所占的辅助空间, 所以下次递归的栈空间和辅助空间与这部分释放的空间就不相关了,因而空间复杂度还是O(n) 稳定性: 稳定性算法。在merge的时候,如果两个值相等,可以控制让左边先进来,然后右边再进来。 """ if len(array) <= 1: return array mid = int(len(array)/2) left = merge_sort(array[:mid]) right = merge_sort(array[mid:]) return merge(left, right) def merge(left, right): """合并算法 刚开始并没与价差两个数组的长度,而是直接相减,直到有一个为为止 时间复杂度:o(n) 需要遍历一边left和right 空间复杂度为 o(n) 需要一个result数组来存储结果 """ result = [] l, r = 0, 0 while(l < len(left) and r < len(right)): if left[l] < right[r]: result.append(left[l]) l += 1 else: result.append(right[r]) r += 1 result += (left[l:]) result += (right[r:]) return result
最大子数组问题:
来自算法导论4.1章,问题是这样的,给定一组数组,求这个数组当中连续数组和的最大值,和数组的边界。比如在数组
[13, -3, -25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7] 中最大子数组是从18到12的连续4个数,它们的和为43。
将原来的数组划分为两个数组,这样最大子数组只可能有三种情况:完全位于左边数组,完全位于右边数组,位于两个数组中间,对于前两个问题,递归的进行求解,对于第三个问题可以设计单独的函数求解。
采用分治法的方法来解决这个问题:1:可以将一个数组分为左右两个部分。 2:变成三个子问题:分别求左边部分中的最大子数组、右边部分的最大子数组 ,还有包含中间节点的最大子数组。3:合并这三种情况。上面划线的分析是错误的,2:变为两个子问题,求解左边部分的最大子数组,求解右边部分的最大子数组,使用递归的方式来解。 3:考虑最大子数组的三种情况,然后进行合并,因为求解包含中间节点的最大子数组没有调用递归算法,所以不属于子问题,而划分到合并里面。
def find_maximum_subarray(arr, low, high): """发现最大子数组""" if low == high: return (low, high, arr[low]) mid = int((low+high)/2) left_low, left_high, left_sum = find_maximum_subarray(arr, low, mid) # 划分并且求解左子部分 right_low, right_high, right_sum = find_maximum_subarray(arr, mid + 1, high) # 划分并且求解右子部分 mid_low, mid_high, mid_sum = find_max_crossing_subarray(arr, low, mid, high) # 求解出现在中间的情况 # 合并子问题 if left_sum >= right_sum and left_sum >= mid_sum: return left_low, left_high, left_sum elif right_sum >= left_sum and right_sum >= mid_sum: return right_low, right_high, right_sum else: return mid_low, mid_high, mid_sum def find_max_crossing_subarray(arr, low, mid, high): """当数组最大值包含mid的时候的求解方法""" # 先求解low到min的最大子数组 left_sum = float('-inf') all_sum = 0 left_index = mid for index in range(mid, low-1, -1): all_sum += arr[index] if all_sum > left_sum: left_sum = all_sum left_index = index # 然后求解mid到high的最大子数组 right_sum = float('-inf') all_sum = 0 right_index = mid+1 for index in range(mid+1, high+1): all_sum += arr[index] if all_sum > right_sum: right_sum = all_sum right_index = index # 将两部分的结果合并 return (left_index, right_index, left_sum+right_sum) def max_subarray(arr): return find_maximum_subarray(arr, 0, len(arr)-1) if __name__ == '__main__': arr = [13, -3, -25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7] x = max_subarray(arr) print(x)
使用动态规划的方式来解:
def dp_max_sub_array(arr): if len(arr) == 0: return 0 max_sub = arr[0] tmp_sub = arr[0] for i in range(1, len(arr)): tmp_sub = max(tmp_sub + arr[i], arr[i]) max_sub = max(max_sub, tmp_sub) return max_sub