2分钟让你彻底通读所有的算法设计

转载请注明本文出自天水笑微的博客(http://blog.csdn.net/fuhaijing2013/article/details/52927450,谢谢支持!


翻出之前的面试资料,移动硬盘上的一篇总结,都不知道是不是原创了~~~~(>_<)~~~~,做了些更新,留做备份


程序员面试题:描述下常见的算法

总结解答如下:

一般常用的算法有分治算法、动态规划、贪心算法、回溯法及分支限界法。


1、  分治算法

分治算法是一种自顶向下的算法,通常借助递归来实现。

1)  设计思想

它的设计思想是:将一个难以解决的大问题,分割成一些规模较小的相同问题,这些子问题相互独立并且与原问题相同。递归的解这些子问题,然后将各个子问题的接合并得到原问题的解。

如果子问题是可解的,则说明算法是可行的,通常子问题是原问题的较小模式,这就为递归提供了方便,递归和分治通常是同时使用产生好多高效算法。

     2)设计模式

分治的一般设计模式如下:

Divide-and-Conquer(P)
{
         if(|p|<=n0)Adhoc(p);
            divide P into smaller subinstances
                 P1,P2,...Pk;
             for(i=1;i<=k;i++)
               yi=Divide-and-Conquer(Pi);
              return Merge(y1,y2,...yk);		 
}

3)  时间复杂度

一般时间复杂度是T=k*O(n/m)形式:即将规模为n的原问题分成k个规模为n/m的子问题。

通常分治算法是可以把时间复杂度从O(n*n)降到O(logN)的。

4)  典型应用

        分治算法的典型应用有类似Hanoi塔问题、二分搜索、大整数乘法、归并排序、快速排序等。

        算法举例:Hanoi塔问题

 void Hanoi(int n,int A,int B,int C)
{       
            if(n>0)
               {
                    Hanoi(n-1,A,C,B);
                     Move(n,A,B)
                     Hanoi(n-1,C,B,A);
               }
}

2.动态规划

  1)设计思想

         与分治算法类似,动态规划的基本思想也是将待求解问题分解成若干个子问题,然后求解这些子问题,然后从这些子问题的解中得到原问题的解。与分治算法不同的是:动态规划采用的是自底向上的思想,在分治算法求解释,有些问题被重复计算了许多次,分得的子问题太多。因此动态规划采用调表的方式,用一个表来记录所有已解决的子问题的答案。不管子问题是否被用到,只要他被计算过,就将其结果填入表中。

   动态规划常用于求解具有某种最优性质的问题。

2)  求解步骤

设计一个动态规划算法,通常可按以下几个步骤进行:

    A.     找出最优解的性质

    B.     递归的定义最优值

    C.     自底向上的方式计算出最优值

    D.     根据计算最优值得到的信息,构造一个最优解。

3)  基本要素

    动态规划的2个基本要素是:

    最优子结构:当问题的最优解包含了其子问题的最优解时,称该问题具有最有子结构性质。

    重叠子问题:在用递归算法自顶向下界问题是,每次产生的子问题并不总是新问题,有些问题被反复计算多次。这也是动态规划问题的填表格式提高效率的关键所在。

4)  备忘录方法

    有一种分治算法与动态规划方法的结合即使备忘录方法。

    与分治算法相同,备忘录方法也是采用自顶向下的方法,但是不同的是,备忘录方法为每个子问题建立了备忘录以备需要时查看,避免了相同子问题的重复求解,与动态规划异曲同工。

5)  一般来讲,当一个问题的所有子问题都要至少求解一遍是,则用动态规划比用备忘录方法好。此时,动态规划没有任何多余的计算,还可以用其规则的表格存取方式,来减少在动态规划中计算时间和空间的需求。当子问题空间中的部分子问题可不必求解释,用备忘录方法则较有力,因为从其控制结构可以看出,该方法只解哪些确实需要求解的子问题。典型应用

动态规划的典型应用如最长公共子序列、最大字段和、01背包问题。

01背包问题动态规划方程:

f(n,m)=max{f(n-1,m), f(n-1,m-w[n])+P(n,m)}

       (常记得大学算法老师的一句话:算法有了,写出程序是早晚的事O(∩_∩)O~,所以伪代码算法就不举例了。)


3 .贪心算法

1)  算法思想

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

2)  基本要素

贪心算法通过一系列的选择来得到一个问题的解,即贪心选择,希望通过每次所做出的贪心选择导致最终结果是问题的一个最优解,这种启发式的策略并不总是奏效,需要满足特定的性质。

对于一个具体的问题,判断能不能用贪心算法,需要考虑两个基本要素。

A.     贪心选择性质:所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,及贪心选择来达到。

贪心算法所做的贪心选择可以依赖以往所做过的选择,但绝不依赖于将来所做的选择,也不依赖于子问题。这正是贪心算法与动态规划算法的区别。

一般证明贪心选择性质会用反证法等或数学归纳法,首先考察问题的一个整体最优解,并证明可修改这个最优解,使其从贪心选择开始,而做了贪心选择之后,原问题简化为一个规模更小的子问题。然后数学归纳法证明,通过每一步贪心算法,最终可以得到最优解。

B.     最优子结构,这跟动态规划类似。

3)  典型应用

哈夫曼编码、最优装载问题。图论里面的最小生成树( Kruskal)算法、最短路径等算法。

哈夫曼编码算法步骤如下:

(1)初始化,根据符号出现概率的大小按由大到小顺序对符号进行排序。

(2)把概率最小的两个符号(咳咳,贪心算法在这)组成一个新符号,即节点,新符号的概率等于这两个符号概率之和。

(3)重复第2步,直到形成一个符号为止(树),其概率最后等于1。

(4)从编码树的根开始回溯到原始的符号,并将每一下分枝赋值为1,上分枝赋值为0。

 

4. 回溯法

         回溯法被称为“通用”的算法,初学时禁不住感叹这当真是 “万能算法”,个人比较崇拜的,哇塞,实质是一种深度优先算法。

1)  算法思想

回溯法是一个既带有系统性又带有跳跃性的算法。他在包含问题的所有解空间树中,按照深度优先遍历,从根节点出发搜索解空间树。对于每一个节点,总是判断该节点是否肯定包含问题的解。如果肯定包含,则跳过,否则,进入子树,继续按深度优先搜索。要回溯到根,且根节点的所有子树都已经被搜索完遍才结束。

2)  算法步骤

a)        针对所给问题,定义问题的解空间

b)        确定易于搜索的解空间结构

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

3)  回溯

回溯法的核心主要是:回溯。

回溯的实现主要有两种:递归回溯和迭代回溯。

递归回溯:

void backtrack(int t)
{
    If(t>n)Output(t);
    else
              for(int I= f(n,t);i<=g(n,t);i++){
              x[t]=h(i);
              if(constrant(t)&&Bound(t))Backtrack(t+1)
}
}


 迭代回溯:

void IterativeBacktrack(void){
       int t=1;
       while(t>0){
            if(f(n,t)<g(n,t))
            for(int I = f(n,t),i<= g(n,t),i++){
           x[t]=h(i);
     if(constrant(t)&&Bound(t)){
if(Solution(t))Output(x);
 else t++;}
 }
 else t--;
 }}
 
 

 

4)  解空间树

有两种典型的解空间树:子集树、排序树

子集树:当所给问题是从n个元素中找出满足某种性质的子集是,相应的解空间树是一种子集树。例如0-1背包问题。一般子集树的复杂度为O(2n).

搜索子集树的一般算法:

void Backtrack(int t)
{
             if(t>n)Output(t);
              else
              for(int i= 0;i<=1;i++){
              x[t]=i;
              if(constrant(t)&&Bound(t))Backtrack(t+1)
}

     排序树:当所给问题是确定那个元素满足某种性质的排列时,相应的解空间成为排列树。排列树通常有n!个节点。因此搜索遍历排列树需要O(n!)时间。例如旅行售货员问题等。

void Backtrack(int t)
{
            if(t>n)Output(t);
             else
             for(int i=t;i<=n;i++){
             swap(x[t],x[i]);//交换,核心
             if(constrant(t)&&Bound(t))Backtrack(t+1);
    swap(x[t],x[i]);//换回来,回归
}


 

 5)  典型应用

装载问题、0-1背包问题、批作业处理调度问题,N后问题是回溯算法的典型案例吧。


5.分支限界法

分支限界法实质是一种广度优先算法,一般借助队列来实现。其实个人感觉并不常用。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值