用動態規劃解決查找問題
本章講解的例題皆有一定的難度。關於最優性原理本文不給予證明。
一.最優二叉查找樹問題
問題描述:
設{r1,r2,r3,…,rn}爲一個有n個記錄的集合,每股記錄對應的查找的概率爲{p1,p2,p3,…,pn},現在要求出這n個記錄構成的最優二叉樹。最有二叉樹指的是這n個記錄查找的平均次數最少。
分析問題:
假設C(i,j)表示{ri,ri+1,…,rj}的最優二叉查找樹的平均查找次數,那麼下面看一個推導
我們得到狀態方程爲:
={++}
接下來爲了可以讓那個遞推進行下去,我們需要定義最基本的子問題,對於求基本子問題的一般方式是根據遞推方程把它的各個情況看一遍,可以直接得結果的就是子問題(一般都是在邊界),根據這個方法我們可以很容易的得到:
接下來我們要分析一下矩陣C的結構情況,根據遞推方程我們可以知道C的行的範圍應該爲1-n+1,列的範圍是0-n
我們還需要一個矩陣R,他與C同構,根據這個矩陣得到最優二叉搜索樹
最後就是這個算法應該咋樣來循環呢?
看上圖我們發現我們在求C(i,j)他的狀態會轉移到他的對角線的下面去。因此我們的循環應該是以對角線來循環。
最後是初始化的問題,前面我們在獲得最基本子問題時發現遞推到最後邊界是C(i,i)與C(i,i-1);因此我們需要初始化的是C(i,i)=Pi和C(i,i-1)=0,矩陣對角線下面的元素我們是使用不到的。
代碼實現:
import org.junit.Test;
public class Main {
@Test
public void Test(){
char []strs = {'A','B','C','D'};
double []p = {0.1,0.2,0.4,0.3};
double C[][] = new double[strs.length+2][strs.length+1];
double R[][] = new double[strs.length+2][strs.length+1];
getBestSearchTree(strs,p,R,C);
for (double[] doubles : R) {
for (double aDouble : doubles) {
System.out.print(aDouble+" ");
}
System.out.println();
}
}
void getBestSearchTree(char[] strs,double[] p,double R[][],double C[][]){
int n = strs.length;
//初始化
for(int i=1;i<=n;i++){
C[i][i]=p[i-1];
C[i][i-1]=0;
}
C[n+1][n]=0;
for(int i=1;i<=n-1;i++)//計數器
for(int j=i+1;j<=n;j++){
int k = j-i;
double sum = getP(p,j-i,j);
C[j-i][j] = sum+C[j-i][k-1]+C[k+1][j];
R[j-i][j] = k;
for(k=j-i+1;k<=j;k++){
if(C[j-i][j]>sum+C[j-i][k-1]+C[k+1][j]){
C[j-i][j] = sum+C[j-i][k-1]+C[k+1][j];
R[j-i][j] = k;
}
}
}
}
double getP(double []p,int i,int j){
double sum = 0;
for(int k=i;k<=j;k++){
sum+=p[k-1];
}
return sum;
}
}
結果:
0.0 0.0 0.0 0.0 0.0
0.0 0.0 2.0 3.0 3.0
0.0 0.0 0.0 3.0 3.0
0.0 0.0 0.0 0.0 3.0
0.0 0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0 0.0
最後可以根據R矩陣來獲得結果,這個任務給讀者自行完成,
這裏提示用分治法(遞歸出口是left==right)
二.近似串匹配
該問題在實踐中具有十分重要的價值,但是理解起來還是有點難度的。
實踐例子:Word等編輯器中的拼寫錯誤檢查就是近似串匹配。
在word等編輯器有這麼個功能:在輸入英文單詞的時候,如果發現單詞拼寫有問題,可以通過右鍵找出與該單詞形近的單詞或短語。
問題描述:
給出兩個字符串P與T,現在要求找出P與T的K-匹配模式下的差別數。(我們假設P是正確串)
三種差別:
修改:P與T對應的字符不同
刪除:T中出現了P中沒有的字符
插入:T中沒有P中出現的一個字符
爲理解什麼是K-匹配模式,我們要明白他的兩種含義:
P與T的差別數最多爲K
P與T的差別數應該是在三種差別方式下的最小差別數
下面我們定義最小子問題:
D(i,j)代表{p1,p2,…,pi}與{t1,t2,…,tj}的差別數,那麼對應遞推公式爲:
①p[i]!=t[j]
D(i,j)=min{D(i-1,j-1)+1,D(i-1,j)+1,D(i,j-1)+1}
②p[i]==p[j]
D(i,j)=min{D(i-1,j-1),D(i-1,j)+1,D(i,j-1)+1}
關於上面公式的由來:
①若p[i]與t[j]對應,那麼若p[i]==t[j],則D(i,j)=D(i-1,j-1),若p[i]!=t[j],則D(i,j)=D(i-1,j-1)+1
②若p[i]是多餘的,則D(i,j)=D(i-1,j)+1
③若t[j]是多餘的,則D(i,j)=D(i,j-1)+1
關於問題的初始化:
D(0,j)=j
D(i,0)=i
代碼實現:
import org.junit.Test;
public class Main {
int [][] GetD(char []P,char []T){
char T1[] = new char[T.length+1];
char P1[] = new char[P.length+1];
int D[][] = new int[P.length+1][T.length+1];
for(int i=1;i<T1.length;i++){
T1[i]=T[i-1];
D[0][i]=0;
}
for(int j=1;j<P1.length;j++){
P1[j]=P[j-1];
D[j][0]=0;
}
for(int i=1;i<P1.length;i++)
for(int j=1;j<T1.length;j++){
if(T1[j]==P1[i]){
D[i][j]=Math.min(D[i-1][j-1],D[i-1][j]+1);
D[i][j]=Math.min(D[i][j],D[i][j-1]+1);
}else{
D[i][j]=Math.min(D[i-1][j-1]+1,D[i-1][j]+1);
D[i][j]=Math.min(D[i][j],D[i][j-1]+1);
}
}
return D;
}
@Test
public void Test(){
char P[]= {'h','a','p','p','y'};
char T[]={'h','s','p','p','a','y'};
int D[][] = GetD(P,T);
System.out.println(D[P.length][T.length]);
}
}