1. 程式人生 > >動態規劃專題之最長公共子序列

動態規劃專題之最長公共子序列

動態規劃系列專題講義

專題三:最長公共子序列

/* 
  Name: 動態規劃專題之最長公共子序列 
Author:  巧若拙 
  Description: 1808_公共子序列
描述:我們稱序列Z = < z1, z2, ..., zk >是序列X = < x1, x2, ..., xm >的子序列當且僅當存在 嚴格上升 的序列< i1, i2, ..., ik >,使得對j = 1, 2, ... ,k, 有xij = zj。比如Z = < a, b, f, c > 是X = < a, b, c, f, b, c >的子序列。
現在給出兩個序列X和Y,你的任務是找到X和Y的最大公共子序列,也就是說要找到一個最長的序列Z,使得Z既是X的子序列也是Y的子序列。
輸入:輸入包括多組測試資料。每組資料包括一行,給出兩個長度不超過200的字串,表示兩個序列。兩個字串之間由若干個空格隔開。
輸出:對每組輸入資料,輸出一行,給出兩個序列的最大公共子序列的長度。
樣例輸入
abcfbc         abfcab
programming    contest 
abcd           mnp
樣例輸出
4
2
0
*/
#include<iostream>
#include<cstring>
#include<string>

using namespace std;

const int N = 200;
int B[N+1][N+1];
int B1[N+1][N+1];
int pre[N+1]; //pre[j]相當於B1[i-1][j]   
int cur[N+1]; //cur[j]相當於B1[i][j] 
string X, Y; 

int LCSLength(int i, int j);//自頂向下的備忘錄演算法
int LCSLength_1(int n, int m);//動態規劃 
int LCSLength_2(int n, int m);//動態規劃 
void PrintLCS(int i, int j);

int main()
{  
    while (cin >> X >> Y)
    {
		memset(B, 0, sizeof(B));
		memset(pre, 0, sizeof(pre));
    	cout << LCSLength(X.length(), Y.length()) << endl;
    	cout << LCSLength_1(X.length(), Y.length()) << endl;
    	cout << LCSLength_2(X.length(), Y.length()) << endl;
    	PrintLCS(X.length(), Y.length());
	}
    
   
    return 0;  
}  

演算法2:備忘錄演算法:自頂而下,需要用到全域性變數B[N+1][N+1]。
int LCSLength(int i, int j) 
{
	if (B[i][j] != 0)
		return B[i][j];
	
	if (i == 0 || j == 0) 
		B[i][j] =   //語句1
	else if (X[i-1] == Y[j-1])
		B[i][j] =  //語句2
	else
		B[i][j] = max(   );  //語句3
 
	return B[i][j];
}
問題1:將語句1,語句2和語句3補充完整。

參考答案:
問題1:語句1:B[i][j] = 0;
       語句2:B[i][j] = LCSLength(i-1, j-1) + 1;
語句3:B[i][j] = max(LCSLength(i-1, j), LCSLength(i, j-1));

演算法2:動態規劃:自底而上,需要用到全域性變數B1[N+1][N+1]。
int LCSLength_1(int n, int m)  
{
	for (int i=1; i<=n; i++)
	{
		for (int j=1; j<=m; j++)
		{
			if (X[i-1] == Y[j-1])
				B1[i][j] =      //語句1
			else
				B1[i][j] =      //語句2
		}
	}
 
	return B1[n][m];
}
問題1:將語句1和語句2補充完整。

參考答案:
問題1:語句1:B1[i][j] = B1[i-1][j-1] + 1;
       語句2:B1[i][j] = max(B1[i-1][j], B1[i][j-1]);

演算法3:動態規劃:使用2個數組儲存記錄,需要用到全域性變數pre[]和cur[]均初始化為0。
int LCSLength_2(int n, int m) 
{
	for (int i=1; i<=n; i++)
	{
		for (int j=1; j<=m; j++)
		{
			if (X[i-1] == Y[j-1])
				cur[j] =     //語句1
			else
				cur[j] =     //語句2
		}
		for (int j=1; j<=m; j++)
		{
			pre[j] = cur[j];
		}
	}
 
	return pre[m];
}
問題1:將語句1和語句2補充完整。

參考答案:
問題1:語句1:cur[j] = pre[j-1] + 1;
       語句2:cur[j] = max(pre[j], cur[j-1]);

拓展練習:在演算法1中,我們用二維陣列B[][]記錄了各種解的資訊,現在請你根據B[][]記錄的資訊,設計一個遞迴函式void PrintLCS(int i, int j);//i和j分別表示字串X的第i個字元和字串Y的第j個字元。
參考答案:
void PrintLCS(int i, int j)
{
    if (i == 0 || j == 0)
        return;
    if (X[i-1] == Y[j-1])
    {
        PrintLCS(i-1, j-1);
        cout << "x[" << i-1 << "]= " << X[i-1] << " : "<< "y[" << j-1 << "]= " << Y[j-1] << endl;
    }
    else if(B[i-1][j] > B[i][j-1])//先向上層走 
        PrintLCS(i-1, j);
    else                          //再向左邊走 
        PrintLCS(i, j-1);
}

課後練習:
練習1:編輯距離
描述:A和B是2個字串。要用最少的字元操作將字串A轉換為字串B。
這裡所說的字元操作包括 (1)刪除一個字元; (2)插入一個字元; (3)將一個字元改為另一個字元。
將字串A變換為字串B所用的最少字元運算元稱為字串A到B的編輯距離,記為 d(A,B)。
試設計一個有效演算法,對任給的2 個字串A和B,計算出它們的編輯距離d(A,B)。
Input:輸入的第一行是字串A,檔案的第二行是字串B。
Output:程式執行結束時,將編輯距離d(A,B)輸出。
Sample Input
fxpimu
xwrs
Sample Output
5

練習2:電路佈線
描述:在一塊電路板的上、下兩端分別有n個接線柱。根據電路設計,要求用導線(i,π(i))將上端接線柱i與下端接線柱π(i)相連。
其中,π(i),1<=i<=n是{1,2,…,n}的一個排列。導線(i,π(i))稱為該電路板上的第i條連線。對於任何1<=i π(j)。
在製作電路板時,要求將這n條連線分佈到若干絕緣層上。在同一層上的連線不相交。
你的任務是要確定將哪些連線安排在第一層上,使得該層上有儘可能多的連線。換句話說,就是確定導線集Nets={ i,π(i),1<=i<=n}的最大不相交子集。
輸入:輸入檔案第一行為整數n;第二行為用一個空格隔開的n個整數,表示π(i)。
輸出:輸出檔案第一行為最多的連線數m,第2行到第m+1行輸出這m條連線(i,π(i))。
【輸入樣例】
10
1 8
2 7
3 4
4 2
5 5
6 1
7 9
8 3
9 10
10 6
【輸出樣例】
4