zoukankan      html  css  js  c++  java
  • 基础图论问题算法总结

    这里介绍了图论中常见算法的原理和实现,所有代码已打包,此处可以下载。

    一、邻接表存图
    用邻接矩阵表示稀疏图会浪费大量内存空间。而在邻接表中是通过把类似于“从顶点0出发有到顶点1、2、3、4的边”这样的信息保存在链表中来表示图的。这样只需要O(|V| + |E|)的内存空间。

    1. #include <cstdio>
    2. #include <vector>
    3. std::vector<int> g[max_v];
    4. /**
    5. *边上有属性的情况
    6. *sturct edge{
    7. * int to, cost;
    8. *};
    9. *std::vector<edge> g[max_v];
    10. */
    11. int main(void)
    12. {
    13. int v, e;
    14. scanf("%d%d", &v, &e);
    15. for(int i = 0; i != e; ++i){
    16. int s, t;
    17. scanf("%d%d", &s, &t);
    18. g[s].push_back(t);
    19. //g[t].push_back(s);//无向图的情况
    20. }
    21. return 0;
    22. }

    二、最短路问题
    1.Bellman-Ford求单源最短路
    记从起点s出发到顶点i的最短距离为d[i],则有d[i] = min{d[j] + (从j到i的边的权值) | e = (j ,i) ∈ E}。对于图中有圈的情况,设d[s] = 0, d[i] = INF则可以在有限次数内算出新的d。只要不存在从s可达到的负圈,最多在|V| - 1次循环后for(;;)就会break,因此复杂度是O(|V| × |E|)。也就是说,若存在负圈,在第|V|次循环也会更新d值,可以利用这个性质检查是否存在负圈。

    1. struct edge{
    2. int from;
    3. int to;
    4. int cost;
    5. };
    6. edge es[max_e];
    7. int d[max_v];
    8. int v, e;
    9. //从顶点s出发的单源最短路
    10. void bellman_ford(int s)
    11. {
    12. for(int i = 0; i != v; ++i)
    13. d[i] = INF;//无限大
    14. d[s] = 0;
    15. for(;;){
    16. bool update = false;
    17. for(int i = 0; i != e; ++i){
    18. edge e = es[i];
    19. if(d[e.from] != INF && d[e.to] > d[e.from] + e.cost){
    20. d[e.to] = d[e.from] + e.cost;
    21. update = true;
    22. }
    23. }
    24. if(!update)
    25. break;
    26. }
    27. }
    28. //检查负圈,返回真为有
    29. bool find_negative_loop(void)
    30. {
    31. memset(d, 0, sizeof(d));
    32. for(int i = 0; i != v; ++i){
    33. for(int j = 0; j != e; ++j){
    34. edge e = es[j];
    35. if(d[e.to] > d[e.from] + e.cost){
    36. d[e.to] = d[e.from] + e.cost;
    37. //第n次仍更新则存在负圈
    38. if(i == v - 1)
    39. return true;
    40. }
    41. }
    42. }
    43. return false;
    44. }

    2.Dijkstra求无负边的单源最短路
    描述:①找到最短距离已经确定的顶点,从它出发更新相邻顶点的最短距离,此后不需要再关心“最短距离已经确定的顶点”。如何得到“最短距离已经确定的顶点”呢?在开始时,只有到起点的最短距离是确定的。在尚未使用过的顶点中,距离d[i]最小的顶点就是最短距离已经确定的顶点。下面给出时间复杂度为O(|V|²)使用邻接矩阵实现的Dijkstra算法。

    1. template <typename t>
    2. inline t min(t a, t b)
    3. {
    4. return a < b ? a : b;
    5. }
    6. int cost[max_v][max_v];//存权值的邻接矩阵
    7. int d[max_v];//最短距离
    8. bool used[max_v];//已经使用过的图
    9. int v;//顶点数量
    10. void Dijkstra(int s)
    11. {
    12. for(int i = 0; i != v; ++i)
    13. d[i] = INF;
    14. for(int i = 0; i != v; ++i)
    15. used[i] = false;
    16. d[s] = 0;
    17. for(;;){
    18. int v = -1;
    19. //从未使用过的顶点中找出一个距离最小的顶点
    20. for(int u = 0; u != v; ++u)
    21. if(!used[u] && (v == -1 || d[u] < d[v]))
    22. v = u;
    23. if(-1 == v)
    24. break;
    25. for(int u = 0; u != v; ++u)
    26. d[u] = min(d[u], d[v] + cost[v][u]);
    27. }
    28. }

    当使用邻接表时,更新最短距离只需要访问每条边1次,因此这部分的复杂度是O(|E|),但是查找顶点需要都枚举一次,最终复杂度还是平方级的。可以使用C++ STL的优先队列priority_queue来实现。在每次更新时往堆里插入当前最短距离和顶点的值对,当取出的最小值不是最短距离的话,就丢弃它。这样算法的复杂度优化到了O(|E|log|V|)。Dijkstra较Bellman-Ford效率更高,但无法应用于存在负边的图。

    1. #include <queue>
    2. #include <vector>
    3. #include <utility>
    4. struct edge{
    5. int to;
    6. int cost;
    7. };
    8. typedef std::pair<int, int> P;//first 最短距离, second 顶点编号
    9. int v;
    10. std::vector<edge> g[max_v];
    11. int d[max_v];
    12. void Dijkstra(int s)
    13. {
    14. std::priority_queue<P, std::vector<P>, std::greater<P> > que;
    15. for(int i = 0; i != v; ++i)
    16. d[i] = INF;
    17. d[s] = 0;
    18. que.push(P(0, s));
    19. while(!que.empty()){
    20. P p = que.top();
    21. que.pop();
    22. int v = p.second;
    23. if(d[v] < p.first)
    24. continue;
    25. for(unsigned int i = 0; i != g[v].size(); ++i){
    26. edge e = g[v][i];
    27. if(d[e.to] > d[v] + e.cost){
    28. d[e.to] = d[v] + e.cost;
    29. que.push(P(d[e.to], e.to));
    30. }
    31. }
    32. }
    33. }

    3.适用于在各种图中求任意两点间距离的Floyd-Warshall
    代码极短,十分有效,不解释原理。同样可用于判断是否存在负圈:检查是否存在d[i][i]是负数。复杂度:O(|V|^3)。

    1. template <typename t>
    2. inline t min(t a, t b)
    3. {
    4. return a < b ? a : b;
    5. }
    6. int d[max_v][max_v];
    7. int v;
    8. void Floyd_Warshall(void)
    9. {
    10. for(int k = 0; k != v; ++k)
    11. for(int i = 0; i != v; ++i)
    12. for(int j = 0; j != v; ++j)
    13. d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
    14. }

    三、最小生成树问题
    1.用于邻接矩阵的Prim
    假设有一颗只包含一个顶点v的树T,贪心地选取T和其他顶点之间相连的最小权值的边并把它加入到T中。不断进行这个操作就可以得到生成树了。下面给出的算法时间复杂度是O(|V|²)。

    1. template <typename t>
    2. inline t min(t a, t b)
    3. {
    4. return a < b ? a : b;
    5. }
    6. int cost[max_v][max_v];
    7. int mincost[max_v];//从集合X出发的边到每个顶点的最小权
    8. bool used[max_v] ;//顶点i是否包含在集合X中
    9. int v;
    10. int Prim(void)
    11. {
    12. for(int i = 0; i != v; ++i){
    13. mincost[i] = INF;
    14. used[i] = false;
    15. }
    16. mincost[0] = 0;
    17. int res = 0;
    18. for(;;){
    19. int v = -1;
    20. //从不属于集合X的顶点中选取从X到其权值最小的顶点
    21. for(int u = 0; u != v; ++u)
    22. if(!used[u] && (v == -1 || mincost[u] < mincost[v]))
    23. v = u;
    24. if(-1 == v)
    25. break;
    26. used[v] = true;//把顶点v加入集合X
    27. res += mincost[v];//把边的长度加入结果
    28. for(int u = 0; u != v; ++u)
    29. mincost[u] = min(mincost[u], cost[v][u]);
    30. }
    31. return res;
    32. }

    2.用于邻接表的Kruskal
    按照边的权值的顺序从小到大查看一遍(需要排序),若不产生圈或重边,就把这条边加入到生成树中。
    如何判断是否产生圈:若要把连接u和v的边e加入到生成树中,只要加入之前u和v不在同一个连通分量里,加入e就不会产生圈,若在就一定会产生圈。引入并查集就可以做到,并查集的实现包含在代码包里。Kruskal最耗时的操作是对边的排序,时间复杂度:O(|E|log|V|)。

    1. #include <algorithm>
    2. #include "Union_Find.cpp"
    3. struct edge{
    4. int u, v, cost;
    5. };
    6. bool comp(const edge &a, const edge &b)
    7. {
    8. return a.cost < b.cost;
    9. }
    10. edge es[max_e];
    11. int v, e;
    12. int Kruskal(void)
    13. {
    14. std::sort(es, es + e, comp);
    15. init(v);//初始化并查集
    16. int res = 0;
    17. for(int i = 0; i != e; ++i){
    18. edge e = es[i];
    19. if(!same(e.u, e.v)){
    20. unite(e.u, e.v);
    21. res += e.cost;
    22. }
    23. }
    24. return res;
    25. }
  • 相关阅读:
    Atitit 集团与个人的完整入口列表 attilax的完整入口 1. 集团与个人的完整入口列表 1 2. 流量入口概念 2 3. 流量入口的历史与发展 2 1.集团与个人的完整入口列表
    atitit 每季度日程表 每季度流程 v3 qaf.docx Ver history V2 add diary cyar data 3 cate V3 fix detail 3cate ,
    Atitit react 详细使用总结 绑定列表显示 attilax总结 1. 前言 1 1.1. 资料数量在百度内的数量对比 1 1.2. 版本16 v15.6.1 1 1.3. 引入js 2
    Atitit r2017 r3 doc list on home ntpc.docx
    Atitit r2017 ra doc list on home ntpc.docx
    Atiitt attilax掌握的前后技术放在简历里面.docx
    Atitit q2016 qa doc list on home ntpc.docx
    Atitit r7 doc list on home ntpc.docx 驱动器 D 中的卷是 p2soft 卷的序列号是 9AD0D3C8 D:\ati\r2017 v3 r01\
    Atitit 可移植性之道attilax著
    Atitit q2016 q5 doc list on home ntpc.docx
  • 原文地址:https://www.cnblogs.com/tham/p/6827284.html
Copyright © 2011-2022 走看看