實驗說明
數據結構實驗四 圖的實驗——圖的主要遍歷算法實現
一、實驗目的
通過本實驗使學生熟悉圖遍歷的兩種方法:深度優先與廣度優先;掌握編程實現圖遍歷具體算法;深刻理解圖的順序存儲(鄰接矩陣)與鏈式存儲(鄰接鏈表)的特性;特別訓練學生在編程上控制復雜結構的能力,為今后控制更為復雜結構,進而解決有一定難度的復雜問題奠定基礎。
二、實驗內容
1.分別采用鄰接表實現圖的深度優先與廣度優先遍歷算法。
2.采用鄰接矩陣實現圖的廣度優先遍歷和深度優先遍歷算法。
實驗報告
1.實現功能描述
采用鄰接表實現圖的深度優先與廣度優先遍歷算法。采用鄰接矩陣實現圖的廣度優先遍歷和深度優先遍歷算法。
2.方案比較與選擇
(1)可以使用鏈表和隊列來實現。因為隊列的功能較全且更符合題目要求,所以使用隊列來實現。
3.設計算法描述
(1)定義一個結構體代表結點,其中包含數據域data和指向第一條依附於該結點的弧指針。
(2)設計隊列。
(3)進行模塊划分,給出功能組成框圖。形式如下:

(4)基本功能模塊:
①創建無向圖
②深度優先遍歷無向圖
③廣度優先遍歷無向圖
(5)用流程圖描述關鍵算法:

4.算法實現(即完整源程序,帶注解)
(1)鄰接表:
點擊查看詳細內容
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#define MAX_VERTEX_NUM 20 //最大結點個數
typedef char VertexType;
typedef int VRType;
typedef int InfoType; //圖中邊上的權值信息
typedef int QElemType; //隊列中結點數據類型
/* 圖的深度優先遍歷和廣度優先遍歷 */
//鄰接表存儲圖
typedef struct ArcNode {
int adjvex; //該弧所指向的結點的位置
struct ArcNode* nextarc; //指向下一條弧的指針
InfoType* info; //該弧相關的信息的指針,如權值
}ArcNode;
typedef struct VNode {
VertexType data; //結點信息
ArcNode* firstarc; //指向第一條依附於該結點的弧指針
}VNode, AdjList[MAX_VERTEX_NUM];
typedef struct {
AdjList vertices;
int vexnum, arcnum; //結點數和弧樹
}ALGraph;
//創建用於廣度優先遍歷的隊列
typedef struct QNode {
QElemType data;
struct QNode* qnext;
}QNode, * PQNode;
typedef struct Queue {
PQNode front;
PQNode rear;
}Queue, * PQueue;
bool visited[MAX_VERTEX_NUM]; //標記結點是否被遍歷過,否為flase,是為true;
PQueue initQueue(); //初始化一個空隊列
void enQueue(PQueue pqueue, QElemType data); //隊尾入隊
bool isEmpty(PQueue pqueue); //判斷隊列是否為空
QElemType deQueue(PQueue pqueue); //隊頭出隊
int locateVex(ALGraph alg, char v); //確定圖中結點位置編號
void createALGraph(ALGraph* alg); //創建無向圖
void DFS(ALGraph alg, int v); //深度優先遍歷無向圖
void BFSTraverse(ALGraph alg); //廣度優先遍歷
void DFSTraverse(ALGraph alg); //對鄰接表存儲的無向圖進行深度優先遍歷
/*
測試用例
8 10
1
2
3
4
5
6
7
8
1 2
1 3
2 4
2 5
3 6
3 7
4 8
5 8
6 8
7 8
測試結果
1 2 4 8 5 6 3 7
1 2 3 4 5 6 7 8
*/
int main() {
ALGraph alg;
createALGraph(&alg); //創建無向圖
DFSTraverse(alg);
printf("\n");
BFSTraverse(alg);
printf("\n");
return 0;
}
PQueue initQueue() {
PQueue pqueue = (PQueue)malloc(sizeof(Queue));
PQNode pqnode = (PQNode)malloc(sizeof(QNode));
if (pqnode == NULL) {
printf("隊列頭空間申請失敗!\n");
exit(-1);
}
pqueue->front = pqueue->rear = pqnode;
pqnode->qnext = NULL;
return pqueue;
}
void enQueue(PQueue pqueue, QElemType data) {
PQNode pqnode = (PQNode)malloc(sizeof(QNode));
if (pqnode == NULL) {
printf("隊列結點申請失敗!\n");
exit(-1);
}
pqnode->data = data;
pqnode->qnext = NULL;
pqueue->rear->qnext = pqnode;
pqueue->rear = pqnode;
}
bool isEmpty(PQueue pqueue) {
if (pqueue->front == pqueue->rear) return true;
return false;
}
QElemType deQueue(PQueue pqueue) {
if (isEmpty(pqueue)) {
printf("隊列為空\n");
exit(-1);
}
PQNode pqnode = pqueue->front->qnext;
pqueue->front->qnext = pqnode->qnext;
if (pqnode == pqueue->rear) pqueue->rear = pqueue->front;
QElemType data = pqnode->data;
free(pqnode);
return data;
}
int locateVex(ALGraph alg, char v) {
int i;
for (i = 0; i < alg.vexnum; i++) {
if (alg.vertices[i].data == v) return i;
}
return -1;
}
void createALGraph(ALGraph* alg) {
int i, j, v, k;
printf("請輸入所創建無向圖的結點數和邊數(用空格隔開):");
scanf("%d %d", &(*alg).vexnum, &(*alg).arcnum);
getchar();
for (i = 0; i < (*alg).vexnum; i++) {
printf("輸入第%d個結點名稱:", i+1);
scanf("%c", &(*alg).vertices[i].data);
(*alg).vertices[i].firstarc = NULL;
getchar();
}
char v1, v2;
ArcNode* s, * p;
for (k = 0; k < (*alg).arcnum; k++) {
printf("輸入第%d條邊的兩個結點名稱:", k+1);
scanf("%c %c", &v1, &v2);
i = locateVex((*alg), v1);
j = locateVex((*alg), v2);
//由於是無向圖因此一條邊需要關聯兩個結點
p = (ArcNode*)malloc(sizeof(ArcNode));
p->adjvex = j;
p->nextarc = NULL;
if ((*alg).vertices[i].firstarc == NULL) {
(*alg).vertices[i].firstarc = p;
}
else {
s = (*alg).vertices[i].firstarc;
while (s->nextarc != NULL)
s = s->nextarc;
s->nextarc = p;
}
p = (ArcNode*)malloc(sizeof(ArcNode));
p->adjvex = i;
p->nextarc = NULL;
if ((*alg).vertices[j].firstarc == NULL) (*alg).vertices[j].firstarc = p;
else {
s = (*alg).vertices[j].firstarc;
while (s->nextarc != NULL)
s = s->nextarc;
s->nextarc = p;
}
getchar();
}
}
void DFS(ALGraph alg, int v) {
//從第v個結點出發遞歸的深度優先遍歷圖alg
ArcNode* p;
visited[v] = true;
printf("%c ", alg.vertices[v].data);
for (p = alg.vertices[v].firstarc; p != NULL; p = p->nextarc) {
if (!visited[p->adjvex])
DFS(alg, p->adjvex);
}
}
void DFSTraverse(ALGraph alg) {
printf("深度優先遍歷序列:");
int v;
for (v = 0; v < alg.vexnum; v++)
visited[v] = false;
for (v = 0; v < alg.vexnum; v++) {
if (!visited[v])
DFS(alg, v);
}
}
void BFSTraverse(ALGraph alg) {
printf("廣度優先遍歷序列:");
PQueue pqueue = initQueue();
ArcNode* p;
int i;
QElemType v;
for (i = 0; i < alg.vexnum; i++)
visited[i] = false;
for (i = 0; i < alg.vexnum; i++) {
if (!visited[i]) {
visited[i] = true;
printf("%c ", alg.vertices[i].data);
enQueue(pqueue, i);
while (!isEmpty(pqueue)) {
v = deQueue(pqueue);
for (p = alg.vertices[v].firstarc; p != NULL; p = p->nextarc) {
if (!visited[p->adjvex]) {
printf("%c ", alg.vertices[p->adjvex].data);
visited[p->adjvex] = true;
enQueue(pqueue, p->adjvex);
}
}
}
}
}
}
(2)鄰接矩陣:
點擊查看詳細內容
#include <stdio.h>
#include <string.h>
#include <windows.h>
#define MaxVertexNum 100 //結點數目最大值
#define maxSize 20 //隊列最大值
typedef char VertexType; //結點的數據類型
typedef int EdgeType; //帶權圖中邊上權值的數據類型
//隊列
typedef struct
{
int data[maxSize];
int front, rear;
}Queue;
typedef struct
{
VertexType Vex[MaxVertexNum]; //結點表
EdgeType Edge[MaxVertexNum][MaxVertexNum]; //鄰接矩陣,邊表
int vexnum, edgenum; //圖的結點數和弧數
}MGraph;
int visitDFS[maxSize];
int visitBFS[maxSize];
void create_Graph(MGraph* G); //創建無向圖
void InitQueue(Queue* Q); //初始化隊列
int IsEmpty(Queue* Q); //判斷隊空
void EnQueue(Queue* Q, int e); //入隊
void DeQueue(Queue* Q, int* e); //出隊
void DFS(MGraph G, int i); //深度優先遍歷
void DFSTraverse(MGraph G); //深度優先遍歷
void BFS(MGraph G); //廣度優先遍歷
/*
測試用例
8 10
1
2
3
4
5
6
7
8
1 2 1
1 3 1
2 4 1
2 5 1
3 6 1
3 7 1
4 8 1
5 8 1
6 8 1
7 8 1
測試結果
1 2 4 8 5 6 3 7
1 2 3 4 5 6 7 8
*/
void main(){
MGraph G;
create_Graph(&G);
DFSTraverse(G);
BFS(G);
printf("\n");
}
void create_Graph(MGraph* G) {
int i, j;
int start, end; //邊的起點序號、終點序號
int numV, numE;
int w; //邊上的權值
printf("請輸入所創建無向圖的結點數和邊數(用空格隔開):");
scanf_s("%d%d", &numV, &numE);
G->vexnum = numV;
G->edgenum = numE;
//圖的初始化
for (i = 0; i < G->vexnum; i++) {
for (j = 0; j < G->vexnum; j++) {
if (i == j) G->Edge[i][j] = 0;
else G->Edge[i][j] = 32767;
}
}
//結點信息存入結點表
for (i = 0; i < G->vexnum; i++) {
printf("輸入第%d個結點名稱:", i + 1);
scanf_s("%d", &G->Vex[i]);
}
printf("\n");
//輸入無向圖邊的信息
for (i = 0; i < G->edgenum; i++) {
printf("請輸入邊的起點序號,終點序號,權值(用空格隔開):");
scanf_s("%d%d%d", &start, &end, &w);
G->Edge[start - 1][end - 1] = w;
G->Edge[end - 1][start - 1] = w; //無向圖具有對稱性
}
}
void InitQueue(Queue* Q) {
Q->front = Q->rear = 0;
}
int IsEmpty(Queue* Q) {
if (Q->front == Q->rear) return 1;
else return 0;
}
void EnQueue(Queue* Q, int e) {
if ((Q->rear + 1) % maxSize == Q->front) return;
else {
Q->data[Q->rear] = e;
Q->rear = (Q->rear + 1) % maxSize;
}
}
void DeQueue(Queue* Q, int* e) {
if (Q->rear == Q->front) return;
*e = Q->data[Q->front];
Q->front = (Q->front + 1) % maxSize;
}
void DFS(MGraph G, int i) {
int j;
visitDFS[i] = 1;
printf("%d ", G.Vex[i]);
for (j = 0; j < G.vexnum; j++) {
if (G.Edge[i][j] != 32767 && !visitDFS[j]) DFS(G, j);
}
}
void DFSTraverse(MGraph G) {
int i;
printf("\n深度優先遍歷序列:");
for (i = 0; i < G.vexnum; i++) visitDFS[i] = 0;
for (i = 0; i < G.vexnum; i++) {
if (!visitDFS[i]) DFS(G, i);
}
}
void BFS(MGraph G) {
int i, j;
Queue Q;
printf("\n廣度優先遍歷序列:");
for (i = 0; i < G.vexnum; i++) visitBFS[maxSize] = 0;
InitQueue(&Q);
for (i = 0; i < G.vexnum; i++) {
if (!visitBFS[i]) {
visitBFS[i] = 1;
printf("%d ", G.Vex[i]);
EnQueue(&Q, i);
while (!IsEmpty(&Q)) {
DeQueue(&Q, &i);
for (j = 0; j < G.vexnum; j++) {
if (!visitBFS[j] && G.Edge[i][j] != 32767) {
visitBFS[j] = 1;
printf("%d ", G.Vex[j]);
EnQueue(&Q, j);
}
}
}
}
}
}
5.實驗結果測試與分析
(1)數據測試程序截圖


(2)對結果進行分析:
①鄰接表:深度優先遍歷正確
②鄰接表:廣度優先遍歷正確
③鄰接矩陣:深度優先遍歷正確
④鄰接矩陣:深度優先遍歷正確
⑤隊列運行正常
6.思考及學習心得
(1)描述實驗過程中對此部分知識的認識:
(2)特別描述在學習方法上的收獲及體會;
(3)針對前面的思考題內容在此回答。
1)實現了隊列的功能,更進一步理解和掌握隊列的使用。
2)這次的實驗,鞏固了我的編程模塊化的思想。模塊化降低了程序的耦合性,提高了程序的內聚性;降低了程序復雜度,使程序設計、調試和維護等操作簡單化。模塊化使得程序設計更加簡單和直觀,從而提高了程序的易讀性和可維護性,而且還可以把程序中經常用到的一些計算或操作編寫成通用函數,以供隨時調用。
3)對於順序存儲結構和鏈式存儲的遍歷算法,在時空效率上與進行分析對比,並得出結論:
鏈表法時間復雜度較高,空間復雜度較低;數組法時間復雜度較低,空間復雜度較高。因為數組法一開始就定義好樹的大小,如果有空節點就浪費了空間,而鏈表法不會創建空結點,因此數組法的空間復雜度較高。鏈表法對指針的操作較繁瑣,所需時間長,因此鏈表法的時間復雜度較低。
