分治法

分治法:

分治算法 这个博客介绍了分治的基本内容

首先回答分治法的基本思想:在解决一个问题的时候,可以把这个问题分成子问题,子问题的求解方式和原问题基本相同,这样可以不断划分,直到问题能够以最小的形式解决,然后将子问题的结果合并起来就是原问题的解决方法。

分治法的适用情况: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\{ \begin{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} \right. $$

 

下面是一些分治法的例子:

归并排序

若将一个数组排序,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

 

posted @ 2018-07-14 21:02  小舔哥  阅读(768)  评论(0编辑  收藏  举报