学科总结

来源:互联网 发布:高校教学软件 编辑:程序博客网 时间:2024/05/17 06:20

摘要

本文主要介绍了几种算法,比如:递归、分治、贪心、回溯等,并且通过一些lintcode上的例子来分析他们的应用。

关键字:算法、递归、分治、贪心、回溯、lintcode

引言

我们用了8周来学习了《算法分析与设计》这门课程,主要学习了递归、分治等算法,并且在lintcode上面运用了这些算法,解决一些编程问题。下面我分几个部分来总结这学期的学习成果。

一:递归与分治策略。

递归:程序直接或间接调用自身的编程技巧称为递归算法。递归算法可以反复的调用自身,把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题,然后通过设置递归出口,从而求出问题的解。因为是反复调用一个函数,所以减少了代码的量。运用递归算法时要注意递归出口与递归条件。递归算法的缺点就是递归次数过多,所以造成运行效率过低。典型问题:二叉树的遍历。

分治:分治就是分而治之,把一个大型复杂的问题,分解成一个个互相独立的、与原题形式相同的规模较小的子问题,然后递归的解决这些子问题,最后合并子问题的解,得到大问题的解。分治算法注意的就是各个子问题之间不能有公共的解,而且分的时候要注意分成的子问题的规模。典型问题:棋盘覆盖问题。

总结:递归与分治的思想很相似,都是大事化小,小事化了,所以通常把递归与分治放在一起使用。

例题:二叉树的最大深度。

题目:给定一棵二叉树,求其深度。

分析:大问题就是求一棵大二叉树的深度,我们可以分别求其左右子树的深度,然后+1;对于左右子树,我们又可以按照上面的方法求。左右子树是互相独立的,所以这就运用分治算法,而且,在求深度的过程中,可以反复调用求大二叉树深度的算法,所以运用了递归算法。

递归出口就是递归到叶子节点的时候,叶子节点没有子树。

代码:class Solution {

public:

    /**

     * @param root: The root of binary tree.

     * @return: An integer

     */

    int maxDepth(TreeNode *root) {

        // write your code here

        if (root==NULL) return 0;

        else {  int l= maxDepth(root->left)+1;//递归分治左子树

              int r= maxDepth(root->right)+1;//右子树

             if (l>=r) return l;

             else return r;}}

    };

   二:贪心算法。

贪心问题一般是在求解最优解的问题中出现。在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解,这种求解方法就是贪心算法。

从贪心算法的定义可以看出,贪心算法不是从整体上考虑问题,它所做出的选择只是在某种意义上的局部最优解,而由问题自身的特性决定了该题运用贪心算法可以得到最优解。所以贪心的缺点也是显而易见的,那就是最后不一定得到的是最优解,局部可以最优,而全部不一定是最优,有可能得到的是最优解的近似解。

贪心算法最关键的就是贪心策略的选择,也就是贪心的选择函数,选择函数可以选择哪些解可以组成最优解,选择函数通常与面函数有关。贪心的步骤一般是先分解问题,然后对各个小问题进行贪心,寻求小问题的最优解,最后得到问题的最优解。典型问题:背包问题。

总结:贪心算法就是一个缺点与优点都很明显的算法,所以能够使用贪心算法的时候一般要考虑两个方面:这个问题能不能使用贪心算法,还有就是怎样选择贪心策略。贪心算法与动态规划问题其实很相似,不过动态规划更加注重全部。

例题:背包问题。

问题:给定一个载重量为M的背包,考虑n个物品,其中第i个物品的重量 ,价值wi (1≤i≤n),要求把物品装满背包,且使背包内的物品价值最大。有两类背包问题(根据物品是否可以分割),如果物品不可以分割,称为0—1背包问题(动态规划);如果物品可以分割,则称为背包问题(贪心算法)。我们这里考虑的是后一种问题。动态规划在后面。

分析:我们首先考虑两种贪心策略:

      1:根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?

      2:根据贪心的策略,每次挑选重量最小的物品装入背包,是否能得到最优解?

综合以上两点:我们得出第三种贪心策略:

3挑选单位价值比较高的物品装入背包,得到的是否是最优解?

那么这三种问题得到的解是否有一个是最优解呢?也不一定。这就是贪心算法的不足了,贪心策略可能有好几种,但是可能这几种得到的都不是最优解,因此贪心策略选的对不对,我们需要证明,但是实际上,我们没有时间去证明。我们按第三种来做。

代码:

struct bag{

int w;//物品的重量

int v;//物品的价值

double c;//性价比

}a[1001];  

bool cmp(bag a, bag b){

return a.c >= b.c;

}

sort(a, a+n, cmp);   //sort排序

double knapsack(int n, bag a[], double c)

{

double cleft = c;//背包的剩余容量

int i = 0;

double b = 0;//获得的价值

 //当背包还能完全装入物品i

while(i<n && a[i].w<cleft)

{

cleft -= a[i].w;

b += a[i].v;

i++;

}    //装满背包的剩余空间

if (i<n) b += 1.0*a[i].v*cleft/a[i].w;

return b;

}

三:回溯算法。

回溯算法是一种系统的算法,从起点开始,一条路开始走,能进则进,不进则退,最终得到解。回溯算法是一种深度优先算法,理论上,它可以解决任何问题。回溯法从开始结点(根结点)出发,以深度优先的方式搜索整个解空间(一般为树结构空间)。这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。换句话说,这个结点不再是一个活结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。回溯算法通常的步骤是:先得到一个包含所有解得解空间,用适于搜索的方式组织解空间,利用深度优先发搜索解空间,利用限界函数避免移动到不可能产生解的子空间。而问题的解空间通常是在搜索问的解得过程中冬天产生的。有许多问题,当需要找出它的解集或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。典型问题:八皇后问题、图的着色问题。

总结:回溯算法是一种特别全面的算法,它实质上是一种穷举法,按照某种规律一直走下去,直到得到解,然后回去走另一条路,得到另一个解.....所以回溯法有“通用的解题法”之称。

例题:图的M着色问题。

问题:给定无向连通图G=(V, E)和m种不同的颜色,用这些颜色为图G的各顶点着色,每个顶点着一种颜色。是否有一种着色法使G中相邻的两个顶点有不同的颜色?

分析:这个问题的解空间是一个满M叉树,可以说是非常巨大。我们从根节点开始遍历,而且没有限界函数。

代码:#define NUM 100

int n;//图的顶点数量

int m;//可用颜色数量

int a[NUM][NUM];//图的邻接矩阵

int x[NUM];//当前的解向量

int sum ;       //已经找到的可m着色的方案数量

void BackTrack(int t )

{

int i;

//到达叶子结点,获得一个着色方案

if( t > n )

{

sum ++ ;

for(i=1; i<=n ;i++)

printf("%d ",x[i]);

printf("\n");

}

else

//搜索当前扩展结点的m个孩子

for(i=1; i<=m; i++ )

{

x[t] = i;

if( Same(t) ) BackTrack(t+1);

x[t] = 0;

}

}

bool Same(int t)

{

int i;

for(i=1; i<=n; i++ )

if( (a[t][i] == 1) && (x[i] == x[t]))

return false;

return true;

}

:分支限界算法

  分支限界法的基本思想是对有约束条件的最优化问题的所有可行解(数目有限)空间进行搜索。该算法在具体执行时,把全部可行的解空间不断分割为越来越小的子集(称为分支),并为每个子集内的解的值计算一个下界或上界(称为限界)。在每次分支后,对凡是界限超出已知可行解值那些子集不再做进一步分支。这样,解的许多子集(即搜索树上的许多结点)就可以不予考虑,从而缩小了搜索范围。这一过程一直进行到找出可行解为止,该可行解的值不大于任何子集的界限。这种算法一般可以求得最优解。典型问题:单源最短路径问题。

  总结:分支限界算法是不可回溯的一种算法,但是同时它也减去了那些不可能组成最优解的解,所以可以产生最优解。

五:动态规划

动态规划就是解决多阶段决策问题,每一个阶段都会影响到下一阶段,在可以选择的那些策略中间,选取一个最优策略,使在预定的标准下达到最好的效果。动态规划是一个重复排除计算的算法,所以是一个用空间换时间的算法。它和贪心算法有相同之处,都是争取到每一步的最优解,然后得到最终的最优解,不同之处是:动态规划更加注重全部,而贪心是注重局部。典型问题:最大字数组和、数字三角形。

总结:动态规划问题是非常常见的问题,就是因为它的普遍性,所以动态规划问题往往没有一个固定的公式,一个题的方法不一定适用于另一个问题,所以在写这些程序的时候是非常令人头痛的,因为有的问题不是很好想。我们通常用一个表来记录记录出来的结果,减少计算量。动态规划问题最重要的可能就是状态转移方程的确立了,就是从kk+1阶段的时候的规律。

例题:数字三角形。

题目:给定一个数字三角形,找到从顶部到底部的最小路径和。每一步可以移动到下面一行的相邻数字上。

比如,给出下列数字三角形:

[

     [2],

    [3,4],

   [6,5,7],

  [4,1,8,3]

]

从顶到底部的最小路径和为11 ( 2 + 3 + 5 + 1 = 11)

分析:以上图为例,最后一行不动,从倒数第二行往上开始:每一行的元素改为下一行能与之相加的两个数较小者与其相加之后的和,最后得到解。

     [2],                 [2]              [11]

    [3,4],      →       [9,10]    

   [7,6,10],    

代码:

 int minimumTotal(vector<vector<int>> &triangle) {

        // write your code here
         int x=triangle.size();
        int y=triangle[x-1].size();
        for(int i=x-2;i>=0;i--)

{
          for(int j=0;j<=i;j++)

{
     triangle[i][j]=min(triangle[i+1][j],triangle[i+1][j+1])+triangle[i][j];
              }
            }
        return triangle[0][0];
    }

 

 

原创粉丝点击