目录
一、图的定义及术语
1.1 图的定义
图是由结点(也称顶点)和边(也称弧)组成的非线性数据结构。结点是图的基本单元,边则用于将结点与结点关联起来。图结构有很多应用,如社交网络、网络拓扑、地图、物流。
在图中,节点和边可以具有不同的属性。例如,在社交网络图中,节点属性可以表示用户,边属性可以表示用户之间的关系,如朋友或家庭关系。边可以是有指向或无指向的,也可以是加权或未加权的。
图的定义:图G由顶点集V和边集E组成,G = (V,E); V是有穷非空集合;E是有穷集合。
1.2 图的类别
有向图:图的边是有指向的,则称为有向图。
如
无向图:图的边是无指向的,则称为无向图。
如
网:边带权重的图,则称为网。网又可分无向网和有向网。 边的权重可表示距离、成本或其他数据。
如
完全图:任意两结点间都有一条边相连。分无向完全图、有向完全图。
稀疏图:边很少的图( )
稠密图:边很多的图
强连通图:图中任何两个顶点 v,u 都存在从 v 到 u 的路径,则称为连通图(强连通图)
1.3 相关术语
邻接: 有边相连的两个顶点间的关系。
存在 (),则称 和 互为邻接点; --- 无向图
存在 ,则称 邻接到 , 邻接于 ; --- 有向图
关联(依附): 边与顶点之间的关系。
存在() 或 ,则称该边关联于 和 ;
顶点的度: 与该顶点相关联的边的数目,记作 TD(V)
有向图中,顶点的度为入度和出度之和。
顶点v的入度是以v为终点的有向边的条数,记作 ID(v)
顶点v的出度是以v为始点的有向边的条数,记作 OD(v)
顶点 | 度 |
v1 | 3 |
v2 | 1 |
v3 | 2 |
v4 | 2 |
顶点 | 入度 | 出度 | 度 |
v1 | 1 | 2 | 3 |
v2 | 1 | 0 | 1 |
v3 | 1 | 1 | 2 |
v4 | 1 | 1 | 2 |
路径: 接续的边构成的顶点序列
路径长度:路径上边的数目 / 权重之后
回路(环):第一个顶点和最后一个顶点相同的路径(首尾相接)
简单路径:出路径起点和终点可以相同外,其余顶点均不相同的路径
简单回路(环):出路径起点和终点相同,其余顶点均不相同的路径
子图:设有两个图 G = (V,E)、G1 = (V1,E1)。若 ,则称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. 无向图中顶点 的度为第 i 个单链表中的结点个数
2)特点(对于无向图):
1. 顶点 的出度为第 i 个单链表中的结点个数
2. 顶点 的入度为整个单链表中邻接点域值为 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,建立边结点;将此边结点分别插入到 和 对应的两个边链表的头部
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. 创建邻接表。依次输入每个顶点的出度信息;确定两个顶点的序号 i、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 出发,访问它的任一邻接顶点 ;
2.再从 出发,访问与 邻接但还未被访问的顶点 ;
3.再从 出发,依次类推的访问邻接顶点;
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 思路
从某顶点出发,首先依次访问该顶点的所有邻接顶点 再按这些顶点被访问的先后次序,访问与它们邻接的所有未被访问的顶点。重复此过程,直至所有顶点被访问
举例:
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 ~ ~ ~ ~ ~ 能够认识屏幕前的你是件非高兴的事!以上是个人的一些浅显的理解,还望小伙伴们批评指出,大家一起共同学习,共同进步!还望大家多多鼓励哟!