图:图的邻接矩阵创建、深度优先遍历和广度优先遍历详解

邻接矩阵介绍

直接说,邻接矩阵是图的一种存储结构。那么图是什么呢?图是一种逻辑结构,和线性结构、树形结构、集合结构一样 是一种逻辑结构用来描述数据对象中的数据元素之间的关系。来看下图的定义:图(Graph)是有顶点的有穷非空集和顶点之间边的集合组成,通常表示为G(V,E)其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合

考虑到图是有顶点和边或弧2部分组成。合在一起比较困难,那就自然的考虑到分为2个结构来分别存储。

图的邻接矩阵(Adjacency Matrix)存储方式是用2个数组来表示图。一个一维数组存储图中顶点的信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息

邻接矩阵创建图

任何算法,我们最先考虑的肯定是数据结构,那么用邻接矩阵的方式,图的数据结构就很好表示了。包含顶点数组,邻接矩阵,当然还有顶点数量和边的数量。数据结构是为算法进行服务的。图的数据结构:

typedef struct
{
VertexType vertex[MAX_VERTEX];//顶点集
EdgeType edge[MAX_VERTEX][MAX_VERTEX];//边集
int vertexNum, edgeNum;//顶点数,边数
}MGraph
;

对图的数据结构清楚后,创建图的算法,其实就是图内容的一个赋值

CreateMGraph 函数

//邻接矩阵创建无向网图
MGraph* CreateMGraph()
{
	MGraph* mGraph = (MGraph*)malloc(sizeof(MGraph));
	int vertexNum, edgeNum;
	printf("请输入顶点数和边数(请用逗号隔开):");
	scanf("%d,%d", &vertexNum, &edgeNum);
	mGraph->vertexNum = vertexNum;
	mGraph->edgeNum = edgeNum;
	getchar();//去除上次输入的换行
	printf("请输入顶点值:");
	//输入顶点值
	for (int i = 0; i < vertexNum; i++)
	{
		scanf("%c", &mGraph->vertex[i]);
	}
	//初始化 边集
	for (int i = 0; i < vertexNum; i++)
	{
		for (int j = 0; j < vertexNum; j++)
		{
			mGraph->edge[i][j] = INFINITY;
		}
	}
	//输入边值
	for (int i = 0; i < edgeNum; i++)
	{
		int i, j, w;
		printf("请输入(Vi,Vj)对应的顶点的下标和权值(用逗号隔开):");
		scanf("%d,%d,%d", &i, &j, &w);
		mGraph->edge[i][j] = mGraph->edge[j][i] = w;//无向图为对称矩阵
	}
	return mGraph;
}

邻接矩阵的深度优先遍历

先说下图的遍历:从图中某一顶点出发访遍图中其余顶点,且使每个顶点仅被访问一次,这一过程就叫做图的遍历(Traversing Graph)。那么何为深度优先遍历呢?深度优先遍历(Depth First Search),也有称为深度优先搜索,简称DFS想一想 因为在图中有很多条道路,怎么访问一个地方后不再访问访问过的顶点呢?其实有2种方案,就对应我们的2种遍历方式。一种方案:图中每个顶点 相连着有很多顶点,我们可以从某个结点v出发,访问此结点,然后访问v的未访问过的邻阶点,然后继续访问该结点 的未访问过的邻接点,有点类似顺藤摸瓜的意思,一直深入,直到图中所有的顶点都被访问到为止。是不是感觉可以用递归呢?肯定要用一个全局数组来记录顶点是否访问过呗,访问过下次就不访问了呗。下面是邻接矩阵的深度优先遍历:

void DFTMGraph(MGraph* graph,int vexIndex)
{
	//访问过 就不再进行访问
	if (g_visited[vexIndex] == TRUE)
	{
		return;
	}
	g_visited[vexIndex] = TRUE;
	//对访问到的结点进行操作
	VisitMGraphVertex(graph->vertex[vexIndex]);

	for (int j = 0; j < graph->vertexNum; j++)
	{
		//权值不为零,继续访问下一个结点
		if (graph->edge[vexIndex][j] != 0)
		{
			DFTMGraph(graph, j);
		}
	}
	return;
}

//深度优先遍历
void TraverseMGraph(MGraph* graph)
{
	if (NULL == graph)
	{
		return;
	}
	//设置结点都没有访问过
	for (int i = 0; i < graph->vertexNum; i++)
	{
		g_visited[i] = FALSE;
	}
	for (int i = 0; i < graph->vertexNum; i++)
	{
		if (g_visited[i] == FALSE)
		{
			DFTMGraph(graph,i);
		}
	}
	return;
}

邻接矩阵的广度优先遍历

广度优先遍历(Breath First Search),又称为广度优先搜索,检测BFS它很类似层序遍历,从图中某结点v出发,将该结点没有访问过的邻接点访问一遍,然后访问已经访问过结点的未访问过的邻结点访问一遍,直到图中的所有结点被访问完。下面是邻接矩阵的广度优先遍历:

//广度优先遍历 邻接矩阵
void BFTMGraph(MGraph* graph)
{
	Queue queue;
	InitQueue(&queue);
	for (int i = 0; i < graph->vertexNum; i++)
	{
		g_visited[i] = FALSE;
	}
	for (int i = 0; i < graph->vertexNum; i++)
	{
		if (g_visited[i] == FALSE)
		{
			g_visited[i] = TRUE;
			VisitMGraphVertex(graph->vertex[i]);
			EnQueue(&queue,i);//当前结点下标入队
			//为什么这里要循环出队呢?出队获取已经访问过结点的下标,在内层的for继续访问相关联结点,将减少外层for循环进入if次数
			while (!EmptyQueue(&queue))
			{
				int vexIndex;
				DeQueue(&queue, &vexIndex);//访问过的结点下标出队
				for (int j = 0; j < graph->vertexNum; j++)
				{
					//将该节点的连接的结点且没有被访问过的结点访问,然后入队
					if (graph->edge[vexIndex][j] != 0 && graph->edge[vexIndex][j] != INFINITY && g_visited[j] == FALSE)
					{
						g_visited[j] = TRUE;
						VisitMGraphVertex(graph->vertex[j]);
						EnQueue(&queue, graph->vertex[j]);
					}
				
				}
			}

			
		}
	}
	return;
}

代码汇总

Queue.h

#pragma once
#ifndef __QUEUE_H__
#define __QUEUE_H__

typedef int EleType;//元素类型
typedef enum { ERROR, OK } Status;
typedef enum {FALSE,TRUE} Boolean;

//队列结点
typedef struct QueueNode
{
	EleType data;
	struct QueueNode* next;
}QueueNode;

//队列
typedef struct Queue
{
	QueueNode* front;
	QueueNode* tail;
}Queue;

//队列初始化
void InitQueue(Queue* queue);

//入队
int EnQueue(Queue* queue, EleType data);

//出队
int DeQueue(Queue* queue, EleType* data);

//队列是否为空
int EmptyQueue(Queue* queue);

#endif // !__QUEUE_H__

Queue.c

#include <stdlib.h>
#include "Queue.h"


//队列初始化
void InitQueue(Queue* queue)
{
	queue->front = NULL;
	queue->tail = NULL;
	return;
}

//入队
int EnQueue(Queue* queue, EleType data)
{
	if (NULL == queue)
	{
		return ERROR;
	}
	QueueNode* node = (QueueNode*)malloc(sizeof(QueueNode));
	node->data = data;
	node->next = NULL;
	if (queue->front == NULL)
	{
		queue->front = queue->tail = node;
	}
	else
	{
		queue->tail->next = node;
		queue->tail = node;
	}
	return OK;
}
//出队
int DeQueue(Queue* queue, EleType* data)
{
	if (NULL == queue)
	{
		return ERROR;
	}
	if (!EmptyQueue(queue))
	{
		QueueNode* node = queue->front;
		*data = node->data;
		queue->front = queue->front->next;
		if (NULL != node)
		{
			free(node);
			node = NULL;
		}
		//队列的最后一个元素出队列后,tail指针也要置为空
		if (EmptyQueue(queue))
		{
			queue->tail = queue->front;
		}
	}
	return OK;
}

//队列是否为空
int EmptyQueue(Queue* queue)
{
	if (NULL == queue)
	{
		return ERROR;
	}
	if (queue->front == NULL)
	{
		return TRUE;
	}
	return FALSE;
}


MGraph.c

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include "Queue.h"
#define MAX_VERTEX 100
#define INFINITY 65535
typedef char VertexType;//顶点类型
typedef int EdgeType;//边上权值类型
typedef struct
{
	VertexType vertex[MAX_VERTEX];//顶点集
	EdgeType edge[MAX_VERTEX][MAX_VERTEX];//边集
	int vertexNum, edgeNum;//顶点数,边数
}MGraph;
Boolean g_visited[MAX_VERTEX] = {0};//全局变量 顶点访问标志位数组
//邻接矩阵创建无向网图
MGraph* CreateMGraph()
{
	MGraph* mGraph = (MGraph*)malloc(sizeof(MGraph));
	int vertexNum, edgeNum;
	printf("请输入顶点数和边数(请用逗号隔开):");
	scanf("%d,%d", &vertexNum, &edgeNum);
	mGraph->vertexNum = vertexNum;
	mGraph->edgeNum = edgeNum;
	getchar();//去除上次输入的换行
	printf("请输入顶点值:");
	//输入顶点值
	for (int i = 0; i < vertexNum; i++)
	{
		scanf("%c", &mGraph->vertex[i]);
	}
	//初始化 边集
	for (int i = 0; i < vertexNum; i++)
	{
		for (int j = 0; j < vertexNum; j++)
		{
			mGraph->edge[i][j] = INFINITY;
		}
	}
	//输入边值
	for (int i = 0; i < edgeNum; i++)
	{
		int i, j, w;
		printf("请输入(Vi,Vj)对应的顶点的下标和权值(用逗号隔开):");
		scanf("%d,%d,%d", &i, &j, &w);
		mGraph->edge[i][j] = mGraph->edge[j][i] = w;//无向图为对称矩阵
	}
	return mGraph;
}
//打印邻接矩阵的无向图数据
void PrintMGraph(MGraph* mGraph)
{
	printf("顶点数组数据:\n");
	for (int i = 0; i < mGraph->vertexNum; i++)
	{
		printf("%c ", mGraph->vertex[i]);
	}
	printf("\n边点数组数据:\n");
	for (int i = 0; i < mGraph->vertexNum; i++)
	{
		for (int j = 0; j < mGraph->vertexNum; j++)
		{
			printf("%d\t", mGraph->edge[i][j]);
		}
		printf("\n");
	}
}
//访问顶点元素
void VisitMGraphVertex(VertexType data)
{
	printf("%c ",data);
	return;
}

void DFTMGraph(MGraph* graph,int vexIndex)
{
	//访问过 就不再进行访问
	if (g_visited[vexIndex] == TRUE)
	{
		return;
	}
	g_visited[vexIndex] = TRUE;
	//对访问到的结点进行操作
	VisitMGraphVertex(graph->vertex[vexIndex]);

	for (int j = 0; j < graph->vertexNum; j++)
	{
		//权值不为零,继续访问下一个结点
		if (graph->edge[vexIndex][j] != 0)
		{
			DFTMGraph(graph, j);
		}
	}
	return;
}

//深度优先遍历
void TraverseMGraph(MGraph* graph)
{
	if (NULL == graph)
	{
		return;
	}
	//设置结点都没有访问过
	for (int i = 0; i < graph->vertexNum; i++)
	{
		g_visited[i] = FALSE;
	}
	for (int i = 0; i < graph->vertexNum; i++)
	{
		if (g_visited[i] == FALSE)
		{
			DFTMGraph(graph,i);
		}
	}
	return;
}
//广度优先遍历 邻接矩阵
void BFTMGraph(MGraph* graph)
{
	Queue queue;
	InitQueue(&queue);
	for (int i = 0; i < graph->vertexNum; i++)
	{
		g_visited[i] = FALSE;
	}
	for (int i = 0; i < graph->vertexNum; i++)
	{
		if (g_visited[i] == FALSE)
		{
			g_visited[i] = TRUE;
			VisitMGraphVertex(graph->vertex[i]);
			EnQueue(&queue,i);//当前结点下标入队
			//为什么这里要循环出队呢?出队获取已经访问过结点的下标,在内层的for继续访问相关联结点,将减少外层for循环进入if次数
			while (!EmptyQueue(&queue))
			{
				int vexIndex;
				DeQueue(&queue, &vexIndex);//访问过的结点下标出队
				for (int j = 0; j < graph->vertexNum; j++)
				{
					//将该节点的连接的结点且没有被访问过的结点访问,然后入队
					if (graph->edge[vexIndex][j] != 0 && g_visited[j] == FALSE)
					{
						g_visited[j] = TRUE;
						VisitMGraphVertex(graph->vertex[j]);
						EnQueue(&queue, graph->vertex[j]);
					}
				
				}
			}

			
		}
	}
	return;
}

int main(int argc, char *argv[])
{
	MGraph* mGraph = CreateMGraph();
	PrintMGraph(mGraph);
	printf("深度优先遍历邻接矩阵:\n");
	TraverseMGraph(mGraph);
	printf("\n广度优先遍历邻接矩阵:\n");
	BFTMGraph(mGraph);
	printf("\n");
	return 0;
}

代码运行检测

我们来创建如下图 的一个图,图是教材上的。

《图:图的邻接矩阵创建、深度优先遍历和广度优先遍历详解》

代码运行结果:

《图:图的邻接矩阵创建、深度优先遍历和广度优先遍历详解》

    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/qq_29542611/article/details/79340112
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞