计算机算法设计与分析

来源:互联网 发布:mac mini 2016 10月 编辑:程序博客网 时间:2024/05/12 00:21

算法初识:

算法概念:

算法就是一组有穷的 规则 ,它们规定了解决某一特定类型问题的一系列运算  。此外,算法还应具有以下五个重要特性确定性 , 有穷性 ,可行性 , 0个或多个输入 , 一个或多个输出

算法分析的目的:分析算法占用计算机资源的情况,对算法做出比较和评价,设计出额更好的算法。

在进行问题的计算复杂性分析之前,首先必须建立求解问题所用的计算模型。3个基本计算模型是随机存取机RAM(Random Access Machine);随机存取存储程序机RASP(Random Access Stored Program Machine);图灵机(Turing Machine)

 

算法复杂度概念:

算法的复杂性是  算法效率     的度量,是评价算法优劣的重要依据。

计算机的资源最重要的是  时间     空间   资源。因而,算法的复杂性有时间复杂性  空间复杂性之分,衡量一个算法好坏的标准是时间复杂度高低。算法的时间复杂性问题的规模相关,是问题大小n的函数。

●  最坏情况下的时间复杂性和平均时间复杂性有什么不同?

     最坏情况下的时间复杂性和平均时间复杂性考察的是n固定时,不同输入实例下的算法所耗时间。最坏情况下的时间复杂性取的输入实例中最大的时间复杂度:

W(n) = max{ T(nI) } ,   I∈Dn

平均时间复杂性是所有输入实例的处理时间与各自概率的乘积和:

A(n) =∑P(I)T(nI) I∈Dn

 

复杂性的渐近性态:

    我们先要引入复杂性渐近性态的概念。设T(N)是关于算法A的复杂性函数。一般说来,当N单调增加且趋于∞时,T(N)也将单调增加趋于∞。对于T(N),如果存在T(N),使得当N→∞时有: 

(T(N )-T(N ))/T(N )  0 

那么,我们就说T(N)T(N)N→∞时的渐近性态,或叫T(N)为算法AN→∞的渐近复杂性而与T(N)相区别,因为在数学上,T(N)T(N)N→∞时的渐近表达式。

    算法的渐进时间复杂性的含义:当问题的规模n趋向无穷大时,影响算法效率的重要因素是T(n)的数量级,而其他因素仅是使时间复杂度相差常数倍,因此可以用T(n)的数量级()评价算法。时间复杂度T(n)的数量级()称为渐进时间复杂性。

     

      综上所述,我们已经给出了简化算法复杂性分析的方法和步骤,即只要考察当问题的规模充分大时,算法复杂性在渐近意义下的阶。与此简化的复杂性分析方法相配套,需要引入五个渐近意义下的记号:O、Ω、θ、ο和ω

1渐近上界记号O

O(g(n)) = f(n):存在正常数cn0,使得对所有n≥n0,有0 ≤f(n)≤cg(n)

2  渐近下界记号Ω

(g(n)) = f(n):存在正常数cn0,使得对所有n≥n00≤cg(n) ≤f(n)

非紧上界记号o

o(g(n)) = f(n)对于任何正常数c>0,存在正数n0>0使得对所有n≥n00≤f(n)<cg(n)

等价于f(n) / g(n)→0as n→∞

非紧下界记号w

w(g(n)) = f(n):对于任何正常数c>0,存在正数和n0>0使得对所有n≥n00≤cg(n) < f(n)

等价于f(n) / g(n) →∞ as  n→∞

5紧渐近界记号θ

(g(n)) = f(n):存在正常数c1,c2n0使得对所有n≥n0c1g(n) ≤f(n) ≤c2g(n)


按照大Ο的定义,容易证明它有如下运算规则: 

1.Ο(f)+Ο(g)=Ο(max(f,g))  

2Ο(f)+ Ο(g)=Ο(f +g)  

3Ο(f)·Ο(g)= Ο(f·g)  

4 如果g(N)= Ο(f(N)),则Ο(f)+ Ο(g)= Ο(f) 

5Ο(Cf(N))= Ο(f(N)),其中C是一个正的常数;

6. f =Ο(f)


按数量级递增排列,常见的时间复杂度有:

常数阶O(1),对数阶O(log2n)(2为底n的对数,下同),线性阶O(n),线性对数阶O(nlog2n),平方阶O(n^2),立方阶O(n^3),...k次方阶O(n^k),指数阶O(2^n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。

例如:按照渐近阶从低到高顺序排列:2lognn2/3,20n,4n2,3n,n!

 



算法复杂度计算分析:

一个算法所耗费的时间=算法中每条语句的执行时间之和.

 

例:求两个n阶方阵的乘积 C=A×B,其算法如下:

# define n 100 // n 可根据需要定义,这里假定为100void MatrixMultiply(int A[a],int B [n][n],int C[n][n]){ //右边列为各语句的频度int i ,j ,k;(1) for(i=0; i<n;i++)                  n+1(2) for (j=0;j<n;j++) {                n(n+1)(3) C[i][j]=0;                         n2(4) for (k=0; k<n; k++)                n2(n+1)(5) C[i][j]=C[i][j]+A[i][k]*B[k][j];   n3}}
该算法中所有语句的频度之和(即算法的时间耗费)为:
T(n)=2n3+3n2+2n+1 (1.1)
分析:(循环,多层,叠加)
语句(1)的循环控制变量i要增加到n,测试到i=n成立才会终止。故它的频度是n+1。但是它的循环体却只能执行n次。语句(2)作为语句(1)循环体内的语句应该执行n次,但语句(2)本身要执行n+1次,所以语句(2)的频度是n(n+1)。同理可得语句(3),(4)和(5)的频度分别是n2,n2(n+1)和n3
渐进时间复杂度评价算法时间性能:
主要用算法时间复杂度的数量级(即算法的渐近时间复杂度)评价一个算法的时间性能。
(1) x=0;y=0;    (2) for(k-1;k<=n;k++)    (3) x++;    (4) for(i=1;i<=n;i++)    (5) for(j=1;j<=n;j++)    (6) y++;
一般情况下,对步进循环语句只需考虑循环体中语句的执行次数,忽略该语句中步长加1、终值判别、控制转移等成分。因此,以上程序段中频度最大的语句是(6),其频度为f(n)=n^2,所以该程序段的时间复杂度为T(n)=O(n^2)。
当有若干个循环语句时,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的频度f(n)决定的。

递归算法复杂度计算分析:

一、代入法:代入法的基本步骤是先推测递归方程的显式解,然后用数学归纳法来验证该解是否合理。

    大整数乘法计算时间的递归方程为:T(n) = 4T(n/2) + O(n),其中T(1) = O(1),我们猜测一个解T(n) = O(n2 ),根据符号O的定义,对n>n0,有T(n) < cn2 - eO(2n)(注意,这里减去O(2n),因其是低阶项,不会影响到n足够大时的渐近性),把这个解代入递归方程,得到:
    T(n) =  4T(n/2) + O(n)
           ≤ 4c(n/2)2 - eO(2n/2)) + O(n)
           =  cn2 - eO(n) + O(n)
           ≤ cn2 
    其中,c为正常数,e取1,上式符合 T(n)≤cn2 的定义,则可认为O(n2 )是T(n)的一个解,再用数学归纳法加以证明。
   
二、迭代法:迭代法的基本步骤是迭代地展开递归方程的右端,使之成为一个非递归的和式,然后通过对和式的估计来达到对方程左端即方程的解的估计。

    某算法的计算时间为:T(n) = 3T(n/4) + O(n),其中T(1) = O(1),迭代两次可将右端展开为:  
    T(n) = 3T(n/4) + O(n)
         = O(n) + 3( O(n/4) + 3T(n/42 ) )
         = O(n) + 3( O(n/4) + 3( O(n/42 ) + 3T(n/43 ) ) )
        
    从上式可以看出,这是一个递归方程,我们可以写出迭代i次后的方程: 
    T(n) = O(n) + 3( O(n/4) + 3( O(n/42 ) + ... + 3( n/4i + 3T(n/4i+1 ) ) ) )


    当n/4i+1 =1时,T(n/4i+1 )=1,则
    T(n) = n + (3/4) + (32 /42 )n + ... + (3i /4i )n + (3i+1 )T(1)
         < 4n + 3i+1
    而由n/4i+1 =1可知,i<log4 n,从而
    3i+1 ≤ 3log4 n+1 = 3log3 n*log4 3 +1 = 3nlog4 3
    代入得:
    T(n) < 4n + 3nlog4 3,即T(n) = O(n)。
   
三、套用公式法:这个方法针对形如“T(n) = aT(n/b) + f(n)”的递归方程。这种递归方程是分治法的时间复杂性所满足的递归关系,即一个规模为n的问题被分成规模均为n/b的a个子问题,递归地求解这a个子问题,然后通过对这a个子间题的解的综合,得到原问题的解。 
    这个方法为估计形如:

  T(n) = aT(n/b) + f(n)

  其中,a≥1和b≥1,均为常数,f(n)是一个确定的正函数。在f(n)的三类情况下,我们有T(n)的渐近估计式:

    1.若对于某常数ε>0,有f(n) = O(nlogb a-ε ),则T(n) = O(nlogb a )  
    2.若f(n) = O(nlogb a ),则T(n) = O(nlogb a *logn) 
    3.若f(n) = O(nlogb a+ε ),且对于某常数c>1和所有充分大的正整数n,有af(n/b)≤cf(n),则T(n)=O(f(n))。
   
    设T(n) = 4T(n/2) + n,则a = 4,b = 2,f(n) = n,计算得出nlogb a = nlog2 4 = n2 ,而f(n) = n = O(n2-ε ),此时ε= 1,根据第1种情况,我们得到T(n) = O(n2 )。 
    这里涉及的三类情况,都是拿f(n)与nlogb a 作比较,而递归方程解的渐近阶由这两个函数中的较大者决定。在第一类情况下,函数nlogb a 较大,则T(n)=O(nlogb a );在第三类情况下,函数f(n)较大,则T(n)=O(f (n));在第二类情况下,两个函数一样大,则T(n)=O(nlogb a *logn),即以n的对数作为因子乘上f(n)与T(n)的同阶。  
    但上述三类情况并没有覆盖所有可能的f(n)。在第一类情况和第二类情况之间有一个间隙:f(n)小于但不是多项式地小于nlogb a ,第二类与第三类之间也存在这种情况,此时公式法不适用。


NP完全性理论:

何谓PNPNPC问题

      P(Polynomial问题):也即是多项式复杂程度的问题。

      NP就是Non-deterministic Polynomial的问题,也即是多项式复杂程度的非确定性问题。

      NPC(NPComplete)问题,这种问题只有把解域里面的所有可能都穷举了之后才能得出答案,这样的问题是NP里面最难的问题,这种问题就是NPC问题。

     写出3NP完全问题: 团问题、子集和问题、旅行售货员问题。

 

在算法设计的实际应用中,遇到的问题主要分为4类:判定性问题、计算问题、最优化问题和构造性问题,请指出递归法、递推法、贪婪算法、分治法、动态规划法、搜索算法各自适合解决的问题:

 "递推法""递归法"适合解决判定性问题和计算问题。 

贪婪算法分治法” 动态规划法” 适合解决最优化问题。 

贪婪算法分治法” 搜索算法” 适合解决 构造性问题。



递归与分治策略:

应用范例:
      二分搜索技术;大整数的乘法;Strassen矩阵乘法;棋盘覆盖;合并排序;快速排序;线性时间选择;最接近点对问题;循环赛日程表;

 .对于流水作业高度问题,必存在一个最优调度π,使得作业π(i)和π(i+1)满足Johnson不等式min{bπ(i),aπ(i+1)}≥min{bπ(i+1),aπ(i)}

分治法的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相同;对这k个子问题分别求解。如果子问题的规模仍然不够小,则再划分为k个子问题,如此递归的进行下去,直到问题规模足够小,很容易求出其解为止;将求出的小规模的问题的解合并为一个更大规模的问题的解,自底向上逐步求出原来问题的解。

 

分治算法的基本步骤包括分解,递归,组合


 简述二分检索(折半查找)算法的基本过程

      设输入是一个按非降次序排列的元素表A[i:j] 和x,选取A[(i+j)/2]与x比较,如果A[(i+j)/2]=x,则返回(i+j)/2,如果A[(i+j)/2]<x,则A[i:(i+j)/2-1]找x,否则在A[ (i+j)/2+1:j] 找x。上述过程被反复递归调用。


快速排序算法的性能取决于 划分的对称性

 试用分治法对数组A[n]实现快速排序:

void Swap(int& x,int& y) {  int  t;  t=x;  x=y;  y=t; }  /* 以a[p]为基准元素将a[p:r]划分为3段a[p:q-1],a[q],a[q+1:r] */  int Partition(int* a,int p,int r) {  int i=p,j=r+1;  int x=a[p];  while(1)  {   while(a[++i]<x&&i<r)   ;   while(a[--j]>x)    ;   if(i>=j)     break;   Swap(a[i],a[j]);  };  a[p]=a[j];  a[j]=x;  return j;  //返回划分点q=i} /* 实现快排*/ void QuickSort(int* a,int p,int r) {  if(p<r)  {   int q=Partition(a,p,r);   QuickSort(a,p,q-1);   QuickSort(a,q+1,r);  } };

 

 

动态规划: 

应用范例:
 最长公共子序列;最大子段和;凸多边形最优三角剖分;多边形游戏;图像压缩;电路布线;流水作业调度;0-1背包问题;最优二叉搜索树


 动态规划算法的基本思想是将待求解问题分解成若干子问题,先求解子问题,然后从这些子问题的解得到原问题的解。

 某一问题可用动态规划算法求解的显著特征是该问题具有最优子结构性质

某个问题的最优解包含着其子问题的最优解。这种性质称为最优子结构性质

 动态规划算法的两个基本要素是最优子结构重叠子问题 。 

 设计动态规划算法的4个步骤:

(1)       找出最优解的性质,并刻画其结构特征。

(2)       递归地定义最优值

(3)       以自底向上的方式计算出最优值

(4)       根据计算最优值得到的信息,构造最优解

 

 简述动态规划方法所运用的最优化原理:

最优化原理”用数学化的语言来描述:假设为了解决某一优化问题,需要依次作出n个决策D1,D2,…,Dn,如若这个决策序列是最优的,对于任何一个整数k,1 < k < n,不论前面k个决策是怎样的,以后的最优决策只取决于由前面决策所确定的当前状态,即以后的决策Dk+1,Dk+2,…,Dn也是最优的。

 

 叙述分治算法和动态规划算法的基本思想,并比较两种算法的异同:

答:两者都是递归算法思想的应用,根本策略是找出大规模问题与小规模子问题之间的关系,直到小规模的子问题容易得到解决,再由小规模子问题的解逐步导出大问题的解。 

分治法能解决问题的特征: 

1)问题的规模缩小到一定的程度就可以容易地解决。 

2)问题可以分解为若干个规模较小的相似问题,即该问题具有最优子结构性质。  

3)利用该问题分解出的子问题的解可以合并为该问题的解。 

4)该问题所分解出的各个子问题是相互独立的且子问题之间不包含公共的子问题。 当问题满足1,2,3,4条时采用分治法,当满足1、2、3条时采用动态规划方法

 

  试用动态规划算法实现最长公共子序列问题

int lcs_len(char* a,char* b,int c[][N]) { int m=strlen(a),n=strlen(b),i,j;  for(i=0;i<=m;i++)c[i][0]=0; for(j=1;j<=n;j++)c[0][j]=0;for(i=1;i<=m;i++) for(j=1;j<=n;j++) if(a[i-1]==b[j-1])     c[i][j]=c[i-1][j-1]+1; else if(c[i-1][j]>=c[i][j-1])       c[i][j]=c[i-1][j]; else        c[i][j]=c[i][j-1]; return c[m][n]; };  char* build_lcs(char s[],char* a,char* b) { int k,i=strlen(a),j=strlen(b),c[N][N]; k=lcs_len(a,b,c); s[k]=’\0’; while(k>0){ if(c[i][j]==c[i-1][j])i--; else if(c[i][j]==c[i][j-1])j--; else{ s[--k]=a[i-1]; i--,j--; } } return s; } 



 .对于流水作业高度问题,必存在一个最优调度π,使得作业π(i)和π(i+1)满足Johnson不等式min{bπ(i),aπ(i+1)}≥min{bπ(i+1),aπ(i)}

流水作业调度问题的johnson算法的思想:

①令N1={i|ai<bi},N2={i|ai>=bi};

②将N1中作业按ai的非减序排序得到N1’,将N2中作业按bi的非增序排序得到N2’;

③N1’中作业接N2’中作业就构成了满足Johnson法则的最优调度。

 流水作业调度中,已知有n个作业,机器M1和M2上加工作业i所需的时间分别为ai和bi,请写出流水作业调度问题的johnson法则中对ai和bi的排序算法。(函数名可写为sort(s,n))

voidsort(flowjope s[],int n){ int i,k,j,l; for(i=1;i<=n-1;i++)//-----选择排序 {  k=i;  while(k<=n&&s[k].tag!=0)  k++;  if(k>n) break;//-----没有ai,跳出  else  {   for(j=k+1;j<=n;j++)     if(s[j].tag==0)        if(s[k].a>s[j].a)   k=j;   swap(s[i].index,s[k].index);   swap(s[i].tag,s[k].tag); }                             } l=i;//-----记下当前第一个bi的下标for(i=l;i<=n-1;i++) { k=i; for(j=k+1;j<=n;j++)   if(s[k].b<s[j].b)  k=j; swap(s[i].index,s[k].index); //-----只移动index和tag     swap(s[i].tag,s[k].tag);         }}


 最优二叉搜索树问题的动态规划算法(设函数名binarysearchtree))

void binarysearchtree(int a[],int b[],int n,int **m,int **s,int **w){                                                                                                                                       inti,j,k,t,l; for(i=1;i<=n+1;i++)  {w[i][i-1]=a[i-1];   m[i][i-1]=0;} for(l=0;l<=n-1;l++)//----l是下标j-i的差for(i=1;i<=n-l;i++) {        j=i+l;w[i][j]=w[i][j-1]+a[j]+b[j];m[i][j]=m[i][i-1]+m[i+1][j]+w[i][j];s[i][j]=i;for(k=i+1;k<=j;k++) {   t=m[i][k-1]+m[k+1][j]+w[i][j];if(t<m[i][j]) { m[i][j]=t;s[i][j]=k;}}}} 

贪心算法:

应用范例:
                活动安排问题,最优装载,哈夫曼编码,单源最短路径,最小生成树,多机调度问题


贪心算法总是做出在当前看来  最好   的选择。也就是说贪心算法并不从整体最优考虑,它所做出的选择只是在某种意义上的  局部最好选择   

许多可以用贪心算法求解的问题一般具有2个重要的性质 贪心选择性质   最优子结构性质

a)  所谓贪心选择性质是指(所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到)

b)  所谓最优子结构性质是指(问题的最优解包含了其子问题的最优解)

背包问题的目标函数和贪心算法最优化量度相同吗?

    不相同。目标函数:获得最大利润。最优量度:最大利润/重量比。


动态规划和贪心算法的区别:
动态规划和贪心算法都是一种递推算法 ,均有局部最优解来推导全局最优解 。

不同点: 
贪心算法: 
1.贪心算法中,作出的每步贪心决策都无法改变,因为贪心策略是由上一步的最优解推导下一步的最优解,而上一部之前的最优解则不作保留。 
2.由(1)中的介绍,可以知道贪心法正确的条件是:每一步的最优解一定包含上一步的最优解。 

动态规划算法: 
1.全局最优解中一定包含某个局部最优解,但不一定包含前一个局部最优解,因此需要记录之前的所有最优解 
2.动态规划的关键是状态转移方程,即如何由以求出的局部最优解来推导全局最优解 
3.边界条件:即最简单的,可以直接得出的局部最优解


回溯法:

应用范例:
         装载问题,批处理作业问题,符号三角形问题,n后问题,0-1背包问题,最大团问题,图的m着色问题,旅行售货员问题,图排列问题,电路板排列问题,连续邮资问题。


        回溯法是在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树,当算法搜索至解空间树的任一结点时,总是先判断该结点是否满足问题的约束条件。如果满足进入该子树,继续按深度优先的策略进行搜索。否则,不去搜索以该结点为根的子树,而是逐层向其祖先结点回溯。

用回溯法解问题时,应明确定义问题的解空间,问题的解空间至少应包含一个(最优)解


 回溯法的搜索特点是什么?

       在解空间树上跳跃式地深度优先搜索,即用判定函数考察x[k]的取值,如果x[k]是合理的就搜索x[k]为根节点的子树,如果x[k]取完了所有的值,便回溯到x[k-1]。


基本步骤:

①      针对拨给问题,定义问题的解空间; 

②      确定易于搜索的解空间结构; 

②      以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。

③      回溯法是回溯法是指(具有限界函数的深度优先生成法)。

 

        用回溯法解题的一个显著特征是在搜索过程中动态产生问题的解空间。在任何时刻,算法只保存从根结点到当前扩展结点的路径。如果解空间树中从根结点到叶结点的最长路径的长度为h(n),则回溯法所需的计算空间通常为(O(h(n)))。

       回溯法的算法框架按照问题的解空间一般分为(子集树)算法框架与(排列树)算法框架。

       用回溯法解0/1背包问题时,该问题的解空间结构为(子集树)结构。

       用回溯法解批处理作业调度问题时,该问题的解空间结构为(排列树)结构。

递归回溯: 

 回溯法对解空间作深度优先搜索,因此,在一般情况下用递归方法实现回溯法。

[cpp] view plaincopy
  1. void backtrack (int t)  
  2. {  
  3.     if (t>n)   
  4.         output(x); //已到叶子结点,输出结果  
  5.     else  
  6.     for (int i=f(n,t);i<=g(n,t);i++) {  
  7.         x[t]=h(i);  
  8.         if (constraint(t)&&bound(t))  
  9.             backtrack(t+1);  
  10.     }  
  11. }  
    f(n,t) ,g(n,t) :表示当前扩展结点处未搜索过的子树的起始编号和终止编号。
    h(i):表示在当前扩展结点处x[t]的第i个可选值。

迭代回溯:

    采用树的非递归深度优先遍历算法,可将回溯法表示为一个非递归迭代过程。

[cpp] view plaincopy
  1. void iterativeBacktrack ()  
  2. {  
  3.     int t=1;  
  4.     while (t>0) {  
  5.         if (f(n,t)<=g(n,t))   
  6.             for (int i=f(n,t);i<=g(n,t);i++) {  
  7.                 x[t]=h(i);  
  8.                 if (constraint(t)&&bound(t)) {  
  9.                     if (solution(t)) output(x);  
  10.                     else t++;  
  11.                 }  
  12.         }  
  13.         else t--;  
  14.     }  
  15. }  
子集树与排列树:

    子集树:当所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间称为子集树。例如,那个物品的0-1背包问题所相应的解空间树就是一颗子集树。这类子集问题通常有2^n个叶节点,其节点总个数为2^(n+1)-1。遍历子集树的任何算法均需要O(2^n)的计算时间。


     用回溯法遍历子集树的一般算法可描述如下:

[cpp] view plaincopy
  1. void backtrack (int t)  
  2. {   
  3.     if (t>n) output(x);  
  4.     else  
  5.         for (int i=0;i<=1;i++) {  
  6.             x[t]=i;  
  7.             if (legal(t)) backtrack(t+1);  
  8.         }  
  9. }  
排列树:当所给问题是确定n个元素满足某种性质的排列时,相应的解空间树称为排列树。排列树通常有n!个叶子节点。因此遍历排列树需要O(n!)的计算时间。


    用回溯法遍历排列树的一般算法可描述如下:

[cpp] view plaincopy
  1. void backtrack (int t)  
  2. {   
  3.     if (t>n) output(x);  
  4.     else  
  5.         for (int i=t;i<=n;i++) {  
  6.             swap(x[t], x[i]);  
  7.             if (legal(t)) backtrack(t+1);  
  8.             swap(x[t], x[i]);  
  9.         }  
  10. }   

采用回溯法求解的问题,其解如何表示?有什么规定?

         问题的解可以表示为n元组:(x1,x2,……xn),xi∈Si, Si为有穷集合,xi∈Si, (x1,x2,……xn)具备完备性,即(x1,x2,……xn)是合理的,则(x1,x2,……xi)(i<n)一定合理。


分别用贪心算法、动态规划法、回溯法设计0-1背包问题:

       已知一个背包的容量为C,有n件物品,物品i的重量为Wi,价值为Vi,求应如何选择装入背包中的物品,使得装入背包中物品的总价值最大。

      分别用贪心算法、动态规划法、回溯法设计0-1背包问题。要求:说明所使用的算法策略;写出算法实现的主要步骤;分析算法的时间。

         0-1背包问题的回溯算法所需的计算时间为o(n*2n),用动态规划算法所需的计算时间为o(min{nc,2n})

(1)贪心算法 O(nlog(n))

•       首先计算每种物品单位重量的价值Vi/Wi,然后,依贪心选择策略,将尽可能多的单位重量价值最高的物品装入背包。若将这种物品全部装入背包后,背包内的物品总重量未超过C,则选择单位重量价值次高的物品并尽可能多地装入背包。依此策略一直地进行下去,直到背包装满为止。

•       具体算法可描述如下:

void Knapsack(int n,float M,float v[],float w[],float x[]){Sort(n,v,w);int i;for (i=1;i<=n;i++) x[i]=0;float c=M;for (i=1;i<=n;i++){if (w[i]>c) break;x[i]=1;c-=w[i];}if (i<=n) x[i]=c/w[i];}


(2)动态规划法  O(nc)

m(i,j)是背包容量为j,可选择物品为i,i+1,…,n时0-1背包问题的最优值。由0-1背包问题的最优子结构性质,可以建立计算m(i,j)的递归式如下。

 

void KnapSack(int v[],int w[],int c,intn,int m[][11]){int jMax=min(w[n]-1,c);for (j=0;j<=jMax;j++)  /*m(n,j)=0   0=<j<w[n]*/m[n][j]=0;for (j=w[n];j<=c;j++)   /*m(n,j)=v[n]   j>=w[n]*/m[n][j]=v[n];for (i=n-1;i>1;i--){ int jMax=min(w[i]-1,c);for (j=0;j<=jMax;j++)  /*m(i,j)=m(i+1,j)  0=<j<w[i]*/   m[i][j]=m[i+1][j];for (j=w[i];j<=c;j++)  /*m(n,j)=v[n]   j>=w[i]*/   m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);}m[1][c]=m[2][c];if(c>=w[1])m[1][c]=max(m[1][c],m[2][c-w[1]]+v[1]);}

 

(3)回溯法   O(2n)

cw:当前重量     cp:当前价值    bestp:当前最优值

void backtrack(int i)    //回溯法  i初值1{    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)//搜索右子树           backtrack(i+1);    }   


分支限界法:

应用范例:
         单源最短路径问题,装载问题,批处理作业问题,0-1背包问题,最大团问题,旅行售货员问题,布线问题,电路板排列问题。

回溯法与分支限界法的区别 

       两者都是问题的解空间树上搜索问题解的算法。回溯法与分支限界法的的求解目标不同,回溯法的求解目标是找出解空间树中满足约束条件的所有解,而分支限界法的求解目标是找出解空间树中满足约束条件的一个解,或是在满足约束条件的 

解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。



附:

一、简要回答下列问题:

1.  算法重要特性是什么?

2.  算法分析的目的是什么?

3.  算法的时间复杂性与问题的什么因素相关?

4.  算法的渐进时间复杂性的含义?

5.  最坏情况下的时间复杂性和平均时间复杂性有什么不同?

6.  简述二分检索(折半查找)算法的基本过程。

7.  背包问题的目标函数和贪心算法最优化量度相同吗?

8.  采用回溯法求解的问题,其解如何表示?有什么规定?

9.  回溯法的搜索特点是什么?

10. n皇后问题回溯算法的判别函数place的基本流程是什么?

11. 为什么用分治法设计的算法一般有递归调用?

12. 为什么要分析最坏情况下的算法时间复杂性?

13. 简述渐进时间复杂性上界的定义。

14. 二分检索算法最多的比较次数?

15. 快速排序算法最坏情况下需要多少次比较运算?

16. 贪心算法的基本思想?

17. 回溯法的解(x1,x2,……xn)的隐约束一般指什么?

18. 阐述归并排序的分治思路。

19. 快速排序的基本思想是什么。  

20. 什么是直接递归和间接递归?消除递归一般要用到什么数据结构?

21. 什么是哈密顿环问题?

22. 用回溯法求解哈密顿环,如何定义判定函数?

23. 请写出prim算法的基本思想。


参考答案:

1.确定性、可实现性、输入、输出、有穷性

2. 分析算法占用计算机资源的情况,对算法做出比较和评价,设计出额更好的算法。

3. 算法的时间复杂性与问题的规模相关,是问题大小n的函数。

4.当问题的规模n趋向无穷大时,影响算法效率的重要因素是T(n)的数量级,而其他因素仅是使时间复杂度相差常数倍,因此可以用T(n)的数量级(阶)评价算法。时间复杂度T(n)的数量级(阶)称为渐进时间复杂性。

5. 最坏情况下的时间复杂性和平均时间复杂性考察的是n固定时,不同输入实例下的算法所耗时间。最坏情况下的时间复杂性取的输入实例中最大的时间复杂度:

W(n) = max{ T(n,I) } ,   I∈Dn

平均时间复杂性是所有输入实例的处理时间与各自概率的乘积和:

A(n) =∑P(I)T(n,I) I∈Dn

6.设输入是一个按非降次序排列的元素表A[i:j] 和x,选取A[(i+j)/2]与x比较,如果A[(i+j)/2]=x,则返回(i+j)/2,如果A[(i+j)/2]<x,则A[i:(i+j)/2-1]找x,否则在A[ (i+j)/2+1:j] 找x。上述过程被反复递归调用。

回溯法的搜索特点是什么

7.  不相同。目标函数:获得最大利润。最优量度:最大利润/重量比。

8.问题的解可以表示为n元组:(x1,x2,……xn),xi∈Si, Si为有穷集合,xi∈Si, (x1,x2,……xn)具备完备性,即(x1,x2,……xn)是合理的,则(x1,x2,……xi)(i<n)一定合理。

9.在解空间树上跳跃式地深度优先搜索,即用判定函数考察x[k]的取值,如果x[k]是合理的就搜索x[k]为根节点的子树,如果x[k]取完了所有的值,便回溯到x[k-1]。

10.将第K行的皇后分别与前k-1行的皇后比较,看是否与它们相容,如果不相容就返回false,测试完毕则返回true。

11. 子问题的规模还很大时,必须继续使用分治法,反复分治,必然要用到递归。

12  最坏情况下的时间复杂性决定算法的优劣,并且最坏情况下的时间复杂性较平均时间复杂性游可操作性。

13.T(n)是某算法的时间复杂性函数,f(n)是一简单函数,存在正整数No和C,n〉No,有T(n)<f(n),这种关系记作T(n)=O(f(n))。

14.二分检索算法的最多的比较次数为 log n 。

15..最坏情况下快速排序退化成冒泡排序,需要比较n2次。

16.是一种依据最优化量度依次选择输入的分级处理方法。基本思路是:首先根据题意,选取一种量度标准;然后按这种量度标准对这n个输入排序,依次选择输入量加入部分解中。如果当前这个输入量的加入,不满足约束条件,则不把此输入加到这部分解中。

17.回溯法的解(x1,x2,……xn)的隐约束一般指个元素之间应满足的某种关系。

18. 讲数组一分为二,分别对每个集合单独排序,然后将已排序的两个序列归并成一个含n个元素的分好类的序列。如果分割后子问题还很大,则继续分治,直到一个元素。

19.快速排序的基本思想是在待排序的N个记录中任意取一个记录,把该记录放在最终位置后,数据序列被此记录分成两部分。所有关键字比该记录关键字小的放在前一部分,所有比它大的放置在后一部分,并把该记录排在这两部分的中间,这个过程称作一次快速排序。之后重复上述过程,直到每一部分内只有一个记录为止。

20.在定义一个过程或者函数的时候又出现了调用本过程或者函数的成分,既调用它自己本身,这称为直接递归。如果过程或者函数P调用过程或者函数Q,Q又调用P,这个称为间接递归。消除递归一般要用到栈这种数据结构。

21.哈密顿环是指一条沿着图G的N条边环行的路径,它的访问每个节点一次并且返回它的开始位置。

22.当前选择的节点X[k]是从未到过的节点,即X[k]≠X[i](i=1,2,…,k-1),且C(X[k-1], X[k])≠∞,如果k=-1,则C(X[k], X[1]) ≠∞。

23.思路是:最初生成树T为空,依次向内加入与树有最小邻接边的n-1条边。处理过程:首先加入最小代价的一条边到T,根据各节点到T的邻接边排序,选择最小边加入,新边加入后,修改由于新边所改变的邻接边排序,再选择下一条边加入,直至加入n-1条边。

 


 .对于流水作业高度问题,必存在一个最优调度π,使得作业π(i)和π(i+1)满足Johnson不等式min{bπ(i),aπ(i+1)}≥min{bπ(i+1),aπ(i)}

        用回溯法解题的一个显著特征是在搜索过程中动态产生问题的解空间。在任何时刻,算法只保存从根结点到当前扩展结点的路径。如果解空间树中从根结点到叶结点的最长路径的长度为h(n),则回溯法所需的计算空间通常为(O(h(n)))。

       回溯法的算法框架按照问题的解空间一般分为(子集树)算法框架与(排列树)算法框架。

       用回溯法解0/1背包问题时,该问题的解空间结构为(子集树)结构。

       用回溯法解批处理作业调度问题时,该问题的解空间结构为(排列树)结构。

0 0
原创粉丝点击