邻接表:所谓邻接表(adjacency list),就是把从同一个顶点发出的边链接在同一个称为边链表的单链表中。边链表的每个结点代表一条边,称为边结点。每个边结点有2 个域:该边终点的序号,以及指向下一个边结点的指针。在邻接表中,还需要一个用于存储顶点信息的顶点数组。例如,图1.19(a)所示的有向图对应的邻接表如图(b)所示。在顶点数组中,每个元素有两个成员:一个成员用来存储顶点信息;另一个成员为该顶点的边链表的表头指针,指向该顶点的边链表。如果没有从某个顶点发出的边,则该顶点没有边链表,因此表头指针为空,如图1.19(b)中的顶点G。在该图中,如果指针为空,则用符号“∧”表示。
图1.19有向图的邻接表
在邻接表每个顶点的边链表中,各边结点所表示的边都是从该顶点发出的边,因此这种邻接表也称为出边表。采用邻接表存储图时,求顶点的出度很方便,只需要统计每个顶点的边链表中边结点的个数即可,但在求顶点的入度时就比较麻烦。在图1.19(b)中,顶点B 的边链表有3 个边结点,分别表示边<B, F>、<B, E>和<B, C>,因此顶点B 的出度为3;顶点C 的边链表中只有1 个边结点,表示边<C, E>,因此顶点C 的出度为1。如果需要统计各顶点的入度,可以采用逆邻接表存储表示图。所谓逆邻接表,也称为入边表,就是把进入同一个顶点的边链接在同一个边链表中。
图1.20有向图的逆邻接表
例如,图1.20(a)所示的有向图对应的逆邻接表如图(b)所示。在图1.20(b)中,顶点B 的边链表有2 个边结点,分别表示边<E, B>和<A, B>,因此顶点B 的入度为2;顶点C 的边链表中也有2个边结点,分别表示边<D, C>和<B, C>,因此顶点C 的入度也为2。
接下来以有向图为例介绍邻接表的实现方法。为了方便求解顶点的出度和入度,在实现时,把出边表和入边表同时包含在图的邻接表结构中。有向图的邻接表用一个结构体LGraph 存储表示,其中包含3 个成员:顶点数组vertexs,顶点数vexnum 和边的数目arcnum,其中顶点数组vertexs 中每个元素都是VNode 结构体变量。VNode 结构体变量存储图中每个顶点,它包含3 个成员:顶点信息、出边表的表头指针和入边表的表头指针,其中后面两个成员都是ArcNode 结构体类型的指针。ArcNode 结构体存储边链表中的边结点,它包含2 个成员:边的另一个邻接点的序号,以及指向下一个边结点的指针。
例题:
问题描述:用邻接表存储有向图,并输出各顶点的出度和入度。
输入描述:输入文件中包含多个测试数据,每个测试数据描述了一个无权有向图。每个测试数据的第一行为两个正整数n 和m,1≤ n ≤ 100,1≤ m ≤ 500,分别表示该有向图的顶点数目和边数,顶点的序号从1 开始计起。接下来有m 行,每行为两个正整数,用空格隔开,分别表示一条边的起点和终点。每条边出现一次且仅一次,图中不存在自身环和重边。输入文件最后一行为0 0,表示输入数据结束。
输出描述:
对输入文件中的每个有向图,输出两行:第1 行为n 个正整数,表示每个顶点的出度;第2行也为n 个正整数,表示每个顶点的入度。每两个正整数之间用一个空格隔开,每行的最后一个正整数之后没有空格。
样例输入:
4 71 4
2 1
2 2
2 3
2 3
4 2
4 3
0 0
样例输出:
1 4 0 2
1 2 3 1
代码实现:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
#define INF 99999999
using namespace std;
const int maxn=105;
struct ArcNode{//边结点
int adjvex;//有向边的另一个邻接点的序号
ArcNode *nextarc;//指向下一个边结点的指针
};
struct VNode{//顶点
int data;//顶点信息
ArcNode *head1;//出边表的表头指针
ArcNode *head2;//入边表的表头指针
};
struct LGraph{//图的邻接表存储结构
VNode vertexs[maxn];//顶点数组
int vexnum,arcnum;//顶点数和边数
}LG;
void CreateLG(){
ArcNode *p;//用来构造边链表的边结点指针
int v1,v2;
//LG.vexnum=LG.arcnum=0;
//scanf("%d %d",&LG.vexnum,&LG.arcnum);
for(int i=0;i<LG.vexnum;i++){//初始化表头指针为空
LG.vertexs[i].head1=LG.vertexs[i].head2=NULL;
}
for(int i=0;i<LG.arcnum;i++){
scanf("%d %d",&v1,&v2);
v1--,v2--;
p = new ArcNode;//假定有足够空间
p->adjvex = v2;
p->nextarc = LG.vertexs[v1].head1;//插入链表
LG.vertexs[v1].head1=p;
p = new ArcNode;
p->adjvex = v1;
p->nextarc = LG.vertexs[v2].head2;
LG.vertexs[v2].head2=p;
}
}
void DeleteLG(){
ArcNode *p;//用来指向边链表中各边结点的指针
for(int i=0;i<LG.vexnum;i++){
p=LG.vertexs[i].head1;//释放第i个顶点出边表各边节点所占的存储空间
while(p!=NULL){
LG.vertexs[i].head1=p->nextarc;
delete p;
p=LG.vertexs[i].head1;
}
p=LG.vertexs[i].head2;//释放第i个顶点入边表各边节点所占的存储空间
while(p!=NULL){
LG.vertexs[i].head2=p->nextarc;
delete p;
p=LG.vertexs[i].head2;
}
}
}
int main()
{
int id,od;//顶点的入度跟出度
ArcNode *p;//用来遍历边链表的边结点指针
while(scanf("%d %d",&LG.vexnum,&LG.arcnum)){
if(LG.arcnum+LG.vexnum==0)
break;
CreateLG();
for(int i=0;i<LG.vexnum;i++){//统计各顶点出度并输出
od=0;
p=LG.vertexs[i].head1;
while(p!=NULL){
od++;
p=p->nextarc;
}
if(i==0) printf("%d",od)
;
else printf(" %d",od);
}
printf("\n");
for(int i=0;i<LG.vexnum;i++){//统计各顶点入度并输出
id=0;
p=LG.vertexs[i].head2;
while(p!=NULL){
id++;
p=p->nextarc;
}
if(i==0) printf("%d",id);
else printf(" %d",id);
}
printf("\n");
DeleteLG();//释放
}
return 0;
}
- 邻接表的简单实现:
typedef struct
{
int to;
int w;
int next;
}Edge;
Edge e[MAX];
int pre[MAX];
//初始化
memset(pre,-1,sizeof(pre));
//输入
scanf("%d %d %d",&from,&to,&w1);
e[i].to = to;
e[i].w = w1;
e[i].next = pre[from];
pre[from] = i++;
上面这段代码中,边的结构体Edge由三个元素组成:弧头结点序号,边权值,下一条边的序号。e[i]指的是第i条边。pre[i]记录的是从当前输入的情况来看,序号为i的弧尾结点发出的第一条边的序号是pre[i]。