贪心算法构造最小生成树:kruskal算法、prim算法

1、定义:
连通无向图G(V,E) 中找到一个边E的无环子集T,使其能够将所有节点连接起来,又具有最小权重。
a)由于T是无环的,可看作是一棵树
b)由于是图G生成的,所以称为(图G的)生成树
c)由于T具有权重最小,所以称为最小生成树
由于T是无环的,所以V个顶点一定有V-1条边。

2、贪心策略:
每次生长最小生成树的一条边,进行V-1次循环完成建树。生长的边必须轻量级边(权重最小)且不构成环。
方法: kruskal(克鲁斯卡尔)算法或prim(普里姆)算法

3、prim(普里姆)算法:
此算法可以称为加点法,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从任一个顶点s开始,逐渐长大覆盖整个连通图的所有顶点。

3.1、算法流程及图:
输入:图G(V,E)与任一节点u作为起始点;输出:边集合P

  1. 节点u加入节点集合U
  2. 选择与集合U中节点相连的代价最小边加入集合P(如边<u,v>),相连节点(如v)加入集合U
    (如果存在有多条边具有相同权值,则可任意选取其中之一);
  3. 重复进行步骤2,直至所有节点加入集合U

在这里插入图片描述
3.2、C++代码:

#include <queue>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct edge {
    
     // 边的定义
	int start;
	int end;
	int weight;
	edge(int x, int y, int z) :start(x), end(y), weight(z) {
    
    }
	bool operator< (const edge& tmp) const {
    
     //用于优先队列的比较函数
		return weight > tmp.weight;
	}
};

const int NUM = 6;
int G[NUM][NUM]; //假设图已经定义好了,如G[0][2] = 5; 5为权重
void MiniSpanTree_prim(int(*G)[NUM], int root)
{
    
    
	vector<int> V; //记录MST已经包含的点;
	V.push_back(root);
	priority_queue<edge> edge_all; // 放入权重边,自动排序
	for (int i = 0; i < NUM; i++) {
    
     // 放入与起点连接的边
		if (G[root][i] != 0) {
    
     // 0 代表没有路
			edge tmp(root, i, G[root][i]);
			edge_all.push(tmp);
		}
	}
	cout << "Prim :" << endl;
	for (int i = 0; i < NUM - 1; i++) {
    
     //共N-1条边
		edge curr = edge_all.top(); //取得代价最小边
		edge_all.pop();
		while (find(V.begin(), V.end(), curr.end) != V.end()) {
    
     //边终点若已包含,则丢弃后换一条边
			curr = edge_all.top();
			edge_all.pop();
		}
		V.push_back(curr.end); //放入这条边的终点v
		cout << curr.start << " --> " << curr.end << " " << curr.weight << endl; // 输出MST的边
		for (int j = 0; j < NUM; j++) {
    
     // 加入终点v连接的边
			if (G[curr.end][j] != 0 && find(V.begin(), V.end(), j) == V.end()) {
    
    
				edge tmp(curr.end, j, G[curr.end][j]);
				edge_all.push(tmp);
			}
		}
	}
}

4、kruskal(克鲁斯卡尔)算法:
此算法可以称为加边法,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。

4.1、算法流程及图:
输入:图G(V,E);输出:边集合P。

  1. 把图中的所有边按代价从小到大排序;
  2. 把图中的n个顶点看成独立的n棵树组成的森林;
  3. 按权值从小到大选择边,所选边的两个顶点u,v若属于两颗不同的树,则成为最小生成树的一条边,同时将这两颗树合并作为一颗树。否则取下一条权值最小的边再试。
  4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。

在这里插入图片描述

4.2、C++代码:

#include <queue>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct edge {
    
     // 边的定义
	int start;
	int end;
	int weight;
	edge(int x, int y, int z) :start(x), end(y), weight(z) {
    
    }
	bool operator< (const edge& tmp) const {
    
     //用于优先队列的比较函数
		return weight > tmp.weight;
	}
};

const int NUM = 6;
int G[NUM][NUM]; //假设图已经定义好了,如G[0][2] = 5; 5为权重
void MiniSpanTree_kruskal(int(*G)[NUM])
{
    
    
	int node_coll[NUM] = {
    
     0 }; //判断节点是否属于一棵树
	int count = 1; // 用于不同树的标记
	priority_queue<edge> edge_all; // 放入权重边,自动排序
	for (int i = 0; i<NUM; i++) {
    
     //放入所有边,并自动排序
		for (int j = 0; j<NUM; j++) {
    
    
			if (G[i][j] != 0) {
    
    
				edge tmp(i, j, G[i][j]);
				edge_all.push(tmp);
			}
		}
	}
	for (int i = 0; i<NUM - 1; i++) {
    
     //共N-1条边
		edge tmp = edge_all.top(); // 取代价最小边
		edge_all.pop();
		while (node_coll[tmp.start] == node_coll[tmp.end] && node_coll[tmp.start] != 0 && node_coll[tmp.end] != 0) {
    
     // 找到属于不同树的边
			tmp = edge_all.top();
			edge_all.pop();
		}
		cout << tmp.start << " --> " << tmp.end << " " << tmp.weight << endl; // 输出MST的边
		if (node_coll[tmp.start] == 0 && node_coll[tmp.end] == 0) {
    
     //不同树的边的情况1
			node_coll[tmp.start] = count;
			node_coll[tmp.end] = count;
			count++;
		}
		else if (node_coll[tmp.start] == 0 && node_coll[tmp.end] != 0) {
    
     //不同树的边的情况2
			node_coll[tmp.start] = count;
			node_coll[tmp.start] = node_coll[tmp.end];
		}
		else if (node_coll[tmp.start] != 0 && node_coll[tmp.end] == 0) {
    
     //不同树的边的情况3
			node_coll[tmp.start] = count;
			node_coll[tmp.end] = node_coll[tmp.start];
		}
		else if (node_coll[tmp.start] != 0 && node_coll[tmp.end] != 0) {
    
     //不同树的边的情况4
			node_coll[tmp.start] = count;
			for (int i = 0; i<NUM; i++) {
    
    
				if (node_coll[i] = node_coll[tmp.end]) {
    
    
					node_coll[i] = node_coll[tmp.start];
				}
			}
		}
	}
}

参考资料:最小生成树的两种方法(Kruskal算法和Prim算法)
算法导论 23.2 Kruskal算法和Prim算法

总结:

1、kruskal(克鲁斯卡尔)算法为加边法,prim算法为加点法,记忆算法思路就好。

猜你喜欢

转载自blog.csdn.net/qq_33726635/article/details/106024969