【算法设计与分析】第四章 分治法

启发式规则:

1. 平衡子问题:最好使子问题的规模大致相同。也就是将一个问题划分成大小相等的k个子问题(通常k=2),这种使子问题规模大致相等的做法是出自一种平衡子问题的思想,它几乎总是比子问题规模不等的做法要好。
2. 独立子问题:各子问题之间相互独立,这涉及到分治法的效率,如果各子问题不是独立的,则分治法需要重复地解公共的子问题。

分治法的求解过程

1.划分:把规模为n的原问题划分为k个规模较小的子问题,并尽量使这k个子问题的规模大致相同。
2.求解子问题:各子问题的解法与原问题的解法通常是相同的,可以用递归的方法求解各个子问题,有时递归处理也可以用循环来实现。
3.合并:把各个子问题的解合并起来,合并的代价因情况不同有很大差异,分治算法的有效性很大程度上依赖于合并的实现。

递 归(Recursion)

递归就是子程序(或函数)直接调用自己或通过一系列调用语句间接调用自己,是一种描述问题和解决问题的基本方法。
递归有两个基本要素:
边界条件:确定递归到何时终止;
递归模式:大问题是如何分解为小问题的。
优点:
递归算法结构清晰,可读性强,而且容易用数学归纳法来证明算法的正确性,因此,它为设计算法和调试程序带来很大方便,是算法设计中的一种强有力的工具。
缺点:
因为递归算法是一种自身调用自身的算法,随着递归深度的增加,工作栈所需要的空间增大,递归调用时的辅助操作增多,因此,递归算法的运行效率较低。

递归与分治

【最大子段和问题】
给定由n个整数组成的序列(a1, a2, …, an),最大子段和问题要求该序列形如
的最大值(1≤i≤j≤n),当序列中所有整数均为负整数时,其最大子段和为0。例如,序列(-20, 11, -4, 13, -5, -2)的
最大子段和为: 20
【求解】
最大子段和问题的分治策略是:
(1)划分:按照平衡子问题的原则,将序列 ( a 1 , a 2 , … , a n ) (a_1, a_2, …, a_n) (a1,a2,,an)划分成长度相同的两个子序列 ( a 1 , … , a n / 2 ) (a_1, …, a_{n/2}) (a1,,an/2) ( a n / 2 + 1 , … , a n ) (a_{n/2} +1, …, a_n) (an/21,,an),则会出现以下三种情况:
a 1 , … , a n a_1, …, a_n a1,,an的最大子段和= a 1 , … , a n / 2 a_1, …,a_{n/2} a1,,an/2的最大子段和;
a 1 , … , a n a_1, …, a_n a1,,an的最大子段和= a n / 2 + 1 , … , a n a_{n/2}+1, …, a_n an/21,,an的最大子段和;
a 1 , … , a n a_1, …, a_n a1,,an的最大子段和= Σ k = i j a k Σ_{k=i}^ja_k Σk=ijak ,且 1 < = i < = n / 2 1<=i<=n/2 1<=i<=n/2并且 n / 2 + 1 < = j < = n n/2+1<=j<=n n/2+1<=j<=n
(2)求解子问题:对于划分阶段的情况①和②可递归求解,情况③需要分别计算左半边和右半边 s 1 = m a x ( Σ k = i n / 2 a k ) , 1 < = i < = n / 2 s_1=max(Σ_{k=i}^{n/2}a_k),1<=i<=n/2 s1=max(Σk=in/2ak),1<=i<=n/2 s 2 = m a x ( Σ k = n / 2 + 1 j a k ) , n / 2 + 1 < = j < = n s_2=max(Σ_{k=n/2+1}^ja_k),n/2+1<=j<=n s2=max(Σk=n/2+1jak),n/2+1<=j<=n,则 s 1 + s 2 s_1+s_2 s1+s2是③的最大值。
(3)合并:比较在划分阶段的三种情况下的最大子段和,取三者之中的较大者为原问题的解。

代码实现:

#include<bits/stdc++.h>
using namespace std;
int n,a[100010],ans;

int calculate(int left,int right)
{
    int sum=0;
    if(left==right)
    {
        if(a[left]>0)
            sum=a[left];
        else
            sum=0;
    }
    else
    {
        int sum1=0,sum2=0,sum3=0;
        int mid=(left+right)/2;
        sum1=calculate(left,mid);
        sum2=calculate(mid+1,right);
        int tmp1=0,cnt1=0;
        for(int i=mid;i>=left;i--)
        {
            cnt1+=a[i];
            tmp1=max(cnt1,tmp1);
        }
        int tmp2=0,cnt2=0;
        for(int i=mid+1;i<=right;i++)
        {
            cnt2+=a[i];
            tmp2=max(cnt2,tmp2);
        }
        sum3=tmp1+tmp2;
        sum=max(sum1,max(sum2,sum3));
    }
    return sum;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    ans=calculate(1,n);
    cout<<ans<<endl;
    return 0;
}

【循环赛日程安排问题】
设有n=2k个选手要进行网球循环赛,要求设计一个满足以下要求的比赛日程表:
(1)每个选手必须与其他n-1个选手各赛一次;
(2)每个选手一天只能赛一次。
按此要求,可将比赛日程表设计成一个 n 行n-1列的二维表,其中,第 i 行第 j 列表示和第 i 个选手在第 j 天比赛的选手。
(a) 2 k ( k = 1 ) 2_k(k=1) 2k(k=1)个选手比赛
在这里插入图片描述
(b) 2 k ( k = 2 ) 2_k(k=2) 2k(k=2)个选手比赛
在这里插入图片描述
© 2 k ( k = 3 ) 2_k(k=3) 2k(k=3)个选手比赛
在这里插入图片描述
根据观察得到规律:
(1)左上角:左上角为 2 k − 1 2_{k-1} 2k1个选手在前半程的比赛日程;
(2)左下角:左下角为另 2 k − 1 2_{k-1} 2k1个选手在前半程的比赛日程,由左上角加 2 k − 1 2_{k-1} 2k1得到,例如 2 2 2^2 22个选手比赛,左下角由左上角直接加2得到, 2 3 2^3 23个选手比赛,左下角由左上角直接加4得到;
(3)右上角:将左下角直接抄到右上角得到另 2 k − 1 2_{k-1} 2k1个选手在后半程的比赛日程;
(4)右下角:将左上角直接抄到右下角得到 2 k − 1 2_{k-1} 2k1个选手在后半程的比赛日程;

代码实现:

#include<bits/stdc++.h>
using namespace std;
int n,a[10010][10010];

void init(int k)
{
    int x=2,tmp=x;
    a[1][1]=1; a[1][2]=2;
    a[2][1]=2; a[2][2]=1;
    for(int p=1;p<k;p++)
    {
        tmp=x,x*=2;
        for(int i=tmp+1;i<=x;i++)
            for(int j=1;j<=tmp;j++)
                a[i][j]=a[i-tmp][j]+tmp;
        for(int i=1;i<=tmp;i++)
            for(int j=tmp+1;j<=x;j++)
                a[i][j]=a[i][j-tmp]+tmp;
        for(int i=tmp+1;i<=x;i++)
            for(int j=tmp+1;j<=x;j++)
                a[i][j]=a[i-tmp][j-tmp];
    }
}

int main()
{
    cin>>n;
    init(n);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            cout<<a[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值