• 算法基础——知识点总结


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

    Week 2 枚举


    基本方法

    列举各元素,进行猜测。

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

    经验总结

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

    • 输出:printf("PUZZLE #%d ", 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/) 关于作者:目前主要研究领域为机器学习与无线定位技术,欢迎讨论与指正!
  • 相关阅读:
    (四)STL中的算法
    (三)openssl库实现对称和非对称加密
    (十一)etcd项目
    (十二)插件之dlopen/dlsym/dlclose 加载动态链接库
    (十一)访问权限关键字publi/private/protected
    RESTful架构
    (零)TCP/IP详解综述
    (二)辗转相除法求最大公约数
    (一)简单的TcpServer
    SpringMVC异常处理
  • 原文地址:https://www.cnblogs.com/rubbninja/p/5107625.html
Copyright © 2020-2023  润新知