用动态规划解决查找问题
本章讲解的例题皆有一定的难度。关于最优性原理本文不给予证明。
一.最优二叉查找树问题
问题描述:
设{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]);
}
}