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