图论基础(1)
入门级文章
1.图基本介绍
- 什么是图?
图是由顶点的非空有穷集合(用V表示该集合)与顶点之间的关系(边或弧)的集合(用E表示该集合)构成的结构。可以形式化表示为G=(V,E)
其中,V为顶点的非空有穷集合,E为关系的有穷集合。
根据顶点与顶点之间的关系分为有向图和无向图!!
说白了就是有很多顶点,点与点之间有联系
- 名词术语
顶点的度:某个顶点V的度是指依附于该顶点的边或弧的数量,说白了就是跟这个点连的有几条线
出度、入度:对于有向图还要区分出度和入度,一个顶点V的出度是指以该顶点为出发点的弧(边)的数量;一个顶点V的入度是指以该顶点为终止点的弧(边)的数量
路径:对于顶点i和j,存在着i->a->b->c->…->j(从i出发能够到达j的顶点序列),如果i==j,则此路径成为回路或者环路。
子图:对于G=(V,E)与G’=(V’,E’),若有V’∈V,E’∈E, 则称G’是G的一个子图,一个图是其自身的子图,说白了就当做子集来理解吧
图的连通:(1)无向图的连通:若顶点i与j之间有路径,就称i与j是连通的,若图中任意两个顶点之间都有路径,就称该无向图是连通的。(2)有向图的连通:若顶点i与j之间有路径,并且顶点j与i之间也有路径,就称i与j是连通的,若图中任意两个顶点之间都有连通,就称该有向图是强连通的。
连通分量:连通分量:无向图中的极大连通子图。任何一个连通图的连通分量只有一个,是它本身
强连通分量:有向图中的极大连通子图。
2.图的存储
- 邻接矩阵来存储
无向图:用一个二维数组来存储点与点之间的关系,如果i到j有直接联系,则令chart[i ] [j ] = 1, 如果边有权值w,则
chart[i] [j] = w;如果没有直接关系则chart[i] [j] = 0(任意一个特殊标记都行,-1等等);由于这是无向图需要反过来再
存一遍即chart[j] [i] = 值;
有向图:用一个二维数组来存储点与点之间的关系,如果i到j有直接联系,则令chart[i ] [j ] = 1, 如果边有权值w,则
chart[i] [j] = w;如果没有直接关系则chart[i] [j] = 0(任意一个特殊标记都行,-1等等);由于这是有向图,所以不需要反过来再存一遍。
- 邻接表来存储
每个点为头结点(i),跟它有直接关系的就直接加在以i为头结点的链表后面
无向图:(需要i->j存一遍,j->i再存一遍)
我们做算法题目的时候为了方便肯定是不会去写链表的,用vector代替或者用链式前向星来存图
有向图:
如果有权值,那么结点就弄成一个结构体
typedef struct{
int end, w;//end表示终点,w表示权值
}Node
3.题目实战
题目连接:https://www.luogu.com.cn/problem/P5318
输入输出样例
输入 #1
8 9
1 2
1 3
1 4
2 5
2 6
3 7
4 7
4 8
7 8
输出 #1
1 2 5 6 3 7 8 4
1 2 3 4 5 6 7 8
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
int n, m;
//标记是否被遍历过了
bool flag[100005];
vector<int> a[100005];//二维动态数组
//深搜
//num表示还有多少个点没遍历
//其实num这个参数删了也行的,完全不影响
void dfs( int now, int num )
{
flag[now] = true;
cout << now << ' ';
cout << now << ' ';
for ( int i = 0; i < a[now].size(); ++i ) {
if ( !flag[a[now][i]] ) {
dfs(a[now][i]);
}
}
}
//广搜
void bfs()
{
queue<int> q;
q.push(1);
flag[1] = true;
while (!q.empty()){
int s = q.front();
q.pop();
cout << s << ' ';
for ( int i = 0; i < a[s].size(); ++i ) {
if ( !flag[a[s][i]] ) {
q.push(a[s][i]);
flag[a[s][i]] = true;
}
}
}
}
int main()
{
cin >> n >> m;
for ( int i = 0; i < m; ++i ) {
int s, e;
cin >> s >> e;
a[s].push_back(e);
//如果是无向图,需要加这个
//a[e].push_back(s);
}
//为什么要排序?
//看题目中:如果有很多篇文章可以参阅,请先看编号较小的那篇(因此你可能需要先排序)。
//排序就保证了每个链表顶点编号是升序的了
for ( int i = 0; i <= n; ++i ) {
sort(a[i].begin(), a[i].end());
}
dfs(1);
cout << '\n';
for ( int i = 0; i <= n; ++i ) {
flag[i] = false;
}
bfs();
return 0;
}
由于这个题目数据太大,用邻接矩阵是存不下的,我下面给出邻接矩阵的版本,下面代码是过不了本题的
#include <iostream>
#include <queue>
using namespace std;
const int N = 20000;//大概只能这么大了
int chart[N][N], n, m;
bool flag[N];
void dfs( int now );
void bfs();
//解释一下这个为什么不需要排序
//因为每次找跟当前点相关的顶点的时候
//都是for( int i = 1; i <= n; ++i )
//默认是按顺序来的
int main()
{
cin >> n >> m;
for ( int i = 1; i <= m; ++i ) {
int s, e;
cin >> s >> e;
chart[s][e] = 1;
//如果是无向图,需要加这个
//chart[e][s] = 1;
}
dfs(1);
for ( int i = 0; i <= n; ++i ) {
flag[i] = false;
}
cout << endl;
bfs();
return 0;
}
void dfs( int now )
{
flag[now] = true;
cout << now << ' ';
for ( int i = 1; i <= n; ++i ) {
if ( !flag[i] && chart[now][i] ) {
dfs(i);
}
}
}
void bfs()
{
queue<int> q;
q.push(1);
flag[1] = true;
while (!q.empty() ) {
int temp = q.front();
cout << temp << ' ';
q.pop();
for ( int i = 1; i <= n; ++i ) {
if ( !flag[i] && chart[temp][i] ) {
q.push(i);
flag[i] = true;
}
}
}
}