算法导论-分而治之-最大子数组(含动态规划解法)

算法导论-分而治之篇

最大子数组问题

分治法
把一个问题分成(同类的)几个子问题。
递归地解决(征服)每个子问题。
将子问题的解决方案组合成整体解决方案。

通常的使用
将大小为n的问题分成两个大小为n / 2的子问题。
递归求解(攻克)两个子问题。
将两个方案组合成整体方案。

当子问题足够大以进行递归求解时,称其为递归情况(recursive case)
一旦子问题变得很小,以至不再需要递归的程度,就说递归“结束(bottom out)”了,已经回到了基本情况(base case)

最大子数组(Max Subarray)问题,是计算机科学与技术领域中一种常见的算法问题,主要可以利用分治思想进行快速实现。

最大子数组问题描述如下:假如我们有一个数组,数组中的元素有正数和负数,如何在数组中找到一段连续的子数组,使得子数组各个元素之和最大。

1 分治法求解最大子数组问题

在最大子数组问题之后,我们一起来看一下如何利用分治思想求解最大子数组问题。这里我们假设待初始的数组为 [12, -3, -16, 20, -19, -3, 18, 20, -7, 12, -9, 7, -10],记为数组 A,并用 A [low,high] 表示这个数组,其中 low,high 是这个数组的最小最大下标, low = 0,high = A.length -1 , 然后我们需要找到该数组的其中某一个最大子数组。

Tips: 这里我们需要注意,同一数组的最大子数组可能有多个,所以我们在这里求解的时候只说求解某一个最大子数组。

1.1 分治算法求解思路

本部分完全参考自这篇文章
https://blog.csdn.net/mukewangguanfang/article/details/128828639
在这里,我们用分治算法求解最大子数组问题,主要思路如下:

步骤 1:
找到数组 A 的中间元素,其下标记为 mid,根据分治策略,将数组 A [low,high] 根据中间元素划分为 A [low,mid], A [mid+1,high] 两个部分;

步骤 2:
假设数组 A 的最大子数组为 A [i, j],那么 A [i, j] 只有以下三种可能:
a: 最大子数组 A [i, j] 完全位于 A [low, mid] 中,此时有 low <= i <= j <= mid;
b: 最大子数组 A [i, j] 完全位于 A [mid+1, high] 中,此时有 mid+1 <= i <= j <= high;
c: 最大子数组 A [i, j] 跨域了中间元素,则 low <= i <= mid <= j <= high。
分别计算上述三种对应的最大子数组的结果;
Tips: 在这里,情况 a 和情况 b 这两种情况所得的子问题和之前求解数组 A 的最大子数组的问题形式完全一样,这里是分治思想的主要体现,将大的问题拆分成了两个相同形式的小问题;情况 c 这时候可以直接求解,在 3.2 节中会具体介绍其求解过程。

步骤 3
对步骤 2 三种情况的求解结果进行比较,其中最大子数组的结果为最大值的情况就是我们的所求结果。

1.2 代码实现
#include <iostream>
#include <cmath>
using namespace std;

// 这个跨越中线的解一定是[l,mid]与[mid+1, r]组成
int findMaxCrossSubarray(int arr[], int l, int r)
{
    if (l == r)
        return arr[l];
    else
    {
        int m = (l + r) / 2;
        int left = m, right = m + 1;
        int maxLeftSum = arr[m], maxRightSum = arr[m + 1];
        int leftSum = 0, rightSum = 0;
        for (int i = m; i >= l; i--)
        {
            leftSum += arr[i];
            if (maxLeftSum < leftSum)
            {
                maxLeftSum = leftSum;
                left = i;
            }
        }

        for (int i = m + 1; i <= r; i++)
        {
            rightSum += arr[i];
            if (maxRightSum < rightSum)
            {
                maxRightSum = rightSum;
                right = i;
            }
        }
        return maxLeftSum + maxRightSum;
    }
}
int findMaxSubarray(int arr[], int l, int r)
{
    if (l == r)
        return arr[l];
    else
    {
        int m = (l + r) / 2;
        int left = findMaxSubarray(arr, l, m);
        int right = findMaxSubarray(arr, m + 1, r);
        int mid = findMaxCrossSubarray(arr, l, r);
        //选择最大的数输出
        if (mid >= right && mid >= left)
            return mid;
        else if (right >= mid && right >= left)
            return right;
        else
            return left;
    }
}
int main()
{
    int arr[] = {5, 4, -1, 7, 8};
    cout << findMaxSubarray(arr, 0, 4);
    return 0;
}
//测试数据来着
https://leetcode.cn/problems/maximum-subarray/

2 动态规划法解决最大子数组问题

此部分参考此文章
https://blog.csdn.net/qq_36445477/article/details/105394002

1)分析问题结构: dp的第一步为分析问题结构,找到该问题的最佳子结构性质:对n个元素的数组arr[n]求解最大子数组和maxSum(n), 首先去找 maxSum(n) 和 maxSum(n-1) 的关系。

假设我们已经知道前n-1个元素的最大子数组和 maxSum(n-1), 设前n个元素中以第n元素结尾的最大子数组和为 P(n) , 则

maxSum(n) = max{ maxSum(n-1), P(n) }

继续观察 P(n) 和 P(n-1) 的关系: 若以第n-1个元素结尾的最大子数组和 P(n-1) 加上第n个元素的和大于第n个元素(即P(n-1)>0),那么以第n个元素结尾的最大子数组和为 P(n) = P(n-1) + arr[n],否则 P(n) 就等于arrr[n] 第n个元素本身, 即:

P(n) = max{ P(n-1)+arr[n], arr[n] }

我们已经找到了maxSum(n)和maxSum(n-1)子问题的关系,这就是最大子数组和问题的最优子结构性质。

2)构造递推式: 我们通过分析最优子结构性质。已经得到了dp的递推式:

P(n) = max{ P(n-1)+arr[n], arr[n] }
maxSum(n) = max{ maxSum(n-1), P(n) }

3)初始化数组,自底向上求解: 构造两个一维数组 maxSum 和 P ; P[i]: 数组前i的元素中包含元素i的最大子数组和; maxSum[i]: 数组前i个元素中的最大子数组和。

设没有元素的数组最大子数组和为负无穷以便于求解,即初始化P[0] 和maxSum[0] 为负无穷,而后以 i := 1 to n,自底向上求解,得到 **maxSum[n]**的值即为最大子数组和

代码部分如下

#include <iostream>
using namespace std;
int DPgetMaxSubarray(int arr[], int l, int r)
{
    // 获得前n个元素以第n元素结尾的最大子数组和为 P(n)
    int p[20];
    int maxSum[20];
    p[0] = arr[0];
    maxSum[0] = arr[0];
    for (int i = 1; i < r - l + 1; i++)
    {
        if (p[i - 1] + arr[i] > arr[i])
            p[i] = p[i - 1] + arr[i];
        else
            p[i] = arr[i];
    }
    for (int i = 1; i <= r - l; i++)
    {
        if (maxSum[i - 1] > p[i])
            maxSum[i] = maxSum[i - 1];
        else
            maxSum[i] = p[i];
    }
    return maxSum[r];
}

int main()
{
    int arr[] = {5, 4, -1, 7, 8};
    cout << DPgetMaxSubarray(arr, 0, 4);
    return 0;
}
  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在人工智能时代,已经部署了高度复杂的算法来提供分析、检测模式、优化解决方案、加速操作、促进自我学习最大程度地减少人为错误和偏见并促进技术产品和服务的改进。 尽管有这些巨大的好处,算法和智能机器并没有为所有人提供平等的好处。 正如数字鸿沟将能够访问互联网、信息技术和数字内容的人与没有访问互联网、信息技术和数字内容的人分开一样,新兴且不断扩大的算法鸿沟现在有可能夺走许多政治、社会、经济、文化、教育和职业机会由机器学习和人工智能提供。尽管政策制定者、评论员和大众媒体越来越关注算法偏见以及机器学习和人工智能的缺点,但算法鸿沟尚未引起政策和学术界的广泛关注。 为了填补这一空白,本文利用数字鸿沟文献来系统地分析这种新的技术贫富差距。 利用作者在 2000 年代初期开发的分析框架,文章首先讨论了算法鸿沟的五个属性:意识、访问、可负担性、可用性和适应性。 -扩大算法鸿沟:(1)算法剥夺; (2) 算法判别; (3) 算法失真。 虽然前两个问题主要影响分歧中不幸一方的人,但最后一个问题影响双方的个人。 本文最后提出了七个非详尽的补救措施集群,以帮助弥合这种新兴且不断扩大的算法鸿沟。 文章结合法律、通信政策、道德原则、体制机制和商业实践,形成了一个整体回应,以帮助促进人工智能时代的平等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值