校课程的简单实验报告。
北京大学出版社-算法设计与分析
目录
一、实验目的
1.理解动态规划算法的概念;
2.掌握动态规划算法的基本要素;
3.掌握设计动态规划算法的步骤和策略。
二、实验内容
使用动态规划法求解以下问题,要求给出程序代码,并编译运行程序:
1. 币值问题。
给定不同面额的硬币coins和一个总金额amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1。
示例1如下。
输入:coins=[1,2,5],amount=11
输出:3
解释:11=5+5+1
示例2如下。
输入:coins=[2], amount= 3
输出:-1
说明:你可以认为每种硬币的数量是无限的。
实质上该题是LeetCode322.零钱兑换
给你一个整数数组
coins
,表示不同面额的硬币;以及一个整数amount
,表示总金额。计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回
-1
。你可以认为每种硬币的数量是无限的。
2.航线问题。
美丽的莱茵河河畔,每边都有N个城市,并且每个城市都有唯一的对应友好城市。因为莱茵河上经常大雾,所以要制定航线,每个航线不可以交叉。现在要求出最大的航线数目。
输入:有若干组测试数据,每组测试数据第一行输入n,接着n行输入a、b表示a城市与b城市通航。(1≤n≤1000)。
输出:最大的航线数。
示例如下。
输入:
4
1 2
2 4
3 1
4 3
8
1 3
4 4
3 5
8 6
6 8
7 7
5 2
2 1
输出:
2
3
实质上是LeetCode300.
给你一个整数数组
nums
,找到其中最长严格递增子序列的长度。子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,
[3,6,2,7]
是数组[0,3,1,6,2,2,7]
的子序列。
3.数组均衡划分问题。
给定一组整数,任务是将其分为两组Subset1和Subset2,使得它们的和之间的绝对差最小。
如果有一个集合S有n个元素,那么如果假设Subset1有m个元素,那么Subset2必须有n-m个元素,abs(sum(Subset1)-sum(Subset2)) 的值应该是最小的。
示例如下。
输入:arr[]= {1,6,11,5}
输出:1
解释:s1={1,5,6},sum=12,s2={11},sum=11
三、实验环境
1. 使用的操作系统及版本:
Windows 10
2. 使用的编译系统及版本:
Clion 2022.2.3
四、实验步骤及说明
1、币值问题
代码如下:
//
// Created by GiperHsiue on 2022/11/2.
//
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
// 币值问题
//O(S*n) O(S)
//F(i) = min{F(i - Ci)} + 1
int coinsAmt(vector<int> coins, vector<int>& trace, int amount){
vector<int> F (amount + 1, amount + 1);
F[0] = 0;
int count = coins.size();
for(int i = 1; i <= amount; i ++){
for(int j = 0; j < count; j ++){
if(i - coins[j] < 0) continue;
else{
F[i] = min(F[i - coins[j]] + 1, F[i]);
if(F[i] == F[i - coins[j]] + 1) trace[i] = coins[j];
}
}
}
return F[amount] == amount + 1 ? -1 : F[amount];
}
// 方案路径
void findPath(vector<int> trace, int amount){
if(!amount) return;
findPath(trace, amount -trace[amount]);
if(amount -trace[amount] == 0) cout << trace[amount];
else cout << " + " << trace[amount];
}
int main(){
cout << "输入币值种类个数:";
int n;
cin >> n;
vector<int> coins(n);
vector<int> trace(100, 0);
for(int i = 0; i < n; i ++) cin >> coins[i];
cout << "输入总金额:";
int amount;
cin >> amount;
int res = coinsAmt(coins, trace, amount);
cout << "最小硬币数量:" << res << endl;
if(res != -1) {
cout << amount << " = ";
findPath(trace, amount);
}
return 0;
}
运行如下:
2.航线问题
代码如下:
//
// Created by Giperx on 2022/11/3.
//
// 航线问题
// 按a城市编号大小sort后,求最长上升子序列
// F[i] = max{F[i - 1] + 1, F[i]} A[i - 1] < A[i]
#include <iostream>
#include <vector>
using namespace std;
int lines(vector<int>& a){
int n = a.size();
int res = -1;
vector<int> F(n, 1);
for(int i = 0; i < n; i ++) {
for (int j = 0; j < i; j++) {
if (a[j] < a[i]) {
F[i] = max(F[i], F[j] + 1);
}
}
res = max(res, F[i]);
}
return res;
}
int main(){
int t;
cin >> t;
while(t--){
int n;
cin >> n;
vector<int> a(n, 0);
for(int i = 0; i < n; i ++){
int tmp1, tmp2;
cin >> tmp1 >> tmp2;
a[tmp1 - 1] = tmp2;
}
cout << lines(a);
}
return 0;
}
运行如下:
3.数组均衡划分问题
代码如下:
//
// Created by GiperHsiue on 2022/11/3.
//
// 数组均衡划分问题
// Subset1 尽量找到总和 接近 sums/2的子序列,本质01背包问题
// F[i][s] = max{F[i - 1][s], F[i - 1][s - arr[i]] + arr[i]}
// F[s] = max{F[s], F[s - arr[i]] + arr[i]}
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int absMin(vector<int> arr){
int sums = 0;
for(auto tmp:arr) sums += tmp;//总和sums
int *F = new int[int(sums/2) + 1](); // 状态数组初始化
for(int i = 0; i < arr.size(); i ++) {
for (int j = sums / 2; j >= arr[i]; j--)
F[j] = max(F[j], F[j - arr[i]] + arr[i]);
// for(int j = 0; j <= sums/2; j ++) cout << F[j] << ' ';
// cout << endl;
}
return F[int(sums/2)];
}
int main(){
int n, sums = 0;
cin >> n;
vector<int> arr(n, 0);
for(auto &tmp:arr) cin >> tmp, sums += tmp;
int res = absMin(arr);
cout << sums - 2 * res;
return 0;
}
运行如下:
五、实验小结及思考
通过本次实验,我对于动态规划算法的概念加深理解,进一步掌握动态规划算法的基本要素,也进一步掌握设计动态规划算法的步骤和策略。