在今年元旦,買了《算法導論》第三版,其實早在它還沒有出版的時候,我就已經關注了,哈哈,於是就迫不及待地讀了起來,儘管是在考試周。不過之後擱置了一段時間,這個月又開始讀起來了。看到課後思考題15-2,231頁,就寫了一下。最長迴文子序列,其具體問題如下:
迴文是正序與逆序相同的非空字符,像civic,racecar等,還包括長度是1的字符串。求給定輸入字符串的最長迴文子序列。例如,給定輸入character,算法返回carac。
假如給定字符是string,其長度爲n,設定指針i、j分別指向string的頭和尾,即是0<=i<j<n,初始時i=0,j=n-1。
解決方法如下:從字符串末尾開始判斷是否有字符與當前指針i指向的string[i]相等,即判斷string[i]與string[j]是否相等:
- 如果不相等,則指針j向前挪一位,即j=j-1,直到j>=i,此時還不相等,則i向後移動,即i=i+1,該過程中始終保持i<j
- 如果某位置的string[i]與string[j]相等,則參照公式,公式中遞歸定義了子問題的解:
p[i,j] = 1,如果j-i<2,又因爲j>i,也就是j-i=1;
p[i,j] = 3,如果j-i=2;
p[i,j] = p[i+1,j-1]+2,如果j-i>2;
其中p[i,j]表示字符串string中以第i個字符開始、第j個字符結束的串的最長迴文子序列長度,當j-i=1時,說明字符串中只有兩個字符,而這兩個字符又相等,明顯p[i,j]應該爲1;當j-i=2時,說明字符串有3個字符,兩個相等的字符中夾了一個其他字符,明顯p[i,j]也爲2+1=3;當j-i>2時,則利用子問題的解來遞歸定義此時問題的解。
其實,這個問題使用了動態規劃的解題方法,找出問題的最優子結構性質,在利用子問題來遞歸定義原問題(使用子問題來表示原問題的解),把解決原問題的解建立在子問題的基礎之上。
在實現動態規劃的算法時,通常有2種實現思路,一是自底向上,也就是先求解小的子問題,然後慢慢擴大子問題的規模再求解,而求解規模較大的子問題時有需要使用到規模較小的子問題,所以說是自底向上,不過要仔細考慮具體實現時的順序;而是帶備忘的自定向下的遞歸方法,與一般遞歸方法不同的是,如果不用重複求解已經解過的子問題,只要直接查表即可。
其中在求解最優化問題的時候,通常涉及到構造最優解,此處是使用數組mark記錄下在比較過程中相等字符的位置,分別是i,j。
實現代碼如下,這是帶備忘的自定向下的遞歸實現:
- #include <string>
- using namespace std;
- size_t huiwen_length(string &x,size_t i,size_t j);
- void print_huiwen(string &x,size_t * mark);
- size_t p[20][20];
- size_t mark[20];
- void main(int argc, char **argv){
- string x("tccaivic");
- huiwen_length(x,0,x.size()-1);
- print_huiwen(x,mark);
- }
- size_t huiwen_length(string &x,size_t i,size_t j){
- if (p[i][j]>0)
- {
- return p[i][j];
- }
- for (size_t begin=i,end=j;i<j;i++,j=end;)
- {
- for (;i<j;)
- {
- if (x[i]==x[j])
- {
- if (j-i>2)//j-i>2
- {
- p[begin][end]=huiwen_length(x,i+1,j-1)+2;//p[i][j] = p[i+1][j-1]+2;
- }else if (j-i==2)//j-i==2相當於相等的2個元素之間夾了一個元素
- {
- p[begin][end]=3;
- mark[i+1]=1;//設置夾了那個
- //return 3;//p[i][j]=3;
- }else {//0<j-i<2,即是j-i==1
- p[begin][end]=2;
- //return 2;//p[i][j]=2;
- }
- mark[i]=1;
- mark[j]=1;
- return p[begin][end];
- }else{
- j--;
- }
- }
- }
- return 0;
- }
- void print_huiwen(string &x,size_t * mark){
- cout<<"huiwen of "<<x<<":";
- for (size_t i=0;i<x.size();i++)
- {
- if (mark[i]==1)
- {
- cout<<x[i];
- }
- }
- cout<<endl;
- }
動態規劃適用於求解最優化問題,掌握了該方法可以更好地增強自己的算法能力。