算法第三章實驗報告
實驗內容: 動態規划的應用
第一題
題目描述:
7-3 最低通行費 (25 分)
一個商人穿過一個N×N的正方形的網格,去參加一個非常重要的商務活動。他要從網格的左上角進,右下角出。每穿越中間1個小方格,都要花費1個單位時間。商人必須在(2N-1)個單位時間穿越出去。而在經過中間的每個小方格時,都需要繳納一定的費用。
這個商人期望在規定時間內用最少費用穿越出去。請問至少需要多少費用?
注意:不能對角穿越各個小方格(即,只能向上下左右四個方向移動且不能離開網格)。
輸入格式:
第一行是一個整數,表示正方形的寬度N (1≤N<100);
后面N行,每行N個不大於100的整數,為網格上每個小方格的費用。
輸出格式:
至少需要的費用。
輸入樣例:
5
1 4 6 8 10
2 5 7 15 17
6 8 9 18 20
10 11 12 19 21
20 23 25 29 33
結尾無空行
輸出樣例:
109
結尾無空行
樣例中,最小值為109=1+2+5+7+9+12+19+21+33。
解題思路:改問題求解的是一個通行費用的最優解,目的是為了讓穿過路費盡可能的少。分析可得,從第一個格子到第n各格子的最少通行費等於經過第一個格子的路費加上從第二個格子到第n個格子的最優解。由此可得,該問題包含最優子結構問題,因此我們可以用動態規划法來求解這個最優解。
題目分析:
(1) 該動態規划法的邊界條件:m[i][1]=m[i][1]+m[i-1][1](i=2;i<=N;i++) m[1][j]=m[1][j]+m[1][j-1](j=2;j<=N; j++)
(2) 遞歸表達式:m[i][j]=min(m[i][j]+m[i-1][j],m[i][j]+m[i][j-1]);
(3) 最優解在數組m[N][N]的位置,因此應采用從左到右,從上到下的方式來計算最優解
代碼實現:
#include <iostream>
using namespace std;
int N;
int m[1000][1000];
int dp(){
// m[1][1]=m[1][1];
for(int i=2;i<=N;i++){
m[i][1]=m[i][1]+m[i-1][1];
}
for(int j=2;j<=N;j++){
m[1][j]=m[1][j]+m[1][j-1];
}
for(int i=2;i<=N;i++){
for(int j=2;j<=N;j++){
m[i][j]=min(m[i][j]+m[i-1][j],m[i][j]+m[i][j-1]);
}
}
return m[N][N];
}
int main(int argc, char** argv) {
cin>>N;
for(int i=1;i<=N;i++){
for(int j=1;j<=N;j++){
cin>>m[i][j];
}
}
cout<<dp();
return 0;
}
第二題
題目描述:
7-1 最大子段和 (25 分)
給定n個整數(可能為負數)組成的序列a[1],a[2],a[3],…,a[n],求該序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。當所給的整數均為負數時,定義子段和為0。
要求算法的時間復雜度為O(n)。
輸入格式:
輸入有兩行:
第一行是n值(1<=n<=10000);
第二行是n個整數。
輸出格式:
輸出最大子段和。
輸入樣例:
在這里給出一組輸入。例如:
6
-2 11 -4 13 -5 -2
結尾無空行
輸出樣例:
在這里給出相應的輸出。例如:
20
解題思路:
該題目的在於求解一個數組的最大字段和,數組里面的數可正可負,因此最大字段和可能從數組的某一個元素開始,故要求解最大字段和需求解從每一個數組元素開頭的最大字段和,從而得到整個數組的最大字段和。由此我們也可以發現該問題存在最優子結構,故可以用動態規划法來求解。
題目分析:
(1) 動態規划遞歸式:b[j]=max{b[j-1]+a[j],a[j]}
(2) 用一個變量記下最大的字段和,計算完成后該變量即為最優解。
代碼實現:
#include <iostream>
using namespace std;
int N;
int a[1000];
int MaxSum(){
int sum = 0;
for(int i=1;i<=N;i++){
int tmp = 0;
for(int j=i;j<=N;j++){
tmp += a[j];
if(tmp>sum){
sum=tmp;
}
}
}
return sum;
}
int main(int argc, char** argv) {
cin>>N;
for(int i=1;i<=N;i++){
cin>>a[i];
}
cout<<MaxSum();
return 0;
}
第三題
題目描述:
7-2 單調遞增最長子序列 (25 分)
設計一個O(n2)時間的算法,找出由n個數組成的序列的最長單調遞增子序列。
輸入格式:
輸入有兩行: 第一行:n,代表要輸入的數列的個數 第二行:n個數,數字之間用空格格開
輸出格式:
最長單調遞增子序列的長度
輸入樣例:
在這里給出一組輸入。例如:
5
1 3 5 2 9
結尾無空行
輸出樣例:
在這里給出相應的輸出。例如:
4
解題思路:
該題求解一個數組中最長的單調遞增子序列,最長單調子序列可以是數組的任意一個元素開始的,因此該數組的最長單調子序列而可以通過求解以其中某一個元素開頭的最長單調子序列來解決,因此可以用動態規划法。
題目分析:
(1) 邊界條件,a[i]=a[i],d[N]=1;
(2) 遞歸表達式:d[i]=d[j]+1;
代碼實現:#include <iostream>
using namespace std;
int a[1000];
int d[1000];
int N;
int dp(){
d[N]=1;
for(int i=N-1;i>=1;i--){
for(int j=i+1;j<=N;j++){
if(a[i]<=a[j]&&d[j]+1>d[i]){
d[i]=d[j]+1;
}
}
}
int max=0;
for(int i=1;i<=N;i++){
if(d[i]>max){
max=d[i];
}
}
return max;
}
int main(int argc, char** argv) {
cin>>N;
for(int i=1;i<=N;i++){
cin>>a[i];
}
cout<<dp();
return 0;
}
第四題
題目描述:
7-4 編輯距離問題 (25 分)
設A和B是2個字符串。要用最少的字符操作將字符串A轉換為字符串B。這里所說的字符操作包括 (1)刪除一個字符; (2)插入一個字符; (3)將一個字符改為另一個字符。 將字符串A變換為字符串B所用的最少字符操作數稱為字符串A到 B的編輯距離,記為d(A,B)。 對於給定的字符串A和字符串B,計算其編輯距離 d(A,B)。
輸入格式:
第一行是字符串A,文件的第二行是字符串B。
提示:字符串長度不超過2000個字符。
輸出格式:
輸出編輯距離d(A,B)
輸入樣例:
在這里給出一組輸入。例如:
fxpimu
xwrs
結尾無空行
輸出樣例:
在這里給出相應的輸出。例如:
5
解題思路:
該題的目的是求解令字符串B變成字符串A所需要的最少編輯次數。編輯操作共有三種,刪除,插入和替換。每一次操作都應該是有利於B變成A的。
題目分析:
(1) 邊界條件,當A、B中有一個字符串為空,則一個字符串要變成另一個字符串只需進行B長度的刪除或A長度的插入
(2) 遞歸表達式:int k = min (c[i-1][j] + 1, c[i][j-1] + 1);
代碼實現:
#include<iostream>
#include<string.h>
using namespace std;
char a[20000], b[20000];
int c[20000][20000];
int main()
{
cin >> a;
cin >> b;
int la = strlen(a), lb = strlen(b), count = 1;
for (int i=0;i<=la;i++) c[i][0] = i;
for (int i=0;i<=lb;i++) c[0][i] = i;//兩種極端情況
for (int i=1;i<=la;i++)
{
for (int j=1;j<=lb;j++)
{
if (a[i-1] == b[j-1]) count = 0;
else count = 1;
int k = min (c[i-1][j] + 1, c[i][j-1] + 1);
c[i][j] = min (k, c[i-1][j-1] + count);
}
}
cout << c[la][lb];
return 0;
}
關於動態規划法的學習心得和體會:
動態規划法主要是考察編程者對問題的分析和理解是否到位,如果能夠正確分析出一個問題的最優解的求解方法,求解過程中的計算順序以及能夠推導出正確的遞歸表達式就可以求解出最優解。