算法基础——知识点总结

算法基础课程的总结,方便以后快速查阅和复习

Week 2 枚举


基本方法

列举各元素,进行猜测。

  1. 给出解空间,建立简洁的数学模型,列举出可能的情况
  2. 减小搜索的空间,避免不必要的计算
  3. 采用合适的搜索顺序

经验总结

  • 输入:scanf("%d", &cases);

  • 输出:printf("PUZZLE #%d \n", i+1);

  • 对一行元素进行枚举的一种常见方法:模拟二进制加法,处理进位
    比如,对press第一行元素的取值(0/1)进行枚举:

      while(guess() == false){         //结束的条件
          press[1][1]++;             //每次循环从从最低位加1(不考虑press[1][0])
          c = 1;                            //从最低位开始,准备进位
          while (press[1][c] > 1) {              //如果当前位超过1
              press[1][c] = 0;                        //当前位置0
              c++ ;                        //进位
              press[1][c]++;
          }
      }//这样将不断地往后增加,直到满足结束条件,普通循环嵌套不如这个方便
    
  • 很多情况下,元素太多,难以直接枚举,这时冷静下来,先想好策略,再开始敲代码

  • 每次取出两个元素,遍历所有取法:

      for (int i = 0; i < n - 1; i++)
          for (int j = i + 1; j < n; j++)
              ...
    
  • 有时定义个结构体还是很方便的:

      struct PLANT{
          int x, y;
      }
    

Week 3 递归


基本方法

  • 递归:某个函数直接或间接地调用自身。

  • 求解过程:将问题划分为许多相同性质的子问题,这些子问题的递归求解就构成了原问题的解。

    原问题为f(x),想尽办法寻找函数g(x),使得f(x) = g(f(x-1)),其他形式也行,只要找出了这样的迭代的规律,就能就能将f(x)不断分解,直到“出口”,比如已知了f(0)的值。

  • 递归的三个要点

    1. 递归式:如何将原问题划分为子问题
    2. 递归出口:递归终止的条件,允许多个出口
    3. 界函数:问题规模变化的函数,保证递归的规模向出口条件靠拢
  • 注意事项:函数的局部变量存在栈上,递归很深的时候,各个子问题的函数的局部变量可能导致栈溢出

经验总结

  • 迷宫求解问题,每一步的探测方式相同(自相似性),可以使用递归

  • 主要考虑:判断是否符合要求,如何递归,需要记录那些数据

  • 递归式可能是:\(f_{(n)}(·) = f_{(n-1)}(·) + f_{(n-2)}(·)\)等各式各样的,需要根据实际问题发散思考

  • 使用记录表(在递归计算的过程中想办法多存下一些有用的东西),减少之后的计算量
    int res[15][9][9][9][9]

  • 初始化记录表:memset(res, -1, sizeof(res))

  • 那些重复的按照某些特定规律进行操作从而解决问题的方法,先用人脑想出操作过程,然后用递归来实现

Week 4,Week 5 动态规划


有时使用递归来求解问题时,子问题可能存在大量重复计算,从而严重影响效率,除了在递归计算的过程中使用记录表存一些数据,还可以使用递推的方式:从简单的状态开始递推到要解决的问题。

基本方法

  1. 将原问题分解成子问题
  2. 确定状态:一个或多个子问题各个变量的一组取值
  3. 确定一些初始状态的值
  4. 确定状态转移方程
  • 适用的情况

    1. 最优化原理:最优解所包含的子问题的解也是最优的
    2. 无后效性:状态确定后不会受以后影响,这样才能正常递推
  • 动归的三种形式:

    1. 记忆递归型
    2. ”我为人人“递推型
    3. ”人人为我“递推型

经验总结

  • #include <algorithm>
    sort(A, A+a);//从小到大

  • 可以直接使用max(a, b)得到最大值。

  • 当选取的状态,难以进行递推时,考虑将状态增加限制条件后分类细化,即增加维度,然后在新的状态上尝试递推

  • 状态往往是很多的值(数组),使用合适的顺序不断更新状态。

  • 关键还是在于找到问题的递推方式。

Week 6, Week 7 深度优先搜索


基本方法

深度优先搜索遍历整个图的框架为:

Dfs(v){
    if(v访问过)
        return;
    do something;
    将v标记为访问过;
    对和v相邻的每个点u: Dfs(u); 
    //程序运行的时候,将会深入某一个分支直到这个分支全部访问完,才会进入下一个分支。
}
int main(){
    while(还能找到未访问的点k)
        Dfs(k);
}

经验总结

  • 图的每个结点常常有很多相关数据,可以定义个结构体来带代表每个结点
    struct Room {int r, c; Room(int rr, int cc): r(rr), c(cc) {} };

  • 保存一系列的结点信息,这时可以使用vector:

      struct Road{
          int d, L, t;
      };
      vector<vector<Road>> cityMap(100);
    
  • 遍历整个图常常很费时,面对具体问题,应该想尽办法尽早判断该结点或分支是否值得继续搜索(剪枝)

Week 8 广度优先搜索


有时不需要遍历整个图,比如希望找到符合条件的步数最少的节点,可以给节点分层,一层一层地去判断,找到了合适的就停止。

基本方法

广度优先搜索算法(使用queue)

  1. 把初始节点S0放入Open表中
  2. 如果Open表为空,则问题无解,失败退出
  3. 把Open表的第一个节点取出放入Closed表,并记该节点为n
  4. 考察节点n是否为目标节点,若是,则得到问题的解,成功退出
  5. 若节点n不可扩展,则转到第2步
  6. 扩展节点n,将其不在Close表和Open表中的子节点(判重)放入Open表的尾部,并为每一个子节点设置指向父节点的指针或记录节点的层次,然后转到第2步

经验总结

  • 使用队列

    • #include <queue>
    • queue<Step> q
    • q.push(Step(N, 0))
    • Step s = q.front()
    • q.pop
  • 判重常常需要根据具体情况,建一个标志位序列。根据时间空间的权衡设计合适的标志位序列。

  • 给定排列求序号

    数出有多少种排列比给定的排列小,比如3241:

    • 1, 2放在第一位,有2*3! = 12种
    • 3在第一位,1放在第2位,有 2! = 2种
    • 32放在前面,第三位可以放1,有 1种,然后就没有其他的了
  • 给定序号n求排列

    • 第一位假定是1,共有3!种,没有到达9,所以第一位至少是2
    • 第一位是2,一共能数到 3!+3!号,>= 9,所以第一位是2 第二位是1,21??,一共能数到 3!+2! = 8 不到9,所以第二位至少 是 3
    • 第二位是3,23??,一共能数到 3!+2!+2! >= 9,因此第二位是3
    • 第三位是1,一共能数到3!+2!+1 = 9,所以第三位是1,第四位是 4
    • 答案:2314
  • 八数码问题,从奇排列不能转化成偶排列或相反。很多问题如果可以人为的找到一些规律,利用这些规律常常能简化搜索过程。

  • bitset的使用

    • #include <bitset>
    • bitset<362880> Flags;
    • Flags.reset();
    • if(Flags[n]) continue;
    • Flags.set(n, true);
  • 求和

    #include <numeric>
    int A[16] = {0}; int sum = accumulate(A, A + 16, 0);//最后那个0是指求和的初值

  • 赋值,将内容拷贝到想要放置的内存位置

    #include <string.h>
    memcpy(A, AA, sizeof(A));

  • 可以直接使用一个int型的整数来模拟二进制的序列,比如Int x = 0xffff;操作的时候使用为运算符

Week 9 二分与贪心算法


基本方法

  • 二分查找

对已经排序好的序列,首先检查序列的中间元素:

  • 如果大于这个元素,在当前序列的后半部分继续查找
  • 如果小于这个元素,在当前序列的前半部分继续查找
  • 知道找到相同的元素,或者所查找的序列范围为空为止
  • 贪心算法

    问题求解时,总是做出在当前看来是最好的选择(不保证全局最优)
    很多看上去很复杂的问题可以设计成贪心算法能解决的形式,设计贪心策略判断是否满足“无后效性”

经验总结

  • 对自定义的对象排序

      bool operator < (const Box &a, const Box &b){
          return a.density < b.density;
      }
      sort(boxes, boxes + n);
    
      bool comp(const int &a,const int &b){
          return a>b;
      }
      sort(v.begin(),v.end(),comp);
    
  • 简化问题:最小值问题-->判定性问题

    • 求某个最小的情况,可以从i = 0开始,判断i = x是否能满足条件,但是这样效率太低,为了提高效率,考虑采用二分法来查找
    • 经典思想:二分+判定
  • 设计贪心策略常常可以对序列先进行排序,然后再依次进行操作


作者:[rubbninja](http://www.cnblogs.com/rubbninja/) 出处:[http://www.cnblogs.com/rubbninja/](http://www.cnblogs.com/rubbninja/) 关于作者:目前主要研究领域为机器学习与无线定位技术,欢迎讨论与指正!
posted @ 2016-01-06 22:19  rubbninja  阅读(2064)  评论(0编辑  收藏  举报