数据结构---图结构(C语言版)

 

目录

一、图的定义及术语

1.1 图的定义

1.2 图的类别

1.3 相关术语

二、图的存储结构

2.1 数组表示法 — 邻接矩阵

2.1.0 无向图的邻接矩阵

2.1.1 有向图的邻接矩阵

2.1.2 网(带权的图)的邻接矩阵

2.2 链表表示法 — 邻接表

2.3 链表表示法 — 十字链表

三、图的实现

3.1 网的邻接矩阵表示法

 3.1.0 头文件 

 3.1.1 函数文件

 3.1.2 主函数文件

3.2 无向图的邻接表表示法

3.2.0 头文件

3.2.1 函数文件 

3.2.2 主函数文件 

3.3 有向图的十字链表表示法

3.3.0 头文件

3.3.1 函数文件

3.3.2 主函数文件

四、图的遍历

4.1 遍历的介绍

 4.1.0 遍历的定义

4.1.1 遍历特点

4.2 图的深度优先遍历 — DFS

4.2.0 思路

4.2.1 代码实现

4.3 图的广度优先遍历 — BFS

4.3.0 思路

4.3.1 代码实现 


一、图的定义及术语

1.1 图的定义

        图是由结点(也称顶点)和(也称弧)组成的非线性数据结构。结点是图的基本单元,边则用于将结点与结点关联起来。图结构有很多应用,如社交网络、网络拓扑、地图、物流。

在图中,节点和边可以具有不同的属性。例如,在社交网络图中,节点属性可以表示用户,边属性可以表示用户之间的关系,如朋友或家庭关系。边可以是有指向或无指向的,也可以是加权或未加权的。

图的定义:图G由顶点集V和边集E组成,G = (V,E);  V是有穷非空集合;E是有穷集合。

1.2 图的类别

有向图:图的边是有指向的,则称为有向图。

无向图:图的边是无指向的,则称为无向图。

网:边带权重的图,则称为网。网又可分无向网和有向网。 边的权重可表示距离、成本或其他数据。

完全图:任意两结点间都有一条边相连。分无向完全图、有向完全图。

稀疏图:边很少的图( e< n\log n )

稠密图:边很多的图

强连通图:图中任何两个顶点 v,u 都存在从 v 到 u 的路径,则称为连通图(强连通图)

1.3 相关术语

邻接: 有边相连的两个顶点间的关系。

        存在 (V_{i},V_{j}),则称 V_{i} 和 V_{j} 互为邻接点;         --- 无向图

        存在 \left \langle V _{i} , V _{j} \right \rangle,则称 V_{i} 邻接到 V_{j} ,V_{j} 邻接于 V_{i} ;        --- 有向图

关联(依附): 边与顶点之间的关系。

        存在(V_{i},V_{j}) 或 \left \langle V _{i} , V _{j} \right \rangle,则称该边关联于  V_{i} 和 V_{j} ;

顶点的度: 与该顶点相关联的边的数目,记作 TD(V)

    有向图中,顶点的度为入度出度之和

    顶点v的入度是以v为终点的有向边的条数,记作 ID(v)

    顶点v的出度是以v为始点的有向边的条数,记作 OD(v) 

无向网
顶点
v13
v21
v32
v42
有向网
顶点入度出度
v1123
v2101
v3112
v4112

路径: 接续的边构成的顶点序列

路径长度:路径上边的数目 / 权重之后

回路(环):第一个顶点和最后一个顶点相同的路径(首尾相接)

简单路径:出路径起点和终点可以相同外,其余顶点均不相同的路径

简单回路(环):出路径起点和终点相同,其余顶点均不相同的路径

 

子图:设有两个图 G = (V,E)、G1 = (V1,E1)。若 V1\subseteq V, E1\subseteq E,则称G1 是 G 的子图

连通分量(强连通分量):

       无向图 G 的极大连通子图称为 G 的连通分量

       极大连通子图:指该子图是 G 的连通子图,将G的任何不在该子图中的顶点加入,子图不在连通。

      有向图 G 的极大强连通子图称为G的强连通分量

 

极小连通子图:该子图是 G 的连通子图,在该子图中删除任何一条边,子图都不在连通

生成树:包含无向图G 所有顶点的极小连通子图

生成森林:对非连通图,由各个连通分量的生成树的集合

二、图的存储结构

图的逻辑结构:多对多的非线性结构

2.1 数组表示法 — 邻接矩阵

2.1.0 无向图的邻接矩阵

* 邻接矩阵中,1 表示两顶点邻接0 表示两顶点未邻接 

分析1:无向图的邻接矩阵是对称

分析2:顶点 i 的= 第 i 行(列)中 1 的个数

* 完全图的邻接矩阵中,对角元素为 0,其余全是 1

2.1.1 有向图的邻接矩阵

 

* 邻接矩阵中,1 表示两顶点邻接0 表示两顶点未邻接  

分析1:有向图的邻接矩阵可能是不对称

分析2:顶点的出度 = 第 i 行元素之和

             顶点的入度 = 第 i 列元素之和

             顶点的度 = 第 i 行元素之和 + 第 i 列元素之和           即 (入度 + 出度)

2.1.2 网(带权的图)的邻接矩阵

* 邻接矩阵中,权值 表示两顶点邻接0 表示两顶点未邻接 

2.2 链表表示法 — 邻接表

顶点:将顶点数据按编号顺序存储在一维数组中

边:关联同一顶点的边,用线性链表存储

邻接表容易求得顶点和边的信息,但是某些操作不方便(如:删除一条边,需要找到到此边关联的两个顶点;而且一条边在邻接表中会出现两次)

1)特点(对于无向图):

        1. 邻接表不唯一 (与链表的头插法、尾插法有关)

        2. 无向图有 n 个顶点、e 条边,则需要 n 个头结点2e 个表结点。适合存储稀疏图

        3. 无向图中顶点 V_{i} 的度为第 i 个单链表中的结点个数

2)特点(对于无向图):

        1. 顶点 V_{i} 的出度为第 i 个单链表中的结点个数

        2. 顶点 V_{i} 的入为整个单链表中邻接点域值为 i-1 的结点个数

        3. 找出度容易,找入度难

2.3 链表表示法 — 十字链表

图解十字链表法的逻辑结构

三、图的实现

3.1 网的邻接矩阵表示法

算法思想:

        1. 输入总顶点数总边数

        2. 依次输入点的信息存入顶点表

        3. 初始化邻接矩阵,使每个权值初始化为0

        4. 构造邻接矩阵

 3.1.0 头文件 

<Graph.h>

#define _CRT_SECURE_NO_WARNINGS 1     // 使用scanf()、printf()函数编译器会报错,次命令以忽略报错

#include<stdio.h>
#include<stdlib.h>

// 顶点的最大个数
#define MaxVertix 30

// 状态值
#define OK 1
#define ERROR 0

// 状态码 -- 状态值
typedef int Status;


// 1.邻接矩阵 - 存储结构
// 定点、边的类型
typedef char VertexType;   // 顶点的数据类型
typedef int ArcType;		// 边的数据类型

// 构造数据类型
typedef struct
{
	VertexType Verts[MaxVertix];
	ArcType UdArcs[MaxVertix][MaxVertix];			// 无向图 -- 矩阵表示法
	int VerNum;										// 顶点个数
	int ArcNum;										// 边的个数
}AMGraph;				// Adjacency  Matrix  Graph 邻接矩阵


// 函数声明
Status CreateUDN(AMGraph* G);	// 创建无向图

int LocateVertex(AMGraph* G, VertexType* v);	// 获取 顶点 ver 的下标

 3.1.1 函数文件

#include "Graph.h"

// 获取 顶点 ver 的下标
int LocateVertex(AMGraph* G, VertexType* v)
{
	if (!G) return ERROR;

	int i;
	for (i = 0; i < G->VerNum; i++)
		if (*v == G->Verts[i])
			return i;

	return -1;	// 返回 -1 表示未找到顶点
}

// 创建无向图
Status CreateUDN(AMGraph* G)			// UndirectNet
{
	if (!G) return ERROR;

	printf("请输入顶点及边个数(Vers Arcs): ");
	scanf("%d %d", &G->VerNum,&G->ArcNum);
	getchar();

	int i;
	//录入顶点
	printf("\n\n请输入顶点的值(英文字母): ");
	for (i = 0; i < G->VerNum; i++)
	{
		do
		{
			VertexType v;
			scanf("%c", &v);		//scanf("%[a-zA-Z]", G->VerNum);	// 只接收26个英文字母
			getchar();
			if ((65 <= v && v <= 90) || (97 <= v && v <= 122))
			{
				G->Verts[i] = v;
				break;
			}
			printf("输入错误,请输入英文字母!\n");

		} while (1);	// do-while循环用于处理错误输入

	}
	
	//初始化所有边的权
	int j;
	for(i = 0; i < G->VerNum; i++)
		for (j = i; j < G->VerNum; j++)
		{
			G->UdArcs[i][j] = 0;
			G->UdArcs[j][i] = 0;
		}

	//录入边的权值
	printf("\n请输入边关联的顶点及权值(v1 v2 weight): \n");
	for (i = 0; i < G->ArcNum; i++)
	{
		VertexType v1, v2;
		int w;
		do
		{
			scanf("%c %c %d", &v1, &v2, &w);
			getchar();

			if (v1 < 65 || (90 < v1 && v1 < 97) || 122 < v1)
			{
				printf("输入错误,请输入英文字母!\n");
				continue;
			}
			if (v2 < 65 || (90 < v2 && v2 < 97) || 122 < v2)
			{
				printf("输入错误,请输入英文字母!\n");
				continue;
			}
			//查找顶点位置
			int a, b;
			a = LocateVertex(G, &v1);
			b = LocateVertex(G, &v2);
		
			if (a < 0)		// 判断顶点是否存在
			{
				printf("输入的顶点%c不存在,请重新输入!\n",v1);
				continue;
			}
			if (b < 0)		// 判断顶点是否存在
			{
				printf("输入的顶点%c不存在,请重新输入!\n", v2);
				continue;
			}

			//链接到两顶点的边赋权值
			G->UdArcs[a][b] = w;
			G->UdArcs[b][a] = w;
			break;

		} while (1);	// do-while循环用于处理错误输入

	}
	return OK;
}

 3.1.2 主函数文件

<Graph.h>

#include "Graph.h"	// 引用头文件

// 测试代码

int main()
{
	/* 测试数据 及 结果
请输入顶点及边个数(Vers Arcs): 5 6

请输入顶点的值(英文字母): A B C D E

请输入边关联的顶点及权值(v1 v2 weight):
A B 2
A C 1
B E 6
B D 3
C E 4
D E 5

  A  B  C  D  E
A 0  2  1  0  0
B 2  0  0  3  6
C 1  0  0  0  4
D 0  3  0  0  5
E 0  6  4  5  0
	*/
		
	AMGraph amg;

	CreateUDN(&amg);

	int i, j;
	printf("\n  ");
	for (i = 0; i < amg.VerNum; i++)
		printf("%c  ", amg.Verts[i]);

	//printf("\n  A  B  C  D  E");
	for (i = 0; i < amg.VerNum; i++)
	{
		//printf("%c ", 'A' + i);
		for (j = 0; j < amg.VerNum; j++)
		{
			if (j > 0) printf(" ");
			else printf("\n%c ", amg.Verts[i]);
			printf("%d ", amg.UdArcs[i][j]);
		}
	}

	return 0;
}

3.2 无向图的邻接表表示法

算法思想:

        1. 输入总顶点数总边数

        2. 建立顶点表依次输入点的信息存入顶点表中,使每个表头结点的指针域初始化为NULL

        3. 创建邻接表。依次输入每条边依附的两个顶点;确定两个顶点的序号 i、j,建立边结点;将此结点分别插入到 V_{i} 和 V_{j} 对应的两个边链表的头部

3.2.0 头文件

<Graph-AdjList.h>

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
#include<stdlib.h>


// 状态值
#define OK 1
#define ERROR 0

// 状态码 -- 状态值
typedef int Status;

// 边的类型
typedef char VertexType;


// 2.邻接表 - 存储结构
#define MaxVerNum 30		// 最大顶点个数
typedef int OtherInfo;		// 记录其它信息

typedef struct ArcNode
{
	int AdjVex;					// 该边所指向的顶点的位置
	struct ArcNode* NextArc;	// 指向下一条边
	OtherInfo Weight;					// 权重
}ArcNode;

typedef struct
{
	VertexType data;		// 顶点数据
	ArcNode* FirstArc;		// 指向第一条依附该顶点的边的指针
}VNode, AdjList[MaxVerNum];	// 表邻接表类型

typedef struct
{
	AdjList Vertexes;		// 顶点表  Vertexes -- Vertex的复数
	//VNode Vertices[30];
	int VexNum, ArcNum;		// 当前顶点个数、边数
}ALGraph;					// 采用邻接表结构的图


Status CreatUDN_ALGraph(ALGraph* Alg);	// 创建图并初始化

int LocVer_ALGraph(ALGraph* Alg, VertexType* v); // 获取 顶点 ver 的下标。找到返回下标,未找到返回 小于0

Status Destroy_ALGraph(ALGraph* Alg);	// 销毁弧结点

3.2.1 函数文件 

#include "Graph-AdjList.h"

// 获取 顶点 ver 的下标
int LocVer_ALGraph(ALGraph* Alg, VertexType* v)
{
	//处理空指针
	if (!Alg || !v) return -1;

	int i;
	for (i = 0; i < Alg->VexNum; i++)
		if (*v == Alg->Vertexes[i].data)
			return i;

	return -1;	// 不存在的点,返回 -1
}

// 创建图并初始化
Status CreatUDN_ALGraph(ALGraph* Alg)
{
	if (!Alg) return ERROR;		// 处理空指针

	printf("请输入顶点、边的数量: ");
	scanf("%d %d", &(Alg->VexNum), &(Alg->ArcNum));
	getchar();

	int k;
	printf("\n请输入顶点信息(字母): ");
	for (k = 0; k < Alg->VexNum; k++)
	{
		VertexType v;

		do
		{
			v = getchar();
			getchar();

			if (v < 65 || (90 < v && v < 97) || 122 < v)
			{
				printf("\n字符%c非法! 再次输入一字母:",v);
				continue;
			}

			Alg->Vertexes[k].data = v;			// 录入顶点
			Alg->Vertexes[k].FirstArc = NULL;	// 初始化表头结点的指针域
			break;

		} while (1);		// 非法输入的处理
	}

	printf("\n请输入边邻接的顶点的信息(v1 v2): ");
	for (k = 0; k < Alg->ArcNum; k++)
	{
		VertexType v1 = '0', v2 = '0';
		
		do
		{
			scanf("%c %c", &v1, &v2);
			getchar();

			if (v1 < 65 || (90 < v1 && v1 < 97) || 122 < v1 || v2 < 65 || (90 < v2 && v2 < 97) || 122 < v2)
			{
				printf("\n(%c,%c)为非法输入! 再次输入字母:",v1,v2);
				continue;
			}

			int i, j;
			i = LocVer_ALGraph(Alg, &v1);
			if (i < 0)
			{
				printf("顶点:%c 未找到! 再次输入\n", v1);
				continue;
			}
			
			j = LocVer_ALGraph(Alg, &v2);
			if (j < 0)
			{
				printf("顶点:%c 未找到! 再次输入\n", v2);
				continue;
			}
			
			ArcNode* p1 = malloc(sizeof(ArcNode));
			if (!p1) return ERROR; // 分配空间失败的处理

			p1->AdjVex = j;		// 记录顶点 v2 位置,表v1与v2有边
			p1->Weight = 0;		// 图无权重

			// 头插法
			p1->NextArc = Alg->Vertexes[i].FirstArc;
			Alg->Vertexes[i].FirstArc = p1;


			ArcNode* p2 = malloc(sizeof(ArcNode));
			if (!p2) return ERROR; // 分配空间失败的处理

			p2->AdjVex = i;		// 记录顶点 v1 位置,表v2与v1有边
			p2->Weight = 0;		// 图无权重

			// 头插法
			p2->NextArc = Alg->Vertexes[j].FirstArc;
			Alg->Vertexes[j].FirstArc = p2;

			break;
		} while (1);
	}
	return OK;
}

// 销毁弧结点
Status Destroy_ALGraph(ALGraph* Alg)
{
	if (!Alg) return ERROR;

	int i;
	for (i = 0; i < Alg->VexNum; i++)
	{
		ArcNode* p;
		p = Alg->Vertexes[i].FirstArc;

		while (p)
		{
			Alg->Vertexes[i].FirstArc = p->NextArc;
			free(p);
			p = Alg->Vertexes[i].FirstArc;
		}
	}
	return OK;
}

3.2.2 主函数文件 

#include "Graph-AdjList.h"

int main()
{
	// 测试数据
	/*
	顶点:a b c d e
	边: 
		0 a->3->1	
		1 b->4->2->0
		2 c->4->3->1
		3 d->2->0
		4 e->2->1

		a------b
		|    / |
		|	c  |
		| /  \ |
		d      e
	*/

/*	 测试如下
请输入顶点、边的数量: 5 6

请输入顶点信息(字母) : a b c d e

请输入边邻接的顶点的信息(v1 v2) : a b
a d
b c
b e
c d
c e

顶点表如下 :
a b c d e
顶点的边信息如下 :
顶点a -> 3 1
顶点b -> 4 2 0
顶点c -> 4 3 1
顶点d -> 2 0
顶点e -> 2 1
弧销毁成功!		*/

	ALGraph alg;

	CreatUDN_ALGraph(&alg);

	int i;
	printf("\n顶点表如下:\n");
	for (i = 0; i < alg.VexNum; i++)
		printf("%c ", alg.Vertexes[i].data);


	printf("\n顶点的边信息如下:");
	for (i = 0; i < alg.VexNum; i++)
	{
		ArcNode* p;
		p = alg.Vertexes[i].FirstArc;
		printf("\n顶点%c -> ", alg.Vertexes[i].data);
		while (p)
		{
			printf("%d ", p->AdjVex);

			p = p->NextArc;
		}
	}
	Destroy_ALGraph(&alg);
	printf("\n弧销毁成功!\n");
	return 0;
}

3.3 有向图的十字链表表示法

算法思想:

        1. 输入总顶点数总边数

        2. 建立顶点表依次输入点的信息存入顶点表中,使每个表头结点的指针域初始化为NULL

        3. 创建邻接表。依次输入每个顶点的出度信息V_{_i}\rightarrow V_{j};确定两个顶点的序号 i、j,建立边结点;将此结点分别连接到 V_{i} 的出度指针、 V_{j} 的入度指针;邻接表结点通过指向下一结点的 *hlink 指针出度顶点相同的表结点链接起来,通过指向下一结点的 *tlink 指针入度顶点相同的表结点链接起来;

3.3.0 头文件

<CrossGraph.h >

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <stdlib.h>

// 状态值
#define OK 1
#define ERROR 0

// 状态码 -- 状态值
typedef int Status;

typedef char VertexType;


// 图的十字链表存储结构 cross link storage structure
#define CroMax 20

//弧结点
typedef struct TenArcNode
{
	int TailVex;	// 尾顶点坐标
	int HeadVex;	// 头顶点坐标
	struct TenArcNode* hlink;	// 指向下一个同顶点出度弧结点;如 a->b 和 a->c 两条弧
	struct TenArcNode* tlink;	// 指向下一个同顶点入度弧结点;如 b<-a 和 b<-d 两条弧
}CroArcNode;

//顶点表
typedef struct
{
	VertexType data;				// 数据域
	CroArcNode* Firstin;	// 指针域 指向出度第一个边
	CroArcNode* Firstout;	// 指针域 指向入度第一个边
}CroVertex;

//图
typedef struct
{
	CroVertex Verts[CroMax];
	int VerNum, ArcNum;
}CroGraph;

//函数声明
Status CreateCroGraph(CroGraph* cgrp);	// 创建十字链表图

int LocateCroGraph(CroGraph* cgrp, const VertexType* cvrt);	// 返回顶点的位置

Status DestroyCroArc(CroGraph* cgrp);	// 销毁弧结点

3.3.1 函数文件

#include "CrossGraph.h"

// 返回顶点的位置
int LocateCroGraph(CroGraph* cgrp, const VertexType* cvrt)
{
	//处理空指针
	if (!cgrp || !cvrt) return -1;

	//查找结点
	int i;
	for (i = 0; i < cgrp->VerNum; i++)
		if (*cvrt == cgrp->Verts[i].data)
			return i;						// 找到结点,返回其位置

	return -1;	// 未找到结点,返回 -1
}

// 创建十字链表图
Status CreateCroGraph(CroGraph* cgrp)
{
	if (!cgrp) return ERROR;

	//录入顶点个数
	printf("请输入顶点、边的个数: ");
	scanf("%d %d", &cgrp->VerNum,&cgrp->ArcNum);
	getchar();
	int i;

	//录入顶点信息
	printf("\n请输入顶点值(字母且以空格分隔): ");
	for (i = 0; i < cgrp->VerNum; i++)
	{
		VertexType v;

		do
		{
			//接受用户输入
			scanf("%c",&v);
			getchar();						// 处理空格、回车

			if (v < 65 || (90 < v && v< 97) || 122 < v)
			{
				printf("请重新输入字母!\n");
				continue;
			}
			//初始化指针
			cgrp->Verts[i].data = v;
			cgrp->Verts[i].Firstin = cgrp->Verts[i].Firstout = NULL;

			break;

		} while (1);	// 处理非法输入
	}


	//录入弧的信息
	printf("\n请输入弧的出度信息(格式 v1(出度) v2(入度)): ");
	for (i = 0; i < cgrp->ArcNum; i++)
	{
		VertexType v1, v2;

		do
		{
			scanf("%c %c", &v1, &v2);
			getchar();

			/*if (v1 < 65 || (90 < v1 && v1 < 97) || 122 < v1)
			{
				printf("%c非字母! 请输入字母!");
				continue;
			}*/										// 此处省略,非字母查找不到

			int h, t;
			h = LocateCroGraph(cgrp, &v1);

			t = LocateCroGraph(cgrp, &v2);

			if (h < 0)
			{
				printf("\n不存在顶点%c,请重新输入(字母)!\n", v1);
				continue;
			}
			else if(t < 0)
			{
				printf("\n不存在顶点%c,请重新输入(字母)!\n", v2);
				continue;
			}
			else if(h < 0 && t < 0)
			{
				printf("\n不存在顶点%c %c,请重新输入(字母)!\n", v1, v2);
				continue;
			}

			// 构建弧结点
			CroArcNode* p = (CroArcNode*)malloc(sizeof(CroArcNode));
			if (!p) return ERROR;
			p->HeadVex = h;
			p->TailVex = t;
			p->hlink = p->tlink = NULL;
			
			// 始结点的出度指针    始->终
			if(cgrp->Verts[h].Firstout == NULL)
				cgrp->Verts[h].Firstout = p;
			else
			{
				CroArcNode* Outward = (CroArcNode*)malloc(sizeof(CroArcNode));
				if (!Outward) return ERROR;
				Outward = cgrp->Verts[h].Firstout;

				// 顺藤摸瓜,找到尾端弧结点
				while (Outward->tlink)
					Outward = Outward->tlink;

				Outward->tlink = p;
			}

			// 终结点的入度指针     终<-始
			if (cgrp->Verts[t].Firstin == NULL)
				cgrp->Verts[t].Firstin = p;
			else
			{
				CroArcNode* Inward = (CroArcNode*)malloc(sizeof(CroArcNode));
				if (!Inward) return ERROR;
				Inward = cgrp->Verts[t].Firstin;

				// 顺藤摸瓜,找到尾端弧结点
				while (Inward->hlink)
					Inward = Inward->hlink;

				Inward->hlink = p;
			}
			break;
			 头插法
			//p->hlink = p->tlink = cgrp->Verts[i].Firstin;
			//cgrp->Verts[h].Firstout = p;				// 出度
			//cgrp->Verts[t].Firstin = p;					// 入度

		} while (1);	// 处理非法输入

	}
	return OK;
}

// 销毁图
Status DestroyCroArc(CroGraph* cgrp)
{
	if (!cgrp) return ERROR;

	int i;
	for (i = 0; i < cgrp->VerNum; i++)
	{
		CroArcNode* p = NULL;

		p = cgrp->Verts[i].Firstout;

		while (p)
		{
			cgrp->Verts[i].Firstout = p->tlink;
			free(p);

			p = cgrp->Verts[i].Firstout;
		}
	}

	return OK;
}

3.3.2 主函数文件

 

#include "CrossGraph.h"

int main()
{
	// 测试数据
/*图      a →→→ b
	    ↑↓ ↖   ↑
	    ↑↓   ↖ ↑
	      c →→→ d
		    ←←←
		   
*/

/* 测试如下
请输入顶点、边的个数: 4 7

请输入顶点值(字母且以空格分隔) : a b c d

请输入弧的出度信息(格式 v1(出度) v2(入度)) : a b
a c
c a
c d
d a
d b
d c
a出度 : 0->1 0->2
a入度 : 2->0 3->0
b出度 :
	b入度 : 0->1 3->1
	c出度 : 2->0 2->3
	c入度 : 0->2 3->2
	d出度 : 3->0 3->1 3->2
	d入度 : 2->3
	弧销毁成功!
	*/

	// 构建图的结构体变量
	CroGraph cgrp;

	//初始化图
	CreateCroGraph(&cgrp);

	int i;
	for (i = 0; i < cgrp.VerNum; i++)
	{
		CroArcNode* outward, * inward;
		outward = inward = NULL;

		outward = cgrp.Verts[i].Firstout;	// 出度
		inward = cgrp.Verts[i].Firstin;	// 入度

		printf("%c出度:", cgrp.Verts[i].data);

		while (outward)
		{
			printf("%d->%d ", outward->HeadVex, outward->TailVex);
			outward = outward->tlink;
		}
		printf("\n");

		printf("%c入度:", cgrp.Verts[i].data);
		while (inward)
		{
			printf("%d->%d ", inward->HeadVex, inward->TailVex);
			inward = inward->hlink;
		}
		printf("\n");
	}

	DestroyCroArc(&cgrp);
	printf("弧销毁成功!\n");
	return 0;
}

四、图的遍历

4.1 遍历的介绍

 4.1.0 遍历的定义

  定义:从某顶点出发,沿着边访问图中所有顶点,且每个顶点仅被访问一次,这个过程称为 图的遍历。

  遍历实质:找每个顶点的邻接点的过程   

  常见的遍历:

        1. 深度优先搜索(Depth First Search — DFS)

        2. 广度优先搜索(Broadth First Search — BFS) 

4.1.1 遍历特点

        图中可能存在回路,且图的任一顶点都可能与其它顶点相通;访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点,出现重复访问

        避免重复访问的思路:

           1. 设置辅助数组 visited[n],用来标记每个被访问过的顶点。

           2. 此数组初始化为 visited[i] = 0,顶点被访问 visited[i] = 1,以避免重复访问

4.2 图的深度优先遍历 — DFS

4.2.0 思路

        1.访问某其始顶点 V 后,有 V 出发,访问它的任一邻接顶点 W_{1}

        2.再从 W_{1} 出发,访问与 W_{1} 邻接但还未被访问的顶点 W_{2}

        3.再从 W_{2} 出发,依次类推的访问邻接顶点;

        4.如此进行下去,直至访问到顺延下去的最后一个邻接顶点;

        5.回退一步到前一次刚访问过的顶点,看是否还有其它未被访问的邻接顶点;

                若有,则访问此顶点,之后再从此顶点出发,重复上述类似的访问

                若无,就再回退一步进行搜索。重复上述过程,直到连通图中的所有顶点均被访问为止

 举例:

       * 连通图的深度优先遍历类似于树的先根遍历 

4.2.1 代码实现

头文件 

<Graph.h> 

#define _CRT_SECURE_NO_WARNINGS 1     // 使用scanf()、printf()函数编译器会报错,次命令以忽略报错

#include<stdio.h>
#include<stdlib.h>

// 顶点的最大个数
#define MaxVertix 30

// 状态值
#define OK 1
#define ERROR 0

// 状态码 -- 状态值
typedef int Status;


// 1.邻接矩阵 - 存储结构
// 定点、边的类型
typedef char VertexType;   // 顶点的数据类型
typedef int ArcType;		// 边的数据类型

// 构造数据类型
typedef struct
{
	VertexType Verts[MaxVertix];
	ArcType UdArcs[MaxVertix][MaxVertix];			// 无向图 -- 矩阵表示法
	int VerNum;										// 顶点个数
	int ArcNum;										// 边的个数
}AMGraph;				// Adjacency  Matrix  Graph 邻接矩阵


// 函数声明
Status CreateUDN(AMGraph* G);	// 创建无向图

int LocateVertex(AMGraph* G, VertexType* v);	// 获取 顶点 ver 的下标

Status DFS(AMGraph* G, int v);	// 深度优先遍历

函数文件

#include "Graph.h"

// 获取 顶点 ver 的下标
int LocateVertex(AMGraph* G, VertexType* v)
{
	if (!G) return ERROR;

	int i;
	for (i = 0; i < G->VerNum; i++)
		if (*v == G->Verts[i])
			return i;

	return -1;	// 返回 -1 表示未找到顶点
}

// 创建无向图
Status CreateUDN(AMGraph* G)			// UndirectNet
{
	if (!G) return ERROR;

	printf("请输入顶点及边个数(Vers Arcs): ");
	scanf("%d %d", &G->VerNum,&G->ArcNum);
	getchar();

	int i;
	//录入顶点
	printf("\n请输入顶点的值(英文字母): ");
	for (i = 0; i < G->VerNum; i++)
	{
		do
		{
			VertexType v;
			scanf("%c", &v);		//scanf("%[a-zA-Z]", G->VerNum);	// 只接收26个英文字母
			getchar();
			if ((65 <= v && v <= 90) || (97 <= v && v <= 122))
			{
				G->Verts[i] = v;
				break;
			}
			printf("输入错误,请输入英文字母!\n");

		} while (1);	// do-while循环用于处理错误输入

	}
	
	//初始化所有边的权
	int j;
	for(i = 0; i < G->VerNum; i++)
		for (j = i; j < G->VerNum; j++)
		{
			G->UdArcs[i][j] = 0;
			G->UdArcs[j][i] = 0;
		}

	//录入边的权值
	printf("\n请输入边关联的顶点及权值(v1 v2 weight): \n");
	for (i = 0; i < G->ArcNum; i++)
	{
		VertexType v1, v2;
		int w;
		do
		{
			scanf("%c %c %d", &v1, &v2, &w);
			getchar();

			if (v1 < 65 || (90 < v1 && v1 < 97) || 122 < v1)
			{
				printf("输入错误,请输入英文字母!\n");
				continue;
			}
			if (v2 < 65 || (90 < v2 && v2 < 97) || 122 < v2)
			{
				printf("输入错误,请输入英文字母!\n");
				continue;
			}
			//查找顶点位置
			int a, b;
			a = LocateVertex(G, &v1);
			b = LocateVertex(G, &v2);
		
			if (a < 0)		// 判断顶点是否存在
			{
				printf("输入的顶点%c不存在,请重新输入!\n",v1);
				continue;
			}
			if (b < 0)		// 判断顶点是否存在
			{
				printf("输入的顶点%c不存在,请重新输入!\n", v2);
				continue;
			}

			//链接到两顶点的边赋权值
			G->UdArcs[a][b] = w;
			G->UdArcs[b][a] = w;
			break;

		} while (1);	// do-while循环用于处理错误输入

	}
	return OK;
}

// 深度优先遍历
int Visited[MaxVertix] = { 0 };

Status DFS(AMGraph* G, int v)	// 遍历邻接矩阵类型图G的第 v 个顶点
{
	if (!G || v < 0)	// 空指针、非法顶点的处理
		return ERROR;

	printf("%c ", G->Verts[v]);	// 打印顶点信息
	Visited[v] = 1;

	int i;
	for (i = 0; i < G->VerNum; i++)
	{
		if (G->UdArcs[v][i] != 0 && Visited[i] == 0)
			DFS(G, i);
	}

	return OK;
}

 主函数文件

邻接矩阵表示的无向图深度遍历实现:

#include "Graph.h"

int main()
{
	/* 测试数据 及 结果
	A--------		
  /	  \	  	|
  B	   C	E
   \  /		|
    D		F

请输入顶点及边个数(Vers Arcs): 6 6

请输入顶点的值(英文字母): A B C D E F

请输入边关联的顶点及权值(v1 v2 weight):
A B 1
A C 1
A E 1
B D 1
C D 1
E F 1

  A  B  C  D  E  F
A 0  1  1  0  1  0
B 1  0  0  1  0  0
C 1  0  0  1  0  0
D 0  1  1  0  0  0
E 1  0  0  0  0  1
F 0  0  0  0  1  0
请输入图遍历的起点: 1

遍历结果: B A C D E F	*/

	AMGraph amg;

	CreateUDN(&amg);

	int i, j;
	printf("\n  ");
	for (i = 0; i < amg.VerNum; i++)
		printf("%c  ", amg.Verts[i]);

	//printf("\n  A  B  C  D  E");
	for (i = 0; i < amg.VerNum; i++)
	{
		//printf("%c ", 'A' + i);
		for (j = 0; j < amg.VerNum; j++)
		{
			if (j > 0) printf(" ");
			else printf("\n%c ", amg.Verts[i]);
			printf("%d ", amg.UdArcs[i][j]);
		}
	}

	int loc = 0;
	printf("\n\n请输入图遍历的起点: ");
	scanf("%d", &loc);

	printf("\n遍历结果: ");

	DFS(&amg, loc);
	printf("\n");

	return 0;
}

4.3 图的广度优先遍历 — BFS

4.3.0 思路

         从某顶点出发,首先依次访问该顶点的所有邻接顶点 V^{_{i_{1}}},V^{_{i_{2}}},...,V^{_{i_{n}}} 再按这些顶点被访问的先后次序,访问与它们邻接的所有未被访问的顶点。重复此过程,直至所有顶点被访问

举例: 

4.3.1 代码实现 

头文件

<Graph.h>

#define _CRT_SECURE_NO_WARNINGS 1     // 使用scanf()、printf()函数编译器会报错,次命令以忽略报错

#include<stdio.h>
#include<stdlib.h>

// 顶点的最大个数
#define MaxVertix 30

// 状态值
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0


// 状态码 -- 状态值
typedef int Status;


// 1.邻接矩阵 - 存储结构
// 定点、边的类型
typedef char VertexType;   // 顶点的数据类型
typedef int ArcType;		// 边的数据类型

// 构造数据类型
typedef struct
{
	VertexType Verts[MaxVertix];
	ArcType UdArcs[MaxVertix][MaxVertix];			// 无向图 -- 矩阵表示法
	int VerNum;										// 顶点个数
	int ArcNum;										// 边的个数
}AMGraph;				// Adjacency  Matrix  Graph 邻接矩阵

typedef struct
{
	int Data[MaxVertix];
	int F;	// 头指针
	int R;  // 尾指针
	int L;	// 当前队列长度
}Queue;

// 函数声明
Status CreateUDN(AMGraph* G);	// 创建无向图

int LocateVertex(AMGraph* G, VertexType* v);	// 获取 顶点 ver 的下标

Status DFS(AMGraph* G, int v);	// 深度优先遍历

Status BFS(AMGraph* G, int v);	// 广度优先遍历,借助队列进行层次遍历

void InitVisted_array(int len);	// 初始化辅助数组

Status InitQueue(Queue *Q);	// 队列初始化

Status EnQueque(Queue* Q, unsigned int v);	// 入队

Status EmptyQue(Queue* Q);	// 空队列,返回 TRUE; 非空,返回 FALSE

Status DeQueue(Queue* Q, int* value);	// 元素出队

函数文件

#include "Graph.h"

// 获取 顶点 ver 的下标
int LocateVertex(AMGraph* G, VertexType* v)
{
	if (!G) return ERROR;

	int i;
	for (i = 0; i < G->VerNum; i++)
		if (*v == G->Verts[i])
			return i;

	return -1;	// 返回 -1 表示未找到顶点
}

// 创建无向图
Status CreateUDN(AMGraph* G)			// UndirectNet
{
	if (!G) return ERROR;

	printf("请输入顶点及边个数(Vers Arcs): ");
	scanf("%d %d", &G->VerNum,&G->ArcNum);
	getchar();

	int i;
	//录入顶点
	printf("\n请输入顶点的值(英文字母): ");
	for (i = 0; i < G->VerNum; i++)
	{
		do
		{
			VertexType v;
			scanf("%c", &v);		//scanf("%[a-zA-Z]", G->VerNum);	// 只接收26个英文字母
			getchar();
			if ((65 <= v && v <= 90) || (97 <= v && v <= 122))
			{
				G->Verts[i] = v;
				break;
			}
			printf("输入错误,请输入英文字母!\n");

		} while (1);	// do-while循环用于处理错误输入

	}
	
	//初始化所有边的权
	int j;
	for(i = 0; i < G->VerNum; i++)
		for (j = i; j < G->VerNum; j++)
		{
			G->UdArcs[i][j] = 0;
			G->UdArcs[j][i] = 0;
		}

	//录入边的权值
	printf("\n请输入边关联的顶点及权值(v1 v2 weight): \n");
	for (i = 0; i < G->ArcNum; i++)
	{
		VertexType v1, v2;
		int w;
		do
		{
			scanf("%c %c %d", &v1, &v2, &w);
			getchar();

			if (v1 < 65 || (90 < v1 && v1 < 97) || 122 < v1)
			{
				printf("输入错误,请输入英文字母!\n");
				continue;
			}
			if (v2 < 65 || (90 < v2 && v2 < 97) || 122 < v2)
			{
				printf("输入错误,请输入英文字母!\n");
				continue;
			}
			//查找顶点位置
			int a, b;
			a = LocateVertex(G, &v1);
			b = LocateVertex(G, &v2);
		
			if (a < 0)		// 判断顶点是否存在
			{
				printf("输入的顶点%c不存在,请重新输入!\n",v1);
				continue;
			}
			if (b < 0)		// 判断顶点是否存在
			{
				printf("输入的顶点%c不存在,请重新输入!\n", v2);
				continue;
			}

			//链接到两顶点的边赋权值
			G->UdArcs[a][b] = w;
			G->UdArcs[b][a] = w;
			break;

		} while (1);	// do-while循环用于处理错误输入

	}
	return OK;
}

// 辅助数组,标记顶点是否访问过,1 表已访问,0 表未访问
int Visited[MaxVertix] = { 0 };	 // 辅助数组,标记顶点是否被访问

// 初始化辅助数组,当遍历一次图后,便于下一次再遍历
void InitVisted_array(int len)
{
	int i;
	for (i = 0; i < len; i++)
		Visited[i] = 0;
	return;
}

// 深度优先遍历
Status DFS(AMGraph* G, int v)	// 遍历邻接矩阵类型图G的第 v 个顶点
{
	if (!G || v < 0)	// 空指针、非法顶点的处理
		return ERROR;

	printf("%c ", G->Verts[v]);	// 打印顶点信息
	Visited[v] = 1;

	int i;
	for (i = 0; i < G->VerNum; i++)
	{
		if (G->UdArcs[v][i] != 0 && Visited[i] == 0)
			DFS(G, i);
	}

	return OK;
}


// 队列初始化 -- 形成一个空队
Status InitQueue(Queue* Q)
{
	if (!Q) return ERROR;	// 处理空指针
	
	int i;
	for(i = 0; i < Q->L; i++)
		Q->Data[i] = -1;

	Q->F = Q->R = 0;	// 空队列,首尾相接

	return OK;
}

// 入队
Status EnQueque(Queue* Q, unsigned int v)	// 数组的下标为非复数
{
	if (!Q) return ERROR;	// 处理空指针

	// 满队。数组表示的循序队列,
	// 为区分满队与空队,规定在入队方向上,
	// 队尾在队首相差一个位置时,为满队(而不是出队方向,出队方向说明队列还有一个未出队元素)
	if ((Q->R + 1) % Q->L == Q->F)	
		return ERROR;

	Q->Data[Q->R++] = v;	// 等价于 Q->Data[Q->R] = v; Q->R++; 队尾指针后移

	// 指针移到数组尾端,则跳转的首端,以实现循环队列结构。
	Q->R %= Q->L;
	// 此函数说的指针并非编程语言上的指针,而是方便标记队列位置的标记

	return OK;
}

// 元素出队
Status DeQueue(Queue* Q, int* value)
{
	if (!Q || !value) return ERROR; // 处理空指针

	if (Q->F == Q->R) return ERROR;	// 空队。无元素可出栈

	*value = Q->Data[Q->F++];	// 等价于 *value = Q->Data[Q->F]; Q->F++; 出队,队头指针后移

	// 指针移到数组尾端,则跳转的首端,以实现循环队列结构。
	Q->F %= Q->L;
	// 此函数说的指针并非编程语言上的指针,而是方便标记队列位置的标记

	return OK;
}

// 判断队列是否为空
Status EmptyQue(Queue* Q)
{
	if (!Q) return ERROR;	// 处理空指针

	if (Q->F == Q->R)	// 空队列,返回 TRUE == 1
		return TRUE;
	else 
		return FALSE;	// 非空队列,返回 FALSE == 0
}

// 广度优先遍历
Status BFS(AMGraph* G, int v)
{
	//思路:借助队列,进行层次遍历。出队返回邻接顶点的位置

	if (!G || v < 0)
		return ERROR;

	Queue Q;

	Q.L = G->VerNum;	// 循环队列,多增加一个空间,方便区分 空队 & 满队

	InitQueue(&Q);	// 初始化队列,形成一个空队

	EnQueque(&Q, v);	// 顶点的下标值作为元素入队
	Visited[v] = 1;

	int i,loc;
	while (!EmptyQue(&Q))	// 队列是否为空。空队,则已遍历完所有顶点
	{
		DeQueue(&Q, &loc);	// 出队
		printf("%c ", G->Verts[loc]);

		for (i = 0; i < G->VerNum; i++)
		{
			if (G->UdArcs[loc][i] != 0 && Visited[i] == 0)	// 寻找未被访问的邻接顶点,让其入队
			{
				EnQueque(&Q, i);
				Visited[i] = 1;		// 入队,稍后就会按顺序被访问,所以标记为 1,表已访问
			}
		}

	}

	return OK;
}

主函数文件

邻接矩阵表示的无向图广度遍历实现:

*包含深度优先遍历,便于进行对照观察

#include "Graph.h"			// 邻接矩阵存储结构

int main()
{
	/* 测试数据 及 结果
	A--------		
  /	  \	  	|
  B	   C	E
   \  /		|
    D		F

请输入顶点及边个数(Vers Arcs): 6 6

请输入顶点的值(英文字母): A B C D E F

请输入边关联的顶点及权值(v1 v2 weight):
A B 1
A C 1
A E 1
B D 1
C D 1
E F 1

  A  B  C  D  E  F
A 0  1  1  0  1  0
B 1  0  0  1  0  0
C 1  0  0  1  0  0
D 0  1  1  0  0  0
E 1  0  0  0  0  1
F 0  0  0  0  1  0

请输入图的深度优先遍历的起点: B

深度优先遍历结果: B A C D E F

请输入图的广度度优先遍历的起点: B

广度优先遍历结果: B A D C E F	*/

	AMGraph amg;

	CreateUDN(&amg);

	int i, j;
	printf("\n  ");
	for (i = 0; i < amg.VerNum; i++)
		printf("%c  ", amg.Verts[i]);

	//printf("\n  A  B  C  D  E");
	for (i = 0; i < amg.VerNum; i++)
	{
		//printf("%c ", 'A' + i);
		for (j = 0; j < amg.VerNum; j++)
		{
			if (j > 0) printf(" ");
			else printf("\n%c ", amg.Verts[i]);
			printf("%d ", amg.UdArcs[i][j]);
		}
	}

	int loc = 0;
	VertexType v1 = 0;
	printf("\n\n请输入图的深度优先遍历的起点: ");
	scanf("%c", &v1);
	getchar();
	loc = LocateVertex(&amg, &v1);

	printf("\n深度优先遍历结果: ");
	DFS(&amg, loc);
	InitVisted_array(amg.VerNum);	// 初始化辅助数组,便于下一次遍历

	printf("\n\n请输入图的广度度优先遍历的起点: ");
	scanf("%c", &v1);
	getchar();
	loc = LocateVertex(&amg, &v1);

	printf("\n广度优先遍历结果: ");
	BFS(&amg, loc);
	printf("\n");

	return 0;
}

em ~ ~ ~ ~ ~ 能够认识屏幕前的你是件非高兴的事!以上是个人的一些浅显的理解,还望小伙伴们批评指出,大家一起共同学习,共同进步!还望大家多多鼓励哟!

​​​​​​​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值