• leetcode 传递信息 题目总结(动态规划,dfs,bfs)


    传递信息

    深度优先搜索

    思想:

    图的深搜;

    如果深搜是一个人,那么他的性格一定倔得像头牛!他从一点出发去旅游,只朝着一个方向走,除非路断了,他绝不改变方向!除非四个方向全都不通或遇到终点,他绝不后退一步!因此,他的姐姐广搜总是嘲笑他,说他是个一根筋、不撞南墙不回头的家伙。

    深搜很讨厌他姐姐的嘲笑,但又不想跟自己的亲姐姐闹矛盾,于是他决定给姐姐讲述自己旅途中的经历,来改善姐姐对他的看法。他成功了,而且只讲了一次。从那以后他姐姐不仅再没有嘲笑过他,而且连看他的眼神都充满了赞赏。他以为是自己路上的各种英勇征服了姐姐,但他不知道,其实另有原因……

    深搜是这样跟姐姐讲的:关于旅行呢,我并不把目的地的风光放在第一位,而是更注重于沿路的风景,所以我不会去追求最短路,而是把所有能通向终点的路都走一遍。可是我并不知道往哪走能到达目的地,于是我只能每到一个地方,就向当地的人请教各个方向的道路情况。

    为了避免重复向别人问同一个方向,我就给自己规定:先问北,如果有路,那就往北走,到达下一个地方的时候就在执行此规定,如果往北不通,我就再问西,其次是南、东,要是这四个方向都不通或者抵达了终点,那我回到上一个地方,继续探索其他没去过的方向。我还要求自己要记住那些帮过他的人,但是那些给我帮倒忙的、让我白费力气的人,要忘记他们。有了这些规定之后,我就可以大胆的往前走了,既不用担心到不了不目的地,也不用担心重复走以前的路。哈哈哈……

    原文链接:https://www.cnblogs.com/ShallByeBye/p/11769071.html

    这文字写的是相当的生动形象!

    深搜的优缺点:

    优点

    • 能找出所有解决方案
    • 优先搜索一棵子树,然后是另一棵,所以和广搜对比,有着内存需要相对较少的优点

    缺点

    • 要多次遍历,搜索所有可能路径,标识做了之后还要取消(因为有可能会重复访问,所以需要对结点做一些标记)。
    • 在深度很大的情况下效率不高
    int numWays(int n, vector<vector<int>>& relation, int k) {
        vector<vector<int>> edges(n);
        for(auto edge : relation) {
            int src = edge[0];
            int dst = edge[1];
            edges[src].push_back(dst);
        }
        int ans = 0;
      //深度优先搜索
        function<void(int,int)> dfs = [&](int index, int steps) {
            if(steps==k) {
                if(index==n-1) {
                    ans++;
                }
                return; //剪枝,停止继续搜索,最大深度为k
            }
            for(auto dst : edges[index]) {
                dfs(dst,steps+1); //先根搜索,因为先判断的是index的值,然后在导入该值对应的dst结点。
            }
        };
        dfs(0,0);
        return ans;
    }
    

    广度优先搜索

    思想:

    图的广搜;

    广搜的优缺点:

    优点

    • 对于解决最短或最少问题特别有效,而且寻找深度小
    • 每个结点只访问一遍,结点总是以最短路径被访问,所以第二次路径确定不会比第一次短

    缺点

    • 内存耗费量大(需要开大量的数组单元用来存储状态)
    int numWays(int n, vector<vector<int>>& relation, int k) {
      vector<vector<int>> edges(n);
        for (auto edge : relation)
        {
            int src = edge[0];
            int dst = edge[1];
            edges[src].push_back(dst);
        }
        int ans = 0;
        int steps = 0;
        function<void(void)> bfs = [&]()
        {
            queue<int> que;
            que.push(0);
            while (!que.empty() && steps < k)
            {
                steps++;
                int sz = que.size();
                while (sz--)
                {
                    int q = que.front();
                    que.pop();
                    for (auto dst : edges[q])
                    {
                        que.push(dst);
                    }
                }
            }
            if (steps == k)
            {
                while (!que.empty())
                {
                    if (que.front() == n - 1)
                        ans++;
                    que.pop();
                }
            }
        };
        bfs();
        return ans;
    }
    

    动态规划

    优缺点:相比于穷举法 - (以最短路径为例)

    优点

    • 减少重复计算,时间复杂度相对于其他算法,优势很明显
    • 计算中得到很多有用的中间过程
      • 不仅得到出发点到终点的最短路径,还得到了中间点到终点的最短路径

    缺点:

    • 消耗空间大,当所给出范围很大时,堆栈中很可能并不能满足所需要的空间大小,往往对其的解决办法是降低数组维度,或者去除一些不必要的状态数等。

    常见问题:

    背包问题、生产经营问题、资金管理问题、资源分配问题、最短路径问题和复杂系统可靠性问题

    • 动态规划主要用于求解以时间划分阶段的动态过程的优化问题

      • 一些与时间无关的静态规划(如线性规划、非线性规划):只要人为地引进时间因素,把它视为多阶段决策过程,也可以用动态规划方法方便地求解。
    • 给定k阶段状态变量x(k)的值后,如果这一阶段的决策变量一经确定,第k+1阶段的状态变量x(k+1)也就完全确定,即x(k+1)的值随x(k)和第k阶段的决策u(k)的值变化而变化,那么可以把这一关系看成(x(k),u(k))与x(k+1)确定的对应关系,用x(k+1)=Tk(x(k),u(k))表示。这是从k阶段到k+1阶段的状态转移规律,称为状态转移方程 。

      [x(k+1)=T_k(x(k),u(k)) ]

    最优性原理实际上是要求问题的最优策略的子策略也是最优

    时间复杂度=状态总数*每个状态转移的状态数*每次状态转移的时间

    这个题目里,我们可以用dp(i,dst)表示第i阶段到达dst处的走法数,因此有:

    [dp[i+1][dst] = Sigma_{src} dp[i][src] ]

    src表示所有k结点相连的前向结点值。

    开始代码如下:

    int numWays(int n, vector<vector<int>>& relation, int k) {
        vector<vector<int>> edges(n);
        for (auto &edge : relation) {
            int src = edge[0], dst = edge[1];
            edges[dst].push_back(src);
        }
        vector<vector<int>> dp(k+1, vector<int>(n,0));
        dp[0][0] = 1; //起始状态
        for(int i = 1; i <= k; i++) {
            for(int j = 0; j < n; j++) {
                int sum = 0;
                for(auto & src:edges[j]) {
                    sum += dp[i-1][src];
                }
                dp[i][j] = sum;
            }
        }
        return dp[k][n-1];
    }
    

    时间复杂度: (O(k*sum_j edges[j].size())=O(k*relation.size()))

    空间复杂度:O(k*n)

    程序中存在很多不必要的计算,例如edges数组的初始化,从时间复杂度可以看出来只需要两层循环即可, 第二层只需要枚举所有边。

    优化后的程序:

    int numWays(int n, vector<vector<int>>& relation, int k) {
        vector<vector<int>> dp(k+1,vector<int>(n,0));
        dp[0][0] = 1;
        for(int i = 1; i <= k; i++) {
           for(auto & rel : relation) {
               dp[i][rel[1]] += dp[i-1][rel[0]]; 
           }
        }
        return dp[k][n-1];
    }
    

  • 相关阅读:
    lc第319场周赛第三题逐层排序二叉树所需的最少操作数目
    实现自动delete?
    什么是死锁?怎么排查死锁?怎么避免死锁?
    为什么视频流一般都用UDP
    Leetcode 537. 复数乘法(网友思路,自愧不如)
    MFC中使用sqlite3操作数据库 创建、插入、查询、修改、删除数据
    C++ std::set<>是什么 怎么用 遍历
    qt qtextedit 限制富文本复制 限制字符
    set容器判断是否插入成功
    C++ 循环for 引用 for(string & : )
  • 原文地址:https://www.cnblogs.com/raiuny/p/15269374.html
Copyright © 2020-2023  润新知