一、目录
二、前言
本来之前自己太弱了,一直只会用邻接矩阵,后来发现邻接表才是主流...
参考&特别鸣谢:
https://www.cnblogs.com/dilthey/p/9016321.html
https://www.cnblogs.com/ECJTUACM-873284962/p/6905416.html
三、邻接表“详解”
专业邻接表知识许多博客和书上都阐述得很详尽了,
自己这里只是简单地解析,可能用语不太准确,主要目的是用自己懂的方式解释一下
(一)边的信息
1.对于每条边而言:
一般做题时题目会提供很多关于每条边的信息,如边权、点权,这时我们要把需要的信息存下来
一般有三个:u—边的起始端点、v—边的终止端点、w—边权or长度,
常规用结构体储存:
struct edge
{
int u,v,w;
edge(int _u=0,int _v=0,int _w=0){u=_u,v=_v,w=_w;}
//edge(edge &e){u=e.u,v=e.v,w=e.w;}
};
2.对于整个图而言:
知道了每条边的信息,还要把它们按照题目所给的关系连起来,组成一个完整的图
对于每条边,想要让它和其他边连起来,肯定会想到一个关键信息:
next / to——该边与之相连的边的编号
这部分就会涉及到不同的实现方法了,方法不同,关键信息也不同
(二)几种邻接表的实现方法“详解”
1.数组邻接表
第一行两个整数n m。n表示顶点个数(顶点编号为1~n),m表示边的条数。接下来m行表示,每行有3个数x y z,表示顶点x到顶点y的边的权值为z。下图就是一种使用链表来实现邻接表的方法。
上面这种实现方法为图中的每一个顶点(左边部分)都建立了一个单链表(右边部分)。这样我们就可以通过遍历每个顶点的链表,从而得到该顶点所有的边了。使用链表来实现邻接表对于痛恨指针的的朋友来说,这简直就是噩梦。这里我将为大家介绍另一种使用数组来实现的邻接表,这是一种在实际应用中非常容易实现的方法。这种方法为每个顶点i(i从1~n)也都保存了一个类似“链表”的东西,里面保存的是从顶点i出发的所有的边,具体如下。
首先我们按照读入的顺序为每一条边进行编号(1~m)。比如第一条边“1 4 9”的编号就是1,“1 3 7”这条边的编号是5。
这里用u、v和w三个数组用来记录每条边的具体信息,即u[i]、v[i]和w[i]表示第i条边是从第u[i]号顶点到v[i]号顶点(u[i]àv[i]),且权值为w[i]。
再用一个first数组来存储每个顶点其中一条边的编号。以便待会我们来枚举每个顶点所有的边(你可能会问:存储其中一条边的编号就可以了?不可能吧,每个顶点都需要存储其所有边的编号才行吧!甭着急,继续往下看)。比如1号顶点有一条边是 “1 4 9”(该条边的编号是1),那么就将first[1]的值设为1。如果某个顶点i没有以该顶点为起始点的边,则将first[i]的值设为-1。现在我们来看看具体如何操作,初始状态如下。
咦?上图中怎么多了一个next数组,有什么作用呢?不着急,待会再解释,现在先读入第一条边“1 4 9”。
读入第1条边(1 4 9),将这条边的信息存储到u[1]、v[1]和w[1]中。同时为这条边赋予一个编号,因为这条边是最先读入的,存储在u、v和w数组下标为1的单元格中,因此编号就是1。这条边的起始点是1号顶点,因此将first[1]的值设为1。
另外这条“编号为1的边”是以1号顶点(即u[1])为起始点的第一条边,所以要将next[1]的值设为-1。也就是说,如果当前这条“编号为i的边”,是我们发现的以u[i]为起始点的第一条边,就将next[i]的值设为-1(貌似的这个next数组很神秘啊⊙_⊙)。
读入第2条边(4 3 8),将这条边的信息存储到u[2]、v[2]和w[2]中,这条边的编号为2。这条边的起始顶点是4号顶点,因此将first[4]的值设为2。另外这条“编号为2的边”是我们发现以4号顶点为起始点的第一条边,所以将next[2]的值设为-1。
读入第3条边(1 2 5),将这条边的信息存储到u[3]、v[3]和w[3]中,这条边的编号为3,起始顶点是1号顶点。我们发现1号顶点已经有一条“编号为1 的边”了,如果此时将first[1]的值设为3,那“编号为1的边”岂不是就丢失了?我有办法,此时只需将next[3]的值设为1即可。现在你知道next数组是用来做什么的吧。next[i]存储的是“编号为i的边”的“前一条边”的编号。
读入第4条边(2 4 6),将这条边的信息存储到u[4]、v[4]和w[4]中,这条边的编号为4,起始顶点是2号顶点,因此将first[2]的值设为4。另外这条“编号为4的边”是我们发现以2号顶点为起始点的第一条边,所以将next[4]的值设为-1。
读入第5条边(1 3 7),将这条边的信息存储到u[5]、v[5]和w[5]中,这条边的编号为5,起始顶点又是1号顶点。此时需要将first[1]的值设为5,并将next[5]的值改为3。
此时,如果我们想遍历1号顶点的每一条边就很简单了。1号顶点的其中一条边的编号存储在first[1]中。其余的边则可以通过next数组寻找到。请看下图。
细心的同学会发现,此时遍历边某个顶点边的时候的遍历顺序正好与读入时候的顺序相反。因为在为每个顶点插入边的时候都直接插入“链表”的首部而不是尾部。不过这并不会产生任何问题,这正是这种方法的其妙之处。
上述解析转载自:https://www.cnblogs.com/ECJTUACM-873284962/p/6905416.html
简而言之,该做法有两个关键信息:
head[ u ] / first[ u ](叫法不同)——最新的、起始端点为u的边的编号
next[ i ] / to[ i ]——该边(与之相连的)前一条边的编号
注意:
在题目卡vector时可以使用,如果include了STL库,可能next数组会产生ambiguous,需要改个名字或者改用链式前向星(如下)
代码
const int MAXN=1e5;
const int MAXM=1e5;
struct Edge
{
int u,v,w;
Edge(int _u=0,int _v=0,int _w=0){u=_u,v=_v,w=_w;}
Edge(Edge &e){u=e.u,v=e.v,w=e.w;}
};
Edge E[MAXM+5];
int head[MAXN+5],next[MAXM+5],cnt;
void init()
{
cnt=0;
memset(head,0,sizeof(head));
}
void addedge(int u,int v,int w)
{
E[++cnt]=Edge(u,v,w);
next[cnt]=head[u];
head[u]=cnt;
}
遍历某个链表的方法:
for(int i=head[u];i;i=next[i])
2.vector邻接表
其实邻接表的几个做法本质上并无太大区别...
vector的邻接表就是把 head[ ] 和 next[ ]用vector合并了一下,把以u为起始端点的边的编号全部塞到G[ u ]中
代码
const int MAXN=1e5;
struct Edge
{
int u,v,w;
Edge(int _u=0,int _v=0,int _w=0){u=_u,v=_v,w=_w;}
};
vector<Edge> E;
vector<int> G[MAXN+5];
void init(int l,int r)
{
E.clear();
for(int i=l;i<=r;i++)
G[i].clear();
}
void addedge(int u,int v,int w)
{
E.push_back(Edge(u,v,w));
G[u].push_back(E.size()-1);
}
遍历某个链表的方法:
for(int i=0;i<G[u].size();i++)
vector邻接表还有一种魔性写法:
const int MAXN=1e5;
struct Edge
{
int u,v,w;
Edge(int u=0,int v=0,int w=0){this->u=u,this->v=v,this->w=w;}
};
vector<Edge> E[MAXN+5];
void init(int l,int r)
{
for(int i=l;i<=r;i++)
E[i].clear();
}
void addedge(int u,int v,int w)
{
E[u].push_back(Edge(u,v,w));
}
其实差不多……属于懒人中的懒人写法
3.链式前向星
把数组邻接表的next数组扔到Edge结构体里保存,就变成了链式前向星……
所以,链式前向星其实就是邻接表
代码
const int MAXN=1e5;
const int MAXM=1e5;
struct Edge
{
int u,v,w;
int next;
};
Edge E[MAXM+5];
int head[MAXN+5],cnt;
void init()
{
cnt=0;
memset(head,0,sizeof(head));
}
void addedge(int u,int v,int w)
{
++cnt;
E[cnt].u=u, E[cnt].v=v, E[cnt].w=w;
E[cnt].next=head[u];
head[u]=cnt;
}
遍历某个链表的方法:
for(int i=head[u];i;i=E[i].next)
四、总结
图论等是OI中一类十分重要的知识、题型,
所以邻接表的掌握是信息学习路上坚实基础的一部分,得掌握好啊