图论

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;
}

6.并查集(详文

7.最小生成树相关算法(详文

8.线段树

9.拓扑排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值