从存图到最短路算法的图论总结

INTRODUCTION:

图论算法在计算机科学中扮演着很重要的角色,它提供了对很多问题都有效的一种简单而系统的建模方式。很多问题都可以转化为图论问题,然后用图论的基本算法加以解决。--百度百科

对于OI而言,图是指由若干给定的点及若干条连接两点的线(边)所构成的图形

借助图论知识,我们往往可以将一些复杂的问题转化到基础的图论算法上,进而使用已有算法解决全新问题

那么想如果想要运用图论,首先要从存图开始

前排感谢教我图论的周润喵老师,syc学长,就序老师

可是我还是没学会

一,存图

对于一个图而言,它可以根据便是否有反向分成两类:有向图与无向图,

不过二者的存图方式大同小异,以下以有向图为例;

1,邻接矩阵(Adjacency matrix)

邻接矩阵作为一种简洁实用的存图方式,具有简单可靠的优势,一般不太会打错,但是他的空间复杂度高达O(n^2),使得他的使用相当受局限

不过,在数据范围比较小或者想要打暴力部分分的时候,邻接矩阵还是具有相当大的优势的。

(比如说邻接矩阵+Floyd打暴力)

在邻接矩阵中,我们用e[i][j]表示点i到点j的距离(也就是边i->j的边权)

 1     const int INF = 9999999999;//设一个较大的数为无穷大
 2     int n, m;//n为点数,m为边数
 3     int e[5005][5005];//貌似开5005*5005就快MLE了...所以要谨慎一点
 4     for (int i = 1; i <= n; i++)
 5         for (int j = 1; j <= n; j++)
 6             if (i == j)
 7                 e[i][j] = 0;//我自己到我自己的距离当然是0
 8             else
 9                 e[i][j] = INF;//一开始还没有边,所以我到其他人的距离先设为无穷大
10     for (int i = 1; i <= m; i++)//读入边
11     {
12         int from, to, weight;//从哪来,到哪去,路多长
13         cin >> from >> to >> weight;
14         e[from][to] = weight;//无向图存两遍
15         e[to][from] = weight;//from到to的距离和to到from的距离是相等的
16     }

个人认为邻接矩阵是一种比较可靠的存图方式,在数据较小的时候一般不会出错,

不过在使用时一定要根据题目含义对有向图,无向图或重边,自环,等特殊情况进行判断,以免出错。

2,邻接表(Adjacency table)

观察之前的邻接矩阵,我们可以看出,当存在很多个点(假设有n个),但边的数量(m)却远小于n2时,矩阵中很多的空间都没有用到,存在着极大的空间浪费

这使得邻接矩阵无法应付n>=10000(甚至更大)的情况,然而这种在OI里是很常见的,所以我们就要引入一种OI里最常见(总之我觉得挺常见的)

的存图方式:邻接表

首先,邻接表本质上是一种链表,表中的每一个节点使用指针或模拟指针进行连接(其实是不连着的)

同时,邻接表不同于邻接矩阵,他是以边为单位进行存储的,所以他所占的空间完全由边的数量决定,和点的数量没什么关系,

他无论在空间还是时间上都相当优秀,在OI中一般情况下不会出现连邻接矩阵都存不下的图(至少本蒟蒻没见过)

 1 #include<iostream>
 2 using namespace std;
 3 struct edge
 4 {
 5     int from;
 6     int to;
 7     int next;//模拟指针
 8     int weight;
 9 }e[2000080];//看吧,他开很大都不会爆,不过要注意无向图开两倍
10 //毕竟一条无向边其实是当作两条有向边存的
11 int head[50005];//head[i]表示点i所发出的第一条边的数组下标
12 int tot;//边的总数
13 int n, m;
14 void add(int from,int to,int weight)
15 {
16     tot++;
17     e[tot].from = from;
18     e[tot].to = to;
19     e[tot].weight = weight;
20     e[tot].next = head[from];
21     head[from] = tot;
22 }//加边的模板
23 int main()
24 {
25     cin >> n >> m;
26     for (int i = 1; i <= m; i++)
27     {
28         int x, y, z;
29         cin >> x >> y >> z;
30         add(x, y, z);
31         add(y, x, z);//依然无向边存两次
32     }
33     for (int i = head[1]; i; i = e[i].next)
34         //遍历该点上所有的边,如果没有下一条了(i=0),我就停
35         //如果还有下一条边,我就继续往后遍历(i=e[i].next)
36         cout << e[i].to;
37     //貌似没解释清楚,感性理解一下?
38     return 0;
39 }

3,vector存图

利用stl库中提供的动态数组vector存图,时空上的效率都和邻接表差不多(据说开了OI优化会稍微快一点)

注意要开<vector>头文件

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
struct edge
{
    int from;
    int to;
    int weight;
};
vector<edge> e[100086];//e[i][j]表示点i的第j条边
//貌似比邻接表稍微简单一点
int n, m;
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        edge t;//定义一条新的的边出来
        int x, y, z;
        cin >> x >> y >> z;
        t.from = x;
        t.to = y;
        t.weight;
        e[x].push_back(t);//把他塞进去
        t.from = y;
        t.to = x;
        e[y].push_back(t);//改一改,反向塞进去
    }
    for (int i = 0; i < e[1].size(); i++)
        //查询很方便
        //不过注意vector是从0开始的
        cout << e[1][i].to;
    return 0;
}

存图时的坑点:

  • 重边:比较一下他和原本的那条边那个权值更小,选更小的存

  • 自环:对于一般的题貌似可以直接不管他

  • 无向图没开两倍:二话不说直接RE

二,最短路

常见的最短路算法主要有三类:

Floyd,Dijkstra以及Bellman Ford

当然还有他们的优化,以及一些其他的算法,不过貌似那些都有很多限制条件,只能在一些特定情况下只用

1,Floyd

这个算法实在太著名了,因为他的核心代码只有5行....

1     for (int k = 1; k <= n; k++)//枚举中间点
2         for (int i = 1; i <= n; i++)//枚举起点
3             for (int j = 1; j <= n; j++)//枚举终点
4                 e[i][j] = min(e[i][j], e[i][k] + e[k][j]);//替换

大概就是说,从i点到j点是直接走比较近还是从i到k再从k到j这样绕一圈比较近,如果我绕一圈近的话就更新一下路径长度

完整代码

 1 #include<iostream>
 2 #include<vector>
 3 #include<algorithm>
 4 using namespace std;
 5 int e[1000][1000];
 6 int main()
 7 {
 8     int n, m;
 9     cin >> n >> m;
10     for (int i = 1; i <= n; i++)
11         for (int j = 1; j <= m; j++)
12             if (i == j)
13                 e[i][j] = 0;
14             else
15                 e[i][j] = 9999999;
16     for (int i = 1; i <= m; i++)
17     {
18         int x, y, z;
19         cin >> x >> y >> z;
20         e[x][y] = z;
21         e[y][z] = z;
22     }
23     for (int k = 1; k <= n; k++)//枚举中间点
24         for (int i = 1; i <= n; i++)//枚举起点
25             for (int j = 1; j <= n; j++)//枚举终点
26                 e[i][j] = min(e[i][j], e[i][k] + e[k][j]);//替换
27     return 0;
28 }

算法优点:

  • 他求的是多源最短路,也就是说跑完一次Floyd,那么图中任意两个点之间的最短路就都知道了,不像后两种求的是单源最短路

  • 好打

算法缺点:

  • 太慢了.....时间复杂度O(n3)

2,Dijkstra

 1 #include<iostream>
 2 #include<algorithm>
 3 using namespace std;
 4 const long inf = 20041001;
 5 int n;
 6 int m;
 7 int s;
 8 int tot;
 9 struct edge
10 {
11     long weight;
12     int to;
13     int next;
14 }e[500005];
15 struct node
16 {
17     int head;
18 }no[10086];
19 long long dis[10086];
20 bool book[10086];
21 void add(int from, int to, int weight)
22 {
23     tot++;
24     e[tot].to = to;
25     e[tot].weight = weight;
26     e[tot].next = no[from].head;
27     no[from].head = tot;
28 }
29 int main()
30 {
31     cin >> n >> m >> s;
32     book[s] = 1;
33     for (int i = 1; i <= n; i++)
34         dis[i] = inf;
35     dis[s] = 0;
36     for (int i = 1; i <= m; i++)
37     {
38         int x, y, w;
39         cin >> x >> y >> w;
40         if (x != y)
41             add(x, y, w);
42     }
43     for (int i = no[s].head; i; i = e[i].next)
44     {
45         if (e[i].weight < dis[e[i].to])
46             dis[e[i].to] = e[i].weight;
47     }
48     for (int i = 1; i < n; i++)
49     {
50         int u = 0;
51         int minn = inf;
52         for (int j = 1; j <= n; j++)
53         {
54             if (book[j] == 0 && dis[j] < minn)
55             {
56                 minn = dis[j];
57                 u = j;
58             }
59         }
60         book[u] = 1;
61         for (int i = no[u].head; i; i = e[i].next)
62         {
63             if (dis[e[i].to] > dis[u] + e[i].weight)
64                 dis[e[i].to] = dis[u] + e[i].weight;
65         }
66     }
67     for (int i = 1; i <= n; i++)
68         cout << dis[i] << " ";
69     return 0;
70 }

算法优点

  • 快了不少,好歹达到了O(n2)的复杂度,用堆儿优化之后甚至可以达到O((n+m)logn),算是比较优秀了

算法缺点

  • 求的是单源最短路,也就是说我每次求完都只能知道点s到各个点的最短距离,如果我要求每个点的,也就要跑n次,就很慢了

  • 关键是他处理不了负权边,尤其是负权回路

3,Bellman Ford

 1 #include<iostream>
 2 #include<string.h>
 3 #include<algorithm>
 4 #include<vector>
 5 #include<map>
 6 #include<bitset>
 7 #include<set>
 8 #include<string>
 9 #if !defined(_WIN32)
10 #include<bits/stdc++.h>
11 #endif // !defined(_WIN32)
12 #define ll long long
13 #define dd double
14 using namespace std;
15 int t;
16 int n, m, w;
17 int tot;
18 struct edge
19 {
20     int from;
21     int to;
22     int weight;
23 }e[100086];
24 int dis[5005];
25 void add(int x, int y, int z)
26 {
27     tot++;
28     e[tot].from = x;
29     e[tot].to = y;
30     e[tot].weight = z;
31 }
32 bool Bellman_Ford()
33 {
34     memset(dis, 0x3f3f3f3f, sizeof(dis));
35     dis[1] = 0;
36     for (int i = 1; i < n; i++)
37     {
38         for (int j = 1; j <= tot; j++)
39         {
40             if (dis[e[j].to] > dis[e[j].from] + e[j].weight)
41                 dis[e[j].to] = dis[e[j].from] + e[j].weight;
42         }
43     }
44     for (int i = 1; i <= tot; i++)
45         if (dis[e[i].to] > dis[e[i].from] + e[i].weight)
46             return 0;
47     return 1;
48 }
49 int main()
50 {
51     cin >> t;
52     while (t--)
53     {
54         tot = 0;
55         memset(e, 0, sizeof(e));
56         memset(dis, 0, sizeof(dis));
57         n = 0, m = 0, w = 0;
58         cin >> n >> m >> w;
59         for (int i = 1; i <= m; i++)
60         {
61             int x, y, z;
62             cin >> x >> y >> z;
63             add(x, y, z);
64             add(y, x, z);
65         }
66         for (int i = 1; i <= w; i++)
67         {
68             int x, y, z;
69             cin >> x >> y >> z;
70             add(x, y, 0 - z);
71         }
72         if (Bellman_Ford())
73             cout << "NO" << endl;
74         else
75             cout << "YES" << endl;
76     }
77     return 0;
78 }

算法优点

  • 可以处理负权,如果有负权回路,则可以把他判断出来

算法缺点

  • 很慢,时间复杂度O(nm),而且求的是单元最短路,一般只用来判断是否有负权回路,而且实际使用时往往要使用他的队列优化,也就是SPFA

(今天刚注册的博客,立刻就像写一篇博客试试,然而实际写起来才觉得没那么简单,写完后返回来以看就发现了很多缺陷,而且也不太清楚从何改起.......不过我相信写博客同样也是熟能生巧的,只要一直写下去,想必将来也能写出优秀的博客)

原文地址:https://www.cnblogs.com/HNFOX/p/11272427.html

时间: 2024-11-11 13:45:01

从存图到最短路算法的图论总结的相关文章

图的最短路算法 Floyd

多源最短路径算法 时间复杂度O(N3) 简单修改可求有向图的传递闭包 #include<iostream> using namespace std; const int maxn=1024; const int inf=1<<30; int d[maxn][maxn]; int n,m; void init() { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) d[i][j]=(i==j?0:inf); } int main()

图的最短路算法 Dijkstra及其优化

单源最短路径算法 时间复杂度O(N2) 优化后时间复杂度为O(MlogN)(M为图中的边数 所以对于稀疏图来说优化后更快) 不支持有负权的图 #include<iostream> using namespace std; const int maxn=1024; const int inf=1<<30; int n,m; int d[maxn]; int v[maxn]; int G[maxn][maxn]; void init() { for(int i=1;i<=n;i+

图论基础——邻接链表存图+拓扑排序

邻接链表存图,在这里其实是用数组进行模拟的 又叫做链式存储法,本来是要用链表实现的,但大多数情况下只需要用数组模拟即可 例: u(边的起点) v(边的终点) w(边的权值) 4 2 1 1 2 3 1 4 1 1 5 2 4 3 4 2 3 1 话不多说,直接上代码 for(int i=1;i<=m;i++) { scanf("%d%d%d",&u1,&v1,&w1); e[i].u =u1;//赋给第i条边的起点 e[i].v =v1;//赋给第i条边的

最短路 spfa 算法 &amp;&amp; 链式前向星存图

推荐博客  https://i.cnblogs.com/EditPosts.aspx?opt=1 http://blog.csdn.net/mcdonnell_douglas/article/details/54379641 spfa  自行百度 说的很详细 spfa 有很多实现的方法  dfs  队列  栈  都可以 时间复杂度也不稳定 不过一般情况下要比bellman快得多 #include <stdio.h> #include <math.h> #include <st

利用无权图的单源最短路算法实现地铁换乘图

//Metro.php $MetroVertex = array( 1 => '体育中心', 2 => '体育西路', 3 => '杨箕', 4 => '东山口', 5 => '烈士陵园', 6 => '农讲所', 7 => '公园前', 8 => '西门口', 9 => '陈家祠', 10 => '长寿路', 11 => '黄沙', 12 => '芳村', 13 => '花地湾', 14 => '坑口', 15 =>

最短路算法 :Bellman-ford算法 &amp; Dijkstra算法 &amp; floyd算法 &amp; SPFA算法 详解

 本人QQ :2319411771   邮箱 : [email protected] 若您发现本文有什么错误,请联系我,我会及时改正的,谢谢您的合作! 本文为原创文章,转载请注明出处 本文链接   :http://www.cnblogs.com/Yan-C/p/3916281.html . 很早就想写一下最短路的总结了,但是一直懒,就没有写,这几天又在看最短路,岁没什么长进,但还是加深了点理解. 于是就想写一个大点的总结,要写一个全的. 在本文中因为邻接表在比赛中不如前向星好写,而且前向星效率并

图论-最短路算法

求最短路暂时掌握了4种,但感觉就dijkstra复杂度能用: 1   floyd算法: 就是暴力的三重循环,以每个点为中转点,每次遍历所有的点,看看能不能通过这个中转点更新最短路径: 优点:n<200时用这种方法,用邻接矩阵存图 ,可求任意的两点的最短路:而且好写: 缺点:复杂度太高,O(n^3)的复杂的,太多不必要的计算: void floyd(){ //dis数组表示i到j的最短路径 for(int k=0;k<n;k++)//重点,必须放在外面 for(int i=0;i<n;i+

【啊哈!算法】算法7:Dijkstra最短路算法

上周我们介绍了神奇的只有五行的Floyd最短路算法,它可以方便的求得任意两点的最短路径,这称为“多源最短路”.本周来来介绍指定一个点(源点)到其余各个顶点的最短路径,也叫做“单源最短路径”.例如求下图中的1号顶点到2.3.4.5.6号顶点的最短路径. <ignore_js_op> 与Floyd-Warshall算法一样这里仍然使用二维数组e来存储顶点之间边的关系,初始值如下. <ignore_js_op> 我们还需要用一个一维数组dis来存储1号顶点到其余各个顶点的初始路程,如下.

只有五行的Floyd最短路算法

暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程. 上图中有4个城市8条公路,公路上的数字表示这条公路的长短.请注意这些公路是单向的.我们现在需要求任意两个城市之间的最短路程,也就是求任意两个点之间的最短路径.这个问题这也被称为"多源最短路径"问题. 现在需要一个数据结构来存储图的信息,我们仍然可以用一个4*4的矩阵(二维数组e)来存储.比如1号城市到2号城市的路程为2,则设e[