• 基础图论问题算法总结


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

    一、邻接表存图
    用邻接矩阵表示稀疏图会浪费大量内存空间。而在邻接表中是通过把类似于“从顶点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. }
  • 相关阅读:
    linux命令-定时任务at
    linux网络监控_网速测试
    Linux磁盘分区扩容
    Ubuntu配置SSH服务
    Ubuntu用户管理
    Ubuntu安装lrzsz
    Ubuntu系统配置apt-get软件更新源
    Ubuntu网络配置IP和DNS等,适用于14.04,16.04,17.10和18.04
    Ubuntu系统安装,适用于14.04,16.04和17.10
    使用nginx反向代理处理前后端跨域访问
  • 原文地址:https://www.cnblogs.com/tham/p/6827284.html
Copyright © 2020-2023  润新知