距离上一次分享最小生成树算法,已经过去1个多月。今天,小编重新来分享下另外一个有关的最小生成树算法,也就是“克鲁斯卡尔(Kruskal)算法”。
对于任意一个连通网的最小生成树来说,在要求总的权值最小的情况下,最直接的想法就是将连通网中的所有边按照权值大小进行升序排序,从小到大依次选择。
由于最小生成树本身是一棵生成树,所以需要时刻满足以下两点:
(1)生成树中任意顶点之间有且仅有一条通路,也就是说,生成树中不能存在回路;
(2)对于具有 n 个顶点的连通网,其生成树中只能有 n-1 条边,这 n-1 条边连通着 n 个顶点。
连接 n 个顶点在不产生回路的情况下,只需要 n-1 条边。
所以克鲁斯卡尔算法的具体思路是:
将所有边按照权值的大小进行升序排序,然后从小到大一一判断,条件为:如果这个边不会与之前选择的所有边组成回路,就可以作为最小生成树的一部分;反之,舍去。直到具有 n 个顶点的连通网筛选出来 n-1 条边为止。筛选出来的边和所有的顶点构成此连通网的最小生成树。
判断是否会产生回路的方法为:
在初始状态下给每个顶点赋予不同的标记,对于遍历过程的每条边,其都有两个顶点,判断这两个顶点的标记是否一致,如果一致,说明它们本身就处在一棵树中,如果继续连接就会产生回路;如果不一致,说明它们之间还没有任何关系,可以连接。
我们直接看例子:
首先,我们设置一个结构体类,用来存储边的相关信息。
template
class EData
{
public:
M start; // 边的起点
M end; // 边的终点
N weight; // 边的权重
};
通过:
EData *edges;
edges = new EData[numEdges];
新建一个结构体数组edges,将有关边的信息存储进来,得到下表:
按照权值进行排序,重新得到下表:
使用该算法找到最小生成树时。需要使用到“并查集”的知识。
我们新建一个数组:
int vends[numEdges];
for(int i=0;i
进行初始化,刚开始默认每个顶点的老大都是它自己。
当然后续这些顶点的老大会更新,那如何查找它们的新的老大呢,这就要使用下面的函数:
template
int GraphMatrix::getEnd(int vends[], int i)
{
if(vends[i]!=i)
{
return getEnd(vends,vends[i]);
}else
{
return i;
}
}
当然,如果两个顶点没有相同的老大,那我们可以把这两个团体并起来:
template
void GraphMatrix::unionn(int vends[],int x,int y)
{
vends[x] = y;
}
那接下来我们就以上图为例子,将算法流程走一遍。
edges数组里的边权值按从小到大排序,我们新建一个数组来存储找到的最小路径的边的信息:
EData rets[DefaultVertices];
1、我们先取出edges[0],可以得到edges[0].start=A,edges[0].end=B,edges[0].weight=4,通过顶点表可知A的位置为0,B的位置为1。我们分别查找这两个点的老大,getEnd(0)=0,getEnd(1)=1,老大不同,因此可以合并,使得vends[0]=1;同时rets[0]=edges[0];
2、取出edges[1],可以得到edges[1].start=C,edges[0].end=D,edges[0].weight=5,通过顶点表可知C的位置为2,D的位置为3。我们分别查找这两个点的老大,getEnd(2)=2,getEnd(3)=3,老大不同,因此可以合并,使得vends[2]=3;同时rets[1]=edges[1];
3、取出edges[2],可以得到edges[2].start=E,edges[2].end=G,edges[2].weight=6,通过顶点表可知E的位置为4,G的位置为6。我们分别查找这两个点的老大,getEnd(4)=4,getEnd(6)=6,老大不同,因此可以合并,使得vends[4]=6;同时rets[2]=edges[2];
4、取出edges[3],可以得到edges[3].start=F,edges[3].end=H,edges[3].weight=8,通过顶点表可知F的位置为5,H的位置为7。我们分别查找这两个点的老大,getEnd(5)=5,getEnd(7)=7,老大不同,因此可以合并,使得vends[5]=7;同时rets[3]=edges[3];
5、取出edges[4],可以得到edges[4].start=A,edges[4].end=F,edges[4].weight=10,通过顶点表可知A的位置为0,F的位置为5。我们分别查找这两个点的老大,getEnd(0)=1,getEnd(5)=7,老大不同,因此可以合并,使得vends[1]=7;同时rets[4]=edges[4];
6、取出edges[5],可以得到edges[5].start=B,edges[5].end=G,edges[5].weight=11,通过顶点表可知B的位置为1,G的位置为6。我们分别查找这两个点的老大,getEnd(1)=7,getEnd(6)=6,老大不同,因此可以合并,使得vends[7]=6;同时rets[5]=edges[5];
7、取出edges[6],可以得到edges[6].start=C,edges[6].end=E,edges[6].weight=15,通过顶点表可知C的位置为2,E的位置为4。我们分别查找这两个点的老大,getEnd(2)=3,getEnd(4)=6,老大不同,因此可以合并,使得vends[3]=6;同时rets[6]=edges[6];
8、取出edges[7],可以得到edges[7].start=G,edges[7].end=H,edges[7].weight=17,通过顶点表可知G的位置为6,H的位置为7。我们分别查找这两个点的老大,getEnd(6)=6,getEnd(7)=6,老大相同,处于同一个团体,意味着形成环路,故这条边不加入数组rets中。
9、取出edges[8],可以得到edges[8].start=A,edges[8].end=D,edges[8].weight=20,通过顶点表可知A的位置为0,D的位置为3。我们分别查找这两个点的老大,getEnd(0)=6,getEnd(3)=6,老大相同,处于同一个团体,意味着形成环路,故这条边不加入数组rets中。
此时,所有边都遍历了一遍,因此算法的查找过程结束,接下来就是将找到的最小生成树打印出来而已:
for (i = 0; i < index; i++)
length += rets[i].weight;
cout << "Kruskal=" << length << ": ";
for (i = 0; i < index; i++)
cout << "(" << rets[i].start << "," << rets[i].end << ") ";
采用此方法得到最小生成树的路径和以及具体信息。
由于小编的代码采用了模板,因此可能直接看文章有点难理解。这里小编没有优化算法,进行路径压缩,各位小伙伴们就自己动动手实现下。小编将完整的代码提供如下:
#include
using namespace std;
const int DefaultVertices = 30; // 默认最大顶点数
template
class EData
{
public:
M start; // 边的起点
M end; // 边的终点
N weight; // 边的权重
public:
EData(){}
EData(M s, M e, N w):start(s),end(e),weight(w){}
};
template
class GraphMatrix {
public:
const E maxWeight = 100000; // 代表无穷大的值(=∞)
GraphMatrix(int sz=DefaultVertices); // 构造函数
~GraphMatrix(); // 析构函数
void inputGraph(); // 创建基于邻接矩阵的图
void outputGraph(); // 输出图的所有顶点和边信息
T getValue(int i); // 取顶点i的值,i不合理返回0
E getWeight(int v1, int v2); // 取边(v1, v2)上的权值
int getFirstNeighbor(int v); // 取顶点v的第一个邻接顶点
int getNextNeighbor(int v, int w); // 取v的邻接顶点w的下一个邻接顶点
bool insertVertex(const T& vertex); // 插入顶点vertice
bool insertEdge(int v1, int v2, E cost); // 插入边(v1, v2)权值为cost,有向图
bool insertUEdge(int v1, int v2, E cost); // 插入边(v1, v2)权值为cost,无向图
bool removeVertex(int v); // 删去顶点v和所有与它相关联的边
bool removeEdge(int v1, int v2); // 在图中删去边(v1, v2)
int getVertexPos(T vertex); // 给出顶点vertice在图中的位置
void DFS();//深度度优先搜索
void DFS1(int i, int *visited);
void BFS();//广度优先搜索
void Dijkstra(int k);//Dijkstra最短路径算法
void Prim(char x); //Prim(普利姆算法)
int getPosition(T ch); // 返回ch在矩阵中的位置
EData* getEdges(); // 获取图中的边
void sortEdges(EData* edges, int elen); // 对边按照权值大小进行排序(由小到大)
int getEnd(int vends[], int i); // //找集体老大,并查集的一部分 ,获取i的终点
void unionn(int vends[],int x,int y);//加入团体,并查集的一部分
void kruskal(); // 克鲁斯卡尔(Kruskal)最小生成树
private:
int maxVertices; // 图中最大的顶点数
int numEdges; // 当前边数
int numVertices; // 当前顶点数
T *VerticesList; // 顶点表
E **Edge; // 邻接矩阵
};
// 构造函数
template
GraphMatrix::GraphMatrix(int sz) {
int i, j;
maxVertices = sz;
numVertices = 0;
numEdges = 0;
VerticesList = new T[maxVertices]; // 创建顶点表数组
Edge = new E*[maxVertices]; // 创建邻接矩阵数组
for(i = 0; i < maxVertices; i++)
Edge[i] = new E[maxVertices];
for(i = 0; i < maxVertices; i++) { // 邻接矩阵初始化
for(j = 0; j < maxVertices; j++)
{
if(i == j) // 矩阵对角处,即为同一顶点
Edge[i][j] = 0;
else // 不是同一顶点的,即两顶点一开始没有边相连,为无穷大∞
Edge[i][j] = maxWeight;
}
}
}
// 析构函数
template
GraphMatrix::~GraphMatrix() {
delete []VerticesList; // 释放动态分配的空间
delete []Edge;
}
// 创建基于邻接矩阵的图
template
void GraphMatrix::inputGraph() {
int i, j, k,h;
int n, m; // 要输入的顶点数和边数
T e1, e2; // 边的两端顶点
E weight; // 边对应的权值
cout << "请输入顶点数和边数:" << endl;
cin >> n >> m;
cout << "请输入顶点:" << endl;
for(i = 0; i < n; i++) { // 建立顶点表数据
cin >> e1;
insertVertex(e1); // 插入
}
cout<<"有向图选0,无向图选1:"<>h;
cout << "请输入边的两端顶点和权值:" << endl;
i = 0;
while(i < m){ // 输入边
cin >> e1 >> e2 >> weight; // 输入端点信息
j = getVertexPos(e1); // 查顶点号
k = getVertexPos(e2);
if(j == -1 || k == -1)
cout << "边两端点信息有误,重新输入!" << endl;
else {
if(h==1)
insertUEdge(j, k, weight);
else if(h==0)
insertEdge(j, k, weight);
else
cout<<"输入错误";
i++;
}
} // for结束
}
// 输出图的所有顶点和边信息
template
void GraphMatrix::outputGraph() {
int i, j, n, m;
T e1, e2;
E w;
n = numVertices;
m = numEdges;
cout << "顶点数为:" << n << ",边数为:" << m << endl;
for(i = 0; i < n; i++) {
for(j = i+1; j < n; j++) {
w = getWeight(i, j); // 取边上权值
if(w > 0 && w < maxWeight) { // 有效,即这两顶点存在边
e1 = getValue(i);
e2 = getValue(j);
cout << "(" << e1 << "," << e2 << "," << w << ")" << endl;
}
}
} // for
}
// 给出顶点vertice在图中的位置
template
int GraphMatrix::getVertexPos(T vertex) {
for(int i = 0; i < numVertices; i++)
if(VerticesList[i] == vertex)
return i;
return -1;
}
// 取顶点i的值,i不合理返回NULL
template
T GraphMatrix::getValue(int i) {
if(i >= 0 && i < numVertices)
return VerticesList[i];
return NULL;
}
// 取边(v1, v2)上的权值
template
E GraphMatrix::getWeight(int v1, int v2) {
if(v1 != -1 && v2 != -1) // 存在这两个顶点
return Edge[v1][v2];
return 0;
}
// 取顶点v的第一个邻接顶点
template
int GraphMatrix::getFirstNeighbor(int v) {
if(v != -1) {
for(int col = 0; col < numVertices; col++)
if(Edge[v][col] > 0 && Edge[v][col]
int GraphMatrix::getNextNeighbor(int v, int w) {
if(v != -1 && w != -1) {
for(int col = w+1; col < numVertices; col++) {
if(Edge[v][col] > 0 && Edge[v][col] < maxWeight)
return col;
}
}
return -1;
}
// 插入顶点vertice
template
bool GraphMatrix::insertVertex(const T& vertex) {
if(numVertices == maxVertices) // 顶点表满
return false;
VerticesList[numVertices++] = vertex;
return true;
}
// 插入边(v1, v2)权值为cost
template
bool GraphMatrix::insertEdge(int v1, int v2, E cost) {
if(v1 > -1 && v1 < numVertices && v2 > -1 && v2 < numVertices && Edge[v1][v2] == maxWeight)
{ // 顶点v1,v2都存在,并且v1,v2没有边
Edge[v1][v2]=cost;
numEdges++;
return true;
}
return false;
}
// 插入边(v1, v2)权值为cost
template
bool GraphMatrix::insertUEdge(int v1, int v2, E cost) {
if(v1 > -1 && v1 < numVertices && v2 > -1 && v2 < numVertices && Edge[v1][v2] == maxWeight)
{ // 顶点v1,v2都存在,并且v1,v2没有边
Edge[v1][v2] = Edge[v2][v1] = cost;
numEdges++;
return true;
}
return false;
}
// 删去顶点v和所有与它相关联的边
template
bool GraphMatrix::removeVertex(int v) {
if(v < 0 && v > numVertices) // v不在图中,不删除
return false;
if(numVertices == 1) // 只剩一个顶点,不删除
return false;
int i, j;
VerticesList[v] = VerticesList[numVertices-1]; // 用最后一个顶点替代当前要删的顶点
// 删除与v相关联边数
for(i = 0; i < numVertices; i++) {
if(Edge[i][v] > 0 && Edge[i][v] < maxWeight)
numEdges--;
}
// 用最后一列,填补第v列
for(i = 0; i < numVertices; i++)
Edge[i][v] = Edge[i][numVertices-1];
numVertices--; // 顶点数减1
// 用最后一行,填补第v行
for(j = 0; j < numVertices; j++)
Edge[v][j] = Edge[numVertices][j];
return true;
}
// 在图中删去边(v1, v2)
template
bool GraphMatrix::removeEdge(int v1, int v2) {
if(v1 > -1 && v1 < numVertices && v2 > -1 && v2 < numVertices && Edge[v1][v2] < maxWeight) {
Edge[v1][v2] = Edge[v2][v1] = maxWeight;
numEdges--; // 边数减1
return true;
}
return false;
}
//
template
void GraphMatrix::DFS() {
int i;
int visited[30]; // 顶点访问标记
// 初始化所有顶点都没有被访问
for (i = 0; i < numVertices; i++)
visited[i] = 0;
cout << "DFS: ";
for (i = 0; i < numVertices; i++)
{
//printf("\n== LOOP(%d)\n", i);
if (!visited[i])
DFS1(i, visited);
}
cout << endl;
}
template
void GraphMatrix::DFS1(int i, int *visited) {
int w;
visited[i] = 1;
cout << VerticesList[i] << " ";
// 遍历该顶点的所有邻接顶点。若是没有访问过,那么继续往下走
for (w = getFirstNeighbor(i); w >= 0; w = getNextNeighbor(i, w))
{
if (!visited[w])
DFS1(w, visited);
}
}
template
void GraphMatrix::BFS() {
int head = 0;
int rear = 0;
int queue[DefaultVertices]; // 辅组队列
int visited[DefaultVertices]; // 顶点访问标记
int i, j, k;
for (i = 0; i < numVertices; i++)
visited[i] = 0;
cout << "BFS: ";
for (i = 0; i < numVertices; i++)
{
if (!visited[i])
{
visited[i] = 1;
cout <= 0; k = getNextNeighbor(j, k)) //k是为访问的邻接顶点
{
if (!visited[k])
{
visited[k] = 1;
cout <
void GraphMatrix::Dijkstra(int k)
{
int* final = new int[numVertices];
int* path = new int[numVertices];
int* distance = new int[numVertices];
int i;
// 初始化结点
for (i = 0; i < numVertices; i++) {
path[i] = -1;
final[i] = 0;
distance[i] = maxWeight;
}
distance[k] = 0; // 初始化源点
for (i = 0; i < numVertices; i++) { // 寻找不同长度的最短路径
int min = maxWeight; // 当前所知里顶点j的最短距离
int index = -1; // 最短距离对应的下标
int j;
for (j = 0; j < numVertices; j++) {
if (!final[j]) { // j在V-S中
if (distance[j] < min) {
min = distance[j];
index = j;
}
}
}// 寻找最短路径
if (index == -1) break;
final[index] = 1; // 离j最近的顶点Index并入S,第一次一定是v0
for (j = 0; j < numVertices; j++) { // 更新距离矩阵,一定要判断是否达,即arc[][]!=OO,否则会整数溢出
if (!final[j] && Edge[index][j] != INT_MAX && (min + Edge[index][j] < distance[j])) {
distance[j] = min +Edge[index][j];
path[j] = index;
}
}
}
for (i = 0; i < numVertices; i++)
{
cout<
void GraphMatrix::Prim(char x)//普利姆算法(参数:起点(即第一个生成的点,可随便取))
{
int lowcost[DefaultVertices], closest[DefaultVertices], i, min, j, k;
int v=getVertexPos(x);
/***初始化lowcost数组,closest数组(即从起点开始设置lowcost数组,closest数组相应的值,以便后续生成使用)***/
for (i = 0; i < numVertices; i++)//赋初值,即将closest数组都赋为第一个节点v,lowcost数组赋为第一个节点v到各节点的权重
{
closest[i] = v;
lowcost[i] = Edge[v][i];//Edge[v][i]的值指的是节点v到i节点的权重
}
/**********************************开始生成其他的节点*********************************/
for (i = 1; i < numVertices; i++)//接下来找剩下的n-1个节点(g.n是图的节点个数)
{
/*****找到一个节点,该节点到已选节点中的某一个节点的权值是当前最小的*****/
min = maxWeight;//INF表示正无穷(每查找一个节点,min都会重新更新为INF,以便获取当前最小权重的节点)
for (j = 0; j < numVertices; j++)//遍历所有节点
{
if (lowcost[j] != 0 && lowcost[j] < min)//若该节点还未被选且权值小于之前遍历所得到的最小值
{
min = lowcost[j];//更新min的值
k = j;//记录当前最小权重的节点的编号
}
}
/****************输出被连接节点与连接节点,以及它们的权值***************/
printf("边(%c,%c)权为:%d\n", VerticesList[closest[k]],VerticesList[k], min);
/***********更新lowcost数组,closest数组,以便生成下一个节点************/
lowcost[k] = 0;//表明k节点已被选了(作标记)
//选中一个节点完成连接之后,作数组相应的调整
for (j = 0; j =0,而已选节点满足lowcost[j] == 0,则已选节点j是不满足Edge[k][j] < lowcost[j],则会被跳过
*/
if (Edge[k][j] != 0 && Edge[k][j] < lowcost[j])
{
//更新lowcost数组,closest数组
lowcost[j] = Edge[k][j];//更新权重,使其当前最小
closest[j] = k;//进入到该if语句里,说明刚选的节点k与当前节点j有更小的权重,则closest[j]的被连接节点需作修改为k
}
}
}
}
template
int GraphMatrix::getPosition(T ch)
{
int i;
for(i=0; i
EData* GraphMatrix::getEdges()
{
int i,j;
int index=0;
EData *edges;
edges = new EData[numEdges];
for (i=0; i < numVertices; i++)
{
for (j=i+1; j < numVertices; j++)
{
if (Edge[i][j]!=maxWeight)
{
edges[index].start = VerticesList[i];
edges[index].end = VerticesList[j];
edges[index].weight = Edge[i][j];
index++;
}
}
}
cout<<"edges数组未排序:"<"<
void GraphMatrix::sortEdges(EData* edges, int elen)
{
int i,j;
for (i=0; i edges[j].weight)
{
// 交换"边i"和"边j"
swap(edges[i], edges[j]);
}
}
}
cout<<"edges数组排序:"<"<
int GraphMatrix::getEnd(int vends[], int i)
{
if(vends[i]!=i)
{
return getEnd(vends,vends[i]);
}else
{
return i;
}
}
template
void GraphMatrix::unionn(int vends[],int x,int y)
{
vends[x] = y;
}
template
void GraphMatrix::kruskal()
{
int i,m,n,p1,p2;
int length;
int index = 0; // rets数组的索引
int vends[DefaultVertices]; // 用于保存"已有最小生成树"中每个顶点在该最小树中的终点。
for(int i=0;i rets[DefaultVertices]; // 结果数组,保存kruskal最小生成树的边
EData *edges; // 图对应的所有边
// 获取"图中所有的边"
edges = getEdges();
// 将边按照"权"的大小进行排序(从小到大)
sortEdges(edges, numEdges);
for (i=0; i st; // 声明对象
bool finished = false;
int choice;
char e1, e2, next;
int weight;
while(!finished) {
cout << "[1]创建基于邻接矩阵的图" << endl;
cout << "[2]输出图的所有顶点和边信息" << endl;
cout << "[3]取顶点v的第一个邻接顶点" << endl;
cout << "[4]取v的邻接顶点w的下一个邻接顶点" << endl;
cout << "[5]插入顶点" << endl;
cout << "[6]无向图插入边" << endl;
cout << "[7]有向图插入边" << endl;
cout << "[8]删除顶点" << endl;
cout << "[9]删除边" << endl;
cout << "[10]深度优先搜索DFS" << endl;
cout << "[11]广度优先搜索BFS" << endl;
cout << "[12]Dijkstra最短路劲" << endl;
cout << "[13]Prim最小生成树算法" << endl;
cout << "[14]kruskal最小生成树算法" << endl;
cout << "[15]退出" << endl;
cout << "请输入选择[1-15]:";
cin >> choice;
switch(choice) {
case 1:
st.inputGraph();
break;
case 2:
st.outputGraph();
break;
case 3:
cout << "请输入顶点:";
cin >> e1;
e2 = st.getValue(st.getFirstNeighbor(st.getVertexPos(e1)));
if(e2)
cout << "顶点" << e1 << "的第一个邻接顶点为:" << e2 << endl;
else
cout << "顶点" << e1 << "没有邻接顶点!" << endl;
break;
case 4:
cout << "请输入顶点v和邻接顶点w:";
cin >> e1 >> e2;
next = st.getValue(st.getNextNeighbor(st.getVertexPos(e1), st.getVertexPos(e2)));
if(next)
cout << "顶点" << e1 << "的邻接顶点" << e2 << "的下一个邻接顶点为:" << next << endl;
else
cout << "顶点" << e1 << "的邻接顶点" << e2 << "没有下一个邻接顶点!" << endl;
break;
case 5:
cout << "请输入要插入的顶点:";
cin >> e1;
if(st.insertVertex(e1))
cout << "插入成功!" << endl;
else
cout << "表已满,插入失败!" << endl;
break;
case 6:
cout << "请输入要插入的边的信息:" << endl;
cin >> e1 >> e2 >> weight;
st.insertUEdge(st.getVertexPos(e1), st.getVertexPos(e2), weight);
break;
case 7:
cout << "请输入要插入的边的信息:" << endl;
cin >> e1 >> e2 >> weight;
st.insertEdge(st.getVertexPos(e1), st.getVertexPos(e2), weight);
break;
case 8:
cout << "请输入要删除的顶点:";
cin >> e1;
if(st.removeVertex(st.getVertexPos(e1)))
cout << "顶点" << e1 << "已删除!" << endl;
else
cout << "顶点" << e1 << "不在图中!" << endl;
break;
case 9:
cout << "请输入要删除的边的两个端点:" << endl;
cin >> e1 >> e2;
st.removeEdge(st.getVertexPos(e1), st.getVertexPos(e2));
break;
case 10:
st.DFS();
break;
case 11:
st.BFS();
break;
case 12:
st.Dijkstra(1);
break;
case 13:
cout<<"请输入起始点:"<>e1;
st.Prim(e1);
break;
case 14:
st.kruskal();
break;
case 15:
finished = true;
break;
default:
cout << "选择输入错误,请重新输入!" << endl;
}
}
return 0;
}
运行截图