一、实验目的
- 掌握动态规划方法贪心算法思想
- 掌握最优子结构原理
- 了解动态规划一般问题
二、实验内容
- 编写一个简单的程序,解决0-1背包问题。设N=5,C=10,w={2,2,6,5,4},v={6,3,5,4,6}
- 合唱队形安排问题
【问题描述】N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<…Ti+1>…>TK(1<=i<=K)。已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
三、算法思想分析
- 动态规划解决0-1背包问题
基本思路为设置一个情况数组,首先降低规模到一个物品放入大小为j的背包m(1,j),这个问题较好解决,针对背包的不同容积j可以较快得到结果,然后上溯推动规表达式,当有两件物品时,第一件物品的不同情况已经安置好,使用第一件物品的结果,如果第二件的大小大于现有容积则无法放入,即m(2,j)=m(1,j);如果第二件大小小于或者等于现有容积,判断放入(m(1,j-w2)+v2)与不放入(m(1,j))哪个的价值更大;将这个思路推广到i个物品,最终的动规表达式为:
最终得到一个动态规划的最大价值数组,找到相对应的数组内容即可;对于选择的方案,可以对比m(i,j)与m(i-1,j)的大小,若大于则第i件在背包中,若等于则不在。 - 合唱队形安排问题
基本思路就是针对当前队列向左向右分别使用LIS问题算法,存到两个数组中,相加减一数组的最大值就是剩下的人数,用总人数减去剩下的人数就是我们需要的结果。
LIS问题中,首先设置初始值为1,然后降规模,最小规模的是最左边的1,然后升规模,第二个的结果的判断是如果a[1]>a[0],则将b[0]+1赋给b[0];对于第三个的结果,需要求小于a[3]的a数组值中最大的,将b数组值加一,推广到i后则求出动规方程,再使用到合唱队形中,得到动规方程:
最后将b与c相加减一求得最大值即可。
四、实验过程分析
本次实验中是对动规算法的应用,由于我本身在上课的时候对动规算法的无后效性理解不够透彻,在做本次实验的题目时一直对降规模后升规模的这个思维方式不是很理解,导致我对题目的做法没法完全参透,在查阅博客等相关资料后才对这个无后效性有了一定的深入理解,这才开始做实验;代码过程中,对于降规模的方向问题又有了一定的迷惑,例如0-1背包是降物品数量的规模还是容积的规模,在通过尝试后发现降容积的规模之后并不能对问题易化,因此选择另一个方向。
整个实验中对算法的理解要求还是不低的,而且动规的算法思想在我看来相对于贪心更难理解和运用,但通过这次实验的两个题目,至少我对动规的最优化原理与无后效性有了自己的一定的理解,这对我在之后的学习生活中是很有帮助的。
五、算法源代码及用户屏幕
1.动态规划解决0-1背包问题
①代码
#include <iostream>
#include <cmath>
#define NUM 100
using namespace std;
void knapSack(int s[NUM][NUM], int weight[], int value[], int C, int n) {
//初始化第一列
int boundary = min(weight[1], C);
for (int i = 0; i < boundary; i++) {
s[1][i] = 0;
}
for (int i = boundary; i <= C; i++) {
s[1][i] = value[1];
}
//递推求得其他内容
for (int i = 2; i <= n; i++) {
//防止溢出情况出现
int bound = min(weight[i], C);
//当0 < j < weight[i]时,剩余容量不足以将当前物品装入
for (int j = 0; j < bound; j++) {
s[i][j] = s[i - 1][j];
}
//当j >= weight[i]时,判断不放或者放入时的value最大值
for (int j = bound; j <= C; j++) {
s[i][j] = max(s[i - 1][j], s[i - 1][j - weight[i]] + value[i]);
}
}
}
int main() {
int C, n;
cout << "Please input the number of objects: ";
cin >> n;
cout << "Please input the capacity of backpack: ";
cin >> C;
int weight[n + 1];
int value[n + 1];
int situation[NUM][NUM] = { 0 };
cout << "Please input the weight of each object: ";
for (int i = 1; i <= n; i++) {
cin >> weight[i];
}
cout << "Please input the value of each object: ";
for (int i = 1; i <= n; i++) {
cin >> value[i];
}
knapSack(situation, weight, value, C, n);
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= C; j++) {
if (j == C)cout << situation[i][j] << endl;
else cout << situation[i][j] << " ";
}
}
cout << "The maximum value is: " << situation[n][C] << endl;
int res[n + 1] = { 0 };
int tempC = 10;
//回推求加入背包的物品
for (int i = n; i >= 1; i--) {
if (i != 1) {
if (situation[i][tempC] > situation[i - 1][tempC]) {
res[i] = 1;
tempC -= weight[i];
}
}
else
{
if (situation[i][tempC] > 0) {
res[i] = 1;
}
}
}
cout << "The input method is: ";
for (int i = 1; i <= n; i++) {
if (res[i] > 0) {
cout << i << " ";
}
}
system("pause");
return 0;
}
②用户界面
首先输入物品个数和背包容积,然后输入物品的重量和价值,回车获得动规的表格,最大价值与相应的选取情况。
2. 合唱队形安排问题
①代码
#include <iostream>
using namespace std;
//从左向右递归LIS
void maxUp(int a[], int up[], int n) {
for (int i = 0; i < n; i++) {
int max = 0;
int index = -1;
for (int j = 0; j < i; j++) {
if (a[i] > a[j] && up[j] > max) {
max = up[j];
index = j;
}
}
if (index == -1)up[i] = 1;
else {
up[i] = up[index] + 1;
}
}
}
//从右向左递归LIS
void maxDown(int a[], int down[], int n) {
for (int i = n - 1; i >= 0; i--) {
int max = 0;
int index = -1;
for (int j = n - 1; j > i; j--) {
if (a[i] > a[j] && down[j] > max) {
max = down[j];
index = j;
}
}
if (index == -1)down[i] = 1;
else {
down[i] = down[index] + 1;
}
}
}
//向左向右求得结果
void chorus(int height[], int up[], int down[], int n) {
maxUp(height, up, n);
maxDown(height, down, n);
}
int main() {
int n;
cout << "Please input the number of students: ";
cin >> n;
int height[n];
int up[n] = { 0 };
int down[n] = { 0 };
cout << "Please input the height of each student: ";
for (int i = 0; i < n; i++) {
cin >> height[i];
}
chorus(height, up, down, n);
for (int i = 0; i < n; i++)cout << up[i] << " ";
cout << endl;
for (int i = 0; i < n; i++)cout << down[i] << " ";
cout << endl;
for (int i = 0; i < n; i++)cout << up[i] + down[i] - 1 << " ";
cout << endl;
cout << "the number of students out of queue is: ";
int max = 0;
int res = -1;
for (int i = 0; i < n; i++) {
int len = up[i] + down[i] - 1;
if (max < len) {
max = len;
}
}
cout << n - max;
system("pause");
return 0;
}
②用户界面
首先输入学生个数,然后分别输入每位学生的身高,回车得到从左到右,从右到左的数组值,最终需要的数组值以及结果。