首先,我们来看一下涉及的知识点:
图:图 G=(V,E) 由顶点集 V 和边集 E 组成。每条边对应一个点对 (v,w),其中 v,w 属于 V 。如果图中的点对是有序的,那么该图就是有向图,反之为无向图。
邻接点:若顶点 v 与 w 之间存在一条边,则认为顶点 v 与 w 邻接。
权:图中的每条边都可以对应一个数值,这种与边相关的数值称为权。
路径:在图 G 中,顶点 v1 到 vk 的路径是一个顶点序列 v1,v2,···,vk。
连通图:在无向图 G 中,若两个顶点之间存在路径,则认为这两个顶点是连通的。如果在无向图 G 中,任意两个顶点都是连通的,则称 G 是连通图。
完全图:若图中任意两个顶点之间都存在一条边,则该图为完全图。
稀疏图和稠密图:当图中边的数量比较少时,称该图为稀疏图;而当图接近完全图时,称该图为稠密图。
接下来,我们进入正题:
1、深度优先遍历(DFS)
深度优先遍历类似于树的先序遍历。具体方法描述如下:
(1)从起始顶点 v 出发,首先访问顶点 v;
(2)选择一个与顶点 v 相邻接且没有被访问过的顶点 w 作为新的起始点,继续深度优先遍历,直到顶点 v 的所有邻接点都被访问过。
另外,若图不是连通图,则一次深度优先遍历只能访问到起始点所在连通分量中的所有顶点,而访问不到其它顶点。因此要从其它连通分量中选择起始点,继续深度优先遍历,才能将图中的所有顶点都访问一遍。为了保证在遍历过程中每个顶点只会被访问一次,我们借助一个辅助数组 Visit[] 来做一下标记。若 Visit[] 为1,则说明顶点 vi 已被访问过,若为0,则没有被访问过。
邻接矩阵的DFS代码:
# include <iostream>
# include <iomanip>
# define maxn 20
using namespace std;
typedef struct VexType { //顶点类型
int code; //顶点编号
char data; //顶点信息
}VexType;
typedef struct Graph { //邻接矩阵类型
int arcs[maxn][maxn]; //邻接矩阵
int vexnum, arcnum; //顶点数和边的个数
VexType vexs[maxn]; //顶点信息
int type; //邻接矩阵的类型(1.无向图 2.有向图)
}Graph;
int Visit[maxn] = { 0 }; //辅助数组,标记点是否已被访问过
void Create_Graph(Graph& G) //创建
{
cout << "请输入图的类型(1.无向图 2.有向图):";
cin >> G.type;
cout << "顶点的个数:";
cin >> G.vexnum;
cout << "请输入各顶点的名称:";
for (int i = 0; i < G.vexnum; i++) {
cin >> G.vexs[i].data;
G.vexs[i].code = i; //按顺序为点编号
}
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
G.arcs[i][j] = 0; //邻接矩阵初始化,将所有元素的初始值设为0
}
}
cout << "请输入边的个数:";
cin >> G.arcnum;
cout << "请输入各边起点和终点的编号及权重:" << endl;
int x, y, w; //x:起始点,y:终点,w:权重
for (int i = 0; i < G.arcnum; i++) {
cin >> x >> y >> w;
if (G.type == 1) { //如果是无向图,对称边也要赋值为权重
G.arcs[x][y] = w;
G.arcs[y][x] = w;
}
else {
G.arcs[x][y] = w;
}
}
}
void DFS(Graph G, int n)
{
Visit[n] = 1; //将起始点标记为已被访问过的状态
cout << G.vexs[n].data; //输出起始点的信息
for (int i = 0; i < G.vexnum; i++) {
//遍历起始点的所有邻接点,若邻接点已被访问过,则p继续向后遍历
//否则,以当前邻接点为新的起始点递归进行深度优先遍历
if (!Visit[i] && (G.arcs[n][i] > 0 || G.arcs[i][n] > 0)) {
DFS(G, i);
}
}
}
void DFS_Graph(Graph G) //深度优先遍历,对每个顶点访问且仅访问一次
{
for (int i = 0; i < G.vexnum; i++) {
Visit[i] = 0; //辅助数组初始化
}
for (int i = 0; i < G.vexnum; i++) {
if (!Visit[i]) { //对每一个未被访问过的顶点均调用一次深度优先遍历
DFS(G, i);
}
}
}
int main()
{
Graph G;
Create_Graph(G); //创建
cout << "DFS:";
DFS_Graph(G); //深度优先遍历,对每个顶点访问且仅访问一次
cout << endl;
return 0;
}
邻接表的DFS代码:
# include <iostream>
# include <queue>
# define SIZE 20
# define NEWSIZE 20
using namespace std;
typedef struct ArcNode { //边的结点结构类型
int adjvex; //该边的终点编号
int weight; //该边的权值
struct ArcNode* nextarc; //指向下一条边的指针
}ArcNode;
typedef struct VexNode { //顶点结构
char data;
ArcNode* firstarc; //指向第一条与该顶点有关的边的指针
}VexNode;
typedef struct Graph { //邻接表结构类型
VexNode* VNode; //定义邻接表
int vexnum, arcnum; //顶点数和边的个数
int type; //图的种类
int size; //邻接表的大小
}Graph;
int* Visit; //辅助数组,标记点是否已被访问过
void Create_Graph(Graph& G) //创建
{
cout << "请输入图的类型(1.无向图 2.有向图):";
cin >> G.type;
cout << "顶点的个数:";
cin >> G.vexnum;
G.VNode = (VexNode*)malloc(SIZE * sizeof(VexNode));
G.size = SIZE;
while (G.size < G.vexnum) { //根据点的个数动态分配空间
G.VNode = (VexNode*)realloc(G.VNode, (G.size + NEWSIZE) * sizeof(VexNode));
G.size += NEWSIZE;
}
Visit = (int*)malloc((G.size + 10) * sizeof(int));
cout << "请输入各顶点的名称:";
for (int i = 0; i < G.vexnum; i++) {
cin >> G.VNode[i].data;
G.VNode[i].firstarc = NULL; //邻接表初始化,所有单向链表均为空表
}
cout << "请输入边的个数:";
cin >> G.arcnum;
cout << "请输入各边起点和终点的编号及权重:" << endl;
int x, y, w; //x:起始点,y:终点,w:权重
ArcNode* p, * q;
for (int i = 0; i < G.arcnum; i++) {
cin >> x >> y >> w;
p = (ArcNode*)malloc(sizeof(ArcNode)); //创建一个用于存放当前边的结点p
p->nextarc = NULL;
p->adjvex = y;
p->weight = w;
q = G.VNode[x].firstarc;
//将边按顺序插入到链表末尾
if (q == NULL) {
G.VNode[x].firstarc = p;
}
else {
while (q->nextarc != NULL) {
q = q->nextarc;
}
q->nextarc = p;
}
if (G.type == 1) { //如果是无向图,要再创建一个表示对称边的结点p
p = (ArcNode*)malloc(sizeof(ArcNode));
p->nextarc = NULL;
p->adjvex = x;
p->weight = w;
q = G.VNode[y].firstarc;
if (q == NULL) {
G.VNode[y].firstarc = p;
}
else {
while (q->nextarc != NULL) {
q = q->nextarc;
}
q->nextarc = p;
}
}
}
}
void DFS(Graph G, int n)
{
Visit[n] = 1; //将起始点标记为已被访问过的状态
cout << G.VNode[n].data; //输出起始点的信息
ArcNode* p = G.VNode[n].firstarc;
while (p) {
//遍历起始点的所有邻接点,若邻接点已被访问过,则p继续向后遍历
//否则,以当前邻接点为新的起始点递归进行深度优先遍历
if (!Visit[p->adjvex]) {
DFS(G, p->adjvex);
}
p = p->nextarc;
}
}
void DFS_Graph(Graph G) //深度优先遍历,对每个顶点访问且仅访问一次
{
for (int i = 0; i < G.vexnum; i++) {
Visit[i] = 0; //辅助数组初始化
}
for (int i = 0; i < G.vexnum; i++) {
if (!Visit[i]) { //对每一个未被访问过的顶点均调用一次深度优先遍历
DFS(G, i);
}
}
}
int main()
{
Graph G;
Create_Graph(G); //创建
cout << "DFS:";
DFS_Graph(G); //深度优先遍历,对每个顶点访问且仅访问一次
cout << endl;
return 0;
}
运行结果:
请输入图的类型(1.无向图 2.有向图):1
顶点的个数:8
请输入各顶点的名称:A B C D E F G H
请输入边的个数:9
请输入各边起点和终点的编号及权重:
0 1 3
0 2 12
1 3 5
1 4 6
2 3 7
2 4 10
5 6 9
5 7 4
6 7 13
DFS:ABDCEFGH
对邻接矩阵DFS的时间复杂度为 O(n^2),对邻接表DFS的时间复杂度为 O(n+e)( n 是图中顶点的个数,e 是边的个数),但邻接表的创建要较为复杂。
2、广度优先遍历(BFS)
广度优先遍历类似于树的层次遍历。具体方法描述如下:
(1)从起始顶点 v 出发,首先访问顶点 v;
(2)依次访问 v 的所有没有被访问过的邻接点 w1,w2,···,wk;
(3)按照 w1,w2,···,wk 的次序,访问它们各自所有未被访问过的邻接点;
(4)以此类推,直到图中所有与起始点 v 连通的顶点都被访问过为止。
同样,若图不是连通图,则一次广度优先遍历只能访问到与起始点连通的那些顶点,而访问不到其它顶点。因此要从其它连通分量中选择起始点,继续广度优先遍历,才能将图中的所有顶点都访问一遍。为了保证在遍历过程中每个顶点只会被访问一次,我们也要借助一个辅助数组 Visit[] 来做一下标记。若 Visit[] 为1,则说明顶点 vi 已被访问过,若为0,则没有被访问过。
邻接矩阵的BFS代码:
# include <iostream>
# include <iomanip>
# include <queue>
# define maxn 20
using namespace std;
typedef struct VexType { //顶点类型
int code; //顶点编号
char data; //顶点信息
}VexType;
typedef struct Graph { //邻接矩阵类型
int arcs[maxn][maxn]; //邻接矩阵
int vexnum, arcnum; //顶点数和边的个数
VexType vexs[maxn]; //顶点信息
int type; //邻接矩阵的类型(1.无向图 2.有向图)
}Graph;
int Visit[maxn] = { 0 }; //辅助数组,标记点是否已被访问过
void Create_Graph(Graph& G) //创建
{
cout << "请输入图的类型(1.无向图 2.有向图):";
cin >> G.type;
cout << "顶点的个数:";
cin >> G.vexnum;
cout << "请输入各顶点的名称:";
for (int i = 0; i < G.vexnum; i++) {
cin >> G.vexs[i].data;
G.vexs[i].code = i; //按顺序为点编号
}
for (int i = 0; i < G.vexnum; i++) {
for (int j = 0; j < G.vexnum; j++) {
G.arcs[i][j] = 0; //邻接矩阵初始化,将所有元素的初始值设为0
}
}
cout << "请输入边的个数:";
cin >> G.arcnum;
cout << "请输入各边起点和终点的编号及权重:" << endl;
int x, y, w; //x:起始点,y:终点,w:权重
for (int i = 0; i < G.arcnum; i++) {
cin >> x >> y >> w;
if (G.type == 1) { //如果是无向图,对称边也要赋值为权重
G.arcs[x][y] = w;
G.arcs[y][x] = w;
}
else {
G.arcs[x][y] = w;
}
}
}
void BFS(Graph G, int n)
{
queue<int>Q;
Visit[n] = 1; //将起始点标记为已被访问过的状态
cout << G.vexs[n].data; //先输出起始点信息
Q.push(n); //起始点入队
while (!Q.empty()) {
n = Q.front(); //队头元素出队
Q.pop();
for (int i = 0; i < G.vexnum; i++) {
//遍历当前队头结点的所有邻接点,若邻接点已被访问过,则p继续向后遍历
//否则入队,并标记为已访问状态,防止重复入队
if (!Visit[i] && (G.arcs[n][i] > 0 || G.arcs[i][n] > 0)) {
Visit[i] = 1;
cout << G.vexs[i].data;
Q.push(i);
}
}
}
}
void BFS_Graph(Graph G) //广度优先遍历,对每个顶点访问且仅访问一次
{
for (int i = 0; i < G.vexnum; i++) {
Visit[i] = 0; //辅助数组初始化
}
for (int i = 0; i < G.vexnum; i++) {
if (!Visit[i]) { //对每一个未被访问过的顶点均调用一次广度优先遍历
BFS(G, i);
}
}
}
int main()
{
Graph G;
Create_Graph(G); //创建
cout << "BFS:";
BFS_Graph(G); //广度优先遍历,对每个顶点访问且仅访问一次
cout<<endl;
return 0;
}
邻接表的BFS代码:
# include <iostream>
# include <queue>
# define SIZE 20
# define NEWSIZE 20
using namespace std;
typedef struct ArcNode { //边的结点结构类型
int adjvex; //该边的终点编号
int weight; //该边的权值
struct ArcNode* nextarc; //指向下一条边的指针
}ArcNode;
typedef struct VexNode { //顶点结构
char data;
ArcNode* firstarc; //指向第一条与该顶点有关的边的指针
}VexNode;
typedef struct Graph { //邻接表结构类型
VexNode* VNode; //定义邻接表
int vexnum, arcnum; //顶点数和边的个数
int type; //图的种类
int size; //邻接表的大小
}Graph;
int* Visit; //辅助数组,标记点是否已被访问过
void Create_Graph(Graph& G) //创建
{
cout << "请输入图的类型(1.无向图 2.有向图):";
cin >> G.type;
cout << "顶点的个数:";
cin >> G.vexnum;
G.VNode = (VexNode*)malloc(SIZE * sizeof(VexNode));
G.size = SIZE;
while (G.size < G.vexnum) { //根据点的个数动态分配空间
G.VNode = (VexNode*)realloc(G.VNode, (G.size + NEWSIZE) * sizeof(VexNode));
G.size += NEWSIZE;
}
Visit = (int*)malloc((G.size + 10) * sizeof(int));
cout << "请输入各顶点的名称:";
for (int i = 0; i < G.vexnum; i++) {
cin >> G.VNode[i].data;
G.VNode[i].firstarc = NULL; //邻接表初始化,所有单向链表均为空表
}
cout << "请输入边的个数:";
cin >> G.arcnum;
cout << "请输入各边起点和终点的编号及权重:" << endl;
int x, y, w; //x:起始点,y:终点,w:权重
ArcNode* p, * q;
for (int i = 0; i < G.arcnum; i++) {
cin >> x >> y >> w;
p = (ArcNode*)malloc(sizeof(ArcNode)); //创建一个用于存放当前边的结点p
p->nextarc = NULL;
p->adjvex = y;
p->weight = w;
q = G.VNode[x].firstarc;
//将边按顺序插入到链表末尾
if (q == NULL) {
G.VNode[x].firstarc = p;
}
else {
while (q->nextarc != NULL) {
q = q->nextarc;
}
q->nextarc = p;
}
if (G.type == 1) { //如果是无向图,要再创建一个表示对称边的结点p
p = (ArcNode*)malloc(sizeof(ArcNode));
p->nextarc = NULL;
p->adjvex = x;
p->weight = w;
q = G.VNode[y].firstarc;
if (q == NULL) {
G.VNode[y].firstarc = p;
}
else {
while (q->nextarc != NULL) {
q = q->nextarc;
}
q->nextarc = p;
}
}
}
}
void BFS(Graph G, int n)
{
queue<int>Q;
Visit[n] = 1; //将起始点标记为已被访问过的状态
cout << G.VNode[n].data; //先输出起始点信息
Q.push(n); //起始点入队
while (!Q.empty()) {
n = Q.front(); //队头元素出队
Q.pop();
ArcNode* p = G.VNode[n].firstarc;
while (p) {
//遍历当前队头结点的所有邻接点,若邻接点已被访问过,则p继续向后遍历
//否则入队,并标记为已访问状态,防止重复入队
if (!Visit[p->adjvex]) {
Visit[p->adjvex] = 1;
cout << G.VNode[p->adjvex].data;
Q.push(p->adjvex);
}
p = p->nextarc;
}
}
}
void BFS_Graph(Graph G) //广度优先遍历,对每个顶点访问且仅访问一次
{
for (int i = 0; i < G.vexnum; i++) {
Visit[i] = 0; //辅助数组初始化
}
for (int i = 0; i < G.vexnum; i++) {
if (!Visit[i]) { //对每一个未被访问过的顶点均调用一次广度优先遍历
BFS(G, i);
}
}
}
int main()
{
Graph G;
Create_Graph(G); //创建
cout << "BFS:";
BFS_Graph(G); //广度优先遍历,对每个顶点访问且仅访问一次
cout << endl;
return 0;
}
运行结果:
请输入图的类型(1.无向图 2.有向图):1
顶点的个数:8
请输入各顶点的名称:A B C D E F G H
请输入边的个数:9
请输入各边起点和终点的编号及权重:
0 1 3
0 2 12
1 3 5
1 4 6
2 3 7
2 4 10
5 6 9
5 7 4
6 7 13
BFS:ABCDEFGH
相似的,对邻接矩阵BFS的时间复杂度为 O(n^2),对邻接表BFS的时间复杂度为 O(n+e)( n 是图中顶点的个数,e 是边的个数)。
以上就是我这次的全部学习成果,很高兴能与大家分享。