欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

动态规划之石子合并

程序员文章站 2022-07-03 08:49:24
...

1、问题

( 1 )路边玩法
有 n 堆石子堆放在路边,现要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量。求将这 N 堆石子合并成一堆的总花费(最小或最大)。




2、分析

( 1 )建立最优值递归式
设 Min [i][j] 代表从第 i 堆石子到第 j 堆石子合并的最小花费, Min [i][k] 代表从第 i 堆石子到第 k 堆石子合并的最小花费,Min[k+1][j] 代表从第 k+1 堆石子到第 j 堆石子合并的最小花费, w ( i , j )代表从 i 堆到 j 堆的石子数量之和。列出递归式:
Min [ i ][ j ] = 0 (i = j)
Min [ i ][ j ] = min ( Min [ i ][ k ] + Min [ k + 1][ j ] + w ( i , j )) , i < j( i ≤ k < j)

Max [i][j] 代表从第 i 堆石子到第 j 堆石子合并的最大花费,Max [i][k] 代表从第 i 堆
石子到第 k 堆石子合并的最大花费,Max [k+1][j] 代表从第 k+1 堆石子到第 j 堆石子合并的最大花费, w ( i , j )代表从 i 堆到 j 堆的石子数量之和。列出递归式:

Max [ i ][ j ] = 0 (i = j)
Max [ i ][ j ] =max( Max [ i ][ k ] + Max [ k + 1][ j ] + w ( i , j )) , i < j
( i ≤ k < j)

二维数组 Min [i][j] 、Max [i][j] 来记录第 i 堆到第 j 堆 a i , a i+1 ,..., a i 堆石子合并的最小花费和最大花费。
( 2 )初始化
输入石子的堆数 n ,然后依次输入各堆石子的数量存储在 a[i] 中,令 Min [i][i]=0 ,
Max [i][i]=0 , sum[0]=0 ,计算 sum[i] ,其中 i= 1 , 2 , 3 ,..., n 。
( 3 )循环阶段
• 按照递归式计算 2 堆石子合并 {a i , a i+1 } 的最小花费和最大花费, i=1 , 2 , 3 ,..., n−1 。
• 按照递归式计算 3 堆石子合并 {a i , a i+1 , a i+2 } 的最小花费和最大花费, i=1 , 2 , 3 ,...,n−2 。

以此类推,直到求出所有堆 {a 1 ,..., a n } 的最小花费和最大花费。

动态规划之石子合并






3、代码实现

#include <iostream>

using namespace std;

#define LEN 1024
#define MAXDATA 200000
int MIN[LEN][LEN];
int MAX[LEN][LEN];
int data[LEN];
int sum[LEN];

int min(int a, int b)
{
	return a > b? b : a;
}

int max(int a, int b)
{
	return a > b ? a : b;	
}

void straight(int len)
{
	//初始化对角线,我们知道MIN[i][i]和MAX[i][i]为0
	for (int i = 1; i <= len; ++i)
	{
		MIN[i][i] = 0;
		MAX[i][i] = 0;
	}
	sum[0] = 0;
	sum[1] = data[1];
	for (int i = 1; i < len; ++i)
	{
		sum[i + 1] = sum[i] + data[i + 1];
	}
	for (int i = 0; i <= len; ++i)
	{
		std::cout << sum[i] << "\t";
	}
	//数据坐标分析,我们知道是需要循环5次
	for (int v = 2; v <= len; ++v)
	{
    /** 
	下面是其中的下标的数据,我们知道i从1到5,1到4,1到3,1到2,1到1,
	j从2到6,3到6,4到6,5到6,6到6
	1,2 1,3 1,4 1,5 1,6
	2,3 2,4 2,5 2,6
	3,4 3,5 3,6
	4,5 4,6
	5,6
	**/
		for (int i = 1; i <= len - v + 1; ++i)
		{
			int j = i + v - 1;
			MIN[i][j] = MAXDATA;
			MAX[i][j] = -1;
			int temp = sum[j] - sum[i - 1];
			for (int k = i; k < j; ++k) 
			{
				MIN[i][j] = min(MIN[i][j], MIN[i][k] + MIN[k + 1][j] + temp);
				MAX[i][j] = max(MAX[i][j], MAX[i][k] + MAX[k + 1][j] + temp);
			}
		}
	}
}


int main()
{
	int all;
	std::cout << "请输入有多少堆石子" << std::endl;
	std::cin >> all;
	std::cout << "请分别输入每堆石子的个数" << std::endl;
	for (int i = 1; i <= all; ++i)
	{
		std::cin >> data[i];
	}
	straight(all);
	std::cout << "\n";
	std::cout << "直线形最小花费是" << MIN[1][all] << std::endl;
	std::cout << "直线形最大花费是" << MAX[1][all] << std::endl;
	return 0;	
}

4、运行结果

请输入有多少堆石子
6
请分别输入每堆石子的个数
3
4
3
34
3
3
0	3	7	10	44	47	50	
直线形最小花费是113
直线形最大花费是219



5、总结

循环的时候我们要知道斜着有5斜杠,所以是5次循环,然后分析i,每次结尾不同,开始一样,然后再找出j的关系,然后每次初始化最大值和最小值,然后每次通过min函数进行每次把MIN[i][j]把自己传递进去和k一次增加的MIN[i][ k] + MIN[K+1][j] + temp进行对比,因为 MIN[i][j]每次保存在数组里面,所以每次都有记录才可以这样循环比,得到最小的和最大的

时间复杂度最坏0(n3)