1. 程式人生 > >圖的兩種最小生成樹演算法之C++封裝

圖的兩種最小生成樹演算法之C++封裝

最小生成樹定義:

    給定一無向帶權圖,頂點數是n,要使圖連通只需n-1條邊,若這n-1條邊的權值和最小,則稱有這n個頂點和n-1條邊構成了圖的最小生成樹(minimum-cost spanning tree)MST。

兩種最小生成樹演算法:

一、prim演算法思想:設圖G頂點集合為U,首先任意選擇圖G中的一點作為起始點a,將該點加入集合V,再從集合U-V(差集)中找到另一點b使得點b到集合V中任意一點的權值最小,此時將b點也加入集合V;以此類推,現在的集合V={a,b},再從集合U-V(差集)中找到另一點c使得點c到集合V中任意一點的權值最小,此時將c點加入集合V,直至所有頂點全部被加入V,此時就構建出了一顆MST。因為有N個頂點,所以該MST就有N-1條邊,每一次向集合V中加入一個點,就意味著找到一條MST的邊。

假設有一無向圖:


1、以v1作為起始點,初始時,V={v1}

2、從 U-V={v2,v3,v4,v5,v6}中選出到V={v1}中任意點最小權值點為v3,則V={v1,v3},則:


3、從 U-V={v2,v4,v5,v6}中選出到V={v1,v3}中任意點最小權值點為v6,則V={v1,v3,v6},則:


從 U-V={v2,v4,v5}中選出到V={v1,v3,v6}中任意點最小權值點為v4,則V={v1,v3,v6,v4},

依此類推,在不能形成環路的條件下,依次選出:v2,v5,則最後的最小生成樹為:


kruskal演算法:

演算法思想:

則令最小生成樹的初始狀態為只有n個頂點而無邊的非連通圖,圖中每個頂點各自成一個連通分量。從圖中的所有邊中選出一條權值最小的邊,若該邊依附的兩個頂點落在T不同的連通分量中,則將該邊作為最小生成樹的一條邊儲存起來,並標記該邊已經選擇過,否則就摒棄該條邊而選擇下一條代價最小的邊,依次類推,直到圖中所有頂點都在同一連通分量上為止。


圖中先將每個頂點看作獨立的子圖,然後查詢最小權值邊,這條邊是有限制條件的,邊得兩個頂點必須不在同一個子圖中,如上圖,第一次找到最小權值邊為(v1,v3),且滿足限制條件,繼續查詢到邊(v4,v6),(v2,v5),(v3,v6),當查詢到最後一條邊時,僅僅只有(v2,v3)滿足限制條件,其他的如(v3,v4),(v1,v4)都在一個子圖裡面,不滿足條件,至此已經找到最小生成樹的所有邊。


兩種演算法的C++實現:

.h檔案

#pragma once
#include <vector>
using namespace std;



/*   無向圖:
            A
         /  |  \
        B---F---E
		 \ / \ /
	      C---D

   索引: A B C D E F
         0 1 2 3 4 5

   權值:A-B 6、A-E 5、A-F 1
         B-C 3、B-F 2
		 C-F 8、C-D 7
		 D-F 4、D-E 2
		 E-F 9
*/


class CEdge//邊的類
{
public:
	CEdge(int NodeIndexA = 0,int NodeIndexB = 0,int WightValue = 0);
	int m_nNodeIndexA; //邊的起始點
	int m_nNodeIndexB;//邊的終點
	int m_nWeightValue;//邊的權值
	bool m_bSelected;//表明此邊是否被選擇過
};

class CNode//點的類
{
public:
	CNode(char cData = 0);
public:
	char m_cData;
	bool m_bIsVisited;
};

class CZzcGrapha
{
public:
	CZzcGrapha(int nCapacity);
	~CZzcGrapha(void);
	bool AddNodeToGrapha(CNode* pNode); //向圖中增加節點
	void ResetNodeVisitFlag();           //將所有節點的訪問標識置為false
	bool SetValueToMatrixForDirectedGraph(int row,int col,int value = 1);//向有向圖矩陣中設定值
	bool SetValueToMatrixForUnDirectedGraph(int row,int col,int value = 1);//向無向圖矩陣設定值
	bool GetValueFromMatrix(int row,int col,int& value);//從鄰接矩陣中獲取值
	void PrintMatrix();//打印出圖的鄰接矩陣
	void DepthFirstTraverse(int nodeindex);//深度優先遍歷
	void WidthFirstTraverse(int nodeindex);//廣度優先遍歷
	void WidthTraverseIteration(vector<int> prevec);

	void PrimTree(int nodeindex);//prim演算法 最小生成樹
	void Kruskal();//Kruskal 最小生成樹
private:
	int GetMinValueEdge(vector<CEdge> edgeVec);
	bool IsInSet(vector<int> nodeVec,int nodeIndex);
	void mergeNodeSet(vector<int> &nodeSetA,vector<int> nodeSetB);
private:
	int m_nCapacity;        //圖的容量(可以容納的節點數)
	int m_nCurNodeCount;    //圖中當前的節點個數
	CNode* m_pNodeArray;    //用來存放定點資料
	int* m_pMatrix;         //用來存放鄰接矩陣資料
	CEdge* m_pEdge;         //用來儲存找到的最小生成樹的邊
};


.Cpp檔案:
#include "StdAfx.h"
#include "ZzcGrapha.h"
#include "Windows.h"
#include <iostream>
using namespace std;


CEdge::CEdge(int NodeIndexA,int NodeIndexB,int WightValue)
{
	m_nNodeIndexA = NodeIndexA;
	m_nNodeIndexB = NodeIndexB;
	m_nWeightValue = WightValue;
	m_bSelected = false;
}

CNode::CNode(char cData)
{
	m_cData = cData;
	m_bIsVisited = false;
}

CZzcGrapha::CZzcGrapha(int nCapacity)
{
	m_nCapacity      = nCapacity;
	m_nCurNodeCount  = 0;
	m_pNodeArray     = new CNode[m_nCapacity];
	m_pMatrix        = new int[m_nCapacity * m_nCapacity];
	ZeroMemory(m_pMatrix,m_nCapacity * m_nCapacity * sizeof(int));

	m_pEdge = new CEdge[m_nCapacity - 1];
}

CZzcGrapha::~CZzcGrapha(void)
{
	if (m_pNodeArray)
	{
		delete[]m_pNodeArray;
		m_pNodeArray = NULL;
	}

	if (m_pMatrix)
	{
		delete[]m_pMatrix;
		m_pMatrix = NULL;
	}

	if (m_pEdge)
	{
		delete[]m_pEdge;
		m_pEdge = NULL;
	}
}

bool CZzcGrapha::AddNodeToGrapha(CNode* pNode)
{
	if(pNode == NULL) return false;

	m_pNodeArray[m_nCurNodeCount].m_cData = pNode->m_cData;
	m_nCurNodeCount++;
	return true;
}

void CZzcGrapha::ResetNodeVisitFlag()
{
	for(int i = 0;i < m_nCapacity;i++)
	{
		m_pNodeArray[i].m_bIsVisited = false;
	}
}

bool CZzcGrapha::SetValueToMatrixForDirectedGraph(int row,int col,int value)
{
	if(row < 0||row >= m_nCapacity) return false;

	if(col < 0||col >= m_nCapacity) return false;

	m_pMatrix[m_nCapacity * row + col] = value;

	return true;
}

bool CZzcGrapha::SetValueToMatrixForUnDirectedGraph(int row,int col,int value)
{
	if(row < 0||row >= m_nCapacity) return false;

	if(col < 0||col >= m_nCapacity) return false;

	m_pMatrix[m_nCapacity * row + col] = value;
	m_pMatrix[m_nCapacity * col + row] = value;

	return true;
}

bool CZzcGrapha::GetValueFromMatrix(int row,int col,int& value)
{
	if(row < 0||row >= m_nCapacity) return false;

	if(col < 0||col >= m_nCapacity) return false;

	value = m_pMatrix[m_nCapacity * row + col];

	return true;
}

void CZzcGrapha::PrintMatrix()
{
	for (int i = 0;i < m_nCapacity;i++)
	{
		for (int k = 0;k < m_nCapacity;k++)
		{
			cout<<m_pMatrix[m_nCapacity * i + k]<<" "; 
		}

		cout<<endl;
	}
}

void CZzcGrapha::DepthFirstTraverse(int nodeindex)
{
	int value = 0;

	cout<<m_pNodeArray[nodeindex].m_cData<<" ";
	m_pNodeArray[nodeindex].m_bIsVisited = true;

	for (int i = 0;i < m_nCapacity;i++)
	{
		GetValueFromMatrix(nodeindex,i,value);

		if (value == 1)
		{
			if(m_pNodeArray[i].m_bIsVisited == true) continue;

			DepthFirstTraverse(i);
		} 
		else
		{
			continue;
		}
	}
}

void CZzcGrapha::WidthFirstTraverse(int nodeindex)
{
	cout<<m_pNodeArray[nodeindex].m_cData<<" ";
	m_pNodeArray[nodeindex].m_bIsVisited = true;

	vector<int> curVec;
	curVec.push_back(nodeindex);
	WidthTraverseIteration(curVec);
}

void CZzcGrapha::WidthTraverseIteration(vector<int> prevec)
{
	int value = 0;

	vector<int> curVec;

	for(int i = 0;i < (int)prevec.size();i++)
	{
		for (int j = 0;j < m_nCapacity;j++)
		{
			GetValueFromMatrix(prevec[i],j,value);

			if (value != 0)
			{
				if(m_pNodeArray[j].m_bIsVisited) continue;

				cout<<m_pNodeArray[j].m_cData<<" ";
				m_pNodeArray[j].m_bIsVisited = true;

				curVec.push_back(j);
			} 
			else
			{
				continue;
			}
		}
	}

	if(curVec.size() == 0) return;

	WidthTraverseIteration(curVec);
}

//引數nodeindex表示第一個加入到點集合中的點
void CZzcGrapha::PrimTree(int nodeindex)//prim演算法 最小生成樹
{
	int value = 0;//存放所取得的邊的權值
	int edgeCount = 0;//標識所找到的邊的數目
	vector<int> nodeVec;//存放所找到的點的索引的集合
	vector<CEdge> edgeVec;//存放所找到的邊的集合

	nodeVec.push_back(nodeindex);//將第一個點的索引加入到點集合
	m_pNodeArray[nodeindex].m_bIsVisited = true;//第一個點已經被訪問過了

	cout<<m_pNodeArray[nodeindex].m_cData<<endl;

	while (edgeCount < m_nCapacity - 1)
	{
		int temp = nodeVec.back();

		for (int i = 0;i < m_nCapacity;i++)//尋找與temp點相連線的點
		{
			GetValueFromMatrix(temp,i,value);

			if (value != 0)//權值不為0,則兩點相連線
			{
				if (m_pNodeArray[i].m_bIsVisited)//此點已經被訪問過了
				{
					continue;
				} 
				else
				{
					CEdge edge(temp,i,value);//構造temp與i兩點之間的邊
					edgeVec.push_back(edge);//將此邊加入到邊的集合
				}
			}
		}

		//for迴圈過後會找到與temp點連線的所有的邊,下面找到權值最小的邊,返回此邊在集合中的索引

		int mixEdgeIndex = GetMinValueEdge(edgeVec);

		edgeVec[mixEdgeIndex].m_bSelected = true;

		cout<<edgeVec[mixEdgeIndex].m_nNodeIndexA<<"---"<<edgeVec[mixEdgeIndex].m_nNodeIndexB<<"   ";
		cout<<edgeVec[mixEdgeIndex].m_nWeightValue<<endl;
		cout<<m_pNodeArray[edgeVec[mixEdgeIndex].m_nNodeIndexB].m_cData<<endl;

		m_pEdge[edgeCount] = edgeVec[mixEdgeIndex];//儲存找到的最小邊

		edgeCount++;

		int nextNodeIndex = edgeVec[mixEdgeIndex].m_nNodeIndexB;//下次要加入到點集合中的索引

		nodeVec.push_back(nextNodeIndex);

		m_pNodeArray[nextNodeIndex].m_bIsVisited = true;
	}
}

int CZzcGrapha::GetMinValueEdge(vector<CEdge> edgeVec)//找到最小權值邊
{
	int value = 0;
	int edgeIndex = 0;
	
	int i = 0;

	for (;i < (int)edgeVec.size();i++)
	{
		if (!edgeVec[i].m_bSelected)
		{
			value = edgeVec[i].m_nWeightValue;
			edgeIndex = i;
			break;
		}
	}

	if (value == 0)
	{
		return -1;
	}

	for (;i < (int)edgeVec.size();i++)
	{
		if(edgeVec[i].m_bSelected) continue;

		if (value > edgeVec[i].m_nWeightValue)
		{
			value = edgeVec[i].m_nWeightValue;
			edgeIndex = i;
		}
	}

	return edgeIndex;
}

void CZzcGrapha::Kruskal()
{
	int value = 0,edgeCount = 0;

	vector<vector<int>> nodeSets;//存放點集合的陣列,相當於是陣列的陣列

	vector<CEdge> edgeVec;//存放邊的陣列

	//第一步:找出所有邊,並放入到邊的陣列中
	for (int i = 0;i < m_nCapacity - 1;i++)
	{
		for (int k = i + 1;k < m_nCapacity;k++)
		{
			GetValueFromMatrix(i,k,value);
			cout<<value<<" ";
			
			if (value != 0)//i和k兩個點之間存在邊
			{
				

				CEdge edge(i,k,value);
				edgeVec.push_back(edge);
			}
		}
		cout<<endl;
	}

	//第二步,從所有邊中取出最小生成樹的邊
	while (edgeCount < m_nCapacity - 1)
	{
		//從邊的集合中找出最小邊
		int minEdgeIndex = GetMinValueEdge(edgeVec);
		edgeVec[minEdgeIndex].m_bSelected = true;

		//取出最小邊的兩個點
		int nodeAIndex = edgeVec[minEdgeIndex].m_nNodeIndexA;
		int nodeBIndex = edgeVec[minEdgeIndex].m_nNodeIndexB;

		bool bNodeAIsInSet = false;
		bool bNodeBIsInSet = false;

		int nNodeAInSetLab = -1;
		int nNodeBInSetLab = -1;

		//分別找出最小邊兩個點所在的集合
		for (int i = 0;i < (int)nodeSets.size();i++)
		{
			bNodeAIsInSet = IsInSet(nodeSets[i],nodeAIndex);//判斷nodeAIndex點在哪個點集合中

			if (bNodeAIsInSet)
			{
				nNodeAInSetLab = i;//將集合索引儲存起來
			}
		}

		for (int i = 0;i < (int)nodeSets.size();i++)
		{
			bNodeBIsInSet = IsInSet(nodeSets[i],nodeBIndex);//判斷nodeAIndex點在哪個點集合中

			if (bNodeBIsInSet)
			{
				nNodeBInSetLab = i;//將集合索引儲存起來
			}
		}

		//兩點都不在已經存在的集合中,新建一個集合放入集合的陣列中
		if(nNodeAInSetLab == -1&&nNodeBInSetLab == -1)
		{
			vector<int> vec;
			vec.push_back(nodeAIndex);
			vec.push_back(nodeBIndex);
			nodeSets.push_back(vec);
		}
		//nodeAIndex不在已經存在的集合中,nodeBIndex在已經存在的集合中,
		//將nodeAIndex放入到nodeBIndex所在的集合中
		else if(nNodeAInSetLab == -1&&nNodeBInSetLab != -1)
		{
			nodeSets[nNodeBInSetLab].push_back(nodeAIndex);
		}
		//nodeAIndex在已經存在的集合中,nodeBIndex不在已經存在的集合中,
		//將nodeBIndex放入到nodeAIndex所在的集合中
		else if(nNodeAInSetLab != -1&&nNodeBInSetLab == -1)
		{
			nodeSets[nNodeAInSetLab].push_back(nodeBIndex);
		}
		//兩點在不同的集合中,合併兩個集合
		else if(nNodeAInSetLab != -1&&nNodeBInSetLab != -1&&nNodeAInSetLab != nNodeBInSetLab)
		{
			//引數2合併到引數1的集合中
			mergeNodeSet(nodeSets[nNodeAInSetLab],nodeSets[nNodeBInSetLab]);
			//將引數2集合從nodeSets集合中去掉
			for (int i = nNodeBInSetLab;i < (int)nodeSets.size()-1;i++)
			{
				nodeSets[i] = nodeSets[i+1];
			}
		}
		//當期的兩個點在同一個集合中,這就會形成迴路,多以當期邊要摒棄掉
		else if(nNodeAInSetLab != -1&&nNodeBInSetLab != -1&&nNodeAInSetLab == nNodeBInSetLab)
		{
			continue;
		}
		//到這裡說明找出的邊符合要求,將此邊儲存起來
		m_pEdge[edgeCount] = edgeVec[minEdgeIndex];
		edgeCount++;

		cout<<edgeVec[minEdgeIndex].m_nNodeIndexA<<"---"<<edgeVec[minEdgeIndex].m_nNodeIndexB<<"   ";
		cout<<edgeVec[minEdgeIndex].m_nWeightValue<<endl;
	}
}

bool CZzcGrapha::IsInSet(vector<int> nodeVec,int nodeIndex)
{
	for (int i = 0;i < (int)nodeVec.size();i++)
	{
		if (nodeVec[i] == nodeIndex)
		{
			return true;
		}
	}

	return false;
}

void CZzcGrapha::mergeNodeSet(vector<int> &nodeSetA,vector<int> nodeSetB)
{
	for (int i = 0;i < (int)nodeSetB.size();i++)
	{
		nodeSetA.push_back(nodeSetB[i]);
	}
}

測試:
// 圖.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"
#include "ZzcGrapha.h"
#include <iostream>
using namespace std;


int _tmain(int argc, _TCHAR* argv[])
{
	CZzcGrapha* pGrapha = new CZzcGrapha(6);

	CNode* pNodeA = new CNode('A');
	CNode* pNodeB = new CNode('B');
	CNode* pNodeC = new CNode('C');
	CNode* pNodeD = new CNode('D');
	CNode* pNodeE = new CNode('E');
	CNode* pNodeF = new CNode('F');

	pGrapha->AddNodeToGrapha(pNodeA);
	pGrapha->AddNodeToGrapha(pNodeB);
	pGrapha->AddNodeToGrapha(pNodeC);
	pGrapha->AddNodeToGrapha(pNodeD);
	pGrapha->AddNodeToGrapha(pNodeE);
	pGrapha->AddNodeToGrapha(pNodeF);

	pGrapha->SetValueToMatrixForUnDirectedGraph(0,1,6);
	pGrapha->SetValueToMatrixForUnDirectedGraph(0,4,5);
	pGrapha->SetValueToMatrixForUnDirectedGraph(0,5,1);
	pGrapha->SetValueToMatrixForUnDirectedGraph(1,2,3);
	pGrapha->SetValueToMatrixForUnDirectedGraph(1,5,2);
	pGrapha->SetValueToMatrixForUnDirectedGraph(2,5,8);
	pGrapha->SetValueToMatrixForUnDirectedGraph(2,3,7);
	pGrapha->SetValueToMatrixForUnDirectedGraph(3,5,4);
	pGrapha->SetValueToMatrixForUnDirectedGraph(3,4,2);
	pGrapha->SetValueToMatrixForUnDirectedGraph(4,5,9);

	pGrapha->PrintMatrix();
	cout<<endl;

	//pGrapha->DepthFirstTraverse(0);
	//cout<<endl;
	//pGrapha->ResetNodeVisitFlag();
	//pGrapha->WidthFirstTraverse(0);

	//pGrapha->PrimTree(0);
	pGrapha->Kruskal();


	return 0;
}