图论基础知识(2)-三种存图法

Markdown真是不知道比TinyMCE高到哪里去 ##前言 若是咱们要用电脑去处理一幅图的化,首先咱们得想办法把这副图给存储起来。存图并非像存数字那么简单开几个变量就能实现的,存图通常来说有3种方法,它们分别是node

  1. 邻接矩阵
  2. 邻接表
  3. 链式前向星

那么咱们先从最容易理解的邻接矩阵来看吧 ###邻接矩阵 邻接矩阵的思想是在矩阵中存储每一条边的起点和终点来达到存储一幅图的目的,能够看下面的这幅图c++

这幅图里面一共有6个点,因此咱们建立一个6*6的0矩阵,而且对于每一条边,咱们找出它的起点和终点,而且在矩阵的[x][y]处的0改为1.数组

上面这幅图在邻接矩阵里面应该这么表示:网络

打个比方,能够观察这幅图,咱们发现对于一条链接着4和5的边,矩阵里面的(4,5)和(5,4)位置上的0都变成了1,这就是邻接矩阵的存图方法,对于图中的任意一条链接着u和v的边,咱们在矩阵里面的(u,v)位置打一个标记,表示有一条边是从u到v的,同时,由于原图是一个无向图,因此对于每一组(u,v),它们在矩阵上的(v,u)位置也要标记。若是对于有向图来讲就不必了spa

顺便说一句,<b>通常</b>在比赛里面咱们输入一个图的形式都是这样的:code

6 6 (表示这幅图一共有6个点和6条边) 1 4 1 3 1 5 2 4 4 5 3 6 因此在写程序的时候要注意一下。 ###邻接矩阵的弊端 虽说邻接矩阵很是容易理解,可是它也有很大的缺陷:邻接矩阵消耗的空间太大了。若是咱们存储一个稀疏图($|E|$远小于$|V|^2$),那么咱们的矩阵实际上大部分都是0,也就是说咱们占用了过多的空间。实际上,邻接矩阵在数学方面是一种很好的存图方式,可是涉及到计算机的时候就须要考虑时间和空间,因此邻接矩阵在咱们实际写程序的时候仍是少用(固然对于数据量比较小的题目仍是能够用一用的)blog

下面给出邻接矩阵的代码:(仍是很是简单的)get

#include <bits/stdc++.h>
using namespace std;
const int maxn=1926;
int n,m,g[maxn][maxn];
int main(void){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d %d",&u,&v);
		g[u][v]=g[v][u]=1;
	}
	for(int i=1;i<=n;i++){
		printf("%d: ",i);
		for(int j=1;j<=n;j++){
			if(g[i][j]) printf("%d ",j);
		}
		printf("\n");
	}
}

###邻接表 既然邻接矩阵被咱们淘汰了,咱们就须要找一个能够替代邻接矩阵的东西,它就是邻接表 咱们能够从上面的代码里面发现,就算咱们一个点u只邻接了1条边,咱们的程序仍是得扫描g[u][1~n],由于咱们一个点对应着它和n个点之间的信息。那么,若是咱们只存储边的信息不久好了吗?邻接表就这么诞生出来了 我的认为比起语言叙述,画一个图更能体现出邻接表的思想源码

仍是这幅图: 数学

他的邻接表长这样:

(字丑因此画了好几遍)

咱们能够从表里面发现,咱们再也不是单纯的存储0和1了,由于邻接表存储的是边。就上图来讲,第一列存储的是1~N个点的编号,而后咱们再看第一行,1后面的3,4,5意思是和1相邻接的第一条边的终点是3,和1相邻接的第二条边的终点是4,和1相邻接的第三条边的终点是5。这样的化咱们的空间就节省了许多,同时访问的速度也快了许多。

能够试一试这道题: https://www.luogu.com.cn/problem/P3916 很是基础的图论练手题(比打着普及-的标签还要你套最短路的水题不知道高到哪里去),我的以为很适合练手 代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=100010;
struct edge{
	int to;
	edge(int to_){
		to=to_;
	}
};
vector<edge> gpe[maxn];
int ans[maxn],m,n;
void dfs(int u,int d){
	if(ans[u]) return;
	ans[u]=max(ans[u],d);
	for(int i=0;i<gpe[u].size();i++){
		int v=gpe[u][i].to;
		dfs(v,d);
	}
} 
int main(void){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d %d",&u,&v);
		gpe[v].push_back(edge(u));
	}
	for(int i=n;i>=1;i--) dfs(i,i);
	for(int i=1;i<=n;i++){
		printf("%d ",ans[i]); 
	}
}

AC记录:

###链式前向星

我我的不多用到链式前向星这种方法,可是周围的dalao都在用,也许这就是我和dalao们的差距吧( emmm...链式前向星其实和邻接表差很少,可是因为咱们在写邻接表的时候常常用到vector, 有些dalao写的stl源码剖析中写了,vector会先开2倍空间,而且还因为每一个人的写法不一样,有些邻接表的写法会致使效率上略逊于链式前向星,可是我目前还没遇到卡邻接表的题目。。。

链式前向星有2个组成部分,edge数组和head数组 edge[i]里面有to,w,next三个变量。to存储的是这条边的终点,w存储的是边权,next存储的是在edge数组里面,此时u所邻接的下一条边的下标(好绕人。。。),而后head[u]数组存储了对于某一个点u,它在edge数组里面的下标 因此咱们能够根据以上的描述写出链式前向星的代码:

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int head[N];
int cnt=0;
struct node{
    int from;
    int to;
    int w;
}edge[N];
void add(int u,int v,int w){
    edge[cnt].w=w;
    edge[cnt].to=v;
    edge[cnt].from=head[u];
    head[u]=cnt++;
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    memset(head,0,sizeof(head));
    while(m--){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
    }
    int start;
    scanf("%d",&start);
    for(int i=head[start];i!=0;i=edge[i].from){
        cout<<start<<"->"<<edge[i].to<<" "<<edge[i].w<<endl;
    }
    return 0;
}

###总结 多是由于我太蒻了,因此链式前向星这部分我讲的不是很清晰,感兴趣的化能够在网上自行搜索相关知识。。。 建图方面还有一些其余的知识(好比说网络流中如何建反边)还没提到,这只是一些比较基本的知识。 若是只是单纯考察建图,我只找到了那一道。。。

相关文章
相关标签/搜索