算法总结

来源:互联网 发布:cocos2dx-js教程 编辑:程序博客网 时间:2024/06/05 22:40

            前言

算法是一系列解决问题的清晰指令,是一种用系统的方法描述解决问题的策略机制。算法不仅是计算机的一个分支,他更是计算机科学的核心。不同的算法可能用不同的时间、空间或效率来完成同样的任务。算法没有优劣,我们要的是针对不同的问题提供能得出最优解的算法,找到解决问题最适合的算法,大概就是我们要学习的这节课的内容。  

递归与分治 策略

递归是一种直接或间接地调用自身的算法,

基本思路:

当面对求解问题规模为n时,通过不断调用调用自身,将他变成更小规模的问题,并通过小规模问题的解求出元为题的解。

特点:

1.有反复的执行过程(不断调用自身)

2.又跳出反复执行的条件(递归出口)

3.解决部分大问题时计算量很大(效率较低)

4.递归次数较多容易出现堆栈溢出的情况(容易出错)

5.只需少量的程序就可以描述问题的重复计算(简洁)

例题:二叉树的前序遍历

 

给出一棵二叉树 {1,#,2,3},

   1

    \

     2

    /

   3

 返回 [1,2,3].

解:

class Solution {

public:

    /*

     * @param root: A Tree

     * @return: Preorder in ArrayList which contains node values.

     */

        vector<int> l;

    vector<int> preorderTraversal(TreeNode * root) {

       

        if(root==NULL)

           { return l;}

        l.push_back(root->val);

        preorderTraversal(root->left);

        preorderTraversal(root->right);

        return l;

    }

};

问题分析:把preorderTraversal作为递归函数,先访问根节点,然后访问左节点,调用该函数,把下一个左节点作为根节点,再次访问左儿子节点,再次调用该函数,如此循环,而if条件作为递归出口,当没有根节点时,跳出循环,防止死锁,这就完成了二叉树的前序遍历。

分治是把一个复杂的问题分成多个相似的子问题,这些子问题再分解成更小的子问题,算是递归的一种特殊形式吧。

基本思路:

将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;将各个子问题的解合并为原问题的解。

特点:

1.把原问题不断分解成较小的相似子问题(分而治之)

2.原问题的规模缩小到一定的程度就可以容易地解决;

3.该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;

4.利用该问题分解出的子问题的解可以合并为该问题的解;

5.该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。

例题:求二叉树的深度

给定一个二叉树,找出其最大深度。例:

 1

 / \

2   3

   / \

  4   5

该二叉树的深度为3。

解:

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;

        int leftDepth = maxDepth(root -> left) + 1;

        int rightDepth = maxDepth(root -> right) + 1;

        return max(leftDepth, rightDepth);

    }

};

问题分析:

简单的分治,就像前面所说的,将求二叉树深度这个大问题分解成两个相似的并且独立的小问题--求左子树深度和求右子树深度。首先,分治的基础是递归,做个递归函数maxDepth,通过不断的递归调用,不断地访问左儿子节点,访问次数加一就得到了左子树的深度,右子树同理,最后左右子树比较大小(合并),那个大的就是最后的答案。

 

 

贪心算法

在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解, 通过若干次的贪心选择,最终得出整个问题的最优解

基本思路:

建立数学模型来描述问题把求解的问题分成若干个子问题。对每一子问题求解,得到子问题的局部最优解把子问题的解局部最优解合成原来解问题的一个解。

特点:

1.运用贪心策略解决的问题在程序的运行过程中无回溯过程,后面的每一步都是当前看似最佳的选择,这种选择依赖于已做出的选择,但不依赖于未做出 的选择。

2.只考虑局部,不考虑整体,不能保证最后的解是最优解。

3.算法是一种对某些求最优解问题的更简单、更迅速的设计方法

4.难想。贪心算法重在贪心策略上。

例题:最大子数组

给定一个整数数组,找到一个具有最大和的子数组,返回其最大和。

样例

给出数组[−2,2,−3,4,−1,2,1,−5,3],符合要求的子数组为[4,−1,2,1],其最大和为6

解:class Solution {

public:

    /*

     * @param nums: A list of integers

     * @return: A integer indicate the sum of max subarray

     */

    int maxSubArray(vector<int> &nums) {

        // write your code here

        int n=nums.size();

        int ans=-10000,sum=0;

        for(int i=0;i<n;i++)

        {sum+=nums[i];

        if(sum>ans)    ans=sum;

        if(sum<0)   sum=0;}

        return ans;}};

问题分析:在一个问题有多种方法解决时,贪心算法是一个不错的选择。首先制定贪心标准:通过求和公式求和,把当前和和超级小的比较,若和大于这个数,就把和赋值给他作为答案,继续做和计较,为了能让连续子数组的和最大,每一次和小于0时,都要将和重新赋值为0,重新求和。一旦sum小于0,立马归0.并且没做一次相加,就把和与ans比较,最后让ans留下最大值输出。这个题考虑的就是每一步的大与小,从每一步的大小比较中,最后求出整体的最大值。

 

回溯算法

一种选优搜索法,按选有条件向前搜索,以达到目标。

基本思路;定义一个解空间,利用适于搜索的方法组织解空间,
利用深度优先法搜索解空间,利用限界函数避免移动到不可能产生解的子空间。

特点:

1.基本行为是搜索。

2.沿一个节点往下进行,能进则进,不能进则退回,换一条路重新进行。

3.实现方法一般是递归和迭代。

回溯的模板:

void backtrack (int t)
{
if (t>n) output(x);
else
for (int i=f(n,t);i<=g(n,t);i++) {
x[t]=h(i);
if (constraint(t)&&bound(t)) backtrack(t+1);
}
}

在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题.

分支限界法

一种在表示问题解空间的树上进行系统搜索的方法。

基本思路:

按照广度有限搜索问题的解空间树,在搜索过程中,对待处理的节点结局界限函数估算目标的可能取值,从中选择是目标函数取得机制的节点有限进行广度有限搜索,从而不断的调整搜索方向,金矿找到问题的解。

特点:

1.采用广度优先和最大收益策略

2.算法设计复杂。

3.空间复杂度比较高。

    分支限界和回溯相似,但有不同。回溯法的求解目标是找出解空间树中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出在某种意义下的最优解。

2)搜索方式的不同:回溯法以深度优先的方式搜索解空间树,而分支限界法则以广度优先或以最小耗费优先的方式搜索解空间树。

 

动态规划

一种在数学和计算机学中用于求解包含重叠子问题的最优化问题的方法。

基本思路:先将问题划分为几个阶段,将问题各个阶段用不同的状态表示出来,确定决策,然后写出状态转移方程,写出规划方程,

以自底向上的方式计算出最优值,根据计算最优值的信息,构造一个最优解。

特点:

1.难于理论设计,实现简单。

2.类似分治,但分治的子问题是独立求解的,dp是允许这些子问题不独立。

3.能有效的针对问题中子问题大量重复的问题,重复问题一次求解保存。

4.使用的问题应该满足最优化原理和无后效性。

例题:数字三角形

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

样例

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

[

     [2],

    [3,4],

   [6,5,7],

  [4,1,8,3]

]

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

解:

class Solution {

public:

    /*

     * @param triangle: a list of lists of integers

     * @return: An integer, minimum path sum

     */

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

        // write your code here

        if(triangle.empty())  return 0;

     if(triangle.size()==1)

     return triangle[0][0];

        for(int i=n-2;i>=0;i--)              

       { for(int j=0;j<triangle[i].size();j++)

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

        }}

        return triangle[0][0]; }   };

问题分析:这个问题如果用递归自顶向下求解的话,每一次求解都会产生重复的求和和比较计算,这些不是新问题的计算大大增加了解决问题的时间。该问题要计算的是数字是三角形的最小值,也就是每一行的最小数相加,那么我们可以自底向上,递归地定义最优值,写出递归方程,每一步计算到上一行的时候,上一行的数都将是最优值,也就是在相加完是最小的。像这样对每一个子问题都只计算了一步,

然后将其结果填入表中,用子问题的解再去求解下一步,这样的方法就是动态规划,大大就少了递归解决该类问题时的重复。

总结

个人知识水平有限,各个算法涉及面和作用广度都很大,本文仅以不专业的角度,给出简单的分析。算法分析这门课虽然结束了,但是这对我来说,或许是一个起点,每一次写下自己的经验进行总结,写下自己的想法和感受,每当在困惑时,再回头看,总是会有更加深刻的理解和意外的收获。