d一、字串刪字元後最長不降子序列
1.問題描述
請在字串“UXDQIRQFJEUURYHKNTUVPBYIDYXQCXITPVSNSKWJIIJTZ“中刪除若干個字元後,餘下的字元構成不降子序列(即序列從第2個字元開始,每一個字元的ASCII碼不小於它前面字元的ASCII碼)。求最長的不降子序列。
2.設計要點
給定一個由n個字元組成的字串,從字串中刪除若干個字元後,使剩下的字串構成非子序列。設字串的各個字元a(1),a(2),...,a(n),對每一個字元操作(刪除還是不刪)為一個階段,共為n個階段。
⑴建立遞推關係
設定b數組,b(i)表示序列的第i個字元到最後第n個字元的最長非降子序列的長度,i=1,2,...,n。對所有j>i,比較當a(i)<=a(j)時的所有b(j)的最大值,顯然b(i)為這一最大值加1,表示加上a(i)本身這一項。
因而有遞推關係:
b(i)=max(b(j))+1 (a(i)<=a(j), 1<=i<j<=n) 邊界條件:b(n)=1
⑵逆推計算最優值
b(n)=1;<br />for (i=n-1; i>=1; i--) {<br />max = 0;<br />for (j = i+1; j <= n; j++)<br />if (a(i) <= a(j) && b(j) > max)<br />max=b(j);<br />b(i)=max+1;// 逆推得b(i)<br />}
逆推依次求得b(n-1),...,b(1),比較這n-1個值得其中的最大值lmax,即為所求的最長非降子序列的長度,即最優值
⑶構造最優解
從序列的第1項開始,依次輸出b(i)分別等於lmax,lmax-1,...,1且a(i)不小於其前面輸出項a(j),這就是所求的一個最長非降子序列。
3.代碼實現
// 在字串中求最長非降子序列<br />#include <stdio.h><br />#include <stdlib.h><br />#include <time.h></p><p>int main(void)<br />{<br />int i, j, n, max, lmax, b[300];<br />char a[300];</p><p>// 初始化隨機數發生器<br />srand(time(0));</p><p>printf("input n (n < 300): ");<br />scanf("%d", &n);<br />printf("已知字串為:/n");<br />for (i = 1; i <= n; i++) {<br />a[i] = rand() % 26 + 65;// 產生並輸出n個字元<br />printf("%c", a[i]);<br />}<br />putchar('/n');</p><p>b[n] = 1; lmax = 0;<br />for (i = n - 1; i >= 1; i--) {// 逆推求最優值lmax<br />max = 0;<br />for (j = i + 1; j <= n; j++)<br />if (a[i] <= a[j] && b[j] > max)<br />max = b[j];<br />b[i] = max + 1;// 逆推得b[i]<br />if (b[i] > lmax)<br />lmax = b[i];// 比較得最大非降序列長<br />}</p><p>printf("其中最長非降子序列的長度為:%d/n", lmax);<br />printf("一個最長非降子序列為:");<br />j = 0; a[0] = 0;<br />for (i = 1; i <= n; i++)<br />if (b[i] == lmax && a[j] <= a[i]) {<br />printf("%c", a[i]);<br />lmax--;<br />j = i;<br />}<br />putchar('/n');</p><p>return 0;<br />}
二、最長公用子序列
1.問題提出
一個序列的子序列是在該序列中刪去若干項後所得到的序列。例如,"bcba"是"abcbdab"和"bdcaba"的公用子序列。給定兩個序列X={x1,x2,...,xm}和Y={y1,y2,...,yn},找出序列X和Y的最長公用子序列。
2.設計要點
最長公用子序列問題具有最優子結構性質,應用動態規劃設計求解。
⑴建立遞推關係
設序列X={x1,x2,...,xm}和Y={y1,y2,...,yn}的最長公用子序列為Z={z1,z2,...,zk},{x1,x2,...,xm}與{y1,y2,...,yn} (i=1, ..., m; j=1, ..., n)的最長公用子序列的長度為c(i,j)。
若i=m+1或j=n+1,此時為空白序列,c(i, j)=0(邊界條件)。
若x(1)=y(1),則有z(1)=x(1),c(1,1)=c(2,2)+1(其中1為z(1)這一項)。
若x(1)!=y(1),則c(1,1)取c(2,1)與c(1,2)中的較大者。
一般地,有遞推關係:
若x(i)=y(i),則c(i,j)=c(i+1,j+1)+1 (1<=i<=m, 1<=j<=n);
若x(i)!=y(i),則c(i,j)=max(c(i+1,j),c(i,j+1))。
邊界條件:c(i,j)=0 (i=m+1或j=n+1)
⑵逆推計算最優值
根據以上遞推關係,逆推計算最優值c(1,1)流程為:
for (i=1; i<=m+1; i++)// 賦初值<br />c[i][n+1]=0;<br />for (j=1; j <= n; j++)<br />c[m+1][j]=0;</p><p>for (i = m; i >= 1; i--)<br />for (j = n; j >= 1; j--) {<br />if (x[i] == y[j])<br />c[i][j] = c[i+1][j+1]+1;<br />else if(c[i][j+1] > c[i+1][j])<br />c[i][j]=c[i][j+1];<br />else<br />c[i][j]=c[i+1][j];<br />}</p><p>// 輸出最優解<br />printf("最長公用子串的長度為:%d", c[1][1]);
⑶構造最優值
為構造最優值,即最優救出最長最長公用子序列,設定數組s(i,j),當x(i)=y(j)時s(i,j)=1;當x(i)!=y(j)時s(i,j)=0。
X序列的每一項與Y序列的每一項逐一比較,根據s(i,j)與c(i,j)取值具體構造最長公用子序列。
實施x(i)與y(j)比較,其中i=1,2,...,m;j=1,2,...,n;變數t從0開始取值,當確定最長公用子序列一項時,t=j+1。這樣處理可避免重複取項。
若s(i, j)=1且c(i,j)=c(1,1)時,取x(i)為最長公用子序列的第1項;
隨後,若s(i,j)=1且c(i,j)=c(1,1)-1時,取x(i)最長公用子序列的第2項;
一般地,若s(i,j)=1且c(i,j)=c(1,1)-2時(w從0開始,每確定最長公用子序列的一項,w增1),取x(i)最長公用子序列的第w+1項。
構造最長公用子序列描述:
for (t = 0, w= 0, i = 1; i <= m; i++){<br />for (j=t; j <= n; j++)<br />if (S[i][j] == 1 && c[i][j] == c[1][1] - w){<br />printf("%c", x[i]);<br />w++;<br />t = j + 1;<br />break;<br />}<br />}
⑶代碼實現
// 求最長公用子序列<br />#include <stdio.h><br />#include <stdlib.h><br />#include <time.h></p><p>int main(void)<br />{<br />int i, j, t, m, n, w;<br />int x[100], y[100], s[100][100], c[100][100];</p><p>srand(time(NULL));</p><p>printf("Input m and n: ");<br />scanf("%d%d", &m, &n);<br />printf("已知字串X為:");<br />for (i = 1; i <= m; i++) {<br />x[i] = rand() % 26 + 65;// 產生並輸出X的m個字元<br />putchar(x[i]);<br />}<br />printf("/n已知字串Y為:");<br />for (i = 1; i <= n; i++) {<br />y[i] = rand() % 26 + 65;// 產生並輸出Y的n個字元<br />putchar(y[i]);<br />}</p><p>// 賦邊界值<br />for (i = 1; i <= m + 1; i++)<br />c[i][n + 1] = 0;<br />for (j = 1; j <= n + 1; j++)<br />c[m + 1][j] = 0;</p><p>// 遞推計算最優值<br />for (i = m; i >= 1; i--) {<br />for (j = n; j >= 1; j--)<br />if (x[i] == y[j]) {<br />c[i][j] = c[i + 1][j + 1] + 1;<br />s[i][j] = 1;<br />}<br />else {<br />s[i][j] = 0;<br />if (c[i][j + 1] > c[i + 1][j])<br />c[i][j] = c[i][j + 1];<br />else<br />c[i][j] = c[i + 1][j];<br />}<br />}</p><p>printf("/n最長公用子序列的長度為:%d", c[1][1]);// 輸出最優值<br />printf("/n最長公用子序列為:");<br />t = 0; w = 0;<br />for (i = 1; i <= m; i++){<br />for (j = t; j <= n; j++)<br />if (s[i][j] == 1 && c[i][j] == c[1][1] - w) {<br />putchar(x[i]);<br />w++;<br />t = j + 1;<br />break;<br />}<br />}<br />putchar('/n');</p><p>return 0;<br />}