2021-07-02

题 目: 环形石子合并问题

一、问题分析
问题描述:在一个圆形操场的四周摆放着n堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。
要求:对于任意给定的n堆石子,计算合并成一堆的最小得分和最大得分。
二、设计思路
解决方案/算法的功能结构框图

三、问题及解决方案
1)审题不清:题目要求的是将每两堆石子合并之后的数量与下一次合并之后的数量相加求得分,我们一开始理解成:相邻两堆石子合并后,用合并后的石子数量再与相邻石子合并相加求得分,如果这样想的话,最后的得分就是一个定值,即所有石子数之和,也就不存在最大得分和最小得分了。
2)方法的使用:
开始以为通过贪心算法可能很快解决问题,可是是行不通的。
首先我们可以把这么堆石子看成一列,我们假如5堆的石子,其中石子数分别为7,6,5,7,100
按照贪心法,合并的过程如下: 每次合并得分
第一次合并 7 6 5 7 100 =11
第二次合并 7 11 7 100=18
第三次合并 18 7 100 =25
第四次合并 25 100 =125
总得分=11+18+25+125=179

另一种合并方案 每次合并得分
第一次合并 7 6 5 7 100 ->13
第二次合并 13 5 7 100->12
第三次合并 13 12 100 ->25
第四次合并 25 100 ->125
总得分=13+12+25+125=175
由此可以看出,利用贪心法来做是错误的,贪心算法在子过程中得出的解只是局部最优,而不能保证使得全局的值最优。
四、算法设计
如果N-1次合并的全局最优解包含了每一次合并的子问题的最优解,那么经这样的N-1次合并后的得分总和必然是最优的。
因此我们需要通过动态规划算法来求出最优解。
在此我们假设有n堆石子,一字排开,合并相邻两堆的石子,每合并两堆石子得到一个分数,最终合并后总分数最少的。
我们设m(i,j)定义为第i堆石子到第j堆石子合并后的最少总分数。a(i)为第i堆石子的石子数量。
当合并的石子堆为1堆时,很明显m(i,i)的分数为0;
当合并的石子堆为2堆时,m(i,i+1)的分数为a(i)+a(i+1);
当合并的石子堆为3堆时,m(i,i+2)的分数为MIN((m(i,i)+m(i+1,i+2)+sum(i,i+2)),(m(i,i+1)+m(i+2,i+2)+sum(i,i+2));
当合并的石子堆为4堆时…

(1)求最小得分相应的代码如下:
int MatrixChain_min(int p[N],int n)//p[N]石子数,n石子堆数
{
int m[N][N]; //定义二维数组m[i][j]来记录i到j的合并过成中最少石子数目(石子积分)

int min=0;                                                     
   for(int g = 1;g<=n;g++)
		m[g][g]=0;  //当一个单独合并时,m[i][i]设为0,表示没有积分。
                                                        
	for(int i=1;i<=n-1;i++)//当相邻的两堆石子合并时,此时的m很容易可以看出是两者之和。
	{
      int j=i+1;
      m[i][j]=p[i]+p[j];
    }
                                                     
	for(int r=3; r<=n;r++)//当相邻的3堆以及到最后的n堆时,执行以下循环(r表示考虑到了多堆石子合并的所有情况) 
       for(int i=1;i<=n-r+1;i++)//i表示开始的那一堆,j表示最后的那一堆 
       {
           int j = i+r-1; //j总是距离i r-1的距离                           
           int fin=0;                                  
           for(int b=i;b<=j;b++)//当i到j堆石子合并时最后里面的石子数求和得fin(fin是所有的石子数) 
               fin+=p[b];

           // 此时m[i][j]为i~j堆石子间以m[i][i]+m[i+1][j]+fin结果,这是其中一种可能,不一定是最优
           //要与下面的情况相比较

            m[i][j] = m[i+1][j]+fin;//最初的积分情况 

           //除上面一种组合情况外的其他组合情况
           for(int k=i+1;k<j;k++)//进行比较 
           {
               int t=m[i][k]+m[k+1][j]+fin;//后来情况的积分 
               if(t<m[i][j])
                   m[i][j] = t;

           }
       }
        //最终得到最优解
       min=m[1][n]; //最小的情况赋值给min 
       return min;
      
}

(2)最大得分的求法与最小得分的求法是相同的道理;

(3)我们回到解法的开始:假设的是石子一次排开求最大得分和最小得分,而题目要求的是:石子以圆形排开,因此我们在主函数中应用了相应方法来达到要求,代码如下:
int main()
{
int stone[N];//每堆石子数
int min=0;//最小的分初始化
int max=0;//最大的分初始化
int n;//石子堆数
printf(“请输入石子堆数:”);
scanf("%d",&n);
for(int i=1;i<=n;i++){
printf(“请输入第%d堆石子数:”,i);
scanf("%d",&stone[i]);
}
min= MatrixChain_min(stone,n);//传到子函数
max= MatrixChain_max(stone,n);//传到子函数

   //因为题目要求圆的原因,要把所有情况都要考虑到,总共有n种情况。
   for(int j=1;j<=n-1;j++)
   {
        int min_cache=0;//积分最小情况 
        int max_cache=0;//积分最大情况
        int cache= stone[1];
        for(int k=2;k<=n;k++)
        {
            stone[k-1]=stone[k];
        }
        stone[n]=cache;
        min_cache= MatrixChain_min(stone,n);
        max_cache= MatrixChain_max(stone,n);
        if(min_cache<min)
           min=min_cache;
        if(max_cache>max)
            max=max_cache;
  }

printf("最小得分:%d\n",min);
printf("最大得分:%d\n",max);
 return 1;

}
总的来说就是:在循环中,通过cache这个中间变量,让每一堆石子都有成为第一堆的机会,满足圆形的要求,然后用排序之后的石子调用上面的方法,得到新的得分数,分别与之前的最大得分和最小得分进行比较,得到最终的最大得分和最小得分。
五、结论
通过所写代码成功得出了正确的最大得分和最小得分。在运行过程中我们也可以感受到计算过程很多,运行速率不高,也许有更高效的方法等待我们发掘。以最少的成本、最快的速度、最好的质量开发出合适各种各样应用需求的软件,必须遵循软件工程的原则,设计出高效率的程序。一个高效的程序不仅需要编程技巧,更需要合理的数据组织和清晰高效的算法。这正是计算机科学领域里数据结构与算法设计所研究的主要内容。一些著名的计算机科学家认为,算法是一种创造性思维活动,并且处于计算机科学与技术学科的核心。
在本题中,我们运用了动态规划方法。动态规划是解决多阶段决策问题的一种方法。据空间顺序或时间顺序对问题的求解划分阶段。根据题意要求,对每个阶段所做出的某种选择性操作。用数学公式描述与阶段相关的状态间的演变规律。如果一类问题的求解过程可以分为若干个互相联系的阶段,在每一个阶段都需作出决策,并影响到下一个阶段的决策。在做每一步决策时,列出各种可能的局部解.依据某种判定条件,舍弃那些肯定不能得到最优解的局部解。以每一步都是最优的来保证全局是最优的。
总的来说,石子合并问题完全提现了动态规划的核心思想,先求解子问题的解(子问题求解不相互独立,相互依赖),然后从这些子问题的解中得到原问题的解,进而得到该问题的最优解。
通过对课程的理论学习与实践,我们掌握了许多经典的算法思想,思维创新能力和实践能力得到了有效的提高,并且一题多解的情况让我对不同的算法有了更加深刻的认识。这些知识在我们今后的学习中将会得到更深层次的理解与应用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值