數據結構實驗(五)

數據結構實驗(五)


  1. 圖的鄰接表封裝

    • “ALGraphs.hpp”

      // Test7-1:圖的鄰接表封裝
      
      #include <iostream>
      using namespace std;
      
      // 弧結點
      struct ArcNode 
      {
      	int adjvex;
      	ArcNode* nextArc;
      	int info;		// 弧自帶的信息
      
      	ArcNode(int adj, int w)
      	{
      		adjvex = adj;
      		info = w;
      		nextArc = NULL;
      	}
      
      	void release()
      	{
      		if (nextArc)
      		{
      			nextArc->release();
      			delete nextArc;
      		}
      	}
      
      	void output()
      	{
      		cout << " -> Adj=" << adjvex + 1 << ",W=" << info;
      	}
      };
      
      // 頂點結點
      struct VexNode
      {
      	char data;		//結點自帶的信息
      	ArcNode* firstArc;
      
      	VexNode(char data = ' ')
      	{
      		this->data = data;
      		firstArc = NULL;
      	}
      
      	~VexNode()
      	{
      		//TODO:析構函數釋放鏈表
      		if (firstArc)
      			firstArc->release();
      
      	}
      
      	void output()
      	{
      		cout << "頂點 " << data;
      		ArcNode* p = firstArc;
      		while (p)
      		{
      			p->output();
      			p = p->nextArc;
      		}
      		cout << endl;
      	}
      
      	void addArc(int adj, int w)			//向頂點添加弧
      	{
      		ArcNode* p = new ArcNode(adj, w);
      		p->nextArc = firstArc;
      		firstArc = p;
      	}
      };
      
      // 鄰接表方式存儲的圖
      struct ALGraph
      {
      	VexNode* vertices;	// 頂點順序表
      	int vexNum;			// 頂點的數量
      	int arcNum;			// 邊集的數量
      	int kind;			// 圖的種類,0-無向圖,1-有向圖
      
      	ALGraph(const char* names, int k = 1)	// 傳入一個字符串,根據其中字符爲每個結點命名
      	{
      		kind = k;
      		vexNum = strlen(names);
      		arcNum = 0;
      		vertices = new VexNode[vexNum];
      		for (int i = 0; i < vexNum; i++)
      			vertices[i].data = names[i];
      	}
      
      	~ALGraph()
      	{
      		//TODO:析構函數釋放結點表
      		//if (vertices != NULL)
      		//	delete[]vertices;
      	}
      
      	void output()			// 輸出圖
      	{
      		for (int i = 0; i < vexNum; i++)
      			vertices[i].output();
      	}
      
      	void addArc(int tail, int head, int w = 0)
      	{
      		vertices[tail].addArc(head, w);
      		if (kind == 0)
      			vertices[head].addArc(tail, w);
      		arcNum++;
      	}
      };
      
    • “ALGraphs.cpp”

      #include <iostream>
      using namespace std;
      #include "./ALGraph.hpp"
      
      int main()
      {
          // 測試圖的創建和輸出
      	ALGraph g("ABCD");
      	g.addArc(0, 2, 12);
      	g.addArc(0, 1, 33);
      	g.addArc(2, 3, 18);
      	g.addArc(3, 0, 42);
      	g.output();
      	return 0;
      }
      
    • 運行截圖:

  2. 圖的遍歷 (Traversal Algorithm: DFS Traverse and BFS Traverse)

    • “Traversal of Graphs.cpp”

      // Test7-2:圖的遍歷
      // Traversal Algorithm: DFS Traverse and BFS Traverse
      
      #include <iostream>
      using namespace std;
      #include "../ALGraph/ALGraph.hpp"
      #include "../Queue/Queue.cpp"
      
      #define MAX_VERTEX_NUM 8
      
      bool visited[MAX_VERTEX_NUM];	// 初始化一個標記數組,如果爲true代表該點已遍歷,否則,未遍歷
      
      void visit(ALGraph& G, int v)
      {
      	visited[v] = true;
      	cout << "v" << G.vertices[v].data << "->";
      }
      
      void DFS(ALGraph& G, int v)			// Depth First Search
      {
      	// TODO:深度優先搜索遞歸函數
      	visit(G, v);
      	ArcNode* p = G.vertices[v].firstArc;
      	if (p)
      	{
      		v = p->adjvex;
      		if (!visited[v])
      			DFS(G, v);
      		else
      			p = p->nextArc;
      	}
      	else
      		return;
      }
      
      void DFSTraverse(ALGraph& G, int i = 0)
      {
      	// TODO:深度優先搜索主控函數
      	memset(visited, false, sizeof(visited)); // 初始化visited數組
      	while (i + 1 < G.vexNum)			// 當每個結點都遍歷後,退出循環
      	{
      		if (!visited[i])				// 如果該點未被遍歷過,執行深度優先搜索遞歸函數
      			DFS(G, i);				
      		i++;							// 繼續遍歷
      	}
      	cout << endl;
      }
      
      void BFS(ALGraph& G, int v)				// Breadth First Search
      {
      	// TODO:廣度優先搜索一個連通分支下的遍歷函數
      	Queue<int> queue;			// 創建隊列
      	queue.enqueue(v);			// 入隊起始頂點
      	visited[v] = true;			// 修改visited[v]爲true,避免再次入隊,導致的多次遍歷
      	while (!queue.isEmpty())	// 當隊列爲空時,完成遍歷
      	{
      		int j = queue.dequeue();
      		cout << "v" << G.vertices[j].data << "->";
      		ArcNode* p = G.vertices[j].firstArc;
      		while (p)
      		{
      			if (!visited[p->adjvex])
      			{
      				queue.enqueue(p->adjvex);
      				visited[p->adjvex] = true;
      			}
      			p = p->nextArc;
      		}
      	}
      }
      
      void BFSTraverse(ALGraph& G, int i = 0)		
      {
      	// TODO:廣度優先搜索主控函數
      	memset(visited, false, sizeof(visited)); // 初始化visited數組
      	while (i + 1 < G.vexNum)			// 當每個結點都遍歷後,退出循環
      	{
      		if (!visited[i])				// 如果該點未被遍歷過,執行廣度優先搜索一個連通分支下的遍歷函數
      			BFS(G, i);
      		i++;							// 繼續遍歷
      	}
      	cout << endl;
      }
      
      int main()
      {
      	// 測試用例
      	ALGraph G("12345678", 0);		// 8個結點的無向圖
      	G.addArc(0, 2); G.addArc(0, 1); G.addArc(1, 4); G.addArc(1, 3);
      	G.addArc(2, 6); G.addArc(2, 5); G.addArc(3, 7); G.addArc(4, 7);
      	cout << "* " << "Traversal Algorithm of Graphs" << " *" << endl;
      	cout << endl;
      	cout << "Depth First Search:" << endl;
      	DFSTraverse(G);		// 測試深度優先搜索
      	cout << endl;
      	cout << "Breadth First Search:" << endl;
      	BFSTraverse(G);		// 測試廣度優先搜索
      	return 0;
      
      }
      
    • 運行截圖:

  3. 圖的最小生成樹 (Minimum Cost Spanning Tree Algorithm)

    • “MST Algorithm.hpp”

      // Test7-3:圖的最小生成樹 (Minimum Cost Spanning Tree Algorithm)
      // MST Algorithm: Prim and Kruskal
      
      #include <iostream>
      using namespace std;
      #include "../ALGraph/ALGraph.hpp"
      
      #define MAX_VERTEX_NUM 6
      
      // 邊
      struct Edges
      {
      	int head =0;
      	int tail = 0;
      	int weight = 0;
      	bool flag = false;
      
      	Edges() {}		// 默認構造函數,用於邊集數組的初始化,不能捨去這句話
      
      	void setEdges(int head, int tail, int weight)
      	{
      		this->head = head;
      		this->tail = tail;
      		this->weight = weight;
      	}
      
      	void output()
      	{
      		cout << "V" << head + 1 << "->" << "V" << tail + 1 << ":" << weight << endl;
      	}
      };
      
      // 通過鄰接表生成邊集
      Edges* EdgeSet(ALGraph& G)
      {
      	Edges* edges;	// 邊集數組
      	edges = new Edges[G.arcNum];	// 依據邊的數量創建邊集數組
      	int j = 0;		// 用於鎖定當前初始化的邊的索引
      	// 通過遍歷圖的鄰接鏈表來爲Edges初始化
      	for (int i = 0; i < G.vexNum; i++)
      	{
      		ArcNode* p = G.vertices[i].firstArc;
      		while (p)
      		{
      			if (i < p->adjvex)		// 對於無向圖而言,只取出前驅小於後繼的邊
      			{
      				edges[j].setEdges(i, p->adjvex, p->info);
      				j++;
      			}
      			p = p->nextArc;
      		}
      	}
      	return edges;
      }
      
      // Prim算法的實現
      void Prim(ALGraph& G, int index = 0)
      {
      	int adjvex[MAX_VERTEX_NUM];					// 前驅表
      	memset(adjvex, 0, sizeof(adjvex));			// 初始化前驅表
      	int lowcost[MAX_VERTEX_NUM];				// 最小權表
      	for (int i = 0; i < MAX_VERTEX_NUM; i++)	// 初始化最小權表	
      		lowcost[i] = INT_MAX;
      	bool U[MAX_VERTEX_NUM];			// 已選頂點的集合,U[v]=true,表示頂點v在U集合中
      	memset(U, 0, sizeof(U));		// 初始化已選頂點集合,爲空集
      	//Edges* edges;					// 邊集數組
      	//edges = EdgeSet(G);			// 獲取邊集
      	// 利用輔助數組adjvex, lowcost實現Prim算法
      	while (!U[index])	// 當每一個頂點都參與最小生成的生成則退出循環
      	{
      		adjvex[index] = -1;		// 初始化,標識該點已經添加至最小生成樹
      		lowcost[index] = 0;		// 初始化,標識該邊已經添加至最小生成樹
      		U[index] = 1;			// 初始化,設置頂點的標誌,作爲循環條件控制循環
      		int minweight = INT_MAX;
      		ArcNode* node = G.vertices[index].firstArc;
      		// 利用鄰接表來查詢邊,首選
      		while (node)
      		{
      			if (node->info < lowcost[node->adjvex])	// 權值若小於lowcost更新lowcost
      			{
      				// 標記其起點
      				adjvex[node->adjvex] = index;			// 更新前驅
      				lowcost[node->adjvex] = node->info;		// 更新最小權
      			}
      			node = node->nextArc;		// 訪問下一個結點
      		}
      		/*
      		// 利用邊集的數據結構來查詢邊,較慢
      		for (int i = 0; i < G.arcNum; i++)
      		{
      			// 尋找MST的邊集
      			if (edges[i].head == index)
      			{
      				if (edges[i].weight < lowcost[edges[i].tail])	// 權值若小於lowcost更新lowcost
      				{
      					// 標記其起點
      					adjvex[edges[i].tail] = index;				// 更新前驅
      					lowcost[edges[i].tail] = edges[i].weight;	// 更新最小權
      				}
      			}
      		}
      		*/
      		// 找到最小權值邊
      		int i = 0;
      		for (int j = 0; j < G.vexNum; j++)
      		{
      			if (lowcost[j] < minweight && lowcost[j] != 0)	// 從剩餘的邊中,找到最小值
      			{
      				minweight = lowcost[j];
      				i = j;		// 記錄該邊的索引值,便於輸出顯示
      			}
      		}
      		if (lowcost[i] != 0)	 // 這句話的出現是爲了過濾掉最小生成樹中最後一個頂點即lowcost數組全爲0的情況
      			cout << "V" << adjvex[i] + 1 << "->" << "V" << i + 1 << ":" << lowcost[i] << endl;
      		index = i;
      	}
      }
      
      // 對圖中的邊集進行排序
      Edges* Sort(ALGraph& G)
      {
      	Edges* edges;	// 邊集數組
      	//Edges* sort;	// 依據權重排序後的邊集數組
      	edges = EdgeSet(G);				// 獲取邊集
      	//sort = new Edges[G.arcNum];	// 初始化排序邊集
      
      	// 採用冒泡排序,減小空間複雜度,優解
      	for (int i = 0; i < G.arcNum; i++)
      	{
      		for (int j = 0; j < G.arcNum - 1; j++)
      		{
      			if (edges[j].weight > edges[j + 1].weight)
      			{
      				Edges temp;
      				temp = edges[j];
      				edges[j] = edges[j + 1];
      				edges[j + 1] = temp;
      			}
      		}
      	}
      
      	/*
      	// 開闢新的地址空間進行排序,增大了空間複雜度,劣解
      	for (int i = 0; i < G.arcNum; i++)
      	{
      		int minIndex = 0;
      		int minWeight = 999;
      		for (int j = 0; j < G.arcNum; j++)
      		{
      			if (!edges[j].flag)		// 如果沒參與過比較進入
      			{
      				if (edges[j].weight < minWeight)
      				{
      					minIndex = j;
      					minWeight = edges[j].weight;
      				}
      			}
      		}
      		sort[i] = edges[minIndex];
      		edges[minIndex].flag = true;		// 將被選中的邊淘汰
      	}
      	*/
      	return edges;
      }
      
      /*
      // 比較函數,作爲qsort函數的參數,告訴qsort排序的依據
      int compare(const void* arg1, const void* arg2)
      {
      	Edges* edge1 = (Edges*)arg1;
      	Edges* edge2 = (Edges*)arg2;
      	return edge1->weight - edge2->weight;
      }
      */
      
      // Kruskal算法的實現
      void Kruskal(ALGraph& G)
      {
      	int union_set[MAX_VERTEX_NUM];
      	for (int v = 0; v < G.vexNum; v++)	// 創建連通分量輔助數組用於感知連通分支
      		union_set[v] = v;
      	// 利用並查集實現Kruskal算法
      	Edges* edges = Sort(G);				// 遍歷鄰接表圖並進行排序
      
      	/*
      	// 利用系統函數qsort進行排序
      	Edges* edges;
      	edges = EdgeSet(G);
      	qsort((void*)edges, G.arcNum, sizeof(edges), compare);
      	*/
      
      	// 取出當前符合要求的權值最小邊並輸出
      	for (int i = 0; i < G.arcNum; i++)
      	{
      		int v1 = edges[i].head;			// 當前邊的起點
      		int v2 = edges[i].tail;			// 當前邊的終點
      		// 一定要提前存好union_set[v2],因爲要確保修改並查集時,判斷條件保持不變
      		int us1 = union_set[v1];		// 標記v1所在的連通分支
      		int us2 = union_set[v2];		// 標記v2所在的連通分支
      		if (us1 != us2)
      		{
      			edges[i].output();
      			for (int j = 0; j < G.vexNum; j++)		// 合併連通分支,保證每一個連通分支下的頂點標記相同
      			{
      				if (union_set[j] == us2)
      					union_set[j] = us1;	// 更新連通分量
      			}
      		}
      		else
      			continue;
      	}
      }
      
      
    • “MST Algorithm.cpp”

      #include <iostream>
      using namespace std;
      #include "./MST Algorithm.hpp"
      
      int main()
      {
      	// 測試用例
      	ALGraph G("123456", 0);		// 6個結點的無向圖
      	G.addArc(0, 1, 6); G.addArc(0, 2, 1); G.addArc(0, 3, 5);
      	G.addArc(1, 2, 5); G.addArc(1, 4, 3);
      	G.addArc(2, 3, 5); G.addArc(2, 4, 6); G.addArc(2, 5, 4);
      	G.addArc(3, 5, 2); G.addArc(4, 5, 6);
      	G.output();		// 輸出圖的Adjacency LinkList
      	cout << endl;
      	cout << "利用Prim算法選擇的邊:" << endl;
      	Prim(G);		// 測試Prim算法
      	cout << endl;
      	cout << "利用Kruskal算法選擇的邊:" << endl;
      	Kruskal(G);		// 測試Kruskal算法
      	return 0;
      }
      
    • 運行截圖:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章