最小生成树 Prim算法 和 Kruskal算法,c++描述
body, table{font-family: 微软雅黑; font-size: 13.5pt}
table{border-collapse: collapse; border: solid gray; border-width: 2px 0 2px 0;}
th{border: 1px solid gray; padding: 4px; background-color: #DDD;}
td{border: 1px solid gray; padding: 4px;}
tr:nth-child(2n){background-color: #f8f8f8;}
//图(2,1)错了,正确的是18 |
文件中读数组的时候,读到-1就填入int类型能表示的最大值 执行完第一趟遍历,找到了一个最小边(0,1)=10,把1加入到生成树里面,1能得到一些新的边,此时就要更新vertex和weight
(1,2)=18,(1,6)=16,(1,8)=12
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/* Prim.cpp */
#include"Prim.h"
#include<iostream>
namespace meihao
{
void MiniSpanTree_Prim(const meihao::Graph& g)
{
//先获取图的顶点数用来建立相应的存储结构
int vertexNum = g.getGraphVertexNumber();
int* vertex = new int[vertexNum](); //初始化一个数组来存储最终的最小生成树的结点信息
//数组下标表示vi,对应的数组值表示vj vertex[vi] = vj,动态申请空间时初始化,最初vertex都是0
weight_vaule_type* weight = new weight_vaule_type[vertexNum]();
//weight数组用来存放边的权值,在算法运行过程中要用来比较
//weight中值为0,表示对应的下标表示的点已经在最小生成树中
//vi对应的weight[vi]就表示(vi,ji) = weight[vi],其中vj = vertex[vi]
//1、随便选取一个点开始求解最小生成树
vertex[0] = 0; //(0,0)=0,选取从0号结点开始
//2、从选取的第0个点开始初始化weight数组,相当于用邻接矩阵的第一行来初始化weight
for(int idx=0;idx!=vertexNum;++idx)
{
weight[idx] = g.getGraphEdgeWeight(0,idx);
}//初始vertex都是0,表示0到其他节点,刚好对应初始化后的weight
//weight[0]=0,vertex[0]=0,表示(0,0)=0->(vertex[0],0)=weight[0]
//3、weight数组存放了从0顶点到其他顶点的距离,开始选一个权值最小边(v0,vj),同时把顶点vj加入vertex中 vertex[vj] = v0; weight[vj] = 0;
for(int idx=0;idx!=vertexNum;++idx)
{
weight_vaule_type min = max_weight_value;
int newVertex = 0; //定义一个变量保存在一次遍历过程中找到的最小权值边的,初始值为0
for(int iidx=0;iidx!=vertexNum;++iidx)
{
if(0!=weight[iidx]&&
weight[iidx]<min) //weight[idx]=0,表示结点idx已经在我们最终要求的最小生成树中了
{
//找到一条权值相对min小的边
min = weight[iidx]; //更新min
newVertex = iidx; //记录结点,目前(0,iidx)边的权值最小
}
}
//输出边
//if(0!=newVertex) //vertex[0]=0,存放的是最开始初始化的,(0,0)指向自身,不在最小生成树中
cout<<"("<<vertex[newVertex]<<","<<newVertex<<")"<<" ";
//把一次遍历找到的newVertex加入到最小生成树中
weight[newVertex] = 0;
//这时候生成树多了一个结点,通过这个顶点又可以通过依附在这个点的边到达其他结点,所以这个时候要更新weight
for(int iiidx=0;iiidx!=vertexNum;++iiidx)
{
if(0!=weight[iiidx]&&
g.getGraphEdgeWeight(newVertex,iiidx)<weight[iiidx])
{
weight[iiidx] = g.getGraphEdgeWeight(newVertex,iiidx);
//weight更新了,vertex存放对应的两个顶点信息,所以这里要同步更新
vertex[iiidx] = newVertex; //(iiidx,newVertex) = weight[iiidx];
}
}
}//每次都能找出一个点,最终找到n个点,n-1条边,
}
};
|
/* Prim.cpp */ 根据上面的表,优化左边的算法,看起来逻辑更清晰
#include"Prim.h"
#include<iostream>
namespace meihao
{
void MiniSpanTree_Prim(const meihao::Graph& g)
{
int vertexNum = g.getGraphVertexNumber();
int* vertex = new int[vertexNum]; //这里可以直接写()全部初始化
int* weight = new int[vertexNum];
vertex[0] = 0;
weight[0] = 0;
for(int idx=1;idx!=vertexNum;++idx)
{
vertex[idx] = 0;
}
for(int idx=1;idx!=vertexNum;++idx)
{
weight[idx] = g.getGraphEdgeWeight(0,idx);
}
for(int idx=1;idx!=vertexNum;++idx)
{
weight_vaule_type min = max_weight_value;
int newVertex;
for(int iidx=1;iidx!=vertexNum;++iidx)
{
if(0!=weight[iidx]&&weight[iidx]<min)
{
min = weight[iidx];
newVertex = iidx; //相当于数组下标
}
}
//一趟遍历找到一条最小权值的边
cout<<"("<<vertex[newVertex]<<","<<newVertex<<")"<<" ";
//newVertex加入生成树,也就是修改weight
weight[newVertex] = 0;
//更新vertex和weight数组
for(int iiidx=1;iiidx!=vertexNum;++iiidx)
{
if(0!=weight[iiidx]&&g.getGraphEdgeWeight(newVertex,iiidx)<weight[iiidx])
{
weight[iiidx] = g.getGraphEdgeWeight(iiidx,newVertex);
vertex[iiidx] = newVertex;
}
}
}
}
};
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/* data.txt */从文件中读取数据初始化图的时候,如果是-1就用最大值替代
9
0 1 2 3 4 5 6 7 8
0 10 -1 -1 -1 11 -1 -1 -1
10 0 18 -1 -1 -1 16 -1 12
-1 -1 0 22 -1 -1 -1 -1 8
-1 -1 22 0 20 -1 -1 16 21
-1 -1 -1 20 0 26 -1 7 -1
11 -1 -1 -1 26 0 17 -1 -1
-1 16 -1 -1 -1 17 0 19 -1
-1 -1 -1 16 7 -1 19 0 -1
-1 12 8 21 -1 -1 -1 -1 0
/* testMain.txt */
#include"Graph.h"
#include"Prim.h"
#include"Kruskal.h"
#include<iostream>
using namespace std;
int main()
{
meihao::Graph g("data.txt");
cout<<"MiniSpanTree_Prim:"<<endl;
meihao::MiniSpanTree_Prim(g);
cout<<endl;
system("pause");
}
利用结构体实现->->
|
#include"Prim.cpp"
#include<iostream>
namespace meihao
{
typedef struct Arr
{
int vi; //顶点vi
weight_vaule_type weight; //(vi,vj)的权值
}node,*pNode;
//思路:
//从结点0开始,定义n-1个node的数组,分别赋值(0,1),(0,2)...
void MiniSpanTree_Prim(const meihao::Graph& g)
{
//获取顶点个数
int vertexNum = g.getGraphVertexNumber();
node* arr = new node[vertexNum]();
for(int idx=1;idx!=vertexNum;++idx)
{
arr[idx].vi = 0; //选取的初始结点0
arr[idx].weight = g.getGraphEdgeWeight(0,idx);
}
for(int idx=1;idx!=vertexNum;++idx)
{
weight_vaule_type min = max_weight_value;
int newVertex;
for(int iidx=1;iidx!=vertexNum;++iidx)
{
if(0!=arr[iidx].weight&&arr[iidx].weight<min)
{
min = arr[iidx].weight;
newVertex = iidx;
}
}
cout<<"("<<arr[newVertex].vi<<","<<newVertex<<")"<<" ";
arr[newVertex].weight = 0;
//更新数组
for(int iiidx=1;iiidx!=vertexNum;++iiidx)
{
if(0!=arr[iiidx].weight&&g.getGraphEdgeWeight(newVertex,iiidx)<arr[iiidx].weight)
{
arr[iiidx].vi = newVertex;
arr[iiidx].weight = g.getGraphEdgeWeight(newVertex,iiidx);
}
}
}
}
};
|
算法每次都选一个最小权值的边加入的生成树的集合中,在加入之前要判断是否会形成回路;所以算法先存储所有的边集数组,对其进行排序,如右图所示;最后每次选一个最小边加入最小生成树。 | 判断加入一条边是否会构成回路,就要利用一个数组(parent)来存放每个结点的父结点。初始全部为0
其中,parent[i] = j,表示i结点的父结点为j结点 ①加入第一条边(4,7)=7,parent[4]=0; parent[7]=0,4和7都是单独的一个根结点,加入边不会形成回路,默认一个加入规则,parent[4]=7,此时最小生成树有一条边,4→7,4的父结点是7。
②加入第二条边(2,8)=8,同上 2→8 4→7
③加入第三条边(0,1)=10, 0→1 2→8 4→7
④加入第四条边(0,5)=11,这里parent[0]=1,0的父结点1,parent[1]=0,1的父结点0;parent[5]=0,所以最后parent[1]=5。这是后的生成树0→1→5 2→8 4→7
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
⑤加入第五条边(1,,8)=12,parent[1]=5,parent[5]=0; parent[8]=0; 0→1→5→8 2→8 4→7 从上面的图可以看出,现在有两个顶点结合出现{0,1,5,8,2}和{4,7}
|
⑥加入第六条边(3,7)=16,parent[3]=0;parent[7]=0; 0→1→5→8 2→8 4→7 3→7
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
⑦加入第七条边(1,6)=16,parent[1]=5,parent[5]=8,parent[8]=0; parent[6]=0; 0→1→5→8→6 2→8 4→7 3→7
|
⑧加入第八条边(5,6)=17,parent[5]=8,parent[8]=6,parent[6]=0; parent[6]=0;同一个顶点,不能加入6←6指向自身了 0→1→5→8 2→8 4→7 3→7 ,如果加入边(5,6),5-8-6-6,这就是一个环了
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
⑨加入第九条边(1,2)=18,parent[1]=5,parent[5]=8,parent[8]=6,parent[6]=0; parent[2]=8,parent[8]=6,parent[6]=0; 0→1→5→8 2→8 4→7 3→7
|
⑩加入第十条边(6,7)=19,parent[6]=0; parent[7]=0; 0→1→5→8 2→8 4→7 3→7 6→7
parent[7] = 0,表示没有父结点,树中只要一个结点没有双亲结点 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
加入第10条边之后,此时黄色部分已经有8个,9个顶点的生成树只能有8条表,此后再加入新的边,都会构成回路
这种查找一直用到了并查集的思想。初始时把每个对象看作是一个单元素集合;然后依次按顺序读入联通边,将连通边中的两个元素合并,即找到父结点。 优化1、 还有另外一种做法,完全按照并查集的搜索合并来解决,我觉得合并有点多余,我这里的优化1就是借鉴了他的https://www.cnblogs.com/yoke/p/6697013.html
|
最终结果 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/* Kruskal.cpp */
#include"Kruskal.h"
#include<iostream>
#include<vector>
#include<algorithm>
namespace meihao
{
bool cmp(const edges& a,const edges& b)
{
return a.weight < b.weight; //从小到大排序
}
void readEgdesFromGraph(const meihao::Graph& g,vector<edges>& edgeArr)
{//无向图邻接矩阵都是对称的,只读取上三角即可
int vertexCnt = g.getGraphVertexNumber();
for(int idx=0;idx!=vertexCnt;++idx)
{
for(int iidx=idx;iidx!=vertexCnt;++iidx)
{
if(0!=g.getGraphEdgeWeight(idx,iidx)&&max_weight_value!=g.getGraphEdgeWeight(idx,iidx))
{
edges tmp;
tmp.begin = idx;
tmp.end = iidx;
tmp.weight = g.getGraphEdgeWeight(idx,iidx);
edgeArr.push_back(::move(tmp));
}
}
}
sort(edgeArr.begin(),edgeArr.end(),cmp); //从小到大排序
}
void MiniSpanTree_Kruskal(const meihao::Graph& g)
{
vector<edges> edgeArr; //边集数组
readEgdesFromGraph(g,edgeArr);
//定义parent数组,数组下标对应唯一的图结点,数组值对应小标结点的父结点,最小生成树就是一棵树
int vertexCnt = g.getGraphVertexNumber();
int* parent = new int[vertexCnt](); //初始化全部为0,parent[i] = 0,表示i结点没有父结点(只有一个根结点的树)
int edgeCnt = edgeArr.size(); //边集数组大小,也就是图中边的数量
for(int idx=0;idx!=edgeCnt;++idx)
{
int firstFather = find(parent,edgeArr[idx].begin);
int secondFather = find(parent,edgeArr[idx].end);
if(firstFather!=secondFather) //待加入的这条边的父结点相同,如果再把这条边加入,就会出现环。这里只能不等
{//这个过程就是一个找爹过程,最小生成树只能有一个根结点,如果待加入边对其两端的顶点去找爹找到相同的,这时候再加入这条边就出现环,∧->△
parent[firstFather] = secondFather; //加入该条边,(firstFather,secondFather),firstFather的父结点secondFather
//输出找到的边
cout<<"("<<edgeArr[idx].begin<<","<<edgeArr[idx].end<<")"<<" ";
}
}
cout<<endl;
}
//没有优化的find
//int find(int* parent,int vi)
//{
// while(parent[vi]>0) //vi结点有父结点
// {
// vi = parent[vi];
// }
// return vi;
//}
};
|
int find(int* parent,int vi)
{//优化1、
int viTmp = vi;
while(parent[vi]>0) //vi结点有父结点
{
vi = parent[vi]; //找父结点
}
while(vi!=viTmp)
{//vi有父结点,遍历,如果有父结点还有祖先结点,假设eg:0→1→5(0的父结1,1的父亲5) 变成 0→5,1→5
int tmp = parent[viTmp]; //暂存最初vi结点(0)的父结点(tmp=1)
parent[viTmp] = vi; //(parent[0]=5)
viTmp = tmp; //0变成1;
}
return vi;
}
/* Kruskal.h */
#ifndef __KRUSCAL_H__
#define __KRUSCAL_H__
#include"Graph.h"
namespace meihao
{
typedef struct EdgeSetArr //边集数组
{
int begin; //边起点
int end; //边终点
weight_vaule_type weight; //边权值
}edges;
void readEgdesFromGraph(const meihao::Graph& g); //从图中读出我们需要的边集数组
int find(int* parent,int vi);
void MiniSpanTree_Kruskal(const meihao::Graph& g);
};
#endif
/* maintext.cpp */ #include"Graph.h"
#include"Prim.h"
#include"Kruskal.h"
#include<iostream>
using namespace std;
int main()
{
meihao::Graph g("data.txt");
cout<<"MiniSpanTree_Prim:"<<endl;
meihao::MiniSpanTree_Prim(g);
cout<<endl<<endl;
cout<<"MiniSpanTree_Kruskal"<<endl;
meihao::MiniSpanTree_Kruskal(g);
cout<<endl;
system("pause");
}
|
边数较少可以用Kruskal,因为Kruskal算法每次查找权值最小的边。 边数较多可以用Prim,因为它是每次加一个顶点,对边数多的适用。
最小生成树 Prim算法 和 Kruskal算法,c++描述的更多相关文章
- 转载:最小生成树-Prim算法和Kruskal算法
本文摘自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html 最小生成树-Prim算法和Kruskal算法 Prim算 ...
- 最小生成树Prim算法和Kruskal算法
Prim算法(使用visited数组实现) Prim算法求最小生成树的时候和边数无关,和顶点树有关,所以适合求解稠密网的最小生成树. Prim算法的步骤包括: 1. 将一个图分为两部分,一部分归为点集 ...
- 最小生成树---Prim算法和Kruskal算法
Prim算法 1.概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (gra ...
- 最小生成树Prim算法和Kruskal算法(转)
(转自这位大佬的博客 http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html ) Prim算法 1.概览 普里姆算法(Pr ...
- 最小生成树——Prim算法和Kruskal算法
洛谷P3366 最小生成树板子题 这篇博客介绍两个算法:Prim算法和Kruskal算法,两个算法各有优劣 一般来说当图比较稀疏的时候,Kruskal算法比较快 而当图很密集,Prim算法就大显身手了 ...
- hdu1233 最小生成树Prim算法和Kruskal算法
Prim算法 时间复杂度:O(\(N^2\),N为结点数) 说明:先任意找一个点标记,然后每次找一条最短的两端分别为标记和未标记的边加进来,再把未标记的点标记上.即每次加入一条合法的最短的边,每次扩展 ...
- 最小生成树--Prim算法,基于优先队列的Prim算法,Kruskal算法,Boruvka算法,“等价类”UnionFind
最小支撑树树--Prim算法,基于优先队列的Prim算法,Kruskal算法,Boruvka算法,“等价类”UnionFind 最小支撑树树 前几节中介绍的算法都是针对无权图的,本节将介绍带权图的最小 ...
- 最小生成树之Prim算法,Kruskal算法
Prim算法 1 .概览 普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树.意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点(英语:Vertex (gr ...
- 最小生成树之Prim算法和Kruskal算法
最小生成树算法 一个连通图可能有多棵生成树,而最小生成树是一副连通加权无向图中一颗权值最小的生成树,它可以根据Prim算法和Kruskal算法得出,这两个算法分别从点和边的角度来解决. Prim算法 ...
- 最小生成树(Minimum Spanning Tree)——Prim算法与Kruskal算法+并查集
最小生成树——Minimum Spanning Tree,是图论中比较重要的模型,通常用于解决实际生活中的路径代价最小一类的问题.我们首先用通俗的语言解释它的定义: 对于有n个节点的有权无向连通图,寻 ...
随机推荐
- postMan用法
增加: 删除: 更改: 查询:
- 第 6 章 存储 - 040 - docker managed volume
docker managed volume 与 bind mount 在最大区别是不需要指定 mount 源,指明 mount point 就行了 通过 -v 告诉 docker 需要一个 data ...
- 第 3 章 镜像 - 019 - 使用公共 Registry
保存和分发镜像的最直接方法就是使用 Docker Hub.https://hub.docker.com/ Docker Hub 是 Docker 公司维护的公共 Registry.用户可以将自己的镜像 ...
- Create a Hadoop Build and Development Environment
Create a Hadoop Build and Development Environment http://vichargrave.com/create-a-hadoop-build-and-d ...
- chmod 没有x权限怎么办
解决方法1: # /lib64/ld-linux-x86-64.so.2 /bin/chmod 755 /bin/chmod //linux动态命令库 解决方法2:方法2提到的两种方法形 ...
- python记录_day23 正则表达式 re模块
一. 正则表达式 使用python的re模块之前应该对正则表达式有一定的了解 正则表达式是对字符串操作的一种逻辑公式.我们一般使用正则表达式对字符串进行匹配和过滤. 正则的优缺点: 优点:灵活, 功能 ...
- seaweedFS
那首先我们来分析一下seaweedfs是什么?seaweedfs是一个非常优秀的由 golang 开发的分布式存储开源项目.它是用来存储文件的系统,并且与使用的语言无关,使得文件储存在云端变得非常方便 ...
- 安卓——implements——OnClickListener
//实现底部的按钮激活监听 设置不同src class click implements OnClickListener{ @Override public void onClick(View v) ...
- bzoj-2038-莫队
2038: [2009国家集训队]小Z的袜子(hose) Time Limit: 20 Sec Memory Limit: 259 MBSubmit: 15784 Solved: 7164[Sub ...
- Leetcode 127 **
class Solution { public: int ladderLength(string beginWord, string endWord, vector<string>& ...