算法分析与设计课程总结

    算法设计的整个过程,可以包含对问题需求的说明、数学模型的拟制、算法的详细设计、算法的正确性验证、算法的实现、算法分析、程序测试和文档资料的编制。

    算法设计的先驱者唐纳德。E。克努特对算法的特征做了如下描述:

    1、有穷性

    算法在执行有限步之后必须终止。

    2、确定性

    算法的每一步骤必须有确切的定义。

    3、输入

    一个算法有0个或多个输入,作为算法开始执行前的初始值,或初始状态。

    4、输出项

    一个算法有一个或多个输出,以反映对输入数据加工后的结果。

    5、可行性

    在有限时间内完成计算过程。

    算法大致分为基本算法、数据结构算法、数论与代数算法、计算几何算法、图论的算法、动态规划以及数值分析、加密算法、排序算法、检索算法、随机化算法和并行算法。算法大致分为以下三类:

    (1)有限的确定性的算法。这类算法在有限的一段时间内终止。

    (2)有限的、非确定性的算法。这类算法在有限的一段时间内终止。

    (3)无限的算法。只那些由于没有定义终止定义条件,或定义的条件无法由输入的数据满足而不终止的算法。

经典的算法有很多,这里主要列举一下算法:

1、穷举搜索法。

    穷举搜索法是对可能解的众多候选解按某种顺序进行逐一枚举和检验,并从中找出那些符合要求的候选解作为问题的解。

    穷举法的特点是算法简单,但运行时间长。有些问题所列举出来的情况数目会大得惊人,就是用高速计算机运行,其等待时间也是使人无法忍受的。我们在运用穷举法解决问题时,应尽可能将明显不符合条件的情况排除在外,以尽快去的问题的解。

2、迭代法。

    迭代法是应用于求方程或方程组近似根的常用的一种算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤操作:

    (1)选一个方程的近似根,赋给变量x0。

    (2)将x0的值保存于变量x1,然后计算g(x1),将结果存于变量x0.

    (3)将x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。

若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就是方程的根。

3、递推算法。

    递推算法是利用问题本身所具有的的一种递推关系求问题解的一种方法。他把问题分成若干步,找出相邻几步的关系,从而达到目的。

4、递归算法。

    递归算法是一种直接或间接的调用自身的算法。在计算机编写程序中,递归算法对解决一大类问题十分有效,他往往使算法简单而且易于理解。

    能采用递归算法的问题一般有以下特征:为求解规模为n的问题,设法将他分解成规模较小的问题,然后从这些规模较小的问题的解构造出大问题的解,并且这些规模较小的问题也能采用相应的分解和综合方法,分解成规模更小的问题,从这些规模更小的问题的解构造出规模较大的问题的解。特别的,当规模n=0或1时,直接的出解。

    递归算法解决问题的特点如下:

    (1)递归就是在过程或函数里调用自身。

    (2)在使用递归策略时,必须有一个明确的递归结束条件,成为递归出口。

    (3)递归算法解题通常显得简洁,但递归法解题的运行效率较低。

    (4)在递归调用的过程中系统为每层的返回点、局部变量等开辟堆栈来存储。递归次数过多容易造成队战満溢等。

5、分治算法。

    分治算法是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后问题可以简单直接求解,原问题的解即子问题的解的合并。

6、贪心算法。

    贪心算法也称贪婪算法。它对问题求解时,总是作出在当前看来最好的选择。也就是说,它不从整体最优上考虑,所得出的仅是在某种意义上的局部最优解。贪心算法不是对所有问题都能得出整体最优解,但对范围相对较广泛的许多问题它能产生整体最优解或整体最优解的近似解。

贪心算法的基本思路如下:

    (1)建立数学模型描述问题。

    (2)把求解的问题分为若干个子问题。

   (3)对每一子问题求解,得到子问题的局部最优解。

    (4)把子问题的局部最优解合成原来解问题的一个解。

7、动态规划。

    动态规划是一种在数学和计算机科学中应用求解包含叠子问题的最优化问题的方法。基本思想是,将原问题分解为相似的子问题,在求解过程中通过子问题的解求出原问题得解。动态规划的思想是多种算法的基础,被广泛应用与计算机科学和工程领域。

8、回溯算法。

    回溯算法是一种优选搜索法,安优选条件向前搜索,以达到目的。探索到某一步时,发现原先的选择并不优或达不到目标。就退回一步重新选择,这种走不通就退回在走的技术称为回溯法,而满足回溯条件的某一个状态的点称为“回溯点”。

    回溯方法解决问题的过程是先选择某一可能的线索进行试探,每一步试探都有多种方法,每一方式都一一试探,如有问题就返回纠正,直到得出全部符合条件的答案或问题无解为止。回溯法的本质是深度优先,所以算法中都需要建立一个堆栈,用来保存搜索路径。一旦产生的部分解序列不符合要求,就要从堆栈中找出回溯的前一个位置,继续试探。

9、分支限界法。

    分支限界法是一种在表示问题解空间的树上进行系统搜索的方法,回溯法使用了深度优先的策略,而分支限界法一般采用广度优先的策略,同时还采用最大收益策略来控制搜索的分支。

    分支限界法的基本思想是对包含具有约束条件的最优化问题的所有可行的解空间进行搜索。该算法在具体执行时,把全部可行解空间不断分割成越来越小的子集,并为每一个子集内的姐计算一个下界。每次分支后,对所有界限超出已知可行解的那些子在做进一步分支,解的许多子集就可以不用考虑了。从而缩小了搜索范围。这一过程一直进行到找出可行解的值不大于任何子集的界限为止。因此,这种算法一般可以求得最优解。

递归与分治策略

   递归一次用于表示直接或间接的调用自身的算法。特别的,用函数自身给出定义的函数被称为递归函数 。

递归函数举例:

1、斐波那契函数:

int fibonacci()
{  
if(n<=2) return 1;  
return fibonacci(n-1)+fibonacci(n-2); 
}

    从这个函数可以看出,递归函数具有方便简洁的特点,但是其在运行过程中效率较低。

一般用以下方法解fibonacci 函数:

int fibonacci()  
{  
if(n<=2)return 1;  
else{  
int a=1,b=1,f;  
for(int i=3;i<=n;i++)  
{f=a+b;  
a=b;b=f;  
}  
return f ;  
} 
2、二叉树的遍历:

(1)前序遍历:

给出一棵二叉树,返回其节点值的前序遍历。

样例

给出一棵二叉树 {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>v;
    vector<int> preorderTraversal(TreeNode * root) {
        // write your code here
        if(root==NULL)
        return v;
        else{
           v.push_back(root->val);
            preorderTraversal(root->left);
            preorderTraversal(root->right);
        }
        return v;
    }
};
(2)中序遍历:

代码:

class Solution {
public:
    /*
     * @param root: A Tree
     * @return: Inorder in ArrayList which contains node values.
     */
     vector<int>v;
    vector<int> inorderTraversal(TreeNode * root) {
        // write your code here
        if(root==NULL)return v;
        else{
            inorderTraversal(root->left);
            v.push_back(root->val);
            inorderTraversal(root->right);
        }
        return v;
    }
};
(3)后序遍历:

class Solution {
public:
    /*
     * @param root: A Tree
     * @return: Postorder in ArrayList which contains node values.
     */
     vector<int>v;
    vector<int> postorderTraversal(TreeNode * root) {
        // write your code here
        if(root==NULL)return v;
        else{
            postorderTraversal(root->left);
            postorderTraversal(root->right);
            v.push_back(root->val);
        }
        return v;
    }
};
3、 把排序数组转换为高度最小的二叉搜索树
给一个排序数组(从小到大),将其转换为一棵高度最小的排序二叉树。
样例

给出数组 [1,2,3,4,5,6,7], 返回

     4
   /   \
  2     6
 / \    / \
1   3  5   7
代码:
class Solution {
public:
    /*
     * @param A: an integer array
     * @return: A tree node
     */
    TreeNode *dfs(vector<int>&A,int a,int b)
    {
        if(a>b)return NULL;
        int c=(a+b)/2;
        TreeNode *root=new TreeNode(A[c]);
        root->left=dfs(A,a,c-1);
        root->right=dfs(A,c+1,b);
        return root;
    }
    TreeNode * sortedArrayToBST(vector<int> &A) {
        // write your code here
        if(A.size()==0)return NULL;
       int a=0,b=A.size()-1;
        return dfs(A,a,b);
    }
};
4、 二叉树的最大深度

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

二叉树的深度为根节点到最远叶子节点的距离。

样例

给出一棵如下的二叉树:

  1
 / \ 
2   3
   / \
  4   5

这个二叉树的最大深度为3

代码:
class Solution {
public:
    /**
     * @param root: The root of binary tree.
     * @return: An integer
     */
     int maxx=-1;
     void dfs(TreeNode *root,int dep)
     {
         if(root->left==NULL&&root->right==NULL)
         {
             if(dep>maxx)maxx=dep;
             return;
         }
         if(root->left!=NULL)
         {
             dfs(root->left,dep+1);
         }
         if(root->right!=NULL)
         dfs(root->right,dep+1);
     }
    int maxDepth(TreeNode *root) {
        // write your code here
        if(root==NULL)return 0;
        dfs(root,1);
        return maxx;
        }
};
    分治策略是对于一个规模为n的问题,若这个问题可以容易地解决则直接解决,否则将其分解为k个相互独立的小规模的问题,递归的节小规模问题,将他们的解合并得到原问题的解。

步骤:

(1)分解:将原问题分解为若干相互独立与原问题规模相同的小规模问题。

(2)解决:若小规模问题易于解决则直接解决,否则递归解决。

(3)合并:将子问题的解合并成原问题的解。

模板:

DC(p)
{
if(|p|<=n0)return abhoc(p);
divide p into smaller substances p1,p2~pk;
for(i=1;i<=k;i++)
yi=DC(pi);
return merge(y1,y2,~yk);
}
5、 二叉树的最大深度

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

二叉树的深度为根节点到最远叶子节点的距离

样例

给出一棵如下的二叉树:

  1
 / \ 
2   3
   / \
  4   5

这个二叉树的最大深度为3.

代码:

class Solution {
public:
    /**
     * @param root: The root of binary tree.
     * @return: An integer
     */
     int maxx=-1;
     void dfs(TreeNode *root,int dep)
     {
         if(root->left==NULL&&root->right==NULL)
         {
             if(dep>maxx)maxx=dep;
             return;
         }
         if(root->left!=NULL)
         {
             dfs(root->left,dep+1);
         }
         if(root->right!=NULL)
         dfs(root->right,dep+1);
     }
    int maxDepth(TreeNode *root) {
        // write your code here
        if(root==NULL)return 0;
        dfs(root,1);
        return maxx;
        }
};

贪心算法
使用贪心算法应考虑以下几方面问题:
(1)候选集合A:为了构造问题的解决方案,有一个候选集合A作为问题的可能解,问题的最终节均取决于问题的可能解。
(2)解集合S:随着贪心选择的进行,集合S不断扩展,知道构成满足问题的解。
(3)解决函数solution:检查解集合S是否构成问题的完整解。
(4)选择函数select;既贪心策略,这是贪心算法的关键,他指出哪个候选对象最有望构成问题的解,选择函数通常和目标函数有关。
(5)可行函数feasible:检查解集合中加入一个候选解的可行性。
模板:
Greedy(A)
{
s={};
while(not solution(s))
{
x=select(A);
if(feasible(s,x)
s=s+{x};
A=A-{x};
}
return s;
}
1、主元素:
给定一个整型数组,找出主元素,它在数组中的出现次数严格大于数组元素个数的二分之一。
样例

给出数组[1,1,1,1,2,2,2],返回 1

代码:
class Solution {
public:
    /*
     * @param nums: a list of integers
     * @return: find a  majority number
     */
    int majorityNumber(vector<int> &nums) {
        // write your code here
        int cnt=0;
        int result=0;
        for(int i=0;i<nums.size();i++)
        {
            if(cnt==0)
            {
                result=nums[i];
                cnt++;
            }
            else if(result!=nums[i])
            cnt--;
            else
            cnt++;
        }
        return result;
    }
};
2、落单的数
给出2*n + 1 个的数字,除其中一个数字之外其他每个数字均出现两次,找到这个数字。
样例

给出 [1,2,2,1,3,4,3],返回 4

代码:
class Solution {
public:
    /*
     * @param A: An integer array
     * @return: An integer
     */
    int singleNumber(vector<int> &A) {
        // write your code here
        int ans=0;
        for(int i=0;i<A.size();++i)
        ans ^=A[i];
        return ans;
    }
};
3、 买卖股票的最佳时机
假设有一个数组,它的第i个元素是一支给定的股票在第i天的价格。 如果你最多只允许完成一次交易(例如,一次买卖股票),设计一个算法来找出最大利润。

样例

给出一个数组样例 [3,2,3,1,2], 返回 1 

代码:
class Solution {
public:
    /*
     * @param prices: Given an integer array
     * @return: Maximum profit
     */
    int maxProfit(vector<int> &prices) {
        // write your code here
        int res=0;
        if(prices.size()<2)
        return res;
        int lowest=prices[0];
        for(int i=1;i<prices.size();i++)
        {
            int cur=prices[i];
            res=max(res,cur-lowest);
            lowest=min(lowest,cur);
        }
        return res;
    }
};
4、硬币排线

有 n 个硬币排成一条线。两个参赛者轮流从右边依次拿走 1 或 2 个硬币,直到没有硬币为止。拿到最后一枚硬币的人获胜。

样例

n = 1, 返回 true.

n = 2, 返回 true.

n = 3, 返回 false.

n = 4, 返回 true.

n = 5, 返回 true.

代码:
class Solution {
public:
    /*
     * @param n: An integer
     * @return: A boolean which equals to true if the first player will win
     */
    bool firstWillWin(int n) {
        // write your code here
        return n%3!=0;
    }
};
5、分糖果:

有 N 个小孩站成一列。每个小孩有一个评级。

按照以下要求,给小孩分糖果:

  • 每个小孩至少得到一颗糖果。

  • 评级越高的小孩可以比他相邻的两个小孩得到更多的糖果。

需最少准备多少糖果?

样例

给定评级 = [1, 2], 返回 3.

给定评级 = [1, 1, 1], 返回 3.

给定评级 = [1, 2, 2], 返回 4. ([1,2,1]).

代码:
class Solution {
public:
    /*
     * @param ratings: Children's ratings
     * @return: the minimum candies you must give
     */
    int candy(vector<int> &ratings) {
        // write your code here
        vector<int>dp(ratings.size(),1);
        if(ratings.size()==1)return 1;
        for(int i=0;i<ratings.size()-1;i++)
        if(ratings[i]<ratings[i+1])
        dp[i+1]=max(dp[i+1],dp[i]+1);
        for(int i=ratings.size()-1;i>=1;i--)
        if(ratings[i]<ratings[i-1])
        dp[i-1]=max(dp[i-1],dp[i]+1);
        return  accumulate(dp.begin(),dp.end(),0);
    }
};
回溯算法
    有时问题要从一个集合的所有子集搜索一个集合,,作为问题的解。或者从一个集合的排列中搜索一个排列,作为问题的解。回溯算法可以很方便的遍历一个集合的所有子集或者所有排列。
    回溯算法搜索子集树的伪代码:
void backtrack(int t)
{
if(t>n)update(x);
else
 for(int i=0;i<=1;i++)
{
x[t]=i;
if(constraint(t)&&bound(t))backtrack(t+1);
}
}
    回溯算法搜索排列树的伪代码:
void backtrack(int t)
{
if(t>n) update(x);
else
  for(int i=t;i<=n;i++)
{
swap(x[t],x[i]);
if(constraint(t)&&bound(t))backtrack(t+1);
swap(x[t],x[i]);
}
}

1、 数字组合 II
给出一组候选数字(C)和目标数字(T),找出C中所有的组合,使组合中数字的和为T。C中每个数字在每个组合中只能使用一次。
样例

给出一个例子,候选数字集合为[10,1,6,7,2,1,5] 和目标数字 8  ,

解集为:[[1,7],[1,2,5],[2,6],[1,1,6]]

代码:
class Solution {
public:
    /*
     * @param num: Given the candidate numbers
     * @param target: Given the target number
     * @return: All the combinations that sum to target
     */
    vector<vector<int>> combinationSum2(vector<int> &num, int target) {
        // write your code here
        set<vector<int>>r;
        vector<int>cur;
        sort(num.begin(),num.end());
        combination(cur,num,0,0,target,r);
        vector<vector<int>>ret;
        copy(r.begin(),r.end(),back_inserter(ret));
        return ret;
    }
    void combination(vector<int>cur,vector<int>&num,int index,int curSum,int target,set<vector<int>>&ret)
    {
        if(curSum>target)
        return;
        if(curSum==target)
        {
            ret.insert(cur);
            return;
        }
        if(index==num.size())
        return;
        combination(cur,num,index+1,curSum,target,ret);
        cur.push_back(num[index]);
        combination(cur,num,index+1,curSum+num[index],target,ret);
    }
};
2、n皇后问题

n皇后问题是将n个皇后放置在n*n的棋盘上,皇后彼此之间不能相互攻击。《不同行,不同列,不同对角线》
给定一个整数n,返回所有不同的n皇后问题的解决方案。
每个解决方案包含一个明确的n皇后放置布局,其中“Q”和“.”分别表示一个女王和一个空位置。

样例

对于4皇后问题存在两种解决的方案:

[

    [".Q..", // Solution 1

     "...Q",

     "Q...",

     "..Q."],

    ["..Q.", // Solution 2

     "Q...",

     "...Q",

     ".Q.."]

]

代码:
class Solution {  
    vector<string> base;  
    vector<vector<string> > ret;  
      
    void search(vector<int> &c,int n,int cur){  
        if(n==cur){  
            ret.push_back(base);  
            return;  
        }  
        for(int i=0;i<n;++i){       //cur表示当前行,i表示要选择的列  
            if(isValid(c,cur,i)){   //检验当前列是否合法  
                c[cur]=i;                 
                string s(n,'.');     
                s[i]='Q';           //当前位置摆放皇后,那么同一行其他位置全部是'.'  
                base.push_back(s);    
                search(c,n,cur+1);  //搜寻下一行  
                base.pop_back();    //别忘了弹出,base用于存放下一组可能位置  
            }  
        }  
    }  
    bool isValid(vector<int> &c,int row,int col){  
        for(int i=0;i<row;++i)  
            if(c[i]==col||row-col==i-c[i]||row+col==i+c[i]) //分别检查纵向,主对角线,副对角线上是否有其他皇后  
                return false;                               //因为是一行一行摆放,所以不用检查同一行  
        return true;          
    }  
public:  
      
    vector<vector<string> > solveNQueens(int n) {  
        if(n<=0)  
            return ret;  
        vector<int> c(n);  
        search(c,n,0);  
        return ret;  
    }  
};  
分支限界法
在分支限界法中每个活节点只有一次机会成为拓展结点。分支限界法常有两种形式:
(1)FIFO:按照先进先出原则选择下一个活节点作为扩展结点,即从活节点表中取出节点的顺序于加入节点的顺序子相同,
(2)最小消耗或最大收益分支限界法:在这种情况下每个结点都有各自的消耗或收益。如果要查找一个具有最小消耗的解,那么要选择的下一个活节点就应是最小消耗的活节点,做大收益的拓展节点亦是这样选择。
若我们把搜索的过程看做对一棵树的遍历,那么剪枝就是将树上的死结点和不能达到最优解的枝条减掉,以减少搜索的时间,剪枝的原则:
1、正确性;2、准确性;3、高效性。
1、装载问题:
有一批共n个集装箱要装上2艘载重量分别为c1,c2的轮船,其中集装箱i的重量为wi,且要求确定是否有一个合理的装载方案可将这n个集装箱装上这2艘轮船。
可证明,采用如下策略可以得到一个最优装载方案:先尽可能的将第一艘船装满,其次将剩余的集装箱装到第二艘船上。
代码:
template<class Type>
void EnQueue(Queue<Type> &Q, Type wt, Type &bestw, int i, int n) 
{
    if(i == n)     //可行叶结点
    {     
        if(wt>bestw) bestw = wt ;
    }
    else Q.Add(wt) ; //非叶结点
}

//装载问题先尽量将第一艘船装满
//队列式分支限界法,返回最优载重量
template<class Type>
Type MaxLoading(Type w[],Type c,int n)
{
    //初始化数据
    Queue<Type> Q;    //保存活节点的队列
    Q.Add(-1);    //-1的标志是标识分层
    int i=1;    //i表示当前扩展节点所在的层数
    Type Ew=0;    //Ew表示当前扩展节点的重量
    Type bestw=0;    //bestw表示当前最优载重量
    
    //搜索子集空间树
    while(true)
    {
        if(Ew+w[i]<=c)    //检查左儿子
            EnQueue(Q,Ew+w[i],bestw,i,n);    //将左儿子添加到队列
        
        //将右儿子添加到队列 即表示不将当前货物装载在第一艘船
        EnQueue(Q,Ew,bestw,i,n);
        Q.Delete(Ew);    //取下一个节点为扩展节点并将重量保存在Ew
        if(Ew==-1)    //检查是否到了同层结束
        {
            if(Q.IsEmpty()) return bestw;    //遍历完毕,返回最优值
            Q.Add(-1);    //添加分层标志
            Q.Delete(Ew);    //删除分层标志,进入下一层
            i++;
        }
    }
}
2、单源最短路径问题;
给定一个带权有向图G=(V,E),其中每条边的权是非负数。给定V中的一个顶点,成为源。现在要计算从源到所有其他个顶点的最短路径长度,这里路径长度指的是各边权之和。这个问题通常被称作单源最短路径问题。
代码:
#include <iostream>  
#include<queue>  
using namespace std;  
  
typedef struct ArcCell{  
    int adj;//保存权值  
    int info;//存储最短路径长度  
}ArcCell,AdjMaxtrix[100][100];  
  
typedef struct{  
    int data;  
    int length;  
}VerType;  
  
typedef struct{  
    VerType vexs[100];//顶点向量  
    AdjMaxtrix arcs;  
    int vexnum;//顶点数  
    int arcnum;//弧数  
}Graph;  
  
Graph G;  
queue<int> q;  
  
void CreateGraph()  
{  
    int m,n,t;  
    printf("输入顶点数和弧数:");  
    scanf("%d%d",&G.vexnum,&G.arcnum);  
    printf("输入顶点:");  
    for(int i=1;i<=G.vexnum;i++)  
    {  
        scanf("%d",&G.vexs[i].data);  
        G.vexs[i].length=10000;  
    }  
  
    for(int i=1;i<=G.vexnum;i++)  
        for(int j=1;j<=G.vexnum;j++)  
        {  
            G.arcs[i][j].adj=0;  
        }  
  
    printf("输入弧及权重:\n");  
    for(int i=1;i<=G.arcnum;i++)  
        {  
            scanf("%d%d%d",&m,&n,&t);  
            G.arcs[m][n].adj=1;  
            G.arcs[m][n].info=t;  
        }  
  
}  
  
int NextAdj(int v,int w)  
{  
    for(int i=w+1;i<=G.vexnum;i++)  
        if(G.arcs[v][i].adj)  
            return i;  
    return 0;//not found;  
}  
  
void ShortestPaths(int v)  
{  
    int k=0;//从首个节点开始访问  
    int t;  
    G.vexs[v].length=0;  
    q.push(G.vexs[v].data);  
    while(!q.empty())  
    {  
        t=q.front();  
        k=NextAdj(t,k);  
        while(k!=0)  
        {  
            if(G.vexs[t].length+G.arcs[t][k].info<=G.vexs[k].length)//减枝操作  
            {  
                G.vexs[k].length=G.vexs[t].length+G.arcs[t][k].info;  
                q.push(G.vexs[k].data);  
            }  
            k=NextAdj(t,k);  
        }  
        q.pop();  
    }  
}  
  
void Print()  
{  
    for(int i=1;i<=G.vexnum;i++)  
        printf("%d------%d\n",G.vexs[i].data,G.vexs[i].length);  
}  
  
int main()  
{  
    CreateGraph();  
    ShortestPaths(1);  
    Print();  
    return 0;  
}  
动态规划
    动态规划算法是从暴力搜索算法优化过来的,难点在于 从实际问题中抽象出动态规划表dp,dp一般是一个数组,可能是一维的也可能是二维的,也可能是其他的数据结构。
    动态规划的关键点:
1、最优化原理,也就是最有子结构性质。这指的是一个最优化策略具有这样的性质,无论过去状态和决策如何,对前面的决策所形成的状态而言,余下的决策必须构成最优策略,简单来说就是一个最优化策略的子策略总是最优的,如果一个问题满足最优化原理,就称其有最优子结构性质。
2、无后效性,指的是某个状态下的决策的收益,只与状态和决策相关,与达到该状态的方式无关。
3、子问题的重叠性,动态规划将原来指数级的暴力搜索算法改进到了具有多项式时间复杂度的算法,其中的关键在于解决了荣誉,重复计算的问题,这是动态规划算法的根本目的。
4、总体来说,动态规划算法就是一系列以空间换取时间的算法
例题:
1、台阶问题:
有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。
代码:
int fun(int n){  
    if (n==1||n==2)  
        return n;  
    /*判断n-1的状态有没有被计算过*/  
    if (!dp[n-1])  
        dp[n-1] = fun(n-1);  
    if(!dp[n-2])  
        dp[n-2]=fun(n-2);  
    return dp[n-1]+dp[n-2];  
}  
2、矩阵:
给定一个矩阵m,从左上角开始每次只能向右走或者向下走,最后达到右下角的位置,路径中所有数字累加起来就是路径和,返回所有路径的最小路径和,如果给定的m如下,那么路径1,3,1,0,6,1,0就是最小路径和,返回12.
1 3 5 9
8 1 3 4
5 0 6 1
8 8 4 0
代码:
int dp[4][4] = {};  
int main(){  
    int arr[4][4] = {1,3,5,9,8,1,3,4,5,0,6,1,8,8,4,0};  
    //cout << fun(arr,4,4) << endl;  
     const int oo = ~0U>>2;  
     for (int i = 0;i<4;i++)  
         for (int j = 0; j < 4;j++)  
             dp[i][j] = oo;  
     //dp[0][0] = oo;  
     for (int i = 0; i < 4;i++){  
         for (int j = 0; j<4;j++){  
             if (dp[i][j] == oo){  
                if (i==0&&j==0)  
                    dp[i][j] = arr[i][j];  
                else if (i==0&&j!=0)  
                    dp[i][j] = arr[i][j] + dp[i][j-1];  
                else if(i!=0&&j==0)  
                    dp[i][j] = arr[i][j] + dp[i-1][j];  
                else{  
                    dp[i][j] = arr[i][j]+min(dp[i-1][j],dp[i][j-1]);  
                }  
             }  
         }  
     }  
    // cout << dp[3][3] << endl;  
     for (int i = 0; i< 4;i++){  
         for (int j = 0; j<4;j++){  
            cout << dp[i][j] << "  ";  
         }  
         cout << endl;  
     }  
}  
3、爬楼梯:
假设你正在爬楼梯,需要n步你才能到达顶部。但每次你只能爬一步或者两步,你能有多少种不同的方法爬到楼顶部?
样例 
比如n=3,1+1+1=1+2=2+1=3,共有3中不同的方法
返回 3
代码:
class Solution {
public:
    /**
     * @param n: An integer
     * @return: An integer
     */
    int climbStairs(int n) {
        // write your code here
        int res;
        if(n<2)return 1;
        int first=1;
        int second=1;
        while(n>1){
            res=first+second;
            first=second;
            second=res;
            n--;
        }
        return res;
    }
};





  • 3
    点赞
  • 7
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

elvira12

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值