算法学习-分治法(一)

问题一:最大子数组问题

在这里插入图片描述

问题分析

我们的目标是找出一段连续的时间[A, B],使得price[B]- price[A]差值最大。将股票的价格数组转换成A[i]表示price[i]- price[i - 1],则问题转换成A数组的最大子数组问题
将问题更新,给定一个数组A[left…right],求出A数组的最大子数组(需要返回子数组的起始下标,终点下标,最大子数组之和)
分治算法中,子问题规模划分是否均匀会极大的影响算法效率,为了获得更好的时间,需要将子问题尽可能均匀的划分,记划分中点为mid,将数组划分为A[left, mid],A[mid+1,right]。最大子数组一定是下列三种情况之一

  • 在A[left, mid]中
  • 在A[mid+1, right]中
  • 跨过mid,将最大子数组记为A[i,j],满足left \leq i \leq mid < j \leq right

算法伪代码

FindMaxSubArray(A, left, right)
    mid = (right + left) / 2
    //分治法算子问题
    (begin_left, end_left, max_sum_left) = FindMaxSubArray(A, left, mid)
    (begin_right, end_right, max_sum_right) = FindMaxSubArray(A, mid + 1, right)
    //处理子问题的解,得到最终解
    (begin_mid, end_mid, max_sum_mid) = FindMaxCrossSubArray(A, mid, left, right)
    if max_sum_mid >= max_sum_left && max_sum_mid >= max_sum_right
        return (begin_mid, end_mid, max_sum_mid)
    else if max_sum_left >= max_sum_right 
        return (begin_left, end_left, max_sum_left)
    else
        return (begin_right, end_right, max_sum_right)

//获得A数组中下标跨过mid的最大子数组
FindMaxCrossSubArray(A, mid, left, right)
    sum = 0
    max_sum_left = 负无穷
    begin = mid
    for i = mid downto left
        sum += A[i]
        if sum > max_sum_left
            max_sum_left = max_sum
            begin = i
    
    sum = 0
    max_sum_right = 负无穷
    end = mid + 1
    for i = mid + 1 to right
        sum += A[i]
        if sum > max_sum_right
            max_sum_right = max_sum
            end = i
    return (begin, end, max_sum_left + max_sum_right)

时间复杂度分析

T ( n ) = 2 T ( n / 2 ) + O ( n ) T(n) = 2T(n / 2) + O(n)
利用Master定理可以得到算法时间复杂度是O( n l o g n nlogn

问题扩展一

在这里插入图片描述

问题分析

只需要在上述伪代码返回部分判断得到的最大子数组之和是不是小于0,如果小于0,返回[-1,-1,0]即可

问题扩展二

在这里插入图片描述

问题分析

(对于数组元素都是负数或者0的数组不予考虑,可以在下面的算法开始前判断一下,时间复杂度是 θ ( n ) \theta(n)
对于任何一个给定的数组,最大子数组一定是以一个正数作为开头,以正数作为结尾,因为如果开头是负数或者是0,将开头去掉,可以得到一个和更大或者和不变的最大子数组,同理,如果结尾是负数或者是0,同理。
算法的自然语言描述

  1. 初始化maxsum = 0,maxbegin = 0, maxend= 0
  2. 遍历数组元素,直到找到首个为正数的数组元素,将其加进persum,记录perbegin和perend.
  3. 比较persum与maxsum,如果persum大于maxsum,将maxsum、maxbegin、maxend更新
  4. 继续遍历数组,直到数组结束
    • 如果遇到的数组元素是正数,将其加进persum,并更新perbegin,perend,跳转到3
    • 如果遇到的数组元素是负数或者0
      • 如果将该数组元素加进persum,persum仍然是正数,则将其加进去
      • 如果是负数,则放弃当前的persum,perbegin,perend

算法伪代码

FindMaxSubArray(A, left, right)
    maxsum = 0, maxbegin = 0, maxend = 0
    persum = 0, perbegin, perend = 0
    for i = left to right
        if A[i] > 0 
            persum += A[i]
            if perbegin == 0
                perbegin = i
            if perend == 0
                perend = i
        
            if (maxsum > persum)
                maxsum = persum
                maxbegin = perbegin
                maxend = perend
        elif A[i] <= 0 
            if persum + A[i] > 0
                perend = i
                persum += A[i]
            
            else 
                persum = 0
                perbegin = 0
                perend = 0
    return (maxbegin, maxend, maxsum)
        

还可以使用动态规划,见动态规划部分

问题二:最近点对

给定平面上n个点,求其中的一点对,使得在n个点的所有点对中,该点对的距离最小。严格地说,最接近点对可能多于1对。为了简单起见,这里只限于找其中的一对。
(1)设计一个时间复杂度为O(n2)的算法求距离最近的点对,要求写出算法伪代码;
(2)利用分治的思想设计一个时间复杂度为O(nlogn)的算法求距离最近的点对,要求写出算法伪代码。

问题分析

第一问比较简单,只要计算平面上所有的点之间的距离即可,算法时间复杂度是 O ( n 2 ) O(n^2)
第二问比较复杂,主要思想是尽可能的将点分成左右两部分,采用分治法分别计算出左右两部分的最小距离,但是点之间的最小距离,可能出现在左侧点和右侧点之间,合并阶段要处理这种情况,具体细节见伪代码

算法伪代码

FindMinDisSet(points) //输入是平面上的点的数组
    length = points.length
    mindis1, mindis2, mindis = 正无穷
    for i = 0 to length
        for j = 0 to length
            if i != j
                dis = Dis(points[i], points[j])//返回两点之间的欧氏距离
                if (mindis > dis)
                    mindis = dis
                    mindis1 = i
                    mindis2 = j
    return (points[i], points[j], mindis)

FindMinDis(points)
    //如果只有两个点,直接返回两点之间的距离
    if points.length == 2
        return Dis(points[1], points[2])
    //如果不够两个点,返回一个正无穷
    if points.length < 2
        return 正无穷
    //可以利用选择算法在线性时间找到所有点横坐标的中位数
    //根据找到的中位数将点分成左右两部分,下面分别处理
    mid = points中所有点横坐标的中位数
    for all point in points
        if point.x <= mid
            points_left.add(point)
        else point_right.add(point)

    dis_left = FindMinDis(points_left)
    dis_right = FindMinDis(points_right)
    //保存上面得到的两个距离中的最小值
    dis_min = min(dis_left, dis_right)

    //遍历所有点,找到所有距离直线X= mid距离小于dis_min的点,放到points_mid中
    for all point in points
        dis = distance between point and line X = mid
        if (dis < dis_min)
            points_mid.add(point)

    points_mid_sorted = 将points_mid按照y从小到大排序

    for all point in points_mid
        记录point与其后六个点之间的距离
        如果距离小于dis_min,则将其更新

    return dis_min
    


发布了13 篇原创文章 · 获赞 2 · 访问量 580

猜你喜欢

转载自blog.csdn.net/qq_43887432/article/details/105051992