参考书籍:算法设计与分析——C++语言描述(第二版)
算法设计策略-动态规划法
最优二叉搜索树
问题描述
二叉搜索树运算有很好的平均时间复杂度(
以上分析均假定二叉搜索树上搜索一个元素的概率是相等的。如果元素集合是固定的,并且已知搜索集合中每个元素的概率,包括不成功的概率,那么可以构造一颗最优二叉搜索树,使其具有最小的平均搜索时间。
设有元素集合
最优二叉搜索树问题是指设法构造一颗具有最小平均搜索时间的二叉搜索树。
动态规划法求解
最优子结构
已知递增有序的元素集合
与二分搜索的二叉判定树类似,每个内结点代表一次成功搜索可能的终止位置,每个外结点表示一次成功搜索可能的终止位置,每个外结点表示一次不成功搜索的终止位置。设
假定
式中,
定义
二叉搜索树T的平均搜索代价
从上式可以知道,如果T是最优二叉搜索树,必定要求其左右子树都是二叉搜索树,否则T就不是最优的。这表明,队医最优二叉搜索树问题,最优性原理成立。
设
一般,
式中,
构造最优二叉搜索树
设w、c和r是上面定义的3个二维数组,计算此3个量,便可以从中得到最优二叉搜索树。运用动态规划法求解这三个量的递推算法如下:
计算主对角线的w、c和r的值
w(i,i)=q(i);c(i,i)=0;r(i,i)=0i=0,1,⋯,n 计算紧邻主对角线上面的那条对角线的w、c和r的值
w(i,i+1)=q(i)+q(i+1)+p(i+1) c(i,i+1)=c(i,i)+c(i+1,i+1)+w(i,i+1)=w(i,i+1) r(i,i+1)=i+1 根据下列公式,计算主对角线以上n-2条斜线的w、c和r的值
w(i,j)=q(j)+p(j)+w(i,j−1)c(i,j)=mini+1≤k≤j{c(i,k−1)+c(k,j)}+w(i,j)r(i,j)=k
最优二叉搜索树算法
一维数组p和q保存成功和不成功搜索的两种概率,n是数组长度,计算结果保存在二维数组w、c和r的值。函数Find计算满足
//构造最优二叉搜索树
int Find(int i,int j,int **r,float **c)
{
float min=INFTY;
int k;
for(int m = i+1;m<=j;m++){
if((c[i][m-1]+c[m][j])<min){
min=c[i][m-1]+c[m][j];
k=m;
}
}
return k;
}
void CreateOBST(float *p,float *q,float **c,int **r,float **w,int n)
{
for(int i=0;i<=n-1;i++){
//初始化
w[i][i]=q[i];c[i][i]=0.0;r[i][i]=0;
w[i][i+1]=q[i]+q[i+1]+p[i+1];
c[i][i+1]=q[i]+q[i+1]+p[i+1];
r[i][i+1]=i+1;
}
w[n][n]=q[n];c[n][n]=0.0;r[n][n]=0;
for(int m = 2;m<=n;m++){
//计算n-2条对角线元素
for(i=0;i<=n-m;i++){
int j = i+m;
w[i][j]=w[i][j-1]+p[j]+q[j];
int k=Find(i,j,r,c);
c[i][j]=w[i][j]+c[i][k-1]+c[k][j];
r[i][j]=k;
}
}
}
Find函数用于计算k,其计算时间为
利用D.E.Kunth[^1]的结论,计算
0/1背包
问题描述
与使用贪心法求解的一般背包问题不同,在0/1背包问题中,物品不能分割只能作为一个整体或者装入或者不装入背包。
0/1背包问题可以描述为:已知一个载重为M的背包和n件物品,物品编号为
0/1背包问题可以形式化为:给定KNAP(0,n-1,M)
表示一个背包问题的实例。
动态规划法求解
判断一个问题是否适用于动态规划法求解,首先必须分析问题解的结构,考察它的最优解是否具有最优子结构特性。其次应当检查分解所得的子问题是否相互独立,是否存在重叠子问题现象。
最优子结构
0/1背包的最优解具有最优子结构特性。设
因此,
显然
最优解的递归算法
给定一个0/1背包问题实例KNAP(0,n-1,M)
,可以通过对n个物品是否加入背包做出一系列决策进行求解,假定变量
xn−1=1 ,将编号为n-1的物品加入背包,接着求解子问KNAP(0,n−2,M−wn−1) ;xn−1=0 ,编号为n-1的物品不放入背包,接着求解子问题KNAP(0,n−2,M) 。
设
对于任意
如果物品
//0/1背包的递归算法
template<class T>
class Knapsack
{
public:
Knapsack(int mSize,float cap,float *wei,T *prof);
T RKnap();
private:
T f(int j,float X);//递归函数f计算0/1背包的最大收益
float m,*w;
T *p;
int n;
};
template<class T>
T Knapsack<T>::f(int j,float X)
{
if(j<0)
return ((X<0)? -INFTY:0);
if(X<w[j])
return f(j-1,X);
else {
T a=f(j-1,X);
T b=f(j-1,X-w[j])+p[j];
if(a>b)
return a;
else
return b;
}
}
template<class T>
T Knapsack<T>::RKnap()
{
if(n>0)
return f(n-1,m);
else
return NoAns;//NoAns可定义为类型T的一个代表无收益的常量
}
上述0/1背包递归算法的时间复杂度在最坏情况下是指数级的(
动态规划算法
如果X是满足
但是如果物品重量和背包载重为实数,那么子问题的最优解值
0/1背包算法框架
求0/1背包的最优解值
0/1背包问题的图解法
用
计算所有
S−1={(0,0)} ,函数f(−1,X) 只有一个阶跃点;Sj1={(X,P)|(X−wj,P−pj)∈Sj−1} ,也就是说,由集合Sj−1 中的每个阶跃点(X,P) ,得到集合Sj1 中的一个阶跃点(X+wj,P+pj) ;Sj 是合并集合Sj−1∪Sj1 ,并舍弃其中被支配的阶跃点和所有X>M 的阶跃点得到的。
设
求0/1背包的最优解
通过回溯方式,可以求得0/1背包的最优解