最小生成树 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;}
文件中读数组的时候,读到-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个节点的有权无向连通图,寻 ...
随机推荐
- 利用WCF实现上传下载文件服务
使用WCF上传文件 在WCF没出现之前,我一直使用用WebService来上传文件,我不知道别人为什么要这么做,因为我们的文件服务器和网站后台和网站前台都不在同一个机器,操作人员觉 ...
- 20180821ImportContactFromExcel
Excel创建vcf文件,借助百度云助手导入Iphone6Plus Sub CreateContractList() Set Wb = Application.ThisWorkbook FilePat ...
- C#方式操作Cookie
1.设置cookie public static void SetCookie(string TokenValue) { HttpCookie tokencookie = new HttpCookie ...
- win10系统同时安装python2和python3
1.官网下载python2和python3版本 2.安装python3,勾上Add Python3.5 to PATH,自定义选择安装目录,安装,验证:WIN+R--->cmd,输入python ...
- Confluence 6 从一个模板中创建一个空间
Confluence 已经存储了一系列的模板,这些模板被称为 空间蓝图(space blueprints),这模板具有一些自定义的主页,边栏或者可能有蓝图页面或一些示例内容来帮助你开始使用 Confl ...
- CPU型号各个字母的含义
CPU 型号的含义 首先介绍 4 个数字的含义(以 i7-3540M) 第一位 3540M 中的 "3"代表:代, 3 表示第三代 第二位 3540M 中的 "5&quo ...
- 配置samba 服务器 共享Linux目录
配置samba 服务器 共享Linux目录 1.安装: yum install -y samba* 2.修改配置文件 vim /etc/samba/smb.conf [web] path = /usr ...
- python low版线程池
1.low版线程池设计思路:运用队列queue 将线程类名放入队列中,执行一个就拿一个出来import queueimport threading class ThreadPool(object): ...
- 【LeetCode】矩阵操作
1. 矩阵旋转 将 n × n 矩阵顺时针旋转 90°. 我的思路是 “ 从外到内一层一层旋转 ”. 一个 n × n 矩阵有 (n + 1) / 2 层,每层有 4 部分,将这 4 部分旋转. 顺时 ...
- 关于cf[转]
还不怎么熟悉cf呢.. 你应当知道的关于Codeforces的事情 Codeforces简称: cf(所以谈论cf的时候经常被误会成TX的那款游戏).网址: codeforces.com 这是一个俄国 ...