資料結構 實驗報告(四)圖的遍歷演算法實現
實驗說明
資料結構實驗四 圖的實驗——圖的主要遍歷演算法實現
一、實驗目的
通過本實驗使學生熟悉圖遍歷的兩種方法:深度優先與廣度優先;掌握程式設計實現圖遍歷具體演算法;深刻理解圖的順序儲存(鄰接矩陣)與鏈式儲存(鄰接連結串列)的特性;特別訓練學生在程式設計上控制複雜結構的能力,為今後控制更為複雜結構,進而解決有一定難度的複雜問題奠定基礎。
二、實驗內容
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)對於順序儲存結構和鏈式儲存的遍歷演算法,在時空效率上與進行分析對比,並得出結論:
連結串列法時間複雜度較高,空間複雜度較低;陣列法時間複雜度較低,空間複雜度較高。因為陣列法一開始就定義好樹的大小,如果有空節點就浪費了空間,而連結串列法不會建立空結點,因此陣列法的空間複雜度較高。連結串列法對指標的操作較繁瑣,所需時間長,因此連結串列法的時間複雜度較低。