1. 用动态规划策略求解0-1背包问题
(1)问题描述与分析
【问题描述】
现有n件物品,一个容量为c的背包,第i件物品的质量为,价值为
。已知对于一件物品,必须选择取(用1表示)或不取(用0表示),且每件物品只能被取一次,要求物品能够放进背包且使得背包中物品价值最大。
【问题分析】
给定背包容量c>0,每个物品的质量和价值>0,
>0,表示物品是否放入背包的n元0-1向量为(
),
(0,1),1≤i≤n。对于n个物品,容量为c的背包,求出所有可能放入背包的物品组合,并计算出每一种组合的物品价值Sum-v和物品质量Sum-w,当Sum-w≤c时,该解可行,比较所有可行解的Sum-v,找出最大的价值,最后输出装入物品价值最大时的放入背包的n元0-1向量和最大的价值。
(2)算法设计思想及两种递推关系的建立
【算法设计思想】
1、动态规划建立在最优原则的基础上,在每一决策步上列出各种可能局部解,按某些条件舍弃不能得到最优解的局部解,通过逐步筛选,减少计算量。依据最优性原理,寻找最优判断序列,不论初始状态和递归策略如何,下一次决策必须相对前一次决策产生的新状态构成最优序列。每一步都经过筛选,以每一步的最优性来保证全局的最优性。具体来说,动态规划算法仍然是将待求解的问题分成若干子问题,采用列表技术,将从小到大的各个子问题的计算答案存储于一张表中,由于将原问题分解后的各个子问题可能存在重复性,所以当重复遇到该子问题时,只需查表继续问题的求解,而不需要重复计算。因此,动态规划算法的基本思想是记录子问题并不断填表。
2、动态规划的基本要素:最优子结构、无后效性和子问题重叠性。
a、最优子结构:如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子 结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
b、无后效性:各阶段按照一定的次序排好之后,一旦某阶段状态已经确定,它以前各阶段的状态无 法直接影响未来的决策,且当前的状态只是对以往决策的总结。
c、子问题重叠性:用递归算法对问题进行求解时,每次产生的子问题并不总是新问题,有些子i问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的解题效率。
【建立递推关系】
设所给0-1背包的子问题为
≤j;
∈
,i≤k≤n
【递推关系1】:自底向上
如果第i个物品的质量大于背包能装下的最大质量,则不装入物品i,计算从i到n的n-i+1个物品得到的最大价值和计算从i+1到n的n-i个物品得到的最大价值是相同的,即
;0≤j≤
如果第i个物品的质量小于背包容量,则会有以下两种情况:
- 如果把第i个物品装入背包,则背包中物品的价值等于把后i+1个物品装入容量为j-
的背包中的价值再加上第i个物品的价值
。
- 如果第i个物品没有装入背包,则背包中物品的价值就等于把后i+1个物品装入容量为j的背包中所取得的价值。
【递推关系2】:自顶向下
如果第i个物品的质量大于背包能装下的最大质量,则不装入物品i,,故此时所能获得的最大价值与只考虑第1到第i-1件物品放入总容量为j的背包所获得的最大价值相同。
如果第i个物品的质量小于背包容量,则会有以下两种情况:
- 如果第i个物品没有装入背包,所以这种情况获得的最大价值与m(i-1,j) 相同。
- 如果把第i个物品装入背包,那么总容量为j的背包必须分配
的空间给第i件物品,相当于第1到第i-1物品是只能放在总容量为j-
的背包中,获得的价值就是
。综上,b情况就要在
与
中选个价值大的。
(3)算法实现
【算法实现1】
#include<stdio.h>
#define MAXX 100
void knapsack(int n, int C, int w[], int v[], int x[])
{
int bag[n+1][C+1]; //dp数组
int i,j;
for(i=0;i<=n;i++){
bag[i][n] = 0;//初始化第n列
bag[i][0] = 0;}
for(i=0;i<=C;i++){ //初始化第n行
bag[n][i] = 0;
bag[0][i] = 0;}
for(i=n-1;i>=0;i--) //物品遍历
{
for(j=1;j<=C;j++) //背包重量遍历
{
if(j<w[i+1]) //物品重量大于背包重量
{
bag[i][j] = bag[i+1][j];
}
else //物品重量小于背包重量
{
if(bag[i+1][j]<bag[i+1][j-w[i+1]]+v[i+1]) //两者价值比较
{
bag[i][j] = bag[i+1][j-w[i+1]]+v[i+1];
}
else
{
bag[i][j] = bag[i+1][j];
}
}
}
}
j=C;
for(i=n-1;i>=0;i--) //从倒数第二个物品反推
if(bag[i][j]!=bag[i+1][j]) //拿了
{
j=j-w[i+1]; //减去重量
x[i+1]=1; //记录拿了的数据
}
else
x[i+1]=0; //记录没拿的数据
x[0]=j>=w[0]?1:0;
}
int main(){
int n,C;
int w[MAXX],v[MAXX],x[MAXX];
printf("物品个数 背包容量:");
scanf("%d %d",&n,&C); //物品个数,背包容量
printf("每个物品质量:");
for(int i=0;i<n;i++)
scanf("%d",&w[i]); //每个物品质量
printf("每个物品价值:");
for(int i=0;i<n;i++)
scanf("%d",&v[i]); //每个物品价值
knapsack(n,C,w,v,x);
int vsum=0;
printf("装载物品和价值:\n");
for(int i=0;i<n;i++){
vsum+=x[i]*v[i];
printf("%d %d",x[i],v[i]);
printf("\n");
}
printf("背包最大价值:");
printf("%d",vsum);
return 1;
}
【算法实现2】
#include<stdio.h>
#define MAXX 100
void knapsack(int n, int C, int w[], int v[], int x[])
{
int bag[n+1][C+1]; //dp数组
int i, j;
for(i=0;i<=n;i++) //初始化第一列
bag[i][0] = 0;
for(i=0;i<=C;i++) //初始化第一行
bag[0][i] = 0;
for(i=1;i<=n;i++) //物品遍历
{
for(j=1;j<=C;j++) //背包重量遍历
{
if(j<w[i-1]) //物品重量大于背包重量
{
bag[i][j] = bag[i-1][j];
}
else //物品重量小于背包重量
{
if(bag[i-1][j]<bag[i-1][j-w[i-1]]+v[i-1]) //两者价值比较
{
bag[i][j] = bag[i-1][j-w[i-1]]+v[i-1];
}
else
{
bag[i][j] = bag[i-1][j];
}
}
}
}
j=C;
for(int i=n;i>=1;i--) //从最后一个物品反推
{
if(bag[i][j]!=bag[i-1][j]) //拿了
{
j=j-w[i-1]; //减去重量
x[i-1]=1; //记录拿了的数据
}
else
x[i-1]=0; //记录没拿的数据
}
}
int main(){
int n,C;
int w[MAXX],v[MAXX],x[MAXX];
printf("物品个数 背包容量:");
scanf("%d %d",&n,&C); //物品个数,背包容量
printf("每个物品质量:");
for(int i=0;i<n;i++)
scanf("%d",&w[i]); //每个物品质量
printf("每个物品价值:");
for(int i=0;i<n;i++)
scanf("%d",&v[i]); //每个物品价值
knapsack(n,C,w,v,x);
int vsum=0;
printf("装载物品和价值:\n");
for(int i=0;i<n;i++){
vsum+=x[i]*v[i];
printf("%d %d",x[i],v[i]);
printf("\n");
}
printf("背包最大价值:");
printf("%d",vsum);
return 1;
}
2.用动态规划求解凸多边形最优三角剖分问题
(1)问题描述与分析
【问题描述】
给定一个凸多边形P=,以及定义在由多边形的边和弦组成的三角形上的权函数ω。要求确定该凸多边形的一个三角剖分,使得该三角剖分对应的权(即剖分中诸三角形上的权)之和为最小。三角形权函数ω的定义方式很多,如定义
,其中,
是点
到
的欧氏距离对应此权函数的最优三角剖分即为最小弦长三角剖分。
【问题分析】
多边形是平面上一条分段线性的闭曲线。也就是说,多边形是由一系列首尾相接的直线段组成的。组成多边形的各直线段称为该多边形的边。连接多边形相继两条边的点称为多边形的顶点。若多边形的边除了连接顶点没有别的交点,称其为一个简单多边形。
通常,用多边形的顶点的逆时针序列表示凸多边形,即P=表示具有n条边
、
、
、…、
的凸多边形。其中我们约
。
若与
是多边形上的不相邻的两个顶点,则线段
称为多边形的一条弦。弦
将多边形分割为两个多边形
和
。
多边形的三角剖分是指将多边形分割成互不相交的三角形的弦的集合T。
具体的三角剖分如图所示:
(2)算法设计思想及递推关系建立
【算法设计思想】
- 区域动态规划解决的问题的特点是:对整个问题设置最优值,枚举划分(合并)点,将问题分解 为左右两个部分,合并两部分的最优值得到原问题的最优值,类似分治算法的解题思想。
- 分治法将一个大规模问题分解为若干子问题(子问题是原问题的小模式,而且相互之间独立),将子问题的解合并后就可以得到原问题的解,具体可使用递归来完成。
- 动态规划建立在最优原则的基础上,在每一决策步上列出各种可能局部解,按某些条件舍弃不能得到最优解的局部解,通过逐步筛选,减少计算量。依据最优性原理,寻找最优判断序列,不论初始状态和递归策略如何,下一次决策必须相对前一次决策产生的新状态构成最优序列。每一步都经过筛选,以每一步的最优性来保证全局的最优性。具体来说,动态规划算法仍然是将待求解的问题分成若干子问题,采用列表技术,将从小到大的各个子问题的计算答案存储于一张表中,由于将原问题分解后的各个子问题可能存在重复性,所以当重复遇到该子问题时,只需查表继续问题的求解,而不需要重复计算。因此,动态规划算法的基本思想是记录子问题并不断填表。
【建立递推关系】
定义,1≤i<j≤n为凸子多边形
的最优三角剖分所对应的权函数值,即其最优值。设退化的多边形
具有权值0。据此定义,要计算的凸(n+1)边形P=
的最优权值为
。
的值可以利用最优子结构性质递归地计算。当j-i≥1时,凸子多边形
至少有3个顶点。由最优子结构性质,
的值应为
的值加上
的值,再加上三角形
的权值,其中i≤k≤j-1。因为k的位置不确定,而k所有可能的位置只有j-i个,因此可以在j-i个位置中选出使
值达到最小的位置。
【递推关系】
(3)算法实现
【算法实现】
#include <stdio.h>
#define N 6 //顶点数/边数
int weight[][N] = {{0,2,2,3,1,4},{2,0,1,5,2,3},{2,1,0,2,1,4},{3,5,2,0,6,2},
{1,2,1,6,0,1},{4,3,4,2,1,0}};
int t[N][N]; //t[i][j]表示多边形{Vi-1VkVj}的最优权值
int s[N][N]; //s[i][j]记录Vi-1到Vj最优三角剖分的中间点K
int get_weight(const int a, const int b, const int c)
{
return weight[a][b] + weight[b][c] + weight[c][a];
}
void minest_weight_val()
{
int i,r,k,j;
int min;
for (i = 1; i < N; i++)
{
t[i][i] = 0;
}
for (r = 2; r < N; r++)
{
for (i = 1; i < N-r+1; i++)
{
j = i + r -1;
min = 9999; //假设最小值
for (k = i; k < j; k++)
{
t[i][j] = t[i][k] + t[k+1][j] + get_weight(i-1,k,j);
if (t[i][j] < min) //判断是否是最小值
{
min = t[i][j];
s[i][j] = k;
}
}
t[i][j] = min; //取得多边形{Vi-1,Vj}的划分三角最小权值
}
}
}
void back_track(int a, int b)
{
if (a == b) return;
back_track(a,s[a][b]);
back_track(s[a][b]+1,b); //记得这是要加一
printf("最优三角: V%d V%d V%d.\n",a-1,s[a][b],b);
}
int main()
{
minest_weight_val();
printf("result:%d\n",t[1][N-1]);
back_track(1,5);
return 0;
}