1. 程式人生 > >資料結構之圖(鄰接表儲存,DFS和BFS遍歷)

資料結構之圖(鄰接表儲存,DFS和BFS遍歷)

     來看下面的一個簡單的圖,


      那麼這樣的一個圖,我們應該用什麼儲存結構來儲存它呢?常用的是鄰接矩陣和鄰接表,這裡鄰接矩陣不做講解,如下所有程式碼都是以鄰接表作為儲存結構,所以這裡就只講解下鄰接表。那麼什麼是鄰接表呢?如何構造呢?

      鄰接表是一種鏈式儲存。就如上圖,一共有四個頂點(分別是A,B,C,D),鄰接表就是為這四個頂點的每個頂點建立一條單鏈表,也就是四條單鏈表,每個連結串列第一個元素就是該頂點自己。下面就以上面的圖為例,具體介紹如何建立鄰接表。

       首先看第一條單鏈表,表頭是頂點A。因為頂點A只與頂點B有鄰接,因此,該單鏈表大概描述為A---->B;

       再看第二條單鏈表,表頭是B,而B與A,C,D都有鄰接,故而描述為B----->A------->C------->D;

       依次第三條單鏈表為C------>B----->D;

       最後第四條單鏈表為D------>B----->C。

       現在來理清一下下面程式碼的三個結構體都代表什麼,理清的話,下面的程式碼就很容易理解了。

struct ArcNode
{
	int adjvex;               //該弧所指向的頂點的位置
	ArcNode * next;           //指向下一條弧的指標
	                          //int weight;邊上是否有權
};

typedef struct VNode
{
	char vertex;              //頂點資訊
	ArcNode * firstarc;       //指向第一條依附該頂點的弧的指標 
}AdjList[20];

struct ALGraph
{
	AdjList adjList;
	int vexNum;               //圖的頂點數
	int arcNum;               //圖的弧數
};

        看第二個結構體VNode,上面已經說過,對於ABCD四個頂點每個建立一條單鏈表,那麼這四條連結串列該如何管理呢?對的,用陣列,這也就是AdjList[20]的由來(這裡假設一個圖的頂點不超過20個,讀者可以根據自己需求改動)。

         第一個結構體ArcNode就是建立連結串列所必需的節點。

         那麼最後一個結構體ALGraph就是用來表示一個圖,在一段程式裡,你要做三個圖,就要用這個結構體去申請三個變數,來儲存你的三個圖。

         好了,接下來直接看程式碼吧。所有原始碼都已經在下面貼出,沒有遺漏。以下程式碼建立的圖是無向,無權圖,並且使用鄰接表表示圖。部分程式碼參考嚴蔚敏的資料結構。

一:main部分

#include<iostream>
#include<queue>
using namespace std;

struct ArcNode
{
	int adjvex;               //該弧所指向的頂點的位置
	ArcNode * next;           //指向下一條弧的指標
	                          //int weight;邊上是否有權
};

typedef struct VNode
{
	char vertex;              //頂點資訊
	ArcNode * firstarc;       //指向第一條依附該頂點的弧的指標 
}AdjList[20];

struct ALGraph
{
	AdjList adjList;
	int vexNum;               //圖的頂點數
	int arcNum;               //圖的弧數
};

bool visited[20];//設定標誌陣列

void CreateGraph(ALGraph & graph);
void PrintGraph(ALGraph & graph);
void DFSTraverse(ALGraph & graph);
void BFSTraverse(ALGraph & graph);

int main()
{

/*

8
9

A B C D E F G H

0 1
0 2
1 3
1 4
2 5
2 6
3 7
4 7
5 6

*/

	ALGraph graph;

	//1.建立鄰接表
	CreateGraph(graph);

	//2.列印鄰接表
	cout << "\n鄰接表列印為: \n";
	PrintGraph(graph);

	//3.深度優先搜尋DFS
	cout << "\n深度優先搜尋DFS: ";
	DFSTraverse(graph);
	cout << endl;

	//4.廣度優先搜尋BFS
	cout << "\n廣度優先搜尋BFS: ";
	BFSTraverse(graph);
	cout << endl<<endl;

	return 0;
}

二:具體實現

1.構造鄰接表

void CreateGraph(ALGraph & graph)
{
	////////1.輸入頂點數和弧數
	cout << "請輸入圖的頂點數: ";
	cin >> graph.vexNum;
	cout << "請輸入圖的弧數: ";
	cin >> graph.arcNum;

	///////2.輸入頂點資訊
	cout << "請輸入" << graph.vexNum << "個頂點資訊: ";
	for (int i = 0; i < graph.vexNum; i++)
	{
		cin >> graph.adjList[i].vertex;
		graph.adjList[i].firstarc = nullptr;
	}

	///////3.根據輸入的弧的資訊構造鄰接表
	cout << "請輸入" << graph.arcNum << "個弧的資訊: \n";
	int h1, h2;
	ArcNode * temp;
	for (int i = 0; i < graph.arcNum; i++)
	{
		cin >> h1 >> h2;

		temp = new ArcNode;
		temp->adjvex = h2;
		temp->next = graph.adjList[h1].firstarc;
		graph.adjList[h1].firstarc = temp;

		temp = new ArcNode;
		temp->adjvex = h1;
		temp->next = graph.adjList[h2].firstarc;
		graph.adjList[h2].firstarc = temp;
	}
}

2.列印鄰接表
void PrintGraph(ALGraph & graph)
{
	for (int i = 0; i < graph.vexNum; i++)
	{
		cout << graph.adjList[i].vertex << "------>";
		ArcNode * p = graph.adjList[i].firstarc;
		while (p)
		{
			cout << graph.adjList[p->adjvex].vertex << "  ";
			p = p->next;
		}

		cout << endl;
	}
}

3.深度優先搜尋DFS。類似於樹的先序遍歷。假設圖中所有頂點未被訪問,則深度優先遍歷是從某個頂點v開始,訪問完後,接著訪問v的未被訪問的鄰接點,直至遍歷完。
void DFS(ALGraph & graph, int v)
{
	visited[v] = true;
	cout << graph.adjList[v].vertex << " ";

	ArcNode * p = graph.adjList[v].firstarc;

	while (p)
	{
		if (!visited[p->adjvex])
			DFS(graph, p->adjvex);

		p = p->next;
	}
}

void DFSTraverse(ALGraph & graph)
{
	for (int i = 0; i < graph.vexNum; i++)//初始化訪問標誌陣列
		visited[i] = false;

	for (int i = 0; i < graph.vexNum; i++)
	{
		if (!visited[i])//如果沒有訪問
			DFS(graph, i);
	}
}

4.廣度優先搜尋BFS。類似於樹的層次遍歷。假設圖中所有頂點未被訪問,則廣度優先遍歷是從某個頂點v開始,訪問後,接著訪問v的未被訪問的鄰接點,然後分別從這些鄰接點開始依次訪問它們的鄰接點(因此用到了佇列),直至遍歷完。
void BFSTraverse(ALGraph & graph)
{
	for (int i = 0; i < graph.vexNum; i++)//初始化訪問標誌陣列 
		visited[i] = false;

	queue<int> q;

	for (int i = 0; i < graph.vexNum; i++)
	{
		if (!visited[i])//如果沒有訪問過
		{
			visited[i] = true;
			q.push(i);//訪問過的入佇列
			cout << graph.adjList[i].vertex << " ";

			while (!q.empty())//佇列不為空時
			{
				int x = q.front();
				q.pop();//先取出隊首第一個元素,然後將第一個元素刪除
				ArcNode * p = graph.adjList[x].firstarc;
				while (p)//訪問未被訪問過的鄰接頂點
				{
					if (!visited[p->adjvex])
					{
						visited[p->adjvex] = true;
						cout << graph.adjList[p->adjvex].vertex << " ";
						q.push(p->adjvex);
					}

					p = p->next;
				}
			}
		}
	}
}

三:資料測試


其中上述資料建立的圖的結構如下圖: