1. 程式人生 > 實用技巧 >資料結構 實驗報告(四)圖的遍歷演算法實現

資料結構 實驗報告(四)圖的遍歷演算法實現

實驗說明

資料結構實驗四 圖的實驗——圖的主要遍歷演算法實現

一、實驗目的

通過本實驗使學生熟悉圖遍歷的兩種方法:深度優先與廣度優先;掌握程式設計實現圖遍歷具體演算法;深刻理解圖的順序儲存(鄰接矩陣)與鏈式儲存(鄰接連結串列)的特性;特別訓練學生在程式設計上控制複雜結構的能力,為今後控制更為複雜結構,進而解決有一定難度的複雜問題奠定基礎。

二、實驗內容

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)對於順序儲存結構和鏈式儲存的遍歷演算法,在時空效率上與進行分析對比,並得出結論:
連結串列法時間複雜度較高,空間複雜度較低;陣列法時間複雜度較低,空間複雜度較高。因為陣列法一開始就定義好樹的大小,如果有空節點就浪費了空間,而連結串列法不會建立空結點,因此陣列法的空間複雜度較高。連結串列法對指標的操作較繁瑣,所需時間長,因此連結串列法的時間複雜度較低。