1.图的基本知识
(1)图的类型:有向图、无向图、加权图;
有向图和无向图从字面理解就可以知道一个有方向,一个无方向;
而加权图则是每条边都有权重,权重可以是任何一种度量,比如时间,距离,尺寸等。
(2)图的专业术语:
>>顶点、边、路径(从一个顶点到另一个顶点之间经过的所有顶点的集合)、路径长度(边数)。
>>环:起点和终点为同一个顶点的路径。
>>负权环:在「加权图」中,如果一个环的所有边的权重加起来为负数,我们就称之为「负权环」。
>>连通性:两个不同顶点之间存在至少一条路径,则称这两个顶点是连通的。
>>顶点的度:「度」适用于无向图,指的是和该顶点相连接的所有边数称为顶点的度。
>>顶点的入度:「入度」适用于有向图,一个顶点的入度为d,则表示有d条与顶点相连的边指向该顶点。
>>顶点的出度:「出度」适用于有向图,它与「入度」相反。一个顶点的出度为dd,则表示有dd条与顶点相连的边以该顶点为起点。
>>单源最短路:已知起始点,求起点到其他任意点最短路径的问题。
>>多源最短路:已知起点和终点,求任意两点之间的最短路径。
>>连通图:在无向图中,若任意两个顶点vi与vj都有路径相通,则称该无向图为连通图。
>>强连通图:在有向图中,若任意两个顶点vi与vj都有路径相通,则称该有向图为强连通图。
>>连通网:在连通图中,若图的边具有一定的意义,每一条边都对应着一个数,称为权;权代表着连接连个顶点的代价,称这种连通图叫做连通网。
>>生成树:一个连通图的生成树是指一个连通子图,它含有图中全部n个顶点,但只有足以构成一棵树的n-1条边。一颗有n个顶点的生成树有且仅有n-1条边,如果生成树中再添加一条边,则必定成环。
>>最小生成树:在连通网的所有生成树中,所有边的代价和最小的生成树,称为最小生成树。
2.图的存储
(1)矩阵存图法:
#include<iostream>
#define maxn 102
using namespace std;
int n,a[maxn][maxn],x,y,k;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>x>>y>>k;
a[x][y]=k;
a[y][x]=k; //无向图需要(元素对称)
}
//依次对每个顶点访问
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i][j]) cout<<i<<"->"<<j<<" "<<a[i][j]<<endl;
}
}
return 0;
}
(2)邻接表存图法:
方法1:
#include<iostream>
#define maxn 1002
using namespace std;
int num;
int head[1001],next[maxn],to[maxn],w[maxn];
void add(int x,int y,int k){
++num;
to[num]=y; //储存终点
w[num]=k; //储存权值
next[num]=head[x]; //进行记录可查找邻边
head[x]=num; //随时更新表头
}
int main(){
int n,m,x,y,k;
cin>>n>>m; //n为边数,m为顶点数
for(int i=1;i<=n;i++){ //i为边的编号
cin>>x>>y>>k;
add(x,y,k);
add(y,x,k); //无向图需要
}
//依次对每个顶点访问
for(int i=1;i<=m;i++){
for(int j=head[i];j ;j=next[j]){
cout<<i<<"->"<<to[j]<<" "<<w[j]<<endl;
}
}
return 0;
}
方法2:这里用到了动态数组容器vector(详文)。
#include<iostream>
#include<vector>
#define maxn 1002
using namespace std;
vector<int> to[maxn]; //终点
vector<int> w[maxn]; //边的权值
void add(int x,int y,int k){
to[x].push_back(y);
w[x].push_back(k);
}
int main(){
int n,m,x,y,k;
cin>>n>>m; //n为边数,m为顶点数
for(int i=1;i<=n;i++){ //i为边的编号
cin>>x>>y>>k;
add(x,y,k);
add(y,x,k); //无向图需要
}
//依次对每个顶点访问
for(int i=1;i<=m;i++){
for(int j=0;j<to[i].size();j++){
cout<<i<<"->"<<to[i][j]<<" "<<w[i][j]<<endl;
}
}
return 0;
}
(3)链式前向星存图法:
和上面邻接表存图的方法1思路一样,不过这里用的是结构体。
#include<iostream>
#include<vector>
#define maxn 1002
using namespace std;
int num,head[1003];
struct{
int to,next,w;
}e[maxn];
void add(int x,int y,int k){
num++;
e[num].to=y;
e[num].w=k;
e[num].next=head[x];
head[x]=num;
}
int main(){
int n,m,x,y,k;
cin>>n>>m; //n为边数,m为顶点数
for(int i=1;i<=n;i++){ //i为边的编号
cin>>x>>y>>k;
add(x,y,k);
add(y,x,k); //无向图需要
}
//依次对每个顶点访问
for(int i=1;i<=m;i++){
for(int j=head[i];j ;j=e[j].next){
cout<<i<<"->"<<e[j].to<<" "<<e[j].w<<endl;
}
}
return 0;
}
3.图的深度优先搜索(dfs)
注:欧拉回路有0个奇点,欧拉路有2个奇点(起点是一个奇点,终点是另一个奇点)!
例题 一笔画问题
时间限制: 1000 ms 内存限制: 65536 KB
提交数: 14352 通过数: 5157
【题目描述】
如果一个图存在一笔画,则一笔画的路径叫做欧拉路,如果最后又回到起点,那这个路径叫做欧拉回路。
根据一笔画的两个定理,如果寻找欧拉回路,对任意一个点执行深度优先遍历;找欧拉路,则对一个奇点执行dfs,时间复杂度为O(m+n),m为边数,n是点数。
【输入】
第一行n,m,有n个点,m条边,以下m行描述每条边连接的两点。
【输出】
欧拉路或欧拉回路,输出一条路径即可。
【输入样例】
5 5
1 2
2 3
3 4
4 5
5 1
【输出样例】
1 5 4 3 2 1
题解:
#include<iostream> //欧拉路2个奇点,欧拉回路0个奇点
#define maxn 1002
using namespace std;
int n,m,a[maxn][maxn],vis[maxn],p[maxn],t=0;
void dfs(int i){
vis[++t]=i;
for(int j=1;j<=n;j++){
if(a[i][j]){
a[i][j]=0,a[j][i]=0; //删除已走边
dfs(j);
}
}
}
int main(){
cin>>n>>m;
for(int x,y,i=1;i<=m;i++){
cin>>x>>y;
a[x][y]=1;
a[y][x]=1;
p[x]++;
p[y]++;
}
int flag=1;
for(int i=1;i<=n;i++){ //找奇点
if(p[i]%2){
flag=0;
dfs(i);
break;
}
}
if(flag) dfs(1);
for(int i=1;i<=t;i++) cout<<vis[i]<<" ";
return 0;
}
骑马修栅栏(fence)
时间限制: 1000 ms 内存限制: 65536 KB
提交数: 10644 通过数: 3109
【题目描述】
农民John每年有很多栅栏要修理。他总是骑着马穿过每一个栅栏并修复它破损的地方。
John是一个与其他农民一样懒的人。他讨厌骑马,因此从来不两次经过一个一个栅栏。你必须编一个程序,读入栅栏网络的描述,并计算出一条修栅栏的路径,使每个栅栏都恰好被经过一次。John能从任何一个顶点(即两个栅栏的交点)开始骑马,在任意一个顶点结束。
每一个栅栏连接两个顶点,顶点用1到500标号(虽然有的农场并没有500个顶点)。一个顶点上可连接任意多(≥1)个栅栏。所有栅栏都是连通的(也就是你可以从任意一个栅栏到达另外的所有栅栏)。
你的程序必须输出骑马的路径(用路上依次经过的顶点号码表示)。我们如果把输出的路径看成是一个500进制的数,那么当存在多组解的情况下,输出500进制表示法中最小的一个 (也就是输出第一个数较小的,如果还有多组解,输出第二个数较小的,等等)。 输入数据保证至少有一个解。
【输入】
第1行:一个整数F(1≤F≤1024),表示栅栏的数目;
第2到F+1行:每行两个整数i,j(1≤=i,j≤500)表示这条栅栏连接i与j号顶点。
【输出】
输出应当有F+1行,每行一个整数,依次表示路径经过的顶点号。注意数据可能有多组解,但是只有上面题目要求的那一组解是认为正确的。
【输入样例】
9
1 2
2 3
3 4
4 2
4 5
2 5
5 6
5 7
4 6
【输出样例】
1
2
3
4
2
5
4
6
5
7
题解:
分析:和一笔画问题有一点点不一样,需要回溯记录路径,两点之间的栅栏不一定只有一个 。
#include<iostream>
#include<cmath>
#define maxn 1030
using namespace std;
int n,a[maxn][maxn],mx,mn=1000,vis[maxn],s,p[maxn];
void dfs(int x){
for(int i=mn;i<=mx;i++){
if(a[x][i]){
a[x][i]--,a[i][x]--;
dfs(i);
}
}
p[++s]=x; //需要回溯记录路径 !!!
}
int main(){
cin>>n;
for(int x,y,i=1;i<=n;i++){
cin>>x>>y;
//两点之间的栅栏不一定只有一个 !!!
a[x][y]++;
a[y][x]++;
mx=max(mx,max(x,y));
mn=min(mn,min(x,y));
vis[x]++,vis[y]++;
}
int flag=1;
for(int i=mn;i<=mx;i++){
if(vis[i]%2){
flag=0;
dfs(i);
break;
}
}
if(flag) dfs(mn);
for(int i=s;i>0;i--){
cout<<p[i]<<endl;
}
return 0;
}
4.图的广度优先搜索(bfs)
5.最短路径(详文)
1376:信使(msner)
时间限制: 1000 ms 内存限制: 65536 KB
提交数: 9231 通过数: 4582
【题目描述】
战争时期,前线有n个哨所,每个哨所可能会与其他若干个哨所之间有通信联系。信使负责在哨所之间传递信息,当然,这是要花费一定时间的(以天为单位)。指挥部设在第一个哨所。当指挥部下达一个命令后,指挥部就派出若干个信使向与指挥部相连的哨所送信。当一个哨所接到信后,这个哨所内的信使们也以同样的方式向其他哨所送信。直至所有n个哨所全部接到命令后,送信才算成功。因为准备充足,每个哨所内都安排了足够的信使(如果一个哨所与其他k个哨所有通信联系的话,这个哨所内至少会配备k个信使)。
现在总指挥请你编一个程序,计算出完成整个送信过程最短需要多少时间。
【输入】
第1行有两个整数n和m,中间用1个空格隔开,分别表示有n个哨所和m条通信线路,且1≤n≤100。
第2至m+1行:每行三个整数i、j、k,中间用1个空格隔开,表示第i个和第j个哨所之间存在通信线路,且这条线路要花费k天。
【输出】
一个整数,表示完成整个送信过程的最短时间。如果不是所有的哨所都能收到信,就输出-1。
【输入样例】
4 4
1 2 4
2 3 7
2 4 1
3 4 6
【输出样例】
11
题解:
(1)Floyd算法(多源最短路算法):
#include<iostream>
#include<cstring>
using namespace std;
int n,m;
int map[102][102];
const int inf=0x3f3f3f3f;
int main(){
int n,m;
cin>>n>>m;
memset(map,inf,sizeof(map));
for(int u,v,w,i=1;i<=m;i++){
cin>>u>>v>>w;
map[u][v]=w;
map[v][u]=w;
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(map[i][j]>map[i][k]+map[k][j])
map[i][j]=map[i][k]+map[k][j];
}
}
}
int mx=0;
for(int i=2;i<=n;i++){
if(map[1][i]>mx) mx=map[1][i];
}
if(mx==inf) cout<<-1;
else cout<<mx;
return 0;
}
(2)Dijkstra算法(单源最短路算法): 详文
#include<iostream>
#include<cstring>
using namespace std;
int n,m,mapp[102][102],vis[102],dis[102];
const int inf=0x3f3f3f3f;
int main(){
cin>>n>>m;
memset(mapp,inf,sizeof(mapp));
for(int a,b,c,i=1;i<=m;i++){
cin>>a>>b>>c;
mapp[a][b]=c;
mapp[b][a]=c;
}
for(int i=1;i<=n;i++) dis[i]=mapp[1][i];
vis[1]=1;
for(int i=1;i<n;i++){
int mn=inf,u;
for(int j=1;j<=n;j++){
if(!vis[j]&&dis[j]<mn){
mn=dis[j];
u=j;
}
}
vis[u]=1;
for(int j=1;j<=n;j++){
if(dis[u]+mapp[u][j]<dis[j])
dis[j]=dis[u]+mapp[u][j];
}
}
int mx=0;
for(int i=2;i<=n;i++){
if(dis[i]>mx) mx=dis[i];
}
if(mx==inf) cout<<-1;
else cout<<mx;
return 0;
}
(3)Bellman-Ford算法:
#include<iostream>
#include<cstring>
using namespace std;
int n,m;
int u[502],v[502],w[502],dis[102];
const int inf=0x3f3f3f3f;
int main(){
int n,m;
cin>>n>>m;
for(int a,b,c,i=1;i<=m;i++){
cin>>a>>b>>c;
u[2*i-1]=a,v[2*i-1]=b,w[2*i-1]=c;
u[2*i]=b,v[2*i]=a,w[2*i]=c;
}
memset(dis,inf,sizeof(dis));
dis[1]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=2*m;j++){
if(dis[u[j]]+w[j]<dis[v[j]])
dis[v[j]]=dis[u[j]]+w[j];
}
}
int mx=0;
for(int i=2;i<=n;i++){
if(dis[i]>mx) mx=dis[i];
}
if(mx==inf) cout<<-1;
else cout<<mx;
return 0;
}
(4)SPFA算法:
spfa+队列(邻接矩阵)
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
int n,m;
int map[102][102],dis[102],vis[102];
const int inf=0x3f3f3f3f;
queue<int> q;
int main(){
int n,m;
cin>>n>>m;
memset(map,inf,sizeof(map));
for(int u,v,w,i=1;i<=m;i++){
cin>>u>>v>>w;
map[u][v]=w;
map[v][u]=w;
}
memset(dis,inf,sizeof(dis));
dis[1]=0;
vis[1]=1;
q.push(1);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=1;i<=n;i++){
if(dis[i]>dis[u]+map[u][i]){
dis[i]=dis[u]+map[u][i];
if(!vis[i]){
vis[i]=1;
q.push(i);
}
}
}
}
int mx=0;
for(int i=2;i<=n;i++){
if(dis[i]>mx) mx=dis[i];
}
if(mx==inf) cout<<-1;
else cout<<mx;
return 0;
}
spfa+队列(链式前向星)
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
int n,m,cnt;
int dis[102],vis[102],head[102];
const int inf=0x3f3f3f3f;
queue<int> q;
struct map{
int to,w,next;
}e[502];
void spfa(){
memset(dis,inf,sizeof(dis));
dis[1]=0;
vis[1]=1;
q.push(1);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i ;i=e[i].next){
int s=e[i].to;
if(dis[s]>dis[u]+e[i].w){
dis[s]=dis[u]+e[i].w;
if(!vis[s]){
vis[s]=1;
q.push(s);
}
}
}
}
}
void add(int a,int b,int c){
cnt++;
e[cnt].to=b;
e[cnt].w=c;
e[cnt].next=head[a];
head[a]=cnt;
}
int main(){
int n,m;
cin>>n>>m;
for(int a,b,c,i=1;i<=m;i++){
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
spfa();
int mx=0;
for(int i=2;i<=n;i++){
if(dis[i]>mx) mx=dis[i];
}
if(mx==inf) cout<<-1;
else cout<<mx;
return 0;
}