图论入门——建图

背景:

本来以前学过的东西都懒得写了,不过小徒弟学图论好像碰到瓶颈了,就写一篇大致讲一下。

概念:

首先,我们要了解图的定义。
什么是图?
一张图 G G G由两个集合组成的二元组,点集 V V V和边集 E E E。即 G = ( V , E ) G=(V,E) G=(V,E)

就是连接点和点之间的关系,分成有向边和无向边两种。
无向图就是都是无向边的图。
有向图就是有向边组成的图。
具体题目中,可能由权值,也就是点权边权

路径是一个点到另一个点经过的边的序列。
简单路径指没有经过重复边的路径。
,也叫回路,指的是一个点经过一条简单路径回到自身。

一个点连的边的数量。
出度一个点连的出边的数量。
入度一个点连的入边的数量。

重边是两条起点和终点相同的边。
自环是自己连到自己的边,理论上也叫环。
因为这两个东西的存在比较奇怪,有时候题目里会特意注明不存在重边和自环,如果没有的话就要小心了。

还有竞赛中常见的一种图有向无环图(Directed Acyclic Graph),顾名思义,就是有方向的没有环的图(都是有向边,没有无向边)。俗称DAG

再常见的一种图,。题目太常见,这里入门就不介绍了。由多棵树组成的图叫森林,名字很形象了。

还有二分图一个图是二分图有一个重要的等价条件是这个图没有奇环。(奇环:奇数个点构成的环)

还有一些会出到题目的高级图:弦图基环树(基环内向树和基环外向树)仙人掌(圆方树)。

也许还有……

建图:

既然知道了图,我们需要学习怎么在代码中构建一张图。

那么构建一张图需要有什么操作呢。
1. 1. 1.加边:我们需要在图中添加一条边。
2. 2. 2.遍历一个点连接的边

用数组把所有边存起来的办法就不说了。
这里介绍三种我常用的建图方式。

一、矩阵存图

就是用一个二维数组,行列都代表点,矩阵内的值表示边权。
那么解决上面两个问题,我们要添加一条边 ( a , b ) (a,b) (a,b),边权为 w w w,那么就 t u [ a ] [ b ] = w tu[a][b]=w tu[a][b]=w。(这里不考虑重边)
如果是无向边,那其实就是添加两条有向边 t u [ a ] [ b ] = t u [ b ] [ a ] = w tu[a][b]=tu[b][a]=w tu[a][b]=tu[b][a]=w

那么另一个问题,怎么去遍历一个点 x x x连的边,其实就是第 x x x行扫过去。

int tu[maxn][maxn];//图

memset(tu,0,sizeof(tu));//初始化图

tu[a][b]=w;//添加一条a到b的边权为w的有向边

tu[a][b]=tu[a][b]=w;//添加一条a到b的边权为w的无向边

//遍历点x连的边
for(int i=1;i<=n;i++)if(i!=x&&tu[x][i]!=0)
{
	printf("点x到点%d,边权为%d的边\n",i,tu[x][i]);
}

这样建图有两个缺陷。一是内存占用是 o ( n 2 ) o(n^{2}) o(n2),当点数为 1 0 5 10^5 105的时候就开不下了。二是我们遍历一个点连的边复杂度是 o ( n ) o(n) o(n),但这个点连的边可能不足 n n n,造成了浪费。
当然也是有好处的,比如删边和加边可以o(1),也可以快速查找某两个点之间的边。

为了有时候对上面两个缺陷的需要,就有了后面两种建图的方式。

二、vector存图(邻接表存图)

数据结构课里的邻接表存图还是老老实实地写链表,这里只介绍竞赛里常用的。
大概介绍一下邻接表存图,我们对每一个点建一个点结点 [ a ] [a] [a],当我们要加一条有向边(a,b)的时候,就把它接在 [ a ] [a] [a]的后面。(如果是无向边,那就 [ a ] [a] [a] [ b ] [b] [b]后面都接一个,即加入有向边(a,b)和(b,a))

比如加入有向边 ( a , b ) (a,b) (a,b), ( a , c ) (a,c) (a,c), ( a , d ) (a,d) (a,d)后, [ a ] [a] [a]的情况如下。
[ a ] − > ( b ) − > ( c ) − > ( d ) [a]->(b)->(c)->(d) [a]>(b)>(c)>(d)
( ) () ()内的表示边的信息,如果还有边权,就变成下面这样了。
[ a ] − > ( b , w b ) − > ( c , w c ) − > ( d , w d ) [a]->(b,wb)->(c,wc)->(d,wd) [a]>(b,wb)>(c,wc)>(d,wd)
我们放到 v e c t o r vector vector里面,就是 v [ a ] = { ( b , w b ) , ( c , w c ) , ( d , w d ) } v[a]=\{(b,wb),(c,wc),(d,wd)\} v[a]={(b,wb),(c,wc),(d,wd)}
或者不想写结构体,也可以用两个 v e c t o r vector vector
v [ a ] = { b , c , d } v[a]=\{b,c,d\} v[a]={b,c,d}
g [ a ] = { w b , w c , w d } g[a]=\{wb,wc,wd\} g[a]={wb,wc,wd}
上下一一出点和边权对应就行了。

//第一种写法,用结构体

struct E
{
	int to,w;
};
vector<E>v[maxn];//图

for(int i=0;i<maxn;i++)v[i].clear();//初始化图

v[a].push_back(E{b,w});//添加一条a到b的边权为w的有向边

//添加一条a到b的边权为w的无向边
v[a].push_back(E{b,w});
v[b].push_back(E{a,w});

//遍历点x的连的出边
for(int i=0;i<v[x].size();i++)
{
	int to=v[x][i].to,w=v[x][i].w;
	printf("点x到点%d,边权为%d的边\n",to,w);
}

//第二种写法,不用结构体

vector<int>v[maxn],g[maxn];//图

for(int i=0;i<maxn;i++)v[i].clear(),g[i].clear();//初始化图

v[a].push_back(b);//添加一条a到b的边权为w的有向边
g[a].push_back(w);

//添加一条a到b的边权为w的无向边
v[a].push_back(b);
g[a].push_back(w);
v[b].push_back(a);
g[b].push_back(w);

//遍历点x的连的出边
for(int i=0;i<v[x].size();i++)
{
	int to=v[x][i],w=g[x][i];
	printf("点x到点%d,边权为%d的边\n",to,w);
}

三、链式前向星

存图方式和前面的一样,都是接在后面。
不过这个是用链表实现,这个写的时候链表也不用指针,用下标索引实现。
具体理解看代码吧。

struct 
{
	int to,w,next;
}edge[maxm];
int head[maxn],tot;//图
void init()//初始化图
{
	tot=0;
	memset(head,-1,sizeof(head));
}
void addedge(int a,int b,int w)//添加一条a到b边权为w的有向边,无向边就正反各加一次
{
	edge[tot].to=b;
	edge[tot].w=w;
	edge[tot].next=head[a];
	head[a]=tot++;
}

//遍历点x的连的出边
for(int i=head[x];i!=-1;i=edge[i].next)
{
	int to=edge[i].to,w=edge[i].w;
	printf("点x到点%d,边权为%d的边\n",to,w);
}

讲完了。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值