图——邻接表的存储及基本操作

1.邻接表的介绍

邻接表(Adjacency List) 是图的一种链式存储结构。
在邻接表中,

  • 对图中每一个顶点建立一个单链表,第 i i 个单链表中的结点表示依附于顶点 v i v_{i} 的边(在有向图中是以顶点 v i v_{i} 为尾的弧)。
  • 单链表中的每一个表结点由三个域组成,其中
    • 邻接点域(adjvex) 指向与顶点 v i v_{i} 邻接的该点在图中的位置;
    • 数据域(info) 存储和边或弧( v i v_{i} 到该点的边)相关的信息,如权值;
    • 链域(nextarc) 指示下一条边或弧的结点(即 v i v_{i} 的下一邻接点)。
      邻接表
  • 每一个单链表上附有一个表头结点,有两个域组成,
    • 数据域(data) 存储顶点 v i v_{i} 的名或其他有关信息;
    • 链域(firstarc) 指向链表中的第一个结点(即 v i v_{i} 的第一个邻接点)。

这些表头结点(可以链相接)通常以顺序结构的形式存储,方便随机访问。
如有向图G1的邻接表如下:
G1邻接表
由于边上没有信息,所以信息域info不需要。
在C语言中图的邻接表的形式定义:

// ---- 图的邻接表存储表示 ----
#define MAX_VERTEX_NUM 20   // 最大的边的数目
typedef char InfoType;  // 弧上的信息类型
typedef string VertexType; // 顶点信息类型
// 边结点的定义
typedef struct ArcNode {
	int adjvex;
	//InfoType* info;  //一般无权图不用
	struct Arcnode* nextarc;
}ArcNode;
// 头结点的定义
typedef struct VNode {
	VertexType data; // 顶点信息
	ArcNode* firstarc; // 第一个邻接点
}VNode,*AdjList[MAX_VERTEX_NUM];
// 邻接表的定义
typedef struct {
	AdjList vertex;     // 头结点表
	int vexnum, arcnum; // 当前图的定点数和边数
	int kind;           // 图的类别
}ALGraph;

若无向图中有n个顶点,e条边,则邻接表共需n个头结点和2e个表结点。
显然,在边稀疏 ( e < < n ( n 1 ) / 2 ) (e<<n(n-1)/2) 的情况下,使用邻接表表示图比邻接矩阵更节省存储空间。
在邻接表上容易找到任一顶点的第一个邻接点和下一个邻接点,但是要判定任意两个顶点间( v i v j v_{i}和v_{j} )是否有边相连,则需要搜索第 i i 个或第 j j 个链表,此时,没有邻接矩阵方便。

2.邻接表的创建

无权有向图G1为例:

// 创建邻接表
void createALGraph(ALGraph& G) {
	printf("请输入邻接表的顶点数和边的数目:\n");
	cin >> G.vexnum >> G.arcnum;
	printf("请输入邻接表的顶点:\n");
	for (int i = 0; i < G.vexnum; i++) {
		cin >> G.vertex[i].data;
		G.vertex[i].firstarc = NULL; //初始化邻接点
	}
	printf("请以vi vj的形式输入给边:\n");
	for (int i = 0; i < G.vexnum; i++) {
		VertexType v1, v2;
		cin >> v1 >> v2;
		int l1 = LocateVex(G, v1);
		int l2 = LocateVex(G, v2);
		// 使用头插法添加邻接点
		ArcNode* ptr = new ArcNode;
		ptr->adjvex = l2;
		ptr->nextarc = G.vertex[l1].firstarc;
		G.vertex[l1].firstarc = ptr;
		/*若是无向图,则还需要对称建立边
		ArcNode* temp;
		temp->adjvex = l1;
		temp->nextarc = G.vertex[l2].firstarc;
		G.vertex[l2].firstarc = temp->nextarc;
		*/
	}
}

其中,LocateVex(G,v)表示在图G中找到顶点v的位置。

3.图的基本操作

3.1 LocateVex

int LocateVex(ALGraph G, VertexType u); // 返回顶点u在图中的位置

int LocateVex(ALGraph G, VertexType v) {
	int i;
	for (i = 0; i < G.vexnum && G.vertex[i].data != v; i++);
	if (i == G.vexnum) return -1; //不存在该顶点
	else return i;
}
3.2 FirstAdjVex

int FirstAdjVex(ALGraph G, VertexType u); // 返回图G中u的第一个邻接节点的下标

//返回图G中结点v的第一个邻接点的下标
int FirstAdjVex(ALGraph G, VertexType v) {
	int index = LocateVex(G, v);
	if (index == -1) {
		//printf("图中不存在顶点%c\n", v); 
		return -1;
	}
	else if (G.vertex[index].firstarc == NULL) {
		//printf("%c没有邻接点\n", v); 
		return -1;
	}
	else return G.vertex[index].firstarc->adjvex;
}
3.3 NextAdjVex

int NextAdjVex(ALGraph G, VertexType v, VertexType u); // 返回G中顶点v相对于u的下一邻接点

int NextAdjVex(ALGraph G, VertexType v, VertexType u) {
	int index = LocateVex(G, v);
	int index2 = LocateVex(G, u);
	if (index == -1) return -1;
	else {
		ArcNode* ptr = G.vertex[index].firstarc;
		while (ptr && ptr->adjvex != index2) ptr = ptr->nextarc;
		if (!(ptr->nextarc)) {
			//printf("%c是%c的最后一个邻接点", v, G.vertex[index].data);
			return -1;
		}
		else return ptr->nextarc->adjvex;
	}
}
3.5 void showAdj(ALGraph G)

void showAdj(ALGraph G)//打印邻接表

void showAdj(ALGraph G) {
	printf("头结点 邻接点\n");
	for (int i = 0; i < G.vexnum; i++) {
		cout << G.vertex[i].data << " ";
		ArcNode* ptr = G.vertex[i].firstarc;
		while (ptr != NULL) {
			cout << "--> " << G.vertex[ptr->adjvex].data << " ";
			ptr = ptr->nextarc;
		}
		cout << endl;
	}
}

4.图的遍历

图的遍历(Traversing Graph) 和树的遍历类似,即从图中某一顶点出发遍历图中其余顶点,且使每一个顶点被访问一次。
为了避免同一顶点被访问多次,在遍历图的过程中,必须记下每个已被访问过的顶点。为此,我们可以设一个辅助数组Visited[N]来标记某一顶点是否被访问过了,初始化为False

4.1 深度优先遍历

类似于树的先根遍历,是树的先根遍历的推广。

void DFS(ALGraph G, int v, int* visited) {
	// 先访问本结点
	visited[v] = 1;
	cout << G.vertex[v].data << " ";
	ArcNode* w = new ArcNode;
	for (w = G.vertex[v].firstarc; w != NULL; w = w->nextarc) {
	if (!visited[w->adjvex]) DFS(G, w->adjvex, visited);
	}	
	delete w;
	/* 也可以用先前定义的函数,但是操作复杂
	int w1;
	for (w1 = FirstAdjVex(G, G.vertex[v].data); w1 >= 0; w1 = NextAdjVex(G, G.vertex[v].data, G.vertex[w1].data)) {
	if (!visited[w1]) DFS(G, w1, visited);
	}
	*/
}
void DFSTraverse(ALGraph G) {
	int* visited = new int[G.vexnum];
	for (int i = 0; i < G.vexnum; i++) visited[i] = 0;
	// 递归进行深度优先遍历
	for (int i = 0; i < G.vexnum; i++) {
		if (!visited[i]) DFS(G,i, visited);
	}
	delete[] visited;
}

可以看到,在邻接表中查找某一顶点的第一邻接点和下一邻接点较邻接矩阵简单

4.2 广度优先遍历

广度优先搜索(Broadth First Search) 类似于树层次遍历的过程,即以v为初始点,由近即远,一次访问和v有路径相通且路径长度为1,2,…的顶点。此过程可借助于队列实现。

void BFSTraverse(ALGraph G) {
	queue<int> Q;
	int* visited = new int[G.vexnum];
	for (int i = 0; i < G.vexnum; i++) visited[i] = 0;
	for (int i = 0; i < G.vexnum; i++) {
		if (!visited[i]) {
			Q.push(i);
			while (!Q.empty()) {
				int f = Q.front(); Q.pop();
				visited[f] = 1;
				cout << G.vertex[f].data << " ";
				ArcNode* w = new ArcNode;
				for (w = G.vertex[f].firstarc; w != NULL; w = w->nextarc) {
					if (!visited[w->adjvex]) Q.push(w->adjvex);
				}
				delete w;
			}
		}
	}
	delete[] visited;
}
5. 测试代码
#include <iostream>
#include <string>
#include <queue>

using namespace std;

// ---- 图的邻接表存储表示 ----
#define MAX_VERTEX_NUM 20   // 最大的边的数目
typedef char InfoType;  // 弧上的信息类型
typedef string VertexType; // 顶点信息类型
// 边结点的定义
typedef struct ArcNode {
	int adjvex;
	//InfoType* info;  //一般无权图不用
	struct ArcNode* nextarc;
}ArcNode;
// 头结点的定义
typedef struct VNode {
	VertexType data; // 顶点信息
	ArcNode* firstarc; // 第一个邻接点
}VNode,AdjList[MAX_VERTEX_NUM];
// 邻接表的定义
typedef struct {
	AdjList vertex;     // 头结点表
	int vexnum, arcnum; // 当前图的定点数和边数
	int kind;           // 图的类别
}ALGraph;
// 显示邻接表
void showAdj(ALGraph G);
void createALGraph(ALGraph& G);
int LocateVex(ALGraph G, VertexType v); //在图G中找到包含顶点信息v的顶点位置
int FirstAdjVex(ALGraph G, VertexType v); // 返回图G中结点v的第一个邻接点
int NextAdjVex(ALGraph G, VertexType v, VertexType u);// 返回G中顶点v相对于u的下一邻接点
void DFSTraverse(ALGraph G); // 图的深度优先遍历
void DFS(ALGraph G, int v,int* visited); // 对尚未遍历的结点v递归调用DFS
void BFSTraverse(ALGraph G); // 图的广度优先遍历
int main() {
	ALGraph G;
	createALGraph(G);
	showAdj(G);
	cout << "V2在图中的位置:" << LocateVex(G, "V2") << endl;
	cout << "V3的第一个邻接点:" << G.vertex[FirstAdjVex(G, "V3")].data << endl;
	cout << "V1的在V3之后的邻接点:" << G.vertex[NextAdjVex(G, "V1", "V3")].data << endl;
	cout << "深度优先遍历结果\n";
	DFSTraverse(G);
	cout << endl;
	cout << "广度优先遍历结果\n";
	BFSTraverse(G);

	system("pause");
}

测试用例:
有向图G1

4 4
V1 V2 V3 V4
V1 V2
V1 V3
V3 V4
V4 V1

输出结果:

头结点 邻接点
V1 --> V3 --> V2
V2
V3 --> V4
V4 --> V1
V2在图中的位置:1
V3的第一个邻接点:V4
V1的在V3之后的邻接点:V2
深度优先遍历结果
V1 V3 V4 V2
广度优先遍历结果
V1 V3 V2 V4 请按任意键继续. . .

参考资料

《数据结构 C语言描述》 严蔚敏著

猜你喜欢

转载自blog.csdn.net/Africa_South/article/details/88595763