图
有若干个节点,有若干条边连接节点。(两个点之间不是必须相连)
比如:
有向图
可以理解为边上面有箭头的图,比如下面这张图:
在这张图中,点
1
1
1 可以通过这条有向边到达点
2
2
2,但是点
2
2
2 到达不了点
1
1
1。
有向图的边是有单向性的。
无向图
只要连了边,边两端的点都可以互相到达。
这张图是无向图,里面任一点都可以互相到达。
这就是一张连通图。
连通图
一张无向图,如果任意一个点都可以到达图上的所有点,那么这张无向图就是连通图。
如图1。
强连通图
一张有向图,如果任意两个点都可以互相到达,这就是一张强连通图。
比如上面的这张图就是不联通的,不是强连通图。
如果再加上一条边:
现在这就是一张强连通图了。
带权图
可以理解为图中的边被加上了一个权值,经过这条边就要付出对应权值的代价。
比如:
在这张图中,点
1
1
1 走到点
4
4
4 的总权值是
3
+
5
+
2
=
10
3+5+2=10
3+5+2=10。
图的存储
邻接矩阵
假设有一个
N
N
N 个点的图,我们可以开一个
N
∗
N
N*N
N∗N 的二维数组
a
a
a,
a
i
j
a_{ij}
aij 表示点
i
i
i 到点
j
j
j 有没有边。
如果是带权图,也可以用
a
i
j
a_{ij}
aij 表示点
i
i
i 到点
j
j
j 边的权值。
例题1
题目描述
给出一个无向图,顶点数为
n
n
n,边数为
m
m
m。
n
≤
1000
,
m
≤
10000
n\le1000,m\le10000
n≤1000,m≤10000
输入格式
第一行两个整数
n
,
m
n,m
n,m,接下来有
m
m
m 行,每行两个整数
u
,
v
u,v
u,v,表示点
u
u
u 到点
v
v
v 有一条边。
输出格式
由小到大依次输出每个点的邻接点,邻接点也按照由小到大的顺序输出。
首先
n
n
n 只有
1000
1000
1000,可以用邻接矩阵存图。
是无向图,所以
u
u
u 到
v
v
v 和
v
v
v 到
u
u
u 都有一条边,所以要双向存。
#include<bits/stdc++.h>
#define psp putchar(' ')
#define endl putchar('\n')
using namespace std;
const int M=1e3+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,m;
int a[M][M];
signed main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read();
//双向存边
a[u][v]=1;
a[v][u]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i][j])print(j),psp;//有边,输出。
}
endl;
}
}
邻接表
假如一张图有
2048
2048
2048 个点,那么用邻接矩阵就要开
204
8
2
2048^2
20482 的数组,那如果这张图只有
10
10
10 条边,开这么大的空间就全浪费了。
这时候就要用到邻接表。
可以用动态数组来存点和边,减少空间的浪费。
vector<int>a[N];
例题2
题目描述
给出一个无向图,顶点数为
n
n
n,边数为
m
m
m。
n
≤
1000
,
m
≤
10000
n\le1000,m\le10000
n≤1000,m≤10000
输入格式
第一行两个整数
n
,
m
n,m
n,m,接下来有
m
m
m 行,每行两个整数
u
,
v
u,v
u,v,表示点
u
u
u 到点
v
v
v 有一条边。
输出格式
第i行输出第点
i
i
i 的所有邻接点,邻接点按照度数由小到大输出,如果度数相等,则按照编号有小到大输出。
按照度数排序,用邻接表更方便。
#include<bits/stdc++.h>
#define endl putchar('\n')
using namespace std;
const int N=1e6+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,m;
vector<int>a[N];
int d[N];
bool cmp(int a,int b){//按照度数排序
return d[a]<d[b]||(d[a]==d[b]&&a<b);
}
signed main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read();
//邻接表存图
a[u].push_back(v);
a[v].push_back(u);
d[u]++,d[v]++;
}
for(int i=1;i<=n;i++){
sort(a[i].begin(),a[i].end(),cmp);
for(int j=0;j<a[i].size();j++)print(a[i][j]),psp;
endl;
}
}
例题3
题目描述
K
(
1
≤
K
≤
100
)
K(1\le K\le100)
K(1≤K≤100) 只奶牛分散在
N
(
1
≤
N
≤
1000
)
N(1\le N\le1000)
N(1≤N≤1000) 个牧场。现在她们要集中起来进餐。
牧场之间有
M
(
1
≤
M
≤
10000
)
M(1\le M\le10000)
M(1≤M≤10000) 条有向路连接,而且不存在起点和终点相同的有向路。她们进餐的地点必须是所有奶牛都可到达的地方。
那么,有多少这样的牧场呢?
输入格式
第1行输入
K
,
N
,
M
K,N,M
K,N,M。
接下来
K
K
K 行,每行一个整数表示一只奶牛所在的牧场编号。
接下来
M
M
M 行,每行两个整数,表示一条有向路的起点和终点。
输出格式
所有奶牛都可到达的牧场个数。
由于 N N N 最大只有 1000 1000 1000, K K K 最大只有 100 100 100,所以可以考虑枚举每一头牛,然后记他们可以到达的点,最后的答案就有所有牛都能到达点的数量。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,m,k;
int cow[N];
vector<int>a[N];
int vis[N];
int dis[N];//dis[i]表示点i有几头牛可以到达
signed main(){
k=read(),n=read(),m=read();
for(int i=1;i<=k;i++)cow[i]=read();
while(m--){
int u=read(),v=read();
a[u].push_back(v);
}
for(int i=1;i<=k;i++){
queue<int>q;
for(int j=1;j<=n;j++)vis[j]=0;
vis[cow[i]]=1;
q.push(cow[i]);
while(!q.empty()){
int x=q.front();
q.pop();
dis[x]++;//记录
for(int p=0;p<a[x].size();p++){
int y=a[x][p];
if(vis[y])continue;
vis[y]=1;
q.push(y);
}
}
}
int ans=0;
for(int i=1;i<=n;i++)ans+=(dis[i]==k);
print(ans);
}
例题4
P3144 [USACO16OPEN] Closing the Farm S
依次遍历每个仓库关闭,检查是否能遍历到除关闭仓库外的所有仓库。
#include<bits/stdc++.h>
#define endl putchar('\n')
using namespace std;
const int N=1e6+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void putstr(string s){
for(char v:s)putchar(v);
}
int n,m;
vector<int>a[N];
int cl[N];
int vis[N];
signed main(){
n=read(),m=read();
while(m--){
int u=read(),v=read();
a[u].push_back(v);
a[v].push_back(u);
}
int st=1;
for(int i=1;i<=n;i++){
while(cl[st])st++;
queue<int>q;
q.push(st);
for(int i=1;i<=n;i++)vis[i]=0;
vis[st]=1;
while(!q.empty()){
int x=q.front();
q.pop();
for(int i=0;i<a[x].size();i++){
int y=a[x][i];
if(vis[y])continue;
if(cl[y])continue;
vis[y]=1;
q.push(y);
}
}
bool flag=1;
for(int i=1;i<=n;i++){
if(cl[i])continue;
if(!vis[i])flag=0;
}
cl[read()]=1;
if(flag)putstr("YES");
else putstr("NO");
endl;
}
}
最短路
一张带权图,一个点 x x x 到另一个点 y y y 所经过边的最小权值和称为点 x x x 到点 y y y 的最短路。
Dijkstra
一种高效的求最短路的算法,缺点是不能求带负权的最短路。
它的主要用途是求一个点到图中其他点的最短路。
具体过程
最开始,除了起点外所有点没有被标记过,起点是最开始被选中的点。
开始模拟:找到被选中的点连接的最小边权的且没有被标记过的点
y
y
y,记录最短路,然后点
y
y
y 被标记,并成为下一个被选中的点,直到所有点都被标记。
例题5
P4779 【模板】单源最短路径(标准版)
具体代码见单源最短路径
SPFA
讲解+例题见带负权的最短路问题
见无向图 ↩︎