计算机算法分析与设计复习资料2.docx
《计算机算法分析与设计复习资料2.docx》由会员分享,可在线阅读,更多相关《计算机算法分析与设计复习资料2.docx(15页珍藏版)》请在冰豆网上搜索。
计算机算法分析与设计复习资料2
1.算法的复杂性:
是算法运行所需要的计算机资源的量,需要时间资源的量称为时间复杂性T(n),需要空间资源的量称为空间复杂性S(n);其中n是问题的规模(输入大小)。
(1)T(N,I)=(k∑i=1)tiei(N,I)是算法在一台抽象的计算机上运行所需要的时间。
ti——每执行一次元运算Oi所需要时间;ei——用到元运算Oi的次数;
N—算法要解得问题的规模;I—算法的输入;A—算法本身。
(2)最坏情况:
Tmax(N)=max(I∈DN)T(N,I)
最好情况:
Tmin(N)=min(I∈DN)T(N,I)
平均情况:
(∑I∈DN)P(I)T(N,I)
DN—规模为N的合法输入的集合;P(I)—在算法的应用中出现输入I的概率。
*实践表明,可操作性最好,且最有实际价值的是最坏情况下的时间复杂性。
(3)当N单调增大且趋于∞时,T(n)也单调增大且趋于∞;
如果存在~T(N),使当N→∞时,有(T(n)-~T(N))/T(n)→0,那就称~T(N)是T(n)当N→∞时的渐近性态,为算法A当N→∞时的渐近复杂性。
在数学上,~T(N)是T(n)当N→∞时的渐近表达式;直观上是T(n)中略去低阶项留下的主项,它比T(n)简单。
(4)O—渐近上界记号
设f(N)和g(N)是定义在正数集上的正函数,如果存在正的常数C和自然数N0,使得当N≥N0时有f(N)≤Cg(N),则称函数f(N)当N充分大时上有界,且g(N)是它的一个上界,记为f(N)=O(g(N))。
①O(f(n))+O(g(n))=O(max{f(n),g(n)});
②O(f(n))+O(g(n))=O(f(n)+g(n));
③O(f(n))*O(g(n))=O(f(n)*g(n));
④O(cf(n))=O(f(n));
⑤若g(n)=O(f(n)),则O(f(n))+O(g(n))=O(f(n));
⑥f(n)=O(f(n)。
1、选择题
1.回溯法的效率不依赖于什么因素?
问题的解空间的形式;
例:
回溯法的效率不依赖于以下哪一个因素?
(C)
A.产生x[k]的时间;B.满足显约束的x[k]值的个数;C.问题的解空间的形式;D.计算上界函数bound的时间;E.满足约束函数和上界函数约束的所有x[k]的个数。
F.计算约束函数constraint的时间;
2、填空题
1.算法是若干指令的有穷序列,满足下述4条性质:
①输入:
有外部提供的量作为算法的输入。
②输出:
算法产生至少一个量作为输出。
③确定性:
组成算法的每条指令是清晰,无歧义的。
④有限性:
算法中每条指令的执行次数是有限的,执行每条指令的时间也是有限的。
2.什么叫算法效率?
即时间复杂度,运行该算法所需要的时间。
最差情况:
最坏情况下的算法时间复杂度:
Tmax(N)=max(I∈DN)T(N,I)
3.O(g(n))={ f(n) | 存在正常数c和n0使得对所有n>n0有:
0<=f(n)<=cg(n) }。
O(g(n))为算法A的渐进时间复杂度,简称时间复杂度。
4.回溯法:
以深度优先方式系统搜索问题解的算法。
(1)回溯法有“通用的解题法”之称,它是一个既带有系统性又带有跳跃性的搜索算法。
【系统性——它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。
跳跃性——算法搜索至解空间树的任一结点时,先判断该结点是否包含问题的解,如果肯定不包含,则跳过对以该节点为根的子树的搜索,逐层向其祖先结点回溯。
否则,进入该子树,继续按深度优先策略搜索。
】回溯法求问题的所有解时,要回溯到根,且根节点的所有子树都已被搜索遍才结束。
回溯法求问题的一个解时,只要搜索到问题的一个解就可结束。
(2)算法框架:
递归回溯,迭代回溯,子集树、排列数算法框架
①当所给问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间树称为子集树,遍历需Ω(2^n)。
②当所给问题是确定n个元素满足某种性质的排列时,相应的解空间树称为排列树,遍历需Ω(n!
)。
(3)回溯法的基本思想:
确定了解空间的组织结构后,回溯法从开始结点(根节点)出发,以深度优先方式搜索整个解空间。
这个开始结点成为活结点,同时也成为当前的扩展结点。
在当前的扩展结点处,搜索向纵深方向移至一个新结点。
这个新结点就成为新的活结点,并成为当前扩展结点。
如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。
此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。
回溯法以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已无活结点时为止。
①活结点:
所搜索到的结点不是叶结点,且满足约束条件和目标函数的界,其儿子结点还未全部搜索完毕。
②扩展结点:
正在搜索其儿子结点的结点,它也是一个活结点。
③死结点:
不满足约束条件和目标函数,或其儿子结点已全部搜索完毕的结点;以死结点作为根的子树可以在搜索过程中删除。
(4)解题步骤:
①针对所给问题,定义问题的解空间;
②确定易于搜索的解空间结构;
③以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
a.用约束函数在扩展结点处剪去不满足约束的子树;(0-1背包问题)
b.用限界函数剪去得不到最优解的子树。
(旅行售货员问题)
*用回溯法解题的一个显著特征是在搜索过程中动态产生问题的解空间。
在任何时刻,算法只保存从根结点到当前扩展结点的路径。
如果解空间树中从根结点到叶结点的最长路径的长度为h(n),则回溯法所需的计算空间通常为O(h(n))。
而显式地存储整个解空间则需要O(2h(n))或O(h(n)!
)内存空间。
4.渐近性态:
如果存在~T(N),使当N→∞时,有(T(n)-~T(N))/T(n)→0,那就称~T(N)是T(n)当N→∞时的渐近性态。
5.完全二叉树:
除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点。
3、简答题
【P(Polynomial问题):
也即是多项式复杂程度的问题。
NP就是Non-deterministic Polynomial的问题,也即是多项式复杂程度的非确定性问题。
NPC(NP Complete)问题,这种问题只有把解域里面的所有可能都穷举了之后才能得出答案,这样的问题是NP里面最难的问题,这种问题就是NPC问题。
】
1.将可由多项式时间算法求解的问题看作是易解问题;而将需要超多项式时间才能求解的问题看作是难解问题,为了研究这类问题的计算复杂性,人们提出了非确定性图灵机计算模型。
2.P:
多项式。
所有可以在多项式时间内求解的判定问题构成P类问题,P类问题是确定性计算模型下的易解问题类。
3.NP:
非确定性多项式。
所有非确定性多项式时间可解的判定问题构成NP类问题,NP类问题是非确定性计算模型下的易验证问题类。
非确定性算法将问题求解分为猜测和验证两个阶段。
算法的猜测阶段是非确定性的,它给出问题解的一个猜测。
算法的验证阶段是确定性的,它验证猜测阶段给出的解的正确性。
4.NPC:
存在一类NP完全问题,即NPC类问题。
其性质:
如果一个NP完全问题能在多项式时间内得到解决,那么NP中的每一个问题都可以在多项式时间内求解,即P=NP。
四、算法分析
1.【装载问题】NP难O(2^n)
void Loading:
:
Backtrack(int i) //构造最优解O(n2^n)
{//搜索第i层结点
if(i>n){ //到达叶节点
if(cw>bestw)//当前解优于当前最优解,更新bestw
{for(int j=1;j<=n;j++) bestx[j]=x[j];bestw=cw;}
return;}
//搜索子树(当前扩展结点有x[i]=1和x[i]=0两个儿子结点)
r-=w[i];
if(cw+w[i]<=c){//搜索左子树
x[i]=1;
cw+=w[i];
Backtrack(i+1);
cw-=w[i];}
if(cw+r>bestw){//搜索右子树
x[i]=0;
Backtrack(i+1);}
r+=w[i];
}①引入上界函数后,在达到一个叶结点时就不必再检查该叶结点是否优于当前最优解。
因为上界函数使算法搜索到的每个叶结点都是当前找到的最优解。
2.【n后问题】O(n)
voidQueen:
:
Backtrack(intt)
{
if(t>n)sum++;//算法搜索至叶结点,得到一个新的放置方案
else//搜索子树(当前扩展结点有x[i]=1,2,…,n共n个儿子结点)
for(inti=1;i<=n;i++){
x[t]=i;
if(Place(t))Backtrack(t+1);//由Place检查其可行性,并以
}//深度优先的方式递归地对可行子树搜索,或剪去不可行子树
}
boolQueen:
:
Place(intk)//检查其可行性
{for(intj=1;jif((abs(k-j)==abs(x[k]-x[j]))||(x[j]==x[k]))returnfalse;
returntrue;}
迭代回溯:
VoidQueen:
:
Backtrack(void)
{x[1]=0;intk=1;
while(k>0){
x[k]+=1;
While((x[k]<=n)&&!
(Place(k)))x[k]+=1;
if(x[k]<=n)
if(k==n)sum++;
else{k++;x[k]=0;}elsek--;}}
3.【0-1背包问题】(特点:
动态、完全二叉树、递归栈)NP难、子集树、O(n2^n)
void Knap:
:
Backtrack(inti)
{ if(i>n)//搜索至叶结点
bestp=cp;
return;}
if(cw+w[i]<=c){//左儿子结点是一个可行结点,进入左子树
cw+=w[i];
cp+=p[i];
Backtrack(i+1);
cw-=w[i];
cp-=p[i];
if(Bound(i+1)>bestp)//进入右子树(当cp+r<=bestp时,可剪去右子树)
Backtrack(i+1);
}
void Knap:
:
Bound(inti)//界限函数,计算上界{Typewcleft=c-cw;
Typepb=cp;
//以物品单位重量价值递减将物品装入背包while(i<=n&&w[i]<=cleft){cleft-=w[i];b+=w[i];
i++;}//将背包装满if(i<=n)b+=(p[i]*cleft/w[i]);returnb;}
①在解空间树的当前扩展结点处,仅当要进入右子树时才用Bound计算当前结点处的上界,以判断是否可将右子树剪去。
进入左子树时不需要计算上界,因为其上界与其父结点的上界相同。
②计算上界需要O(n)时间;在最坏情况下,有O(2^n)个右儿子结点需要计算上界。
∴总的计算时间为O(n2^n)
4.【最大团问题】O(n2^n)
void Clique:
:
Backtrack(int i){
//计算最大团
if (i>n){ //到达叶结点
for(int j=1;j<=n;j++) bestx[j]=x[j];
bestn=cn;
return;}
//检查顶点i与当前团的连接(当前扩展结点位于解空间树的第i层)
int OK=1;
for(intj=1;j
if(x[j]&&a[i][j]==0){
//i与j不相连
OK = 0;
break;}
if(OK ) {//进入左子树
x[i] = 1;
cn++;
Backtrack(i+1);
x[i] = 0;
cn--; }
if(cn+n-i>bestn){// 进入右子树
x[i] = 0;
Backtrack(i+1); }
}在进入左子树前,必须确认从顶点i到已选入的顶点集中每一个顶点都有边相连;在进入右子树前,必须确认还有足够多的可选择顶点使得算法有可能在右子树中找到更大的团。
5.【图的m着色问题】O(nm^n)
bool Color:
:
Ok(int k){ //检查颜色可用性
for(intj=1;j<=n;j++)
if((a[k][j]==1)&&x[j]==x[k])return false;
return true;
}
void Color:
:
Backtrack(int t)
{if(t>n){ //算法搜索到叶结点,得到新的m着色方案
sum++;
for(int i=1;i<=n;i++)
cout<cout<}
else//当前扩展结点有x[i]=1,2,…,m共m个儿子结点
for(int i=1;i<=m;i++){
x[k]=i;
if(Ok(t)) Backtrack(t+1);//对每一个儿子结点,由函数Ok检查其
可行性,并以深度优先的方式递归地对可
行子树搜索,或剪去不可行子树。
x[k]=0;
}
}解空间树中的内结点个数是(n-1∑i=0)m^i;对于每一个内结点,在最坏情况下,用OK检查当前扩展结点的每一个儿子所相应的颜色的可用性需耗时O(mn)。
因此,回溯法总耗时(n-1∑i=0)m^i(mn)=nm(m^n-1)/(m-1)=O(nm^n)。
6.【旅行售货员问题】排列树、O(n!
)
void Traveling:
:
Backtrack(int i)
{if (i == n) {
if (a[x[n-1]][x[n]] !
= NoEdge && a[x[n]][1] !
= NoEdge && (cc+a[x[n-1]][x[n]]+a[x[n]][1]for (int j = 1; j <= n; j++) bestx[j] = x[j];
bestc = cc + a[x[n-1]][x[n]] + a[x[n]][1];}
}
else {
for (int j = i; j <= n; j++)
// 是否可进入x[j]子树?
if (a[x[i-1]][x[j]] !
= NoEdge &&
(cc + a[x[i-1]][x[i]] < bestc || bestc == NoEdge)){ // 搜索子树
Swap(x[i], x[j]);
cc += a[x[i-1]][x[i]];
Backtrack(i+1);
cc -= a[x[i-1]][x[i]];
Swap(x[i], x[j]);
} } }
①当i=n时,当前扩展结点是排列树的叶结点的父结点。
此时算法检测图G是否存在一条从顶点x[n-1]到顶点x[n]的边和一条从顶点x[n]到顶点1的边。
如果这两条边都存在,则找到一条旅行售货员回路。
此时,算法还需判断这条回路的费用是否优于已找到的当前最优回路的费用bestc。
如果是,则更新当前最优值bestc和当前最优解bestx。
当i图G中存在从顶点x[i-1]到顶点x[i]的边时,x[1:
i]构成图G的一条路径,且当x[1:
i]的费用(cc)小于当前最优值时算法进入排列树的第i层,否则剪去相应的子树。
②算法backtrack在最坏情况下可能需要更新当前最优解O((n-1)!
)次,每次更新bestx需计算时间O(n),从而整个算法的计算时间复杂性为O(n!
)
7.【圆排列问题】排列树、O((n+1)!
)
floatCircle:
:
Center(intt){//计算当前所选择圆的圆心横坐标
floattemp=0;
for(intj=1;jfloatvaluex=x[j]+2.0*sqrt(r[t]*r[j]);
if(valuex>temp)temp=valuex;}
returntemp;}
voidCircle:
:
Compute(void){//计算当前圆排列的长度
floatlow=0,high=0;
for(inti=1;i<=n;i++){
if(x[i]-r[i]if(x[i]+r[i]>high)high=x[i]+r[i];}
if(high-lowvoidCircle:
:
Backtrack(intt){if(t>n)Compute();
Else
for(intj=t;j<=n;j++){swap(r[t],r[j]);
floatcenterx=Center(t);
if(centerx+r[t]+r[1]n时,算法搜索至叶节点,得到新的圆排列方案。
此时算法调用Compute计算当前圆排列的长度,适时更新当前最优值。
当i此时算法选择下一个要排列的圆,并计算相应的下界函数。
在满足下界约束的结点处,以深度优先的方式递归地对相应子树搜索;对于不满足下界约束的结点,则减去相应的子树。
②如果不考虑计算当前圆排列中各圆的圆心横坐标和计算当前圆排列长度所需的计算时间按,则Backtrack需要O(n!
)计算时间。
由于算法Backtrack在最坏情况下需要计算O(n!
)次圆排列长度,每次计算需要O(n)计算时间,从而整个算法的计算时间复杂性为O((n+1)!
)。
8.【电路板排列问题】NP难、O(mn!
)
void Board:
:
Backtrack(int i,int cd)//cd下标应为i-1
{//回溯法搜索排列树
if(i == n+1){//算法搜索到叶结点
for(int j=1; j<=n; j++)
bestx[j] = x[j];
bestd = cd;}//由于算法仅完成那些比当前最优解更好的排列,更新
else//
for(int j=i; j<=n; j++)
//选择x[j]为下一块电路板
int ld = 0;//扩展当前部分排列产生当前扩展结点的一个子结点,对于这个子结点,设定一个新的变量ld来表示新的部分排列密度
for(int k=1; k<=m; k++){
now[k]+=B[x[j]][k];//值为1仅当电路板x[j]在连接块k中
if(now[k]>0 && total[k]!
=now[k])
ld++;
}
//更新ld
if(cd>ld) ld = cd;
if(ld Swap(x[i],x[j]);
Backtrack(i+1,ld);
Swap(x[i],x[j]);}
//恢复状态
for(int k=1; k<=m; k++)
now[k] -= B[x[j]][k];
}
}
①当i=n时,算法搜索到叶结点,所有n块电路板都已经排定,其密度为cd;
当iX[1:
i-1]是当前扩展结点所相应的部分排列,cd是相应的部分排列密度。
在当前部分排列之后要加入一块未排定的电路板,扩展当前部分排列,产生当前扩展结点的一个子结点。
对于这个子结点,计算新的部分排列密度ld,如果ld②在解空间排列树的每个节点处,算法Backtrack花费O(m)计算时间为每个儿子节点计算密度。
因此计算密度所消耗的总计算时间为O(mn!
)。
另外,生成排列树需要O(n!
)时间。
每次更新当前最优解至少使bestd减少1,而算法运行结束时bestd>=0。
因此最优解被更新的额次数为O(m)。
更新最优解需要O(mn)时间。
综上,解电路板排列问题的回溯算法Backtrack所需要的计算时间为O(mn!
)。
9.【连续邮资问题】
voidStamp:
:
Backtrack(inti,intr)
{for(intj=0;j<=x[i-2]*(m-1);j++)
if(y[j]for(intk=1;k<=m-y[j];k++)
if(y[j]+kwhile(y[r]if(i>n){//算法搜索至叶结点,得到新的邮票面值设计方案x[1:
n]
if(r-1>maxvalue){//若该方案贴出的最大连续邮资区间大于当前已找到的最大连续邮资区间maxvalue,则更新当前最优值maxvalue和最优解
maxvalue=r-1;
for(intj=1;j<=n;j++)
bestx[j]=x[j];}
return;
}
//
int*z=newint[maxl+1];
for(intk=1;k<=maxl;k++)
z[k]=y[k];
for(j=x[i-1]+1;j<=r;j++){
x[i]=j;
Backtrack(i+1,r);
for(intk=1;k<=maxl;k++)
y[k]=z[k];}
delete[]z;
}①为什么要引入y[k]?
计算X[1:
i]的最大连续邮资区间在本算法中被频繁使用到,因此势必要找到一个高效的方法。
直接递归的求解复杂度太高,不妨尝试计算用不超过m张面值为x[1:
i]的邮票贴出邮资k所需的最少邮票数y[k]。
通过y[k]可以很快推出r的值。
如果y[r]的值在上