最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++圖論基礎(chǔ)之圖的遍歷與最小生成樹算法詳解

 更新時(shí)間:2026年02月23日 10:57:46   作者:落羽的落羽  
這篇文章主要介紹了C++圖論基礎(chǔ)之圖的遍歷與最小生成樹算法,它是學(xué)習(xí)更復(fù)雜圖算法的基石,我們將通過代碼模板與圖解,幫助你掌握如何高效地遍歷圖結(jié)構(gòu),需要的朋友可以參考下

一、圖的遍歷

遍歷一個(gè)圖,針對(duì)的是遍歷所有頂點(diǎn)。主要是兩種思路:廣度優(yōu)先(BFS)和深度優(yōu)先(DFS)
上一篇文章講過了圖可以用領(lǐng)接矩陣和領(lǐng)接表存儲(chǔ)邊,我們以領(lǐng)接矩陣的模版進(jìn)行講解

1. BFS

圖的廣度優(yōu)先遍歷思路是:從起始頂點(diǎn)出發(fā),先訪問當(dāng)前頂點(diǎn)的所有直接鄰接頂點(diǎn)(一層),再依次訪問這些鄰接頂點(diǎn)的鄰接節(jié)點(diǎn)(下一層),以此類推,直到遍歷完所有可達(dá)頂點(diǎn)。
曾經(jīng)我們講二叉樹的廣度優(yōu)先遍歷時(shí),是利用了隊(duì)列結(jié)構(gòu),這里也是一樣的。每次隊(duì)頭元素出隊(duì)列時(shí),隊(duì)頭元素頂點(diǎn)的所有領(lǐng)接頂點(diǎn)全部入隊(duì)列。為了防止一個(gè)頂點(diǎn)多次遍歷,還需要一個(gè)數(shù)組用于標(biāo)記。

// 參數(shù)是遍歷的起始頂點(diǎn)
void BFS(const V& src)
{
	// 得到起始頂點(diǎn)的下標(biāo)
	size_t srcindex = GetVertexIndex(src);
	// 防止一個(gè)頂點(diǎn)被多次遍歷,用一個(gè)數(shù)組標(biāo)記被遍歷過的下標(biāo)
	vector<bool> visited;
	visited.resize(_vertexs.size(), false);

	// 起點(diǎn)入隊(duì)列
	queue<int> q;
	q.push(srcindex);
	visited[srcindex] = true;

	cout << "BFS遍歷: ";
	while (!q.empty())
	{
		size_t front = q.front();
		// 打印出當(dāng)前遍歷頂點(diǎn)
		cout << _vertexs[front] << ' ';

		// 隊(duì)頭元素出隊(duì)列
		q.pop();

		// 隊(duì)頭元素頂點(diǎn)所有沒遍歷過的相鄰頂點(diǎn)入隊(duì)列,在領(lǐng)接矩陣中查詢相鄰頂點(diǎn)
		for (size_t i = 0; i < _vertexs.size(); ++i)
		{
			if (visited[i] == false && _matrix[front][i] != MAX_W)
			{
				// 遍歷過的頂點(diǎn)標(biāo)記為true
				visited[i] = true;
				q.push(i);
			}
		}
		
	}
	
	// 如果該圖不是連通圖,這種方法會(huì)使某些頂點(diǎn)沒遍歷到
	for (bool check : visited)
	{
		if (check == false)
		{
			cout << "該圖不是連通圖,還有未遍歷到的頂點(diǎn)";
		}
	}
	cout << endl;
}

2. DFS

圖的深度優(yōu)先遍歷核心思想是 “一條路走到黑”:從起始頂點(diǎn)出發(fā),沿著一條路徑盡可能深地探索,直到無法繼續(xù)(遇到已訪問節(jié)點(diǎn)或無鄰接頂點(diǎn)),再回溯到上一個(gè)頂點(diǎn),繼續(xù)探索其他未走的分支。為了防止一個(gè)頂點(diǎn)多次遍歷,也需要一個(gè)數(shù)組用于標(biāo)記。

void _DFS(size_t srcIndex, vector<bool>& visited)
{
	// 當(dāng)前遍歷頂點(diǎn)
	cout << _vertexs[srcIndex] << ' ';
	visited[srcIndex] = true;

	// 找srcIndex的相鄰頂點(diǎn),遍歷下去
	for (size_t i = 0; i < _vertexs.size(); ++i)
	{
		if (visited[i] == false && _matrix[srcIndex][i] != MAX_W)
		{
			_DFS(i, visited);
		}
	}
}

void DFS(const V& src)
{
	// 得到起始頂點(diǎn)的下標(biāo)
	size_t srcindex = GetVertexIndex(src);

	// 防止一個(gè)頂點(diǎn)被多次遍歷,用一個(gè)數(shù)組標(biāo)記被遍歷過的下標(biāo)
	vector<bool> visited;
	visited.resize(_vertexs.size(), false);

	cout << "DFS遍歷: ";
	_DFS(srcindex, visited);
	cout << endl;
}

3. 測(cè)試

我們用這張圖進(jìn)行測(cè)試:

完整代碼:

#pragma once
#include<iostream>
#include<vector>
#include<map>
#include<queue>
using namespace std;

// 鄰接矩陣 圖
namespace Matrix
{
	// V頂點(diǎn)類型 W邊權(quán)值類型 MAX_W表示邊不存在的值 Direction表示圖是否有向
	template<class V, class W, W MAX_W = INT_MAX, bool Direction = false>
	class Graph
	{
	public:

		Graph(const V* vertexs, size_t n)
		{
			_vertexs.reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				_vertexs.push_back(vertexs[i]);
				_vIndexMap[vertexs[i]] = i;
			}

			// MAX_W 作為不存在邊的標(biāo)識(shí)值
			// 初始化時(shí)默認(rèn)沒有邊,邊需要一條一條手動(dòng)添加,用AddEdge函數(shù)
			_matrix.resize(n);
			for (auto& e : _matrix)
			{
				e.resize(n, MAX_W);
			}
		}

		// 找到一個(gè)頂點(diǎn)的映射下標(biāo)
		size_t GetVertexIndex(const V& v)
		{
			auto ret = _vIndexMap.find(v);
			if (ret != _vIndexMap.end())
			{
				return ret->second;
			}
			else
			{
				throw invalid_argument("不存在的頂點(diǎn)");
				return -1;
			}
		}

		// 添加一條邊,src和dst代表兩端頂點(diǎn),w是權(quán)值
		void AddEdge(const V& src, const V& dst, const W& w)
		{
			size_t srci = GetVertexIndex(src);
			size_t dsti = GetVertexIndex(dst);

			_matrix[srci][dsti] = w;
			//如果是無向圖,則[dsti][srci]也需添加邊
			if (Direction == false)
			{
				_matrix[dsti][srci] = w;
			}
		}

		// 參數(shù)是遍歷的起始頂點(diǎn)
		void BFS(const V& src)
		{
			// 得到起始頂點(diǎn)的下標(biāo)
			size_t srcindex = GetVertexIndex(src);
			// 防止一個(gè)頂點(diǎn)被多次遍歷,用一個(gè)數(shù)組標(biāo)記被遍歷過的下標(biāo)
			vector<bool> visited;
			visited.resize(_vertexs.size(), false);

			// 起點(diǎn)入隊(duì)列
			queue<int> q;
			q.push(srcindex);
			visited[srcindex] = true;

			cout << "BFS遍歷: ";
			while (!q.empty())
			{
				size_t front = q.front();
				// 打印出當(dāng)前遍歷頂點(diǎn)
				cout << _vertexs[front] << ' ';

				// 隊(duì)頭元素出隊(duì)列
				q.pop();

				// 隊(duì)頭元素頂點(diǎn)所有沒遍歷過的相鄰頂點(diǎn)入隊(duì)列,在領(lǐng)接矩陣中查詢相鄰頂點(diǎn)
				for (size_t i = 0; i < _vertexs.size(); ++i)
				{
					if (visited[i] == false && _matrix[front][i] != MAX_W)
					{
						// 遍歷過的頂點(diǎn)標(biāo)記為true
						visited[i] = true;
						q.push(i);
					}
				}
				
			}

			// 如果該圖不是連通圖,這種方法會(huì)使某些頂點(diǎn)沒遍歷到
			for (bool check : visited)
			{
				if (check == false)
				{
					cout << "該圖不是連通圖,還有未遍歷到的頂點(diǎn)";
				}
			}
			cout << endl;
		}


		void _DFS(size_t srcIndex, vector<bool>& visited)
		{
			// 當(dāng)前遍歷頂點(diǎn)
			cout << _vertexs[srcIndex] << ' ';
			visited[srcIndex] = true;

			// 找srcIndex的相鄰頂點(diǎn),遍歷下去
			for (size_t i = 0; i < _vertexs.size(); ++i)
			{
				if (visited[i] == false && _matrix[srcIndex][i] != MAX_W)
				{
					_DFS(i, visited);
				}
			}
		}

		void DFS(const V& src)
		{
			// 得到起始頂點(diǎn)的下標(biāo)
			size_t srcindex = GetVertexIndex(src);

			// 防止一個(gè)頂點(diǎn)被多次遍歷,用一個(gè)數(shù)組標(biāo)記被遍歷過的下標(biāo)
			vector<bool> visited;
			visited.resize(_vertexs.size(), false);

			cout << "DFS遍歷: ";
			_DFS(srcindex, visited);
			cout << endl;
		}

	private:
		map<V, size_t> _vIndexMap;   // 每個(gè)頂點(diǎn)映射一個(gè)下標(biāo)
		vector<V> _vertexs;			 // 頂點(diǎn)集合
		vector<vector<W>> _matrix;   // 領(lǐng)接矩陣 存儲(chǔ)邊
	};

}

int main()
{
	char arr[] = {'C','A','D','B','E'};
	Matrix::Graph<char, int> graph(arr, sizeof(arr)/sizeof(char));
	// 添加邊,權(quán)值不用管隨便寫的
	graph.AddEdge('A', 'D', 1);
	graph.AddEdge('D', 'B', 2);
	graph.AddEdge('D', 'E', 3);
	graph.AddEdge('B', 'E', 4);
	graph.AddEdge('B', 'C', 5);

	graph.BFS('A');
	graph.BFS('B');

	graph.DFS('A');
	graph.DFS('B');

	return 0;
}

結(jié)果分析,符合BFS與DFS的規(guī)則:

二、圖的最小生成樹算法

連通圖的每一棵生成樹,都是原圖的一個(gè)極大無環(huán)子圖。最小生成樹,就是指所有邊的權(quán)值加起來總權(quán)最小的生成樹,可以理解為用最小的成本構(gòu)成的生成樹。
最小生成樹也是生成樹,要符合:

  • 要包括原圖的所有頂點(diǎn),只能使用原圖中的邊來構(gòu)造
  • 只能使用恰好n-1條邊來連接圖中n個(gè)頂點(diǎn)
  • 選擇的n-1條邊不能構(gòu)成回路
  • 邊的總權(quán)值要最小

構(gòu)造最小生成樹一般有兩種算法:克魯斯卡爾(Kruskal)算法、普里姆(Prim)算法,都是用了逐步求解的貪心策略。

1. Kruskal算法

這種算法的思路是“從小到大選邊”:將所有邊按權(quán)值從小到大排序,依次選擇最小的邊,若這條邊連接的兩個(gè)頂點(diǎn)不在同一個(gè)已連通集合中,就將這條邊加入生成樹;否則跳過,避免形成環(huán)。重復(fù)此過程,直到選夠n−1條邊。

判斷兩個(gè)頂點(diǎn)是否在一個(gè)已連通集合,可以利用并查集!詳見:并查集的原理與使用

typedef Graph<V, W, MAX_W, Direction> Self;
struct Edge
{
	V _srci;
	V _dsti;
	W _w;
	Edge(const V& srci, const V& dsti, const W& w)
		:_srci(srci)
		, _dsti(dsti)
		, _w(w)
	{ }
	bool operator<(const Edge& eg) const
	{
		return _w < eg._w;
	}
	bool operator>(const Edge& eg) const
	{
		return _w > eg._w;
	}
};
Graph() = default;
// 傳遞一個(gè)圖,作為構(gòu)造最小生成樹的結(jié)果。返回總權(quán)值
W Kruskal(Self& minTree)
{
	// 所有頂點(diǎn)拷貝,初始不帶任何邊
	minTree._vertexs = _vertexs;
	minTree._vIndexMap = _vIndexMap;
	minTree._matrix.resize(_vertexs.size());
	for (auto& e : minTree._matrix)
	{
		e.resize(_vertexs.size(), MAX_W);
	}
	// priority_queue用于按照權(quán)值排序邊
	priority_queue<Edge, vector<Edge>, greater<Edge>> pq;
	for (size_t i = 0; i < _matrix.size(); ++i)
	{
		for (size_t j = 0; j < _matrix[i].size(); ++j)
		{
		    // 無向圖,只要判斷領(lǐng)接矩陣一半的邊
			if (i < j && _matrix[i][j] != MAX_W)
			{
				pq.push(Edge(i, j, _matrix[i][j]));
			}
		}
	}
	// 記錄總權(quán)值
	W total = W();
	// 貪心算法,從最小的邊開始選,將選出的邊兩端頂點(diǎn)放入一個(gè)集合
	// size記錄已選出邊數(shù)
	int size = 0;
	UnionFindSet ufs(_vertexs.size());
	while (!pq.empty())
	{
		Edge min = pq.top();
		pq.pop();
		// 邊兩端頂點(diǎn)不在一個(gè)集合,說明不會(huì)構(gòu)成環(huán),則添加這條邊到最小生成樹,兩個(gè)頂點(diǎn)放到一個(gè)集合
		if (ufs.FindRoot(min._srci) != ufs.FindRoot(min._dsti))
		{
			minTree.AddEdge(min._srci, min._dsti, min._w);
			total += min._w;
			size++;
			ufs.Union(min._srci, min._dsti);
		}
	}
	// 若size不等于n-1,說明構(gòu)建最小生成樹失敗,返回一個(gè)默認(rèn)值W()
	if (size == _vertexs.size() - 1)
	{
		return total;
	}
	else
	{
		return W();
	}
}

2. Prim算法

Prim算法,是按點(diǎn)貪心:X集合存放已連入生成樹的點(diǎn),Y集合存放未連入生成樹的點(diǎn)。一開始所有頂點(diǎn)都在Y中,首先將參數(shù)起點(diǎn)放入X并從Y中刪除。從X中所有點(diǎn)連出的邊中選出“權(quán)最小的且有一端頂點(diǎn)在Y中的邊”,插入到最小生成樹中,再把這條邊的端點(diǎn)放入X中并從Y中刪除。如此循環(huán)往復(fù),直到所有頂點(diǎn)都在X中。

這種算法天然避免了環(huán)的發(fā)生!

// 給一個(gè)起點(diǎn)
W Prim(Self& minTree, const V& src)
{
	size_t srci = GetVertexIndex(src);
	size_t n = _vertexs.size();
	minTree._vertexs = _vertexs;
	minTree._vIndexMap = _vIndexMap;
	minTree._matrix.resize(n);
	for (size_t i = 0; i < n; ++i)
	{
		minTree._matrix[i].resize(n, MAX_W);
	}
	// X和Y集合
	vector<bool> X(n, false);
	vector<bool> Y(n, true);
	X[srci] = true;
	Y[srci] = false;
	// 從X->Y集合中連接的邊里面選出最小的邊
	priority_queue<Edge, vector<Edge>, greater<Edge>> minq;
	// 先把srci連接的邊添加到隊(duì)列中
	for (size_t i = 0; i < n; ++i)
	{
		if (_matrix[srci][i] != MAX_W)
		{
			minq.push(Edge(srci, i, _matrix[srci][i]));
		}
	}
	size_t size = 0;
	W total = W();
	while (!minq.empty())
	{
		Edge min = minq.top();
		minq.pop();
		if (!X[min._dsti])
		{
			minTree.AddEdge(min._srci, min._dsti, min._w);
			X[min._dsti] = true;
			Y[min._dsti] = false;
			++size;
			total += min._w;
			if (size == n - 1)
				break;
			for (size_t i = 0; i < n; ++i)
			{
				if (_matrix[min._dsti][i] != MAX_W && Y[i])
				{
					minq.push(Edge(min._dsti, i, _matrix[min._dsti][i]));
				}
			}
		}
	}
	if (size == n - 1)
	{
		return total;
	}
	else
	{
		return W();
	}
}

以上就是C++圖論基礎(chǔ)之圖的遍歷與最小生成樹算法詳解的詳細(xì)內(nèi)容,更多關(guān)于C++圖的遍歷與最小生成樹的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • VS2019開發(fā)簡(jiǎn)單的C/C++動(dòng)態(tài)鏈接庫并進(jìn)行調(diào)用的實(shí)現(xiàn)

    VS2019開發(fā)簡(jiǎn)單的C/C++動(dòng)態(tài)鏈接庫并進(jìn)行調(diào)用的實(shí)現(xiàn)

    這篇文章主要介紹了VS2019開發(fā)簡(jiǎn)單的C/C++動(dòng)態(tài)鏈接庫并進(jìn)行調(diào)用的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • 數(shù)據(jù)結(jié)構(gòu)之Treap詳解

    數(shù)據(jù)結(jié)構(gòu)之Treap詳解

    這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)之Treap詳解,本文講解了Treap的基本知識(shí)、Treap的基本操作、Treap的高級(jí)操作技巧等,需要的朋友可以參考下
    2014-08-08
  • C++實(shí)現(xiàn)鼠標(biāo)控制的黑框象棋

    C++實(shí)現(xiàn)鼠標(biāo)控制的黑框象棋

    這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)鼠標(biāo)控制的黑框象棋,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • C語言零基礎(chǔ)精通變量與常量

    C語言零基礎(chǔ)精通變量與常量

    這篇文章主要為大家詳細(xì)介紹了C語言的變量和常量,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-04-04
  • Visual?Studio中的解決方案中不顯示項(xiàng)目分析

    Visual?Studio中的解決方案中不顯示項(xiàng)目分析

    這篇文章主要為大家介紹了Visual?Studio中的解決方案中不顯示項(xiàng)目問題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-11-11
  • C語言使用廣度優(yōu)先搜索算法解決迷宮問題(隊(duì)列)

    C語言使用廣度優(yōu)先搜索算法解決迷宮問題(隊(duì)列)

    這篇文章主要介紹了C語言使用廣度優(yōu)先搜索算法解決迷宮問題,結(jié)合迷宮問題分析了C語言隊(duì)列廣度優(yōu)先搜索算法的相關(guān)使用技巧,需要的朋友可以參考下
    2017-09-09
  • MFC實(shí)現(xiàn)全屏功能代碼實(shí)例

    MFC實(shí)現(xiàn)全屏功能代碼實(shí)例

    這篇文章主要介紹了MFC實(shí)現(xiàn)全屏功能的代碼,對(duì)于學(xué)習(xí)MFC有一定的借鑒價(jià)值,需要的朋友可以參考下
    2014-07-07
  • Pipes實(shí)現(xiàn)LeetCode(192.單詞頻率)

    Pipes實(shí)現(xiàn)LeetCode(192.單詞頻率)

    這篇文章主要介紹了Pipes實(shí)現(xiàn)LeetCode(192.單詞頻率),本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • C++使用泛型導(dǎo)致的膨脹問題

    C++使用泛型導(dǎo)致的膨脹問題

    這篇文章主要介紹了C++使用泛型導(dǎo)致的膨脹,智能家居主機(jī)的嵌入式平臺(tái)上使用C++進(jìn)行開發(fā)。FLASH存儲(chǔ)空間有限,這是必須要考慮的因素,一定要重視,下面我們一起進(jìn)入文章看看詳細(xì)內(nèi)容
    2021-11-11
  • 應(yīng)用程序操作NorFlash示例代碼分享(norflash接口使用方法)

    應(yīng)用程序操作NorFlash示例代碼分享(norflash接口使用方法)

    相對(duì)于操作NandFlash,操作NorFlash相對(duì)簡(jiǎn)單,因?yàn)榛静恍枰紤]壞塊,NorFlash也沒有OOB區(qū)域,也跟ECC沒有關(guān)系。讀寫擦除相對(duì)容易,下面看個(gè)例子吧
    2013-12-12

最新評(píng)論

东兰县| 安岳县| 板桥市| 丰县| 长沙市| 武功县| 衡山县| 博罗县| 湟中县| 仲巴县| 荔浦县| 盐源县| 巴东县| 龙江县| 满洲里市| 大荔县| 科技| 云南省| 通州市| 昭觉县| 辽阳市| 华亭县| 五大连池市| 东方市| 社会| 七台河市| 和静县| 手游| 友谊县| 吉安县| 高邮市| 万载县| 苍溪县| 施秉县| 文昌市| 彰武县| 子长县| 平塘县| 阳东县| 全南县| 盐山县|