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];
}

原文地址:https://www.cnblogs.com/raiuny/p/15269374.html