最小生成樹:Prim算法


一,最小生成樹算法基本概念

最小生成樹是數據結構中圖的一種重要應用,它的要求是從一個有n個節點的帶權完全圖中選擇n-1條邊並使這個圖仍然連通(也即得到了一棵生成樹),同時還要考慮使樹的權之和最小。最小生成樹可以用,Kruskal(克魯斯卡爾)算法或Prim(普里姆)算法求出。


1,Prim算法

1),算法簡單描述:

1).輸入:一個加權連通圖,其中頂點集合為V,邊集合為E;

2).初始化:Vnew = {x},其中x為集合V中的任一節點(起始點),Enew = {},為空;

3).重復下列操作,直到Vnew = V:

a.在集合E中選取權值最小的邊<u, v>,其中u為集合Vnew中的元素,而v不在Vnew集合當中,並且v∈V(如果存在有多條滿足前述條件即具有相同權值的邊,則可任意選取其中之一);

b.將v加入集合Vnew中,將<u, v>邊加入集合Enew中;

4).輸出:使用集合Vnew和Enew來描述所得到的最小生成樹

2),下面對算法的圖例描述:

綠色頂點及邊表示:已經被選中為最小生成樹中的頂點

淡藍色頂點及邊表示:當前距離已選中的頂點中的最小權值邊及其相連的頂點

藍色表示頂點及邊表示:當前已經選中頂點可達的頂點或者邊

圖例 說明 不可選 可選 已選(Vnew
 

此為原始的加權連通圖。每條邊一側的數字代表其權值。 - - -

1,選取任意頂點為起始點,比如D。,
2,頂點ABEF通過單條邊與D相連。A是距離D最近的頂點,因此將A及對應邊AD以高亮表示。
C, G A, B, E, F D
 

1,下一個頂點為距離DA最近的頂點。BD為9,距A為7,E為15,F為6。因此,FDA最近,因此將頂點F與相應邊DF以高亮表示。 C, G B, E, F A, D
算法繼續重復上面的步驟。距離A為7的頂點B被高亮表示。 C B, E, G A, D, F
 

在當前情況下,可以在CEG間進行選擇。CB為8,EB為7,GF為11。E最近,因此將頂點E與相應邊BE高亮表示。 C, E, G A, D, F, B
 

這里,可供選擇的頂點只有CGCE為5,GE為9,故選取C,並與邊EC一同高亮表示。 C, G A, D, F, B, E

頂點G是唯一剩下的頂點,它距F為11,距E為9,E最近,故高亮表示G及相應邊EG G A, D, F, B, E, C

現在,所有頂點均已被選取,圖中綠色部分即為連通圖的最小生成樹。在此例中,最小生成樹的權值之和為39。 A, D, F, B, E, C, G


2,Kruskal算法

Kruskal算法與Prim算法的不同之處在於,Kruskal在找最小生成樹結點之前,需要對所有權重邊做從小到大排序。將排序好的權重邊依次加入到最小生成樹中,如果加入時產生回路就跳過這條邊,加入下一條邊。當所有結點都加入到最小生成樹中之后,就找出了最小生成樹。


1).算法簡單描述

1).記Graph中有v個頂點,e個邊

2).新建圖Graphnew,Graphnew中擁有原圖中相同的e個頂點,但沒有邊

3).將原圖Graph中所有e個邊按權值從小到大排序

4).循環:從權值最小的邊開始遍歷每條邊 直至圖Graph中所有的節點都在同一個連通分量中

                if 這條邊連接的兩個節點於圖Graphnew中不在同一個連通分量中

                                         添加這條邊到圖Graphnew

2),下面對算法的圖例描述:

(每次所選的邊都要與原所選邊進行是否為圈的判斷)

首先第一步,我們有一張圖Graph,有若干點和邊 

 

將所有的邊的長度排序,用排序的結果作為我們選擇邊的依據。這里再次體現了貪心算法的思想。資源排序,對局部最優的資源進行選擇,排序完成后,我們率先選擇了邊AD。這樣我們的圖就變成了右圖

 

 

 

在剩下的變中尋找。我們找到了CE。這里邊的權重也是5

依次類推我們找到了6,7,7,即DF,AB,BE。

下面繼續選擇, BC或者EF盡管現在長度為8的邊是最小的未選擇的邊。但是現在他們已經連通了(對於BC可以通過CE,EB來連接,類似的EF可以通過EB,BA,AD,DF來接連)。所以不需要選擇他們。類似的BD也已經連通了(這里上圖的連通線用紅色表示了)。

最后就剩下EG和FG了。當然我們選擇了EG。最后成功的圖就是右:

 



二,C++模板實現

1,Graph.h代碼實現如下:

#include "stdafx.h"
#include "iostream"
#include "queue"
using namespace std;

template<class DistType/*邊的權值的類型*/> 
class Edge//邊的定義
{
public:
	Edge(int dest, DistType weight)
	{
		m_nposTable=dest;
		m_distWeight=weight; 
		m_pnext=NULL;
	}
	~Edge()
	{

	}
public:
	int m_nposTable;//該邊的目的頂點在頂點集中的位置
	DistType m_distWeight;//邊的權重值
	Edge<DistType> *m_pnext;//下一條邊(注意不是下一個頂點,因為m_nposTable已經知道了這個頂點的位置)
};
//聲明
template<class NameType/*頂點集名字類型*/, class DistType/*距離的數據類型*/> class Graph;

template<class NameType/*頂點集名字類型*/, class DistType/*距離的數據類型*/> 
class Vertex//頂點的定義
{
public:
	Vertex()
	{
		padjEdge=NULL;
		m_vertexName=0;
	}
	~Vertex()
	{
		Edge<DistType> *pmove = padjEdge;
		while (pmove)
		{
			padjEdge = pmove->m_pnext;
			delete pmove;
			pmove = padjEdge;
		}
	}

private:
	friend class Graph<NameType,DistType>;//允許Graph類任意訪問
	NameType m_vertexName;//頂點中的數據內容
	Edge<DistType> *padjEdge;//頂點的鄰邊

};


template<class NameType/*頂點集名字類型*/, class DistType/*距離的數據類型*/> 
class Graph
{
public:
	Graph(int size = m_nDefaultSize/*圖頂點集的規模*/)
	{
		m_pVertexTable = new Vertex<NameType, DistType>[size];  //為頂點集分配內存
		if (m_pVertexTable == NULL)
		{
			exit(1);
		}
		m_numVertexs=0;
		m_nmaxSize=size;
		m_nnumEdges=0;
	}

	~Graph()
	{
		Edge<DistType> *pmove;
		for (int i=0; i < this->m_numVertexs; i++)
		{
			pmove = this->m_pVertexTable[i].padjEdge;
			if (pmove){
				this->m_pVertexTable[i].padjEdge = pmove->m_pnext;
				delete pmove;
				pmove = this->m_pVertexTable[i].padjEdge;
			}
		}
		delete[] m_pVertexTable;
	}
	int GetNumEdges()
	{//獲得邊的數目
		return m_nnumEdges/2;
	}
	int GetNumVertexs()
	{//獲得頂點數目
		return m_numVertexs;
	}

	bool IsGraphFull() const
	{     //圖滿的?
		return m_nmaxSize == m_numVertexs;
	}
	//在頂點集中位置為v1和v2的頂點之間插入邊
	bool InsertEdge(int v1, int v2, DistType weight=m_Infinity); 
	//插入頂點名字為vertex的頂點
	bool InsertVertex(const NameType vertex);  
	//打印圖
	void PrintGraph();   
	//頂點v到其他各個頂點的最短路徑(包括自身)
	void Dijkstra(int v, DistType *shotestpath);
	//獲取頂點集中位置為v1和v2的頂點之間邊的權重值
	DistType GetWeight(int v1, int v2); 
	//獲得在頂點集中的位置為v的頂點的名字
	NameType GetVertexValue(int v);
	//用該頂點的名字來尋找其在頂點集中的位置
	int GetVertexPosTable(const NameType vertex);  


	//得到頂點v的鄰點中權值最小的那條邊
	Edge<DistType> *GetMin(int v, int *visited);   
	//最小生成樹
	void Prim(Graph<NameType, DistType> &graph);  

	//深度搜索優先
	void DFS(int v, int *visited);      
	void DFS();
	//廣度優先搜索
	void BFS(int v, int *visited);
	void BFS();
	//獲取第v個頂點的名字(或者說內容)
	NameType GetVertexName(int v);   
	//獲得頂點v的第一個相鄰頂點,如果沒有就返回-1
	int GetFirst(int v);       
	//獲得頂點v1的鄰點v2后的鄰點
	int GetNext(int v1, int v2);

private:
	Vertex<NameType, DistType> *m_pVertexTable;   //頂點集
	int m_numVertexs;//圖中當前的頂點數量
	int m_nmaxSize;//圖允許的最大頂點數
	static const int m_nDefaultSize = 10;       //默認的最大頂點集數目
	static const DistType m_Infinity = 65536;  //邊的默認權值(可以看成是無窮大)
	int m_nnumEdges;//圖中邊的數目
	
};


//返回頂點vertexname在m_pVertexTable(頂點集)中的位置
//如果不在頂點集中就返回-1
template<class NameType, class DistType> 
int Graph<NameType, DistType>::GetVertexPosTable(const NameType vertexname)
{
	for (int i=0; i < this->m_numVertexs; i++)
	{
		if (vertexname == m_pVertexTable[i].m_vertexName)
		{
			return i;
		}
	}
	return -1;
}

//打印圖中的各個頂點及其鏈接的邊的權重
template<class NameType, class DistType> 
void Graph<NameType, DistType>::PrintGraph()
{
	Edge<DistType> *pmove;
	for (int i=0; i<this->m_numVertexs; i++)
	{
		cout << this->m_pVertexTable[i].m_vertexName << "->";
		pmove = this->m_pVertexTable[i].padjEdge;
		while (pmove)
		{
			cout << pmove->m_distWeight << "->" << this->m_pVertexTable[pmove->m_nposTable].m_vertexName << "->";
			pmove = pmove->m_pnext;
		}
		cout << "NULL" << endl;
	}
}
//獲得在頂點集中的位置為v的頂點的名字
template<class NameType, class DistType> 
NameType Graph<NameType, DistType>::GetVertexValue(int v)
{
	if (v<0 || v>=this->m_numVertexs)
	{
		cerr << "查找的頂點位置參數有誤,請檢查!" <<endl;
		exit(1);
	}
	return m_pVertexTable[v].m_vertexName;

}
//返回頂點v1和v2之間的邊權值,
//如果沒有直接相連(即不是一條邊直接相連)則返回無窮大
template<class NameType, class DistType> 
DistType Graph<NameType, DistType>::GetWeight(int v1, int v2)
{
	if (v1>=0 && v1<this->m_numVertexs && v2>=0 && v2<this->m_numVertexs)
	{
		if (v1 == v2)
		{
			return 0;
		}
		Edge<DistType> *pmove = m_pVertexTable[v1].padjEdge;
		while (pmove)
		{
			if (pmove->m_nposTable == v2)
			{
				return pmove->m_distWeight;
			}
			pmove = pmove->m_pnext;
		}
	}
	
	return m_Infinity;	
}

//頂點依次插入到分配好的頂點集中
template<class NameType, class DistType> 
bool Graph<NameType, DistType>::InsertVertex(const NameType vertexname)
{
	if (IsGraphFull())
	{
		cerr<<"圖已經滿,請勿再插入頂點!"<<endl;
		return false;
	}else
	{
		this->m_pVertexTable[this->m_numVertexs].m_vertexName = vertexname;
		this->m_numVertexs++;
	}
	
	return true;
}

//在頂點集位置為v1和v2的頂點之間插入權值為weght的邊(務必保持輸入的准確性,否則.....)
template<class NameType, class DistType> 
bool Graph<NameType, DistType>::InsertEdge(int v1, int v2, DistType weight)
{
	if (v1 < 0 && v1 > this->m_numVertexs && v2 < 0 && v2 > this->m_numVertexs)
	{
		cerr<<"邊的位置參數錯誤,請檢查! "<<endl;
		return false;
	}
	else
	{
		Edge<DistType> *pmove = m_pVertexTable[v1].padjEdge;
		if (pmove == NULL)//如果頂點v1沒有鄰邊
		{ //建立頂點v1的第一個鄰邊(該鄰邊指明了目的頂點)
			m_pVertexTable[v1].padjEdge = new Edge<DistType>(v2, weight);
			m_nnumEdges++;//圖中邊的數目
			return true;
		}else//如果有鄰邊
		{
			while (pmove->m_pnext)
			{
				pmove = pmove->m_pnext;
			}
				pmove->m_pnext = new Edge<DistType>(v2, weight);
				m_nnumEdges++;//圖中邊的數目
				return true;
		}
	}
}


template<class NameType, class DistType>
void Graph<NameType, DistType>::Dijkstra(int v, DistType *shPath)
{
	int num =GetNumVertexs();
	int *visited = new int[num];
	for (int i=0; i < num; i++)
	{//初始化
		visited[i] = 0;//未訪問
		shPath[i] = this->GetWeight(v, i);//頂點v(當前中間點)到各個相鄰頂點的邊權值,其他情況返回無窮大
	}

	visited[v] = 1;//第v個頂點初始化為被訪問,並以他為中點點開始找最短路徑

	for (int i = 1; i < num; i++)
	{
		DistType min = this->m_Infinity;
		int u=0;
		
		//尋找新的中間點u,依據就是數組中權值最小的那個點的位置(且沒被訪問過)
		for (int j=0; j < num; j++)
		{   
			if (!visited[j])
			{
				if (shPath[j]<min)
				{
					min = shPath[j];//獲得當前shPath數組中的最小邊權重
					u = j;//用u來記錄獲取最小值時的頂點位置,即新的中間點
				}
			}
		}

		visited[u] = 1;//已經確定的最短路徑

		//以u為中間點尋找頂點v到頂點w的最短路徑
		for (int w=0; w < num; w++)
		{  
			DistType weight = this->GetWeight(u, w);//頂點u(當前中間點)到各個相鄰頂點的邊權值,其他情況返回無窮大
			if (!visited[w] && weight != this->m_Infinity )
			{
				if ( shPath[u]+weight < shPath[w] )
				{
					shPath[w] = shPath[u] + weight;//更新頂點v到w的最短路徑值
				}
			}
		}
	}
	delete[] visited;
}



//獲得頂點v1的鄰點v2后的鄰點
template<class NameType, class DistType> 
int Graph<NameType, DistType>::GetNext(int v1, int v2)
{
	if (-1 != v1)
	{
		Edge<DistType> *pmove = this->m_pVertexTable[v1].padjEdge;
		while (NULL != pmove->m_pnext)
		{
			if (pmove->m_nposTable==v2)
			{
				return pmove->m_pnext->m_nposTable;
			}
			pmove = pmove->m_pnext;
		}        
	}
	return -1;
}

//從第v個頂點開始深度遍歷
template<class NameType, class DistType> 
void Graph<NameType, DistType>::DFS(int v, int *visited)
{
	cout << "->" << this->GetVertexName(v);
	visited[v] = 1;
	int firstVertex = this->GetFirst(v);//獲得頂點v的第一個相鄰頂點,若沒有則返回-1
	while (-1 != firstVertex)
	{
		if (!visited[firstVertex])//如果沒有訪問過
		{
			cout << "->" << this->GetWeight(v, firstVertex);//獲得頂點v及其鄰點firstVertex之間的權值
			DFS(firstVertex, visited);
		}
		firstVertex = this->GetNext(v, firstVertex);//獲得頂點v的鄰點firstVertex后的鄰點,如果沒有就返回-1
	}
}

template<class NameType, class DistType> 
void Graph<NameType, DistType>::DFS()
{
	int *visited = new int[this->m_numVertexs];
	for (int i=0; i<this->m_numVertexs; i++)
	{
		visited[i] = 0;
	}
	cout << "head";
	DFS(0, visited);//從第一個頂點開始遍歷
	cout << "--->end";
}

template<class NameType, class DistType> 
void Graph<NameType, DistType>::BFS()
{
	int *visited = new int[this->m_numVertexs];
	for (int i=0; i<this->m_numVertexs; i++)
	{
		visited[i] = 0;
	}
	cout << "head";
	BFS(0, visited);//從第一個頂點開始遍歷
	cout << "--->end";
}

//從第v個頂點開始廣度遍歷
template<class NameType, class DistType> 
void Graph<NameType, DistType>::BFS(int v, int *visited)
{
	cout << "->" << this->GetVertexName(v);
	visited[v]=1;
	queue<int> que;//=new queue<int>[this->GetNumVertexs()];
	que.push(v);//進隊(隊列的末端)
	while (!que.empty())
	{
		v=que.front();//出隊首元素
		que.pop();//刪除隊首元素
		int firstvertex=GetFirst(v);
		while(firstvertex != -1)
		{
			if (!visited[firstvertex])
			{
				cout << "->" << this->GetWeight(v, firstvertex);//獲得頂點v及其鄰點firstVertex之間的權值
				que.push(firstvertex);
				visited[firstvertex]=1;
				cout << "->" << this->GetVertexName(firstvertex);
			}
			firstvertex=GetNext(v,firstvertex);
		}
	}
}

//獲得在頂點集中的位置為v的頂點的名字
template<class NameType, class DistType> 
NameType Graph<NameType, DistType>::GetVertexName(int v)
{
	if (v<0 || v>=this->m_numVertexs)
	{
		cerr << "查找的頂點位置參數有誤,請檢查!" <<endl;
		exit(1);
	}
	return m_pVertexTable[v].m_vertexName;

}

//獲得頂點v的第一個相鄰頂點,如果沒有就返回-1
template<class NameType, class DistType> 
int Graph<NameType, DistType>::GetFirst(int v)
{
	if (v<0 || v>=this->m_numVertexs)
	{
		return -1;
	}
	Edge<DistType> *ptemp = this->m_pVertexTable[v].padjEdge;
	return m_pVertexTable[v].padjEdge ? m_pVertexTable[v].padjEdge->m_nposTable : -1;
}






template<class NameType, class DistType> 
Edge<DistType>* Graph<NameType, DistType>::GetMin(int v, int *visited)
{
	Edge<DistType> *pmove = this->m_pVertexTable[v].padjEdge; 
	Edge<DistType> *ptemp = new Edge<DistType>(0, this->m_Infinity);
	Edge<DistType> *pmin = ptemp;
	while (pmove)
	{
		if (!visited[pmove->m_nposTable] && pmin->m_distWeight>pmove->m_distWeight){
			pmin = pmove;
		}
		pmove = pmove->m_pnext;
	}
	if (pmin == ptemp)
	{
		delete ptemp;
		return NULL;
	}
	delete ptemp;
	return pmin;
}

template<class NameType, class DistType> 
void Graph<NameType, DistType>::Prim(Graph<NameType, DistType> &graphprim)
{ 
	int *nodeVertex = new int[this->m_numVertexs];    //用來存儲被訪問過的頂點
	int *visited = new int[this->m_numVertexs];//設置頂點被訪問過與否
	int count = 0;
	Edge<DistType> *ptemp1;
	Edge<DistType> *ptemp2 = new Edge<DistType>(0, this->m_Infinity);
	Edge<DistType> *pmin;
	int min=0;
	//初始化最小生成樹
	for (int i=0; i<this->m_numVertexs; i++)
	{
		graphprim.InsertVertex(this->m_pVertexTable[i].m_vertexName);
		nodeVertex[i] = 0;
		visited[i] = 0;
	}
	visited[0] = 1;//從第一個頂點開始,並標記為以訪問
	while(++count < this->m_numVertexs)
	{
		pmin = ptemp2;
		pmin->m_distWeight = this->m_Infinity;

		//獲得已訪問頂點和未訪問頂點之間的最小權值
		for (int i=0; i<count; i++){
			ptemp1 = GetMin(nodeVertex[i], visited);
			if (NULL == ptemp1)
			{
				continue;
			}
			if (pmin->m_distWeight > ptemp1->m_distWeight)
			{
				pmin = ptemp1;
				min = nodeVertex[i];            
			}
		}

		nodeVertex[count] = pmin->m_nposTable;
		visited[nodeVertex[count]] = 1;
		graphprim.InsertEdge(pmin->m_nposTable, min, pmin->m_distWeight);
		graphprim.InsertEdge(min, pmin->m_nposTable, pmin->m_distWeight);
	}

	delete ptemp2;
	delete[] nodeVertex;
	delete[] visited;
}


2,主程序代碼如下:

// ConsoleAppMyGraph.cpp : 定義控制台應用程序的入口點。
//

#include "stdafx.h"
#include "Graph.h"
#include <iostream>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	
	Graph<char *, int> graph(7);
	char *vertex[7] = {"【地大】", "【武大】", "【華科】", "【交大】", "【北大】", "【清華】", "【復旦】"};//頂點集
	for (int i=0; i<7; i++)
	{
		graph.InsertVertex(vertex[i]);
	}
	cout<<"一,圖的初始化(鄰結表存儲):======================================"<<endl;
	graph.PrintGraph();
	cout<<"圖中頂點的數目:"<<graph.GetNumVertexs()<<endl;
	cout <<endl;


	int edge[][3] = {{0, 1, 43}/*地大到武大的距離*/, {0, 2, 12}, {1, 2, 38}, {2, 3 ,1325},
	{3, 6, 55},{4, 5, 34}, {4, 6, 248},{0,3,400},{2,6,314},{2,4,37}};    //分配距離 
	int len=sizeof(edge)/sizeof(edge[0]);
	for (int i=0; i < len; i++)
	{
		graph.InsertEdge(edge[i][0], edge[i][1], edge[i][2]);
		graph.InsertEdge(edge[i][1], edge[i][0], edge[i][2]);
	}
	cout<<"二,添加邊后的圖(無向圖):=================================="<<endl;
	graph.PrintGraph();
	cout<<"圖中邊的數目(實際上是所示邊數的兩倍,因為是雙向的):"<<graph.GetNumEdges()<<endl;
	cout <<endl;


	cout<<"三,Dijkstra法最短路徑為:=========================="<<endl;
	int shortestPath[7];//存儲Dijkstra算法最短路徑值
	graph.Dijkstra(0, shortestPath);
	for (int i=0; i<7; i++)
	{
		cout << graph.GetVertexValue(0) << "--->" << graph.GetVertexValue(i) 
			<< ":   " << shortestPath[i] <<endl;
	}
	cout<<endl;


	cout<<"四,圖的遍歷:=========================="<<endl;
	cout<<"1,DFS深度優先遍歷結果:"<<endl;
	graph.DFS();
	cout<<endl<<endl;
	cout<<"2,BFS廣度優先遍歷結果:"<<endl;
	graph.BFS();
	cout<<endl<<endl;


	cout<<"五,最小生成生成樹:================================="<<endl;
	Graph<char *, int> graphPrim;
	graph.Prim(graphPrim);
	cout<<"使用DFS遍歷:"<<endl;
	graphPrim.DFS();
	cout<<endl<<endl;
	cout<<"使用BFS遍歷:"<<endl;
	graphPrim.BFS();

	system("color 0A");
	system("pause");
	return 0;
}



3,測試結果:




參考資源:

【1】《算法導論》

【2】《百度文庫》

【3】《維基百科》

【4】https://en.wikipedia.org/wiki/Kruskal%27s_algorithm

【5】https://en.wikipedia.org/wiki/Prim%27s_algorithm

【6】http://baike.baidu.com/linkurl=tHoWD0_Xcu_fSMMoQgLmwh_0nR1Uk0xfYRhd8zgVYSX8DMsHPSXRsQZhgvw7SL9NyHDrQkU7j7B80uusAZbl4PzSGZzJwxqHba_FJ7jAkApSmLUjq2PswoytLdvZpL7nWoUCR4jJp_MxGyGA06YQKf5WU6sHZNBbUePG6NiytO

【7】http://blog.csdn.net/todd911/article/details/9219937

【8】http://blog.chinaunix.net/uid-25324849-id-2182922.html

【9】http://www.cnblogs.com/rollenholt/archive/2012/04/09/2439055.html



注:

本文部分文字學習並copy自網絡,代碼參考並改寫於《算法導論》.

如果侵犯了您的版權,請聯系本人tangyibiao520@163.com,本人將及時編輯掉!




注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com