1.コンセプト
グラフ走査操作は、グラフ内の特定の頂点から開始され、グラフ内のすべての頂点を 1 回だけ訪問します。
(1) グラフ内で、最も小さい番号の頂点がトラバースの開始頂点となります。
(2) 開始点がすべての頂点に到達できない場合、すべての頂点にアクセスするために複数の呼び出しが行われます。
(3) 走査がループによる無限ループに陥るのを避けるために、訪問済みフラグの配列 Visited[n] が添付されます (すべての頂点添字に対応する場合、訪問済みの場合は 1 に設定し、訪問済みの場合は設定します)。訪問されていない場合は 0 になります)
(4) すべてのノードの番号は 0 から始まります
2. アイデア
(1) キュー Q を初期化します。
(2) 頂点 v にアクセスし、頂点 v がキュー Q に入ります。
(3) キューが空でない限り、値をポップし、それを v に割り当てます(外側のループ)。
(4) v のすべての隣接点をループし、未訪問の隣接点をスタックします (内側ループ)。
たとえば、下の図: (この図は、Lazy Cat 氏による関連コース「データ構造」の配布資料から引用したものです)
疑似コード:
ポップおよびプッシュ操作の機能は、レベルに従ってノードを順番に保存することであり、最初にポップアウトされたノードは層数が最も少ないノードでもあり、したがって、それに応じてアクセスするための幅優先トラバーサルの要件を実現します。レイヤーの数に合わせて
3. コードの実装
3.1 ファンクションコード
(1) 隣接行列の幅優先走査
void BFSTraverse(int arc[][MAX_VERTEX], DataType *vertex, int vertexNum, int v) {//广度优先遍历
int visited[vertexNum];
initvertex(vertexNum, visited);//初始化数组
SqQueue Q;
InitQueue(&Q);//初始化队列
visit(vertex, v); //输出访问过的顶点信息
visited[v] = 1;
EnQueue(&Q, v); //将顶点v的下标入队
while (QueueEmpty(Q) != 1) { //当队列非空时
v = DeQueue(&Q); //将队头元素出队并送到v中
for (int w = 0; w < vertexNum; w++) { //将该顶点的所有连接点都扫描一遍
if (arc[v][w] == 1 && visited[w] == 0) {
visit(vertex, w);
visited[w] = 1;
EnQueue(&Q, w); //将顶点w的下标入队
}
}
}
}
(2) 隣接リストの幅優先走査
void BFSTraverse(VertexNode *adjList, int vertexNum, int v) { //广度优先遍历
int visited[vertexNum];
initvertex(vertexNum, visited);
SqQueue Q;
InitQueue(&Q);//初始化队列
ArcNode *p;
visit(adjList, v); //输出访问过的顶点信息
visited[v] = 1;
EnQueue(&Q, v); //将顶点v的下标入队
while (QueueEmpty(Q) != 1) { //当队列非空时
v = DeQueue(&Q); //将队头元素出队并送到v中
p = adjList[v].firstEdge; //工作指针p指向顶点v的边表
while (p != NULL) {
//遍历该顶点的边表
if (visited[p->adjvex] == 0) {
visit(adjList, p->adjvex);
visited[p->adjvex] = 1;
EnQueue(&Q, p->adjvex); //将顶点w的下标入队
}
p = p->next;
}
}
}
3.2 完全なコードとテスト
次のコードには、グラフの構築、グラフの深さ優先トラバース、および上記のグラフの幅優先トラバースの関数実装が含まれています。最初の 2 つの部分の詳細な説明が必要な場合は、ここにアクセスしてください。
グラフ構築:グラフストレージ (隣接行列、隣接リスト、相互リンクリスト)
グラフの深さ優先トラバーサル:グラフ トラバーサル (深さ優先トラバーサル、DFS)
(1) キュー
幅優先トラバーサル操作ではキューの基本操作が必要となるため、次のキュー (循環キュー) は 2 つのグラフの幅優先トラバーサルで共有されます。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXQSIZE 100//最大队列长度
typedef int QElemType;
typedef struct {
QElemType *data; //指向队列储存空间
int front; //队首下标
int rear; //队尾下标
} SqQueue;
int InitQueue(SqQueue *Q) {//初始化队列函数,初始化成功返回1,失败返回0
Q->data = (QElemType *)malloc(MAXQSIZE * sizeof(QElemType));
if (Q->data != NULL) {
Q->front = Q->rear = 0;
return 1;
}
return 0;
}
void DestroyQueue(SqQueue *Q) { //销毁队列函数
if (Q->data != NULL)
free(Q->data);
}
void ClearQueue(SqQueue *Q) {
//这里其实只需要把指针重新归位就好了,其他的数据会被后来的数据覆盖掉
Q->rear = Q->front;
}
int QueueEmpty(SqQueue Q) { //是否为空,为空返回1,非空返回0
if (Q.front == Q.rear)
return 1;
else
return 0;
}
int QueueFull(SqQueue Q) { //队列是否为满,已满返回1,未满返回0
if ((Q.rear + 1) % MAXQSIZE == Q.front)
return 1;
else
return 0;
}
int QueueLength(SqQueue Q) {//返回队列长度
int count = 0;
while (Q.front != Q.rear) {
if (Q.front >= MAXQSIZE)
Q.front -= MAXQSIZE;
Q.front++;
count++;
}
return count;
}
QElemType GetHead(SqQueue Q) { //若队列非空,返回队列首的值
int e;
if (QueueEmpty(Q) != 1) {
e = Q.data[Q.front + 1];
return e;
} else
printf("队列为空!\n");
}
void EnQueue(SqQueue *Q, QElemType e) { //将e元素插入队列
if (QueueFull(*Q) == 1) {
printf("队列已满,无法添加!\n");
return;
} else {
Q->rear = (Q->rear + 1) % MAXQSIZE; //通过这个语句实现循环
Q->data[Q->rear] = e;
}
}
QElemType DeQueue(SqQueue *Q) { //删除队头元素,并返回其值
QElemType e;
if (QueueEmpty(*Q) != 1) {
e = Q->data[Q->front + 1];
Q->front = (Q->front + 1) % MAXQSIZE;
//这里不用对移动后的front位置上的元素做处理,就当作没有,因为如果后续有也会被覆盖
return e;
} else {
printf("队列为空!\n");
}
}
(2) 隣接行列
1) グラフ隣接行列.h
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "循环队列.h"
#define MAX_VERTEX 10//最大的顶点个数
typedef int DataType;
//以下是无向图的定义
void MGraph(DataType *vertex, int arc[][MAX_VERTEX], int vertexNum, int arcNum) { //初始化构造图(邻接矩阵法)
printf("请逐个输入顶点的内容:");
DataType x;
DataType vi, vj; //构建邻接矩阵时,一条边的两个结点编号
for (int i = 0; i < vertexNum; i++) { //顶点数组赋值
scanf("%d", &x);
vertex[i] = x;
}
for (int i = 0; i < vertexNum; i++) //初始化邻接矩阵
for (int j = 0; j < vertexNum; j++)
arc[i][j] = 0;
int count = 1;
for (int i = 0; i < arcNum; i++) { //依次输入每一条边
printf("请输入第%d条边依附的两个顶点的编号:", count++);
scanf("%d %d", &vi, &vj); //输入该边依附的顶点的编号
arc[vi][vj] = 1; //置有边标志
arc[vj][vi] = 1;
}
}
void printMGraph(DataType *vertex, int arc[][MAX_VERTEX], int vertexNum) { //输出
printf("vertex:");
for (int i = 0; i < vertexNum; i++) {
printf("%d ", vertex[i]);
}
printf("\n");
printf("arc:\n");
for (int i = 0; i < vertexNum; i++) {
for (int j = 0; j < vertexNum; j++) {
if (j == vertexNum - 1)
printf("%d\n", arc[i][j]);
else
printf("%d ", arc[i][j]);
}
}
}
int isLinked(int arc[][MAX_VERTEX], int i, int j) { //两顶点i,j是否有边相连,1是相连,0是不相连
if (arc[i][j] == 1)
return 1;
else
return 0;
}
int nodeDepth(int arc[][MAX_VERTEX], int index, int vertexNum) { //任意一顶点的度
//无向图任意遍历行\列求和
int count = 0;
for (int i = 0; i < vertexNum; i++) {
if (arc[index][i] == 1)
count++;
}
return count;
}
void initvertex(int vertexNum, int *visited) { //初始化visited函数
for (int i = 0; i < vertexNum; i++)
visited[i] = 0;
}
void visit(DataType *vertex, int v) { //输出访问过的顶点信息
printf("%d ", vertex[v]);
}
void DFSTraverse(int arc[][MAX_VERTEX], DataType *vertex, int *visited, int vertexNum,//深度优先遍历
int v) { //v是遍历的起始位置的编号
static int flag = 0;
if (flag == 0) {//第一次需要初始化
initvertex(vertexNum, visited);
flag = 1;
}
visit(vertex, v); //输出访问过的顶点信息
visited[v] = 1;
for (int i = 0; i < vertexNum; i++) { //因为是邻接矩阵,所以编号的顺序本来就是由小到大的.所以不用遍历找
if (arc[v][i] != 0 && visited[i] == 0)
DFSTraverse(arc, vertex, visited, vertexNum, i); //二维数组传参,调用时实参直接写数组名
}
}
void BFSTraverse(int arc[][MAX_VERTEX], DataType *vertex, int vertexNum, int v) {//广度优先遍历
int visited[vertexNum];
initvertex(vertexNum, visited);//初始化数组
SqQueue Q;
InitQueue(&Q);//初始化队列
visit(vertex, v); //输出访问过的顶点信息
visited[v] = 1;
EnQueue(&Q, v); //将顶点v的下标入队
while (QueueEmpty(Q) != 1) { //当队列非空时
v = DeQueue(&Q); //将队头元素出队并送到v中
for (int w = 0; w < vertexNum; w++) { //将该顶点的所有连接点都扫描一遍
if (arc[v][w] == 1 && visited[w] == 0) {
visit(vertex, w);
visited[w] = 1;
EnQueue(&Q, w); //将顶点w的下标入队
}
}
}
}
2) グラフ test_adjacency 行列.c
#include "图_邻接矩阵.h"
main() {
DataType vertex[MAX_VERTEX];//储存所有的顶点
int arc[MAX_VERTEX][MAX_VERTEX];//邻接矩阵,结点间的连通关系
int vertexNum, arcNum; //结点个数,边的个数
printf("输入顶点个数:");
scanf("%d", &vertexNum);
printf("输入边个数:");
scanf("%d", &arcNum);
MGraph(vertex, arc, vertexNum, arcNum);
printMGraph(vertex, arc, vertexNum);
printf("测试判断两顶点是否相连,请输入两个顶点下标:");
int i, j;
scanf("%d %d", &i, &j);
if (isLinked(arc, i, j) == 1)
printf("相连!\n");
else
printf("不相连!\n");
printf("测试求顶点的度,请输入一个顶点下标:");
int index;
scanf("%d", &index);
printf("该顶点的度为:%d\n", nodeDepth(arc, index, vertexNum));
printf("-----------------测试邻接矩阵的深度优先遍历-----------------\n");
printf("\n");
int visited[vertexNum];//判断结点是否访问过,访问过设置1,未访问过为0
int v;
printf("请输入深度优先遍历的第一个结点编号:");
scanf("%d", &v);
printf("深度优先遍历序列:");
DFSTraverse(arc, vertex, visited, vertexNum, v);
printf("\n");
printf("\n");
printf("-----------------测试邻接矩阵的广度优先遍历-----------------\n");
printf("\n");
printf("请输入广度优先遍历的第一个结点编号:");
scanf("%d", &v);
printf("广度优先遍历序列:");
BFSTraverse(arc, vertex, vertexNum, v);
printf("\n");
}
3) テスト出力
テストケース:(無向グラフ)
出力: (ノードの内容と番号を区別することに注意してください。ノードのデータは 1 から始まり、ノード番号は 0 から始まります)
(2) 隣接リスト
1) Figure_隣接リスト.h
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "循环队列.h"
#define MAX_VERTEX 10//最大的顶点个数
typedef char DataType;
/*
当DataType为char时要特别注意输入时空格和换行符,分别利用getchar()
和fflush(stdin);来清空内存区
*/
typedef struct ArcNode { /*边表结点*/
int adjvex;//边表数据域,即下标
struct ArcNode *next; //指针域
} ArcNode;
typedef struct VertexNode { /*顶点表结点*/
DataType vertex;//顶点的数据
ArcNode *firstEdge; //指向储存该顶点所连接所有结点的边表
} VertexNode;
void initVertex(DataType *, int);
//以下是有向图的定义
void ALGraph(DataType *vertex, VertexNode *adjList, int vertexNum, int arcNum) { //初始化构造图(邻接表法)
int vi, vj;
int count = 0;
initVertex(vertex, vertexNum);
for (int i = 0; i < vertexNum; i++) { //初始化顶点表
adjList[i].vertex = vertex[i];
adjList[i].firstEdge = NULL;
}
for (int i = 0; i < arcNum; i++) {
//输入边的信息储存在边表中
printf("请输入第%d条边依附的两个顶点的编号(方向->):", count++);
fflush(stdin);//清除输入缓冲区(否则这里就会直接跳过scanf)
scanf("%d %d", &vi, &vj); //输入该边依附的顶点的编号
ArcNode *s;
s = (ArcNode *)malloc(sizeof(ArcNode));
if (s != NULL) {
s->adjvex = vj;
s->next = adjList[vi].firstEdge; //头插法建立链表
adjList[vi].firstEdge = s;
} else
printf("init error!\n");
}
}
void initVertex(DataType *vertex, int vertexNum) {//输入函数
printf("请逐个输入顶点的内容:");
DataType x;
for (int i = 0; i < vertexNum; i++) { //顶点数组赋值
getchar();//吸收空格
scanf("%c", &x);
vertex[i] = x;
}
}
void printALGraph(VertexNode *adjList, int vertexNum) {
printf("vertex firstEdge\n");
ArcNode *p ;
for (int i = 0; i < vertexNum; i++) {
printf("%3c -->", adjList[i].vertex);
p = adjList[i].firstEdge;
while (p != NULL) {
printf("%d -->", p->adjvex);
p = p->next;
}
printf("NULL\n");
printf("\n");
}
}
int isLinked(VertexNode *adjList, int i, int j) { //两顶点i,j是否有边相连,1是相连,0是不相连
ArcNode *p = adjList[i].firstEdge ;
while (p != NULL) {
if (p->adjvex == j)
return 1;
else
p = p->next;
}
p = adjList[j].firstEdge;
while (p != NULL) {
if (p->adjvex == i)
return 1;
else
p = p->next;
}
return 0;
}
int nodeDepth(VertexNode *adjList, int index, int vertexNum) { //任意一顶点的度
int count = 0;
ArcNode *p = adjList[index].firstEdge;
while (p != NULL) {
count++;
p = p->next;
}
return count;
}
void freeArcNode(VertexNode *adjList, int vertexNum) {
ArcNode *p;
ArcNode *temp;
for (int i = 0; i < vertexNum; i++) {
p = adjList[i].firstEdge ;
while (p != NULL) {
temp = p;
p = p->next;
free(temp);
}
}
}
void initvertex(int vertexNum, int *visited) { //初始化visited函数
for (int i = 0; i < vertexNum; i++)
visited[i] = 0;
}
void visit(VertexNode *adjList, int v) { //输出访问过的顶点信息
printf("%c ", adjList[v].vertex);//记得改变输出时要改变数据类型
}
void DFSTraverse(VertexNode *adjList, int *visited, int vertexNum, int v) {//深度优先遍历
static int flag = 0;
if (flag == 0) {//第一次需要初始化
initvertex(vertexNum, visited);
flag = 1;
}
visit(adjList, v); //输出访问过的顶点信息
visited[v] = 1;
ArcNode *p = adjList[v].firstEdge;
while (p != NULL) {
if (visited[p->adjvex] == 0)
DFSTraverse(adjList, visited, vertexNum, p->adjvex);
p = p->next;
}
}
void ranklist(VertexNode *adjList, int vertexNum) { //将边表进行升序的排序,便于遍历操作
ArcNode *p, *q;
int temp;
for (int i = 0; i < vertexNum; i++) {
p = adjList[i].firstEdge;
q = p;
while (p != NULL) {
while (q != NULL) {
if (p->adjvex > q->adjvex) {
temp = p->adjvex;
p->adjvex = q->adjvex;
q->adjvex = temp;
}
q = q->next;
}
p = p->next;
}
}
}
void BFSTraverse(VertexNode *adjList, int vertexNum, int v) { //广度优先遍历
int visited[vertexNum];
initvertex(vertexNum, visited);
SqQueue Q;
InitQueue(&Q);//初始化队列
ArcNode *p;
visit(adjList, v); //输出访问过的顶点信息
visited[v] = 1;
EnQueue(&Q, v); //将顶点v的下标入队
while (QueueEmpty(Q) != 1) { //当队列非空时
v = DeQueue(&Q); //将队头元素出队并送到v中
p = adjList[v].firstEdge; //工作指针p指向顶点v的边表
while (p != NULL) {
//遍历该顶点的边表
if (visited[p->adjvex] == 0) {
visit(adjList, p->adjvex);
visited[p->adjvex] = 1;
EnQueue(&Q, p->adjvex); //将顶点w的下标入队
}
p = p->next;
}
}
}
2) グラフ test_adjacency list.c
#include "图_邻接表.h"
int main() {
DataType vertex[MAX_VERTEX];//储存所有的顶点
int vertexNum, arcNum; //结点个数,边的个数
printf("输入顶点个数:");
scanf("%d", &vertexNum);
printf("输入边个数:");
scanf("%d", &arcNum);
VertexNode adjList[vertexNum];//顶点表
ALGraph(vertex, adjList, vertexNum, arcNum);
ranklist(adjList, vertexNum);
printALGraph(adjList, vertexNum);
printf("测试判断两顶点是否相连,请输入两个顶点下标:");
int i, j;
scanf("%d %d", &i, &j);
if (isLinked(adjList, i, j) == 1)
printf("相连!\n");
else
printf("不相连!\n");
printf("测试求顶点的度,请输入一个顶点下标:");
int index;
scanf("%d", &index);
printf("该顶点的度为:%d\n", nodeDepth(adjList, index, vertexNum));
printf("-----------------测试邻接表的深度优先遍历-----------------\n");
printf("\n");
int visited[vertexNum];//判断结点是否访问过,访问过设置1,未访问过为0
int v;
printf("请输入深度优先遍历的第一个结点编号:");
scanf("%d", &v);
printf("深度优先遍历序列:");
DFSTraverse(adjList, visited, vertexNum, v);
printf("\n");
printf("\n");
printf("-----------------测试邻接表的广度优先遍历-----------------\n");
printf("\n");
printf("请输入广度优先遍历的第一个结点编号:");
scanf("%d", &v);
printf("广度优先遍历序列:");
BFSTraverse(adjList, vertexNum, v);
freeArcNode(adjList, vertexNum);
}
3) テスト出力
テストケース: (有向グラフ)
出力:(ノードデータがchar型の場合、入力時はスペースや改行の読み取りに注意してください)
初心者のシャオバイさん、間違いがあれば修正してください!