120 likes | 390 Views
算法设计与分析 动态规划. 杨圣洪. 3.4 最大子段和. 问题描述: 给定由 n 个整数 ( 可为负 ) 组成的序列 a 1 , a 2 ,..., a n ,求该序列子段 ( 连续元素 ) 和的最大值。 当和为负值时约定为 0 。 依此定义,所求的最优值为: ( 板 ) 例如, (2, 11,-4,13 ,-5,-2) 最大子段和 =2+11+(-4)+13=22 。 ( 板 ) 连续子集 与 公子序列 , 离散子集 并 不一样!!! 暴力求连续子集: 起点 i 从 1 到 n ,终点 j 从 i~n ,, O(n 3 ). 一个简单算法: 连续元素和
E N D
算法设计与分析动态规划 杨圣洪
3.4 最大子段和 • 问题描述: • 给定由n个整数(可为负)组成的序列a1,a2,...,an,求该序列子段(连续元素)和的最大值。当和为负值时约定为0。 • 依此定义,所求的最优值为:(板) • 例如,(2,11,-4,13,-5,-2)最大子段和=2+11+(-4)+13=22。(板) • 连续子集与公子序列,离散子集并不一样!!! • 暴力求连续子集: • 起点i从1到n,终点j从i~n,,O(n3)
一个简单算法:连续元素和 int MaxSum(int n, a, &besti, &bestj) { //起点为i,结束点为j,一段试过去 int sum=0; for(i=1;i<=n;i++) for(j=i;j<=n;j++){ int thissum=0; for(k=i;k<=j;k++) {thissum+=a[k];} if(thissum>sum){ sum=thissum; besti=i; bestj=j; } } return sum; } 算法有3重循环,复杂性为O(n3)。 由于和是 逐渐累加 改进: int MaxSum(int n, a, &besti, &bestj) { int sum=0; for(i=1;i<=n;i++){ int thissum=0; for(j=i;j<=n;j++){ thissum+=a[j]; if(thissum>sum){ sum=thissum; besti=i; bestj=j; } } } } 改进后的算法复杂性为O(n2)。 起点i从1到n, 终点j从i~n(演示) 一个简单算法
2. 分治方法求解 当和为负值时约定为0 分治法:a[left:right]分为a[1:center],a[center+1:n]: A) a[left:right]与a[left:center]相同. B) a[left:right]与a[center+1:right]相同; C)a[left:right]最大子段和横跨两段aleft+ai+1 +…an/2+an/2+1…+aright A、B递归求得。C直接求?分别求出两子段最大和 分治算法(板分划图) int MaxSubSum(int a, int left, int right) { int sum=0; //负数为0 if (left==right) sum=a[left]>0?a[left]:0; else{int center=(left+right)/2;//取整!!! int leftsum=MaxSubSum(a,left,center); int rightsum=MaxSubSum(a,center+1,right); int s=0; lefts=0; //直接用暴力求交叉处 for (int i=left,i<=center;i++) for (int j=center+1;j<=right;j++) { lefts=0; for (int k=i;k<=j;k++) lefts+=a[k]; sum=(sum<lefts?lefts:sum); } //但这是O(n3) if (sum<leftsum) sum=leftsum; if (sum<rightsum) sum=rightsum; } return sum; }um//演示,起点在左段,终点在右段,但是O(n2)
int MaxSubSum(int a, int left, int right) { int sum=0; if (left==right) sum=a[left]>0?a[left]:0; else{int center=(left+right)/2;//取整!!! int leftsum=MaxSubSum(a,left,center); int rightsum=MaxSubSum(a,center+1,right); int s1=0;lefts=0; //中间连续往左 for (int i=center;i>=left; i--) { lefts+=a[i]; if (lefts>s1) s1=lefts; } int s2=0;rights=0; //中间连续往右 for (int i=center+1;i<=right; i++) { rights+=a[i]; if (rights>s2) s2=rights; } sum=s1+s2; //连接左右两段 if (sum<leftsum) sum=leftsum; if (sum<rightsum) sum=rightsum; } return sum; } //求s1/s2的代码就是动态规划算法(板) 2. 分治方法求解 当和为负值时约定为0 • 分治法:将a[left:right]分为a[left:center]和a[center+1,right]: • a[left:right]的最大子段和与a[left:center]的最大子段和相同; • a[left:right]的最大子段和与a[center+1:right]最大子段和相同; • a[left:rigt]最大子段和横跨两段ai+ai+1+…an/2+an/2+1…+aj A、B递归求得。C先求a[left:center]和a[center+1:right]的最大子段和s1和s2,再求s1+s2。不必像前法求交叉段最大和 • 递归仅大化小!
分治法的基本思想 • 分治法的基本思想 • 将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同。 • 对这k个子问题分别求解。如果子问题的规模仍然不够小,则再划分为k个子问题,如此递归的进行下去,直到问题规模足够小,很容易求出其解为止。 • 将求出的小规模的问题的解合并为一个更大规模的问题的解,自底向上逐步求出原来问题的解。 • 将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
动态规划基本步骤 • 找出最优解的性质,并刻划其结构特征。判断整体最优是否有局部最优 • 递归定义最优值。有难度。 • 以自底向上的方式计算出最优值。非递归 • 根据计算最优值时得到的信息,构造最优解。!
3. 动态规划方法求解 • a1,a2,…,ai,…,aj,…an中最大子段和ai+ai+1+…+aj,结束点j在[1,n]之间,起点i在[1,j] 。先终点后起点,提出共同的终点 • bj是终点为j的子段和的最大值,其值可迭代出来。 • 当bj-1>0时bj=bj-1+aj,否则bj-1=0故bj=aj。(板,直接推公式) • 递归式bj=max{bj-1+aj,aj},1≤j≤n。每求出一个bj,判断是否当前最大
3. 动态规划方法求解 • 最大子段和是ai+ai+1+…+aj的最大值,为a1,a2,…,ai,…,aj,…an中一段的最大值,结束点j在[1,n]之间,起点i在[1,j] 。先终点后起点,提出共同的终点 • bj是终点为j的子段和的最大值,其值可迭代出来。 • 当bj-1>0时bj=bj-1+aj,否则bj=aj。 • 递归式bj=max{bj-1+aj,aj},1≤j≤n。每求出一个bj,判断是否当前最大 int MaxSum(int n, int a){ //板 int sum=0; //整体最优 b=0; //终点为j的子段最大值 for (j=1;j<=n;j++){ if (b>0){ b+=a[j];} else{ b=a[j];} if (b>sum) sum=b;} return sum; } //计算时间为O(n)。
4. 算法的推广-最大矩阵和问题 • 给定一个m行n列的整数矩阵A,试求矩阵A的一个子矩阵,使其各元素之和为最大。 int MaxSum2(int m,int n,int **a) { int sum=0; int *b=new int[n+1];//1行数据 for (int i=1;i<=m;i++){ //1行~m行 for (int k=1;k<=n;k++) b[k]=0; for (int j=i;j<=m;j++) { //i~j行各列和 for (int k=1;k<=n;k++) { b[k]+=a[j][k];} //i1~i2行各列和最大子项 int max=MaxSum(n,b); if (max>sum) sum=max; }} return sum;} (10, 3, 7, 8) 最大子段和28 (5, 8, 11,-1) 最大子段和24 (11, 7, 19, 6) 最大子段和43 (20,13,16,10) 最大子段和59