给定一个带权值的无向图,要求权值之和最小的生成树,常用的算法有Kruskal算法和Prim算法。这两个算法其实都是贪心思想的使用,但又能求出最优解。(代码借鉴http://blog.csdn.net/u014488381)

一.Kruskal算法

Kruskal算法的基本思想:先将所有边按权值从小到大排序,然后按顺序选取每条边,假如一条边的两个端点不在同一个集合中,就将这两个端点合并到同一个集合中;假如两个端点在同一个集合中,说明这两个端点已经连通了,就将当前这条边舍弃掉;当所有顶点都在同一个集合时,说明最小生成树已经形成。(写代码的时候会将所有边遍历一遍)

来看一个例子:

步骤:

(1)先根据权值把边排序:

AD 5

CE 5

DF 6

AB 7

BE 7

BC 8

EF 8

BD 9

EG 9

FG 11

(2)

选择AD这条边,将A、D加到同一个集合1中

选择CE这条边,将C、E加到同一个集合2中(不同于AD的集合)

选择DF这条边,由于D已经在集合1中,因此将F加入到集合1中,集合变为A、D、F

选择AB这条边,同理,集合1变为A、B、D、F

选择BE这条边,由于B在集合1中,E在集合2中,因此将两个集合合并,形成一个新的集合ABCDEF

由于E、F已经在同一集合中,舍弃掉BC这条边;同理舍弃掉EF、BD

选择EG这条边,此时所有元素都已经在同一集合中,最小生成树形成

象征性地舍弃掉FG这条边

实现代码如下:

#include <iostream>
#include <cstring>
#define MaxSize 20
using namespace std; struct Edge{
int begin;
int end;
int weight;
};
struct Graph{
char ver[MaxSize + ];
int edg[MaxSize][MaxSize];
}; void CreateGraph(Graph *g) {
int VertexNum;
char Ver;
int i = ;
cout << "输入图的顶点:" << endl;
while ((Ver = getchar()) != '\n') {
g->ver[i] = Ver;
i++;
}
g->ver[i] = '\0';
VertexNum = strlen(g->ver);
cout << "输入相应的邻接矩阵" << endl;
for (int i = ; i < VertexNum; i++) {
for (int j = ; j < VertexNum; j++) {
cin >> g->edg[i][j]; //输入0则为没有边相连啊
}
}
} void PrintGraph(Graph g) {
int VertexNum = strlen(g.ver);
cout << "图的顶点为:" << endl;
for (int i = ; i < VertexNum; i++) {
cout << g.ver[i] << " ";
}
cout << endl;
cout << "图的邻接矩阵为:" << endl;
for (int i = ; i < VertexNum; i++) {
for (int j = ; j < VertexNum; j++) {
cout << g.edg[i][j] << " ";
}
cout << endl;
}
} int getVerNum(Graph g) {
return strlen(g.ver);
} int getEdgeNum(Graph g) {
int res = ;
int VertexNum = getVerNum(g);
for (int i = ; i < VertexNum; i++) {
//邻接矩阵对称,计算上三角元素和即可
for (int j = i + /*假设没有自己指向自己的*/; j < VertexNum; j++) {
if (g.edg[i][j] != ) res++;
}
}
return res;
} Edge *CreateEdges(Graph g) {
int k = ;
int EdgeNum = getEdgeNum(g);
int VertexNum = getVerNum(g);
Edge * p = new Edge[EdgeNum];
for (int i = ; i < VertexNum; i++) {
for (int j = i; j < VertexNum; j++) {
if (g.edg[i][j] != ) {
p[k].begin = i;
p[k].end = j;
p[k].weight = g.edg[i][j];
k++;
}
}
}
for (int i = ; i < EdgeNum - ; i++) {
Edge minWeightEdge = p[i];
for (int j = i + ; j < EdgeNum; j++) {
if (minWeightEdge.weight > p[j].weight) {
Edge temp = minWeightEdge;
minWeightEdge = p[j];
p[j] = temp;
}
}
p[i] = minWeightEdge;
}
return p;
} void Kruskal(Graph g) {
int VertexNum = getVerNum(g);
int EdgeNum = getEdgeNum(g);
Edge *p = CreateEdges(g);
int *index = new int[VertexNum]; //index数组,其元素为连通分量的编号,index[i]==index[j]表示编号为i和j的顶点在同一连通分量中
int *MSTEdge = new int[VertexNum - ]; //用来存储已确定的最小生成树的**边的编号**,共VertexNum-1条边
int k = ;
int WeightSum = ;
int IndexBegin, IndexEnd;
for (int i = ; i < VertexNum; i++) {
index[i] = -; //初始化所有index为-1
}
for (int i = ; i < VertexNum - ; i++) {
for (int j = ; j < EdgeNum; j++) {
if ( !(index[p[j].begin] >= && index[p[j].end] >= && index[p[j].begin] == index[p[j].end] /*若成立表明p[j].begin和p[j].end已在同一连通块中(且可相互到达,废话)*/) ) {
MSTEdge[i] = j;
if (index[p[j].begin] == - && index[p[j].end] == -) {
index[p[j].begin] = index[p[j].end] = i;
}
else if (index[p[j].begin] == - && index[p[j].end] >= ) {
index[p[j].begin] = i;
IndexEnd = index[p[j].end];
for (int n = ; n < VertexNum; n++) {
if (index[n] == IndexEnd) {
index[n] == i;
}
}
}
else if (index[p[j].begin] >= && index[p[j].end] == -) {
index[p[j].end] = i;
IndexBegin = index[p[j].begin];
/*将连通分量合并(或者说将没加入连通分量的顶点加进去,然后将原来连通分量的值改了)*/
for (int n = ; n < VertexNum; n++) {
if (index[n] == IndexBegin) {
index[n] == i;
}
}
}
else {
IndexBegin = index[p[j].begin];
IndexEnd = index[p[j].end];
for (int n = ; n < VertexNum; n++) {
if (index[n] == IndexBegin || index[n] == IndexEnd) {
index[n] = i;
}
}
}
break;
}
}
}
cout << "MST的边为:" << endl;
for (int i = ; i < VertexNum - ; i++) {
cout << g.ver[p[MSTEdge[i]].begin] << "--" << g.ver[p[MSTEdge[i]].end] << endl;
WeightSum += p[MSTEdge[i]].weight;
}
cout << "MST的权值为:" << WeightSum << endl;
}

二.Prim算法(代码还没理解)

Prim算法的基本思想:设置两个存放顶点的集合,第一个集合初始化为空,第二个集合初始化为一个包含所有顶点的集合。首先把图中的任意一个顶点a放进第一个集合,然后在第二个集合中找到一个顶点b,使b到第一个集合中的任意一点的权值最小,然后把b从第二个集合移到第一个集合。接着在第二个集合中找到顶点c,使c到a或b的权值比到第二个集合中的其他任何顶点到a或b的权值都要小,然后把c从第二个集合移到第一个集合中。以此类推,当第二个集合中的顶点全部移到第一个集合时,最小生成树产生。

以上面的图再次作为例子:

设第一个集合为V,第二个集合为U。

V={A}, U={B, C, D, E, F, G}

(1)A连接了两个顶点,B和D,AB权值为7,AD权值为5,选择权值小的一条边和相应的顶点D,将D加入集合V中。V={A, D}, U={B, C, E, F, G}

(2)观察包含V中的元素A和D的边,AB权值为7,BD权值为9,DE权值为15,DF权值为6,将F加入V中。V={A, D, F}, U={B, C, E, G}

(3)依次将B(AB)、E(BE)、C(CE)、G(EG)加入到集合V中。

(4)最小生成树的边包括:AD DF AB BE CE EG,problem solved

实现代码如下:

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
#define MaxSize 20
struct Graph{
char ver[MaxSize + ];
int edg[MaxSize][MaxSize];
}; void CreateGraph(Graph *g) {
int VertexNum;
char Ver;
int i = ;
cout << "输入图的顶点:" << endl;
while ((Ver = getchar()) != '\n') {
g->ver[i] = Ver;
i++;
}
g->ver[i] = '\0';
VertexNum = strlen(g->ver);
cout << "输入相应的邻接矩阵" << endl;
for (int i = ; i < VertexNum; i++) {
for (int j = ; j < VertexNum; j++) {
cin >> g->edg[i][j]; //输入0则为没有边相连啊
}
}
} void PrintGraph(Graph g) {
int VertexNum = strlen(g.ver);
cout << "图的顶点为:" << endl;
for (int i = ; i < VertexNum; i++) {
cout << g.ver[i] << " ";
}
cout << endl;
cout << "图的邻接矩阵为:" << endl;
for (int i = ; i < VertexNum; i++) {
for (int j = ; j < VertexNum; j++) {
cout << g.edg[i][j] << " ";
}
cout << endl;
}
} int getVerNum(Graph g) {
return strlen(g.ver);
} //将不邻接的顶点之间的权值设为
void SetWeight(Graph *g) {
for (int i = ; i < getVerNum(*g); i++) {
for (int j = ; j < getVerNum(*g); j++) {
if (g->edg[i][j] == ) {
g->edg[i][j] = INT_MAX;
}
}
}
} void Prim(Graph g, int *parent) {
//V为所有顶点的集合,U为最小生成树的节点集合
int lowcost[MaxSize]; //lowcost[k]保存着编号为k的顶点到U中所有顶点的最小权值
int closest[MaxSize]; //closest[k]保存着U到V-U中编号为k的顶点权值最小的顶点的编号
int used[MaxSize];
int min;
int VertexNum = getVerNum(g);
for (int i = ; i < VertexNum; i++) {
lowcost[i] = g.edg[][i];
closest[i] = ;
used[i] = ;
parent[i] = -;
}
used[] = ;
for (int i = ; i < VertexNum - ; i++) {
int j = ;
min = INT_MAX;
for (int k = ; k < VertexNum; k++) { //找到V-U中的与U中顶点组成的最小权值的边的顶点编号
if (used[k] == && lowcost[k] < min) {
min = lowcost[k];
j = k;
}
}
parent[j] = closest[j];
used[j] = ;
for (int k = ; k < VertexNum; k++) { //由于j顶点加入U中,更新lowcost和closest数组中的元素,检测V-U中的顶点到j顶点的权值是否比j加入U之前的lowcost数组的元素小
if (used[k] == && g.edg[j][k] < lowcost[k]) {
lowcost[k] = g.edg[j][k];
closest[k] = j;
}
}
}
} void PrintMST(Graph g, int *parent) {
int VertexNum = getVerNum(g);
int weight = ;
cout << "MST的边为:" << endl;
for (int i = ; i < VertexNum; i++) {
cout << g.ver[parent[i]] << "--" << g.ver[i] << endl;
weight += g.edg[parent[i]][i];
}
cout << "MST的权值为" << weight << endl;
} int main() {
Graph g;
int parent[];
CreateGraph(&g);
PrintGraph(g);
SetWeight(&g);
Prim(g, parent);
PrintMST(g, parent);
return ;
}

三.Kruskal算法和Prim算法的适用情况

Kruskal算法适用于边稀疏的情况(要进行排序),Prim算法适用于边稠密的情况。

求最小生成树——Kruskal算法的更多相关文章

  1. SWUST OJ 1075 求最小生成树(Prim算法)

    求最小生成树(Prim算法) 我对提示代码做了简要分析,提示代码大致写了以下几个内容 给了几个基础的工具,邻接表记录图的一个的结构体,记录Prim算法中最近的边的结构体,记录目标边的结构体(始末点,值 ...

  2. 【转】最小生成树——Kruskal算法

    [转]最小生成树--Kruskal算法 标签(空格分隔): 算法 本文是转载,原文在最小生成树-Prim算法和Kruskal算法,因为复试的时候只用到Kruskal算法即可,故这里不再涉及Prim算法 ...

  3. 求最小生成树——Kruskal算法和Prim算法

    给定一个带权值的无向图,要求权值之和最小的生成树,常用的算法有Kruskal算法和Prim算法.这两个算法其实都是贪心思想的使用,但又能求出最优解.(代码借鉴http://blog.csdn.net/ ...

  4. 最小生成树 kruskal算法&prim算法

    (先更新到这,后面有时间再补,嘤嘤嘤) 今天给大家简单的讲一下最小生成树的问题吧!(ps:本人目前还比较菜,所以最小生成树最后的结果只能输出最小的权值,不能打印最小生成树的路径) 本Tianc在刚学的 ...

  5. 数据结构之最小生成树Kruskal算法

    1. 克鲁斯卡算法介绍 克鲁斯卡尔(Kruskal)算法,是用来求加权连通图的最小生成树的算法. 基本思想:按照权值从小到大的顺序选择n-1条边,并保证这n-1条边不构成回路. 具体做法:首先构造一个 ...

  6. 数据结构:最小生成树--Kruskal算法

    Kruskal算法 Kruskal算法 求解最小生成树的还有一种常见算法是Kruskal算法.它比Prim算法更直观.从直观上看,Kruskal算法的做法是:每次都从剩余边中选取权值最小的,当然,这条 ...

  7. 图的最小生成树——Kruskal算法

    Kruskal算法 图的最小生成树的算法之一,运用并查集思想来求出最小生成树. 基本思路就是把所有边从小到大排序,依次遍历这些边.如果这条边所连接的两个点在一个连通块里,遍历下一条边,如果不在,就把这 ...

  8. 【一个蒟蒻的挣扎】最小生成树—Kruskal算法

    济南集训第五天的东西,这篇可能有点讲不明白提前抱歉(我把笔记忘到别的地方了 最小生成树 概念:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的 ...

  9. 最小生成树Kruskal算法(1)

    概念 一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边. [1] 最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆) ...

随机推荐

  1. 201521123106 《Java程序设计》第13周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  2. Java 最常用类(前100名)来自一万个开源项目

    大部分的 Java 软件开发都会使用到各种不同的库.近日我们从一万个开源的 Java 项目中进行分析,从中提取出最常用的 Java 类,这些类有来自于 Java 的标准库,也有第三方库.每个类在同一个 ...

  3. Android之View绘制流程开胃菜---setContentView(...)详细分析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 1 为什么要分析setContentView方法 作为安卓开发者相信大部分都有意或者无意看过如下图示:PhoneWindow,DecorView这些 ...

  4. MyBatis框架(一)

    MyBatis介绍: MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为 ...

  5. MySQL binlog 的恢复操作

     测试出有个问题:mysqlbinlog 不加任何参数 恢复整个binlog 日志文件发现里面有这个操作 SET @@SESSION.GTID_NEXT 的操作,  如果需要恢复文件的时候就需要把他过 ...

  6. django的admin或者应用中使用KindEditor富文本编辑器

    由于django后台管理没有富文本编辑器,看着好丑,展示出来的页面不美观,无法做到所见即所得的编辑方式,所以我们需要引入第三方富文本编辑器. 之前找了好多文档已经博客才把这个功能做出来,有些博客虽然写 ...

  7. JS(三)

    上周介绍了JS中两个比较重要的东西,循环和函数,这周再给大家介绍一下BOM和DOM 一.BOM 1.首先来说一下什么是BOM,BOM即浏览器对象模型,说白一点就是与浏览器进行的交互的对象模型. 2.B ...

  8. JS(二)

    上周给大家介绍了一下JS基础中一点东西,今天给大家介绍一下JS基础中一个重要部分,循环和函数. 04-JS中的循环结构 一.[循环结构的步骤] 1.首先要先声明循环变量. 2.判断循环条件 3.执行循 ...

  9. Tensorflow之卷积神经网络(CNN)

    前馈神经网络的弊端 前一篇文章介绍过MNIST,是采用的前馈神经网络的结构,这种结构有一个很大的弊端,就是提供的样本必须面面俱到,否则就容易出现预测失败.如下图: 同样是在一个图片中找圆形,如果左边为 ...

  10. linux kill 命令

    kill 命令的用途 kill 命令很容易让人产生误解,以为它仅仅就是用来杀死进程的.我们来看一下 man page 对它的解释:kill - send a signal to a process. ...