图的最小生成树算法

图的最小生成树有两种算法:

一种是Kruscal算法:把图的每个节点看成是不同的集合,把所有的边都放入到优先级队列中去,不断从优先级队列中取出边,如果这个边的两端点属于不同的集合,那么就把这条边加入到最小生成树的边,并且把这两个集合合并,如果这两个边的端点属于同一集合,则相当于是环状,就丢弃当前这条边。

用到的主要的数据结构就是优先级队列和一个简单的并查集,优先级队列是用的STL标准库,是用vector和heap作为辅助结构实现的,而并查集是一个很经典的集合数据结构,其中每个集合都有一个唯一的代表元素,如果比较两个集合是不是同一个集合,只需要判断两个集合的代表元素是否相同即可。如果需要合并两个集合,只需要将两个集合的代表元素设置成相同的即可。算法的复杂度为:O(ElogV)

另一种是prim算法:把图中的第一个定点认为是树根或子图,将子图中的边入队到优先级队列中,不断从队列中挑选最小的边,将其另一端所指向的节点加入到子图中去,同时把和这个节点相关的边加入到优先级队列中。知道队列中为空位置。

用到的数据结构是优先级队列,算法的复杂度为:O(VlogV+ElogV)=O(ElogV)。

代码:

//GraphMatrix.h
#ifndef __GraphMatrix__
#define __GraphMatrix__
#include<iostream>
#include<vector>
using namespace std;
struct Vertex
{
	char data;
	bool visited;
	Vertex* p;//kruskal算法要用
	int rank;//kruskal算法要用
	Vertex(char d=0)
	{
		rank=1;
		data=d;
		visited=false;
	}
};

struct Edge
{
	int weight;
	Edge(int w=INT_MAX)
	{
		weight=w;
	}
};

struct Node
{
	char from;
	char to;
	int weight;
	Node(char f,char t,int w=INT_MAX)
	{
		from=f;
		to=t;
		weight=w;
	}
	bool operator < (const Node &a) const
	{
		return weight>a.weight;
	}
};

struct KruscalNode
{
	int from;
	int to;
	int weight;
	KruscalNode(int f,int t,int w=INT_MAX)
	{
		from=f;
		to=t;
		weight=w;
	}
	bool operator < (const KruscalNode &a) const
	{
		return weight>a.weight;
	}
};

class GraphMatrix
{
	int n;
	int e;
	vector<Vertex> V;//顶点集合
	vector<vector<Edge> >E;//边的集合
	Vertex* findSet(Vertex *node);
	void unionSet(Vertex &node1,Vertex &node2);
public:
	GraphMatrix()
	{
		n=e=0;
	}
	int insertVertex(Vertex & v);
	void insertEdge(Vertex from,Vertex to,Edge e);
	void prime(vector<Vertex> &result_V,vector<Node> &result_E);
	void kruskal(vector<Node> &result_E);
	
};
#endif

//GraphMatrix.cpp
#include"GraphMatrix.h"
#include<queue>
using namespace std;

int GraphMatrix::insertVertex(Vertex & v)
{
	V.push_back(v);
	E.push_back(vector<Edge>(V.size()));
	if(E.size()>1)
		for(int i=0;i<V.size()-1;i++)
			E[i].push_back(Edge());
	n++;
	return V.size()-1;
}
void GraphMatrix::insertEdge(Vertex from,Vertex to,Edge e)
{
	int k=0;
	int p=-1,q=-1;
	for(int i=0;i<V.size();i++)
	{
		if(V[i].data==from.data)
		{
			k+=1;
			p=i;
		}
		if(V[i].data==to.data)
		{
			k+=2;
			q=i;
		}
	}
	if((k&1)==0)
		p=insertVertex(from);
	if((k&2)==0)
		q=insertVertex(to);
	E[p][q]=e;
	E[q][p]=e;//如果不屏蔽则是无向图
	this->e++;
}

void GraphMatrix::prime(vector<Vertex> &result_V,vector<Node> &result_E)
{
	priority_queue<Node> pq;
	pq.push(Node(V[0].data,V[0].data,0));//先把第一个点放入优先级队列中
	while(!pq.empty())
	{
		Node temp=pq.top();
		int k=-1;
		for(int i=0;i<V.size();i++)//其实可以把pq定义成为一个存储3个int型的变量,来去掉这个for循环
		{						   //即node temp=pq.front(),访问V[temp.second].isvisited即可
			if(V[i].data==temp.to) //这里为了方便存储边,就没有用这个方法。
			{
				if(V[i].visited==false)
					k=i;//即开始访问第k个节点
				break;
			}
		}
		pq.pop();
		if(k==-1)
			continue;
		result_V.push_back(V[k]);
		V[k].visited=true;
		result_E.push_back(temp);
		for(int i=0;i<E[k].size();i++)
		{
			if(E[k][i].weight!=INT_MAX && V[i].visited==false)
				pq.push(Node(V[k].data,V[i].data,E[k][i].weight));
		}
	}
}

void GraphMatrix::kruskal(vector<Node> &result_E)
{
	priority_queue<KruscalNode> pq;
	for(int i=0;i<E.size();i++)//创建边的优先级队列
	{
		V[i].p=&V[i];
		for(int j=0;j<E.size();j++)
		{
			if(E[i][j].weight!=INT_MAX)
			{
				pq.push(KruscalNode(i,j,E[i][j].weight));
			}
		}
	}
	unionSet(V[pq.top().from],V[pq.top().to]);//把前两个点作为一个集合
	result_E.push_back(Node(V[pq.top().from].data,V[pq.top().to].data,pq.top().weight));
	pq.pop();
	while(!pq.empty())
	{
		KruscalNode temp=pq.top();
		if(findSet(&V[temp.from])!=findSet(&V[temp.to]))
		{
			unionSet(V[temp.from],V[temp.to]);
			result_E.push_back(Node(V[temp.from].data,V[temp.to].data,temp.weight));
		}
		pq.pop();
	}
}

Vertex* GraphMatrix::findSet(Vertex *node)
{
	if(node->p!=node)
		node->p=findSet(node->p);
	return node->p;
}

void GraphMatrix::unionSet(Vertex &node1,Vertex &node2)
{
	Vertex *p1=findSet(&node1);
	Vertex *p2=findSet(&node2);
	if(p1->rank>p2->rank)
		p2->p=&(*p1);
	else
	{
		p1->p=&(*p2);
		if(node1.rank==node2.rank)
			node2.rank++;
	}
}


#include<iostream>
#include"GraphMatrix.h"
#include<queue>
#include<deque>
using namespace std;

#define DEBUG

int main()
{
#ifdef DEBUG
	freopen("input.txt","r",stdin);
#endif
	GraphMatrix gm;
	char from,to;
	int e;
	//测试边和顶点的插入
	cout<<"输入边"<<endl;
	while(cin>>from>>to>>e)
	{
		cout<<"输入边"<<endl;
		gm.insertEdge(from,to,e);//调用默认构造函数
	}
	vector<Vertex> result_V;
	vector<Node> result_E;
	gm.prime(result_V,result_E);
	vector<Node> result_E1;
	gm.kruskal(result_E1);

	system("pause");
	return 0;
}
测试数据为:

A B 4
A H 8
B H 11
B C 8
C D 7
D E 9
D F 14
E F 10
C F 4
C I 2
F G 2
I G 6
H G 1
I H 7
参考:《算法导论》、《数据结构 C++语言版》清华大学邓俊辉

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完全是指中任意两个顶点之间都有边相连的。完全的最小生成算法有两种,分别是Prim算法和Kruskal算法。 1. Prim算法 Prim算法是一种贪心算法,它的基本思想是从一个顶点开始,每次选择一个与当前生成相邻的权值最小的顶点加入生成中,直到所有顶点都被加入生成为止。 具体实现步骤如下: - 任选一个顶点作为起点,将其加入生成中。 - 找到与当前生成相邻的所有顶点,并计算它们与当前生成的距离(即边权),选择其中距离最小的顶点加入生成中。 - 重复上述步骤,直到所有顶点都被加入生成为止。 下面是Prim算法的Python代码实现: ```python def prim(graph): # 初始化生成和已访问的顶点集合 mst = [] visited = set() # 任选一个顶点作为起点 start = list(graph.keys())[0] visited.add(start) # 循环直到所有顶点都被加入生成为止 while len(visited) < len(graph): # 计算与当前生成相邻的所有顶点的距离 min_edge = None for v in visited: for w, weight in graph[v].items(): if w not in visited and (min_edge is None or weight < min_edge[2]): min_edge = (v, w, weight) # 将距离最小的顶点加入生成中 mst.append(min_edge) visited.add(min_edge[1]) return mst ``` 2. Kruskal算法 Kruskal算法也是一种贪心算法,它的基本思想是将所有边按照权值从小到大排序,然后依次加入生成中,如果加入某条边会形成环,则不加入该边。 具体实现步骤如下: - 将所有边按照权值从小到大排序。 - 依次加入每条边,如果加入某条边会形成环,则不加入该边。 - 重复上述步骤,直到所有顶点都被加入生成为止。 下面是Kruskal算法的Python代码实现: ```python def kruskal(graph): # 初始化生成和边集合 mst = [] edges = [] for v in graph: for w, weight in graph[v].items(): edges.append((v, w, weight)) edges.sort(key=lambda x: x[2]) # 初始化并查集 parent = {v: v for v in graph} # 循环直到所有顶点都被加入生成为止 for edge in edges: v, w, weight = edge # 判断是否形成环 root_v = find(parent, v) root_w = find(parent, w) if root_v != root_w: mst.append(edge) parent[root_v] = root_w return mst def find(parent, v): while parent[v] != v: v = parent[v] return v ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值