• 恭喜你~遇到了最有趣的算法(二)


    哈喽~大家好呀,上篇呢我们将了排序算法(一),后台有小伙伴私信说,追呀,啥时候更新下一篇呀?这不今天它来了,(它来了,它来了,它穿着大裤衩走来了),那么这篇呢我们来看最有趣的算法(二)。

     🥇个人主页:个人主页​​​​​        

    🥈 系列专栏:【算法】​​​​​​      

    🥉与这篇算法相关的文章:

    目录

    一、深度优先搜索

    📸实际问题

    二、广度优先搜索

    📸实际问题

    三、prim 算法

    📸实际题目

    四、Kruskal算法

    📸实际题目

    五、小结


    一、深度优先搜索

    深度优先搜索(DFS)又叫深搜,我们可以这么理解,DFS 像一个很固执的一个人(不撞南墙不回头,不见黄河不死心),只要你这里有路我就一定会去走,而且一条路走到底的那种(燕子~没你我怎么活呀,开玩笑了)。

    我们先来看看效果图。(对了,上次有小伙伴问,你这效果图是怎么实现的呀,我是在这个网站上绘制效果图的)

    3529eb83618c4b0589d3148c31103d39.gif

    看完效果图后感觉是不是挺通俗易懂的?好,我们来看看 DFS 的模板。

    1. dfs(int u)
    2. {
    3. if(找到了||走不下去了)
    4. {
    5. return
    6. }
    7. 开始下一个情况(dfs(u+1));
    8. }

    📸实际问题

    我们来看看一个经典的问题——n皇后问题。我们先来看看题目。

    给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。
    现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上
    任意的两个白皇后都不在同一行、同一列或同一条对角线上。

    我们来看看代码

    1. #include <iostream>
    2. #include <bits/stdc++.h>
    3. using namespace std;
    4. typedef long long ll;
    5. const int N = 20;
    6. int n;
    7. char g[N][N];
    8. int l[N],ug[N],ng[N];
    9. void dfs(int u)
    10. {
    11. if(u == n)
    12. {
    13. for(int i = 0; i < n; i ++)
    14. {
    15. cout << g[i] << endl;
    16. }
    17. cout << endl;
    18. return ;
    19. }
    20. for(int i = 0; i < n; i ++)
    21. {
    22. if(!l[i] && !ug[i + u] && !ng[i - u + n])
    23. {
    24. g[u][i] = 'Q';
    25. l[i] = ug[i + u] = ng[i - u + n] = 1;
    26. dfs(u + 1);
    27. l[i] = ug[i + u] = ng[i - u + n] = 0;
    28. g[u][i] = '.';
    29. }
    30. }
    31. }
    32. int main()
    33. {
    34. cin >> n;
    35. for(int i = 0; i < n; i ++)
    36. {
    37. for(int j = 0; j < n; j ++)
    38. {
    39. g[i][j] = '.';
    40. }
    41. }
    42. dfs(0);
    43. return 0;
    44. }

    d4473014d3a74064b04a50c0181863c8.png

    二、广度优先搜索

    广度优先搜索(BFS)又叫广搜,它像一个有远见的人,它是一层一层来实现搜索的,也挺像下楼梯的。

    思路:

      1.先初始化队列 q;
      2.从起点开始访问,并且改变他的状态为已经访问;
      3.如果他的队列非空,取出首个元素,将它弹出!
      4.如果u == 目标状态,然后对所以与 u 邻近的点进入队列;
      5.标记它已经被访问!

    我们先来看看效果图

    5fb58338f9ff4431ab2421162133c0e5.gif

    我们来看看模板

    1. queue<int> q;
    2. st[0] = true; // 表示1号点已经被遍历过
    3. q.push(0);
    4. while (q.size())
    5. {
    6. int t = q.front();
    7. q.pop();
    8. for (int i = h[t]; i != -1; i = ne[i])
    9. {
    10. int j = e[i];
    11. if (!s[j])
    12. {
    13. st[j] = true; // 表示点j已经被遍历过
    14. q.push(j);
    15. }
    16. }
    17. }

    📸实际问题

    63405698705642ae9e50e09d689bd95c.png

    ff90c126b7fb4f89b51196f9dbd4008c.png

    来看看代码

    1. #include <iostream>
    2. #include <bits/stdc++.h>
    3. #define x first
    4. #define y second
    5. using namespace std;
    6. const int N = 1000 + 10;
    7. int n;
    8. typedef pair<int,int> PII;
    9. PII q[N * N];
    10. bool st[N][N];
    11. char g[N][N];
    12. int dx[4] = {-1, 0, 1, 0},dy[4] = {0, 1, 0, -1};
    13. void bfs(int sx, int sy, int &tl, int &bd)
    14. {
    15. int tt = 0,hh = 0;
    16. st[sx][sy] = true;
    17. q[0] = {sx, sy};
    18. while(hh <= tt)
    19. {
    20. PII t = q[hh ++];
    21. tl ++;
    22. bool is_bd = false;
    23. for(int i = 0; i < 4; i ++)
    24. {
    25. int x = t.x + dx[i],y = t.y + dy[i];
    26. if(x < 0 || x >= n || y < 0 || y >= n || st[x][y]) continue;
    27. if(g[x][y] == '.')
    28. {
    29. is_bd = true;
    30. continue;
    31. }
    32. st[x][y] = true;
    33. q[++ tt] = {x, y};
    34. }
    35. if(is_bd) bd++;
    36. }
    37. }
    38. int main(){
    39. cin >> n;
    40. for(int i = 0;i < n;i++) cin >> g[i];
    41. int cnt = 0;
    42. for(int i = 0;i < n;i++)
    43. for(int j = 0;j < n;j++)
    44. {
    45. if(!st[i][j] && g[i][j] == '#')
    46. {
    47. int tl = 0,bd = 0;
    48. bfs(i, j, tl, bd);
    49. if(tl == bd) cnt ++;
    50. }
    51. }
    52. cout << cnt << endl;
    53. return 0;
    54. }

    c8dd3f733c144fccb91bdf3856510f60.png

    看到这里感觉这么样?对 dfs 与 bfs 有了更新的认识吗?我们接下来再来看看两大算法。

    6cdec1e1af044bfdb5c4937e72d74912.jpeg

    扩展:什么是最小生成树?

    给定一个无向图,如果它的某个子图中任意两个顶点都互相连通并且是一棵树,那么这棵树就叫生成树。如果边上有权值,那么使得边权和最小的生成树叫做最小生成树(MST,Minimum Spanning Tree)。

    那么下面我们要讲的两大算法就是与最小生成树有关的。

    三、prim 算法

    prim 算法也像一个有远见的人,他选择一个节点(假设这个节点上有 n 条边),直接来找这 n 条边上最小的边,然后选择这条最小的边选完之后剩下(n - 1)。再选择连接的最小的边的节点(假设这个节点上有 m 条边)(在 m + n - 1 条边是哪个选择)。选完之后剩下(m + n - 2),依次类推。我们来看看效果图。

    ebfbad2a200a49c4bfbae746afc74899.gif

     算法模板

    1. int prim()
    2. {
    3. memset(dist, 0x3f, sizeof dist);
    4. int res = 0;
    5. dist[1] = 0;
    6. for(int i = 0; i < n; i ++)
    7. {
    8. int t = -1;
    9. for(int j = 1; j <= n; j ++)
    10. if(!st[j] && (t == -1 || dist[j] < dist[t]))
    11. t = j;
    12. st[t] = true;
    13. res += dist[t];
    14. for(int j = 1; j <= n; j ++)
    15. if(dist[j] > g[t][j] && !st[i])
    16. {
    17. dist[j] = g[t][j];
    18. pre[j] = t;
    19. }
    20. }
    21. return res;
    22. }

    📸实际题目

    给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负数。

    求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。

    给定一张边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|,m=|E|。

    由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。

    这里我们看到上面的效果图,我们可以构成一组数据,如果这组数据没有输出 impossible,那么它存在最小生成树。

    1. 9 16
    2. 0 1 8
    3. 0 2 12
    4. 1 4 9
    5. 1 3 25
    6. 1 2 13
    7. 2 6 21
    8. 2 3 14
    9. 2 6 12
    10. 3 5 8
    11. 3 7 12
    12. 3 8 16
    13. 4 5 19
    14. 4 3 20
    15. 5 7 11
    16. 7 8 6
    17. 6 8 11

    00b9a0f4788e46e7a5e44f0ea254fcff.png

    四、Kruskal算法

    Kruskal算法它很像一个比较莽撞的人,它直接选择最短的边,只要它满足最小生成树的条件,那么这条边就可行。 我们先来看看效果图。

    e35f9fbcd9454b46aaa87818211fb624.gif

    思路:

    ①将所有边按权重从小到大排序

    ②枚举每条边 a,b,权重是c

    if a,b不连通 

    ​    将这条边加入集合

    📸实际题目

    同样是上面的题目与数据

    给定一个n个点m条边的无向图,图中可能存在重边和自环,边权可能为负数。

    求最小生成树的树边权重之和,如果最小生成树不存在则输出 impossible。

    给定一张边带权的无向图G=(V, E),其中V表示图中点的集合,E表示图中边的集合,n=|V|,m=|E|。

    由V中的全部n个顶点和E中n-1条边构成的无向连通子图被称为G的一棵生成树,其中边的权值之和最小的生成树被称为无向图G的最小生成树。

    我们来看看代码是如何实现的。

    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. const int N = 2e5+5;
    4. int n,m;
    5. struct Edge
    6. {
    7. int u, v, w;
    8. bool operator<(const Edge &a) const
    9. {
    10. return w<a.w;
    11. }
    12. }edge[N];
    13. int p[N];
    14. int find(int x)
    15. {
    16. return p[x] == x ? x : p[x] = find(p[x]);
    17. }
    18. int main()
    19. {
    20. int n, m;
    21. scanf("%d%d",&n, &m);
    22. for(int i=0;i<m;i++)
    23. {
    24. int u, v, w;
    25. scanf("%d%d%d",&u, &v, &w);
    26. edge[i] = {u,v,w};
    27. }
    28. sort(edge, edge + m);
    29. for(int i = 1; i <= n; i ++) p[i] = i;
    30. int cnt = 0, sum = 0;
    31. for(int i = 0; i < m; i ++)
    32. {
    33. int a = edge[i].u, b=edge[i].v, w = edge[i].w;
    34. a = find(a);
    35. b = find(b);
    36. if(a != b)
    37. {
    38. cnt ++;
    39. sum += w;
    40. p[a] = b;
    41. }
    42. }
    43. if(cnt < n - 1) puts("impossible");
    44. else printf("%d", sum);
    45. }

    6299b897cac04905a2ff811161df3d20.png

     用同样的数据,不同的算法得到相同的结果,没毛病。

    五、小结

    我们来看看时间复杂度的分析

    BFS的复杂度分析
    最坏的情况下,空间复杂度为O(v)。

    每个顶点均需搜索一次,时间复杂度T1=O(v),每条边至少访问1次,时间复杂度为O(E),算法总的时间复 度为O(|V|+|E|)。

    查找每个顶点的邻接点所需时间为O(V),即该节点所在的该行该列。又有n个顶点,故算总的时间复杂度为O(|V|^2)。

    DFS复杂度分析
    它的空问复杂度为O(V)。

    查找所有顶点的邻接点所需时间为O(E),访问顶点的邻接点所花时间为O(V),此时,总的时间复杂度为O(V+E)。

    查找每个顶点的邻接点所需时间为O(V),要查找整个矩阵,故总的时间度为O(V^2)。 

    注意: v为图的顶点数,E为边数。

    快期末了,接下来的时间会比较紧,更新频率可能会比以前低了,见谅解🤝。

    (求关注)持续更新中……

    4517f436fc0c45b4847abe5f0ac6f66b.gif

  • 相关阅读:
    《Java编程思想》读书笔记(五)
    Redis之持久化实操(Linux版)
    2 OpenCV实现的F矩阵+RANSAC原理与实践
    移动应用安全需求分析与安全保护工程
    线程池与CompletableFuture
    【Unity程序技巧】公共Update管理器
    实验篇(7.2) 09. 通过安全隧道走对方宽带上网 (FortiClient-IPsec) ❀ 远程访问
    多目标优化算法:多目标哈里斯鹰优化算法(Multi-Objective Harris Hawks Optimizer,MOHHO)
    [2023.09.11]: Yew的SSR中的Cargo.toml配置
    DSP 应用领域及内部结构
  • 原文地址:https://blog.csdn.net/aasd23/article/details/124908384