c/c++ 用克鲁斯卡尔(kruskal)算法构造最小生成树

c/c++ 用克鲁斯卡尔(kruskal)算法构造最小生成树

最小生成树(Minimum Cost Spanning Tree)的概念:

假设要在n个城市之间建立公路,则连通n个城市只需要n-1条线路。这时,自然会考虑,如何在最节省经费的前提下建立这个公路网络。

每2个城市之间都可以设置一条公路,相应地都要付出一定的经济代价。n个城市之间,最多可以设置n(n-1)/2条线路,那么,如何在这些可能的线路中选择n-1条,以使总的耗费最少?

克鲁斯卡尔(kruskal)算法的大致思路:

把每条边的权重按照从小到大排序后,连接。连接时,需要查看要连接的两个顶点的父节点是否相同,不同才可以连接,连接后,更新父节点。

图为下图:

第一步

图1

第二步

图2

第三步

图3

第四步

A->D,C->D,B->C的权重都是5,这时就不知道连哪个了,所以要创建2个辅助函数is_same,mark_same。

is_same用来判断要连接的2个点的父节点是否相同,如果相同就说明了,连接后,图就存在了环,所以不可以连接,放弃这条边,去寻找下一条边。

mark_same用来更新节点的父节点。

当拿到的节点是AD时,发现AD的父节点都是A,所以放弃;

当拿到的节点是CD时,发现AD的父节点都是A,所以放弃;

当拿到的节点是BC时,发现B的父节点是自己,C的父节点是A,父节点不同,所以连接,并更父节点

图4

找一半的矩阵,把各条边的起点,终点,权重,放到edge数组里

图5

mixSpanTree.h

#ifndef __mixspantree__
#define __mixspantree__

#include <stdio.h>
#include <malloc.h>
#include <assert.h>
#include <memory.h>
#include <stdlib.h>
#include <stdbool.h>

#define Default_vertex_size 20

#define T char//dai biao ding dian de lei xing
#define E int
#define MAX_COST 0x7FFFFFFF

typedef struct GraphMtx{
  int MaxVertices;//zui da ding dian shu liang]
  int NumVertices;//shi ji ding dian shu liang
  int NumEdges;//bian de shu lian

  T* VerticesList;//ding dian list
  int** Edge;//bian de lian jie xin xi, bu shi 0 jiu shi 1
}GraphMtx;

typedef struct Edge{
  int begin;//边的起点
  int end;  //边的终点
  E   cost; //边的权重
}Edge;

//chu shi hua tu
void init_graph(GraphMtx* gm);
//打印二维数组
void show_graph(GraphMtx* gm);
//插入顶点
void insert_vertex(GraphMtx* gm, T v);
//添加顶点间的线
void insert_edge(GraphMtx* gm, T v1, T v2, E cost);

//用kruskal算法构造最小生成树
void minSpanTree_kruskal(GraphMtx* gm);

#endif

mixSpanTree.c

#include "mixSpanTree.h"

void init_graph(GraphMtx* gm){
  gm->MaxVertices = Default_vertex_size;
  gm->NumEdges = gm->NumVertices = 0;

  //kai pi ding dian de nei cun kong jian
  gm->VerticesList = (T*)malloc(sizeof(T) * (gm->MaxVertices));
  assert(NULL != gm->VerticesList);

  //创建二维数组
  //让一个int的二级指针,指向一个有8个int一级指针的数组
  //开辟一个能存放gm->MaxVertices个int一级指针的内存空间
  gm->Edge = (int**)malloc(sizeof(int*) * (gm->MaxVertices));
  assert(NULL != gm->Edge);
  //开辟gm->MaxVertices组,能存放gm->MaxVertices个int的内存空间
  for(int i = 0; i < gm->MaxVertices; ++i){
    gm->Edge[i] = (int*)malloc(sizeof(int) * gm->MaxVertices);
  }
  //初始化二维数组
  //让每个顶点之间的边的关系都为不相连的
  for(int i = 0; i < gm->MaxVertices; ++i){
    for(int j = 0; j < gm->MaxVertices; ++j){
      if(i == j)
    gm->Edge[i][j] = 0;
      else
    gm->Edge[i][j] = MAX_COST;
    }
  }
}
//打印二维数组
void show_graph(GraphMtx* gm){
  printf("  ");
  for(int i = 0; i < gm->NumVertices; ++i){
    printf("%c  ", gm->VerticesList[i]);
  }
  printf("\n");
  for(int i = 0; i < gm->NumVertices; ++i){
    //在行首,打印出顶点的名字
    printf("%c:", gm->VerticesList[i]);
    for(int j = 0; j < gm->NumVertices; ++j){
      if(gm->Edge[i][j] == MAX_COST){
    printf("%c  ", ‘*‘);
      }
      else{
    printf("%d  ", gm->Edge[i][j]);
      }
    }
    printf("\n");
  }
  printf("\n");
}
//插入顶点
void insert_vertex(GraphMtx* gm, T v){
  //顶点空间已满,不能再插入顶点了
  if(gm->NumVertices >= gm->MaxVertices){
    return;
  }
  gm->VerticesList[gm->NumVertices++] = v;
}

int getVertexIndex(GraphMtx* gm, T v){
  for(int i = 0; i < gm->NumVertices; ++i){
    if(gm->VerticesList[i] == v)return i;
  }
  return -1;
}
//添加顶点间的线
void insert_edge(GraphMtx* gm, T v1, T v2, E cost){
  if(v1 == v2)return;

  //查找2个顶点的下标
  int j = getVertexIndex(gm, v1);
  int k = getVertexIndex(gm, v2);
  //说明找到顶点了,并且点之间还没有线
  if(j != -1 && k != -1 ){
    //因为是无方向,所以更新2个值
    gm->Edge[j][k] = gm->Edge[k][j] = cost;
    //边数加一
    gm->NumEdges++;
  }
}

//比较边的权重,本函数作为快速排序函数的参数来使用。
int cmp(const void* a, const void* b){
  return ((*(Edge*)a).cost - (*(Edge*)b).cost);
}
//判断参数的2个顶点的父节点是否相同
bool is_same(int* father, int begin, int end){
  while(father[begin] != begin){
    begin = father[begin];
  }
  while(father[end] != end){
    end = father[end];
  }
  return begin == end;
}
//找到end节点的父节点x,再找到begin节点的父节点y,更新x节点的父节点为y
void mark_same(int* father, int begin, int end){
  while(father[begin] != begin){
    begin = father[begin];
  }
  while(father[end] != end){
    end = father[end];
  }
  father[end] = begin;

}
//用kruskal算法构造最小生成树
void minSpanTree_kruskal(GraphMtx* g){

  int n = g->NumVertices;
  Edge* edge = (Edge*)malloc(sizeof(Edge) * n*(n-1)/2);
  assert(edge != NULL);

  int k = 0;
  //查找一半的矩阵,把各条边的起点,终点,权重,放到edge数组里,参照上面的图5
  for(int i = 0; i < n; ++i){
    for(int j = i; j < n; j++){
      if(g->Edge[i][j] != 0 && g->Edge[i][j] != MAX_COST){
    edge[k].begin = i;
    edge[k].end = j;
    edge[k].cost = g->Edge[i][j];
    k++;
      }
    }
  }

  //按照权重来排序(用系统函数)
  //第一个参数:要被排序的数组
  //第二个参数:数组中元素的个数
  //第三个参数:每个数组元素占用的内存空间
  //第四个参数:函数指针,指定排序的规则
  qsort(edge, k, sizeof(Edge), cmp);

  //初始化每个节点的父节点,让每个节点的父节点为自身
  int *father = (int*)malloc(sizeof(int) * n);
  assert(NULL != father);
  for(int i = 0; i < n; ++i){
    father[i] = i;
  }

  for(int i = 0; i < n; ++i){
    //判断2个节点的父节点是否相同,不相同就连接
    if(!is_same(father, edge[i].begin, edge[i].end)){
      printf("%c->%c:%d\n",g->VerticesList[edge[i].begin],g->VerticesList[edge[i].end], edge[i].cost);
      //连接后,找到节点end的父节点x,再找到节点begin的父节点y,把节点x的父节点更新为y
      mark_same(father, edge[i].begin, edge[i].end);
    }
  }
}

mixSpanTreemain.c

#include "mixSpanTree.h"

int main(){
  GraphMtx gm;
  //初始化图
  init_graph(&gm);
  //插入顶点
  insert_vertex(&gm, ‘A‘);
  insert_vertex(&gm, ‘B‘);
  insert_vertex(&gm, ‘C‘);
  insert_vertex(&gm, ‘D‘);
  insert_vertex(&gm, ‘E‘);
  insert_vertex(&gm, ‘F‘);

  //添加连线
  insert_edge(&gm, ‘A‘, ‘B‘, 6);
  insert_edge(&gm, ‘A‘, ‘D‘, 5);
  insert_edge(&gm, ‘A‘, ‘C‘, 1);
  insert_edge(&gm, ‘B‘, ‘E‘, 3);
  insert_edge(&gm, ‘B‘, ‘C‘, 5);
  insert_edge(&gm, ‘C‘, ‘E‘, 6);
  insert_edge(&gm, ‘C‘, ‘D‘, 5);
  insert_edge(&gm, ‘C‘, ‘F‘, 4);
  insert_edge(&gm, ‘F‘, ‘E‘, 6);
  insert_edge(&gm, ‘D‘, ‘F‘, 2);
  //打印图
  show_graph(&gm);

  //kruskal
  minSpanTree_kruskal(&gm);

}

完整代码

编译方法:gcc -g mixSpanTree.c mixSpanTreemain.c

原文地址:https://www.cnblogs.com/xiaoshiwang/p/9420541.html

时间: 2024-11-12 22:00:11

c/c++ 用克鲁斯卡尔(kruskal)算法构造最小生成树的相关文章

图的生成树(森林)(克鲁斯卡尔Kruskal算法和普里姆Prim算法)、以及并查集的使用

图的连通性问题:无向图的连通分量和生成树,所有顶点均由边连接在一起,但不存在回路的图. 设图 G=(V, E) 是个连通图,当从图任一顶点出发遍历图G 时,将边集 E(G) 分成两个集合 T(G) 和 B(G).其中 T(G)是遍历图时所经过的边的集合,B(G) 是遍历图时未经过的边的集合.显然,G1(V, T) 是图 G 的极小连通子图,即子图G1 是连通图 G 的生成树. 深度优先生成森林   右边的是深度优先生成森林: 连通图的生成树不一定是唯一的,不同的遍历图的方法得到不同的生成树;从不

克鲁斯卡尔(Kruskal)算法

1 # include <stdio.h> 2 3 # define MAX_VERTEXES 100//最大顶点数 4 # define MAXEDGE 20//边集数组最大值 5 # define INFINITY 65535//代表不可能的数(无穷大) 6 7 typedef struct 8 {//图 结构体定义 9 int arc[MAX_VERTEXES][MAX_VERTEXES];//二位数组 矩阵 10 int numVertexes, numEdges;//当前图中的顶点数

图解最小生成树 - 克鲁斯卡尔(Kruskal)算法

我们在前面讲过的<克里姆算法>是以某个顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树的.同样的思路,我们也可以直接就以边为目标去构建,因为权值为边上,直接找最小权值的边来构建生成树也是很自然的想法,只不过构建时要考虑是否会形成环而已,此时我们就用到了图的存储结构中的边集数组结构,如图7-6-7 假设现在我们已经通过邻接矩阵得到了边集数组edges并按权值从小到大排列如上图. 下面我们对着程序和每一步循环的图示来看: 算法代码:(改编自<大话数据结构>) C++ Code 1

c/c++ 用普利姆(prim)算法构造最小生成树

c/c++ 用普利姆(prim)算法构造最小生成树 最小生成树(Minimum Cost Spanning Tree)的概念: ? 假设要在n个城市之间建立公路,则连通n个城市只需要n-1条线路.这时,自然会考虑,如何在最节省经费的前提下建立这个公路网络. ? 每2个城市之间都可以设置一条公路,相应地都要付出一定的经济代价.n个城市之间,最多可以设置n(n-1)/2条线路,那么,如何在这些可能的线路中选择n-1条,以使总的耗费最少? 普利姆(prim)算法的大致思路: ? 大致思想是:设图G顶点

利用Kruskal算法求最小生成树解决聪明的猴子问题 -- 数据结构

题目:聪明的猴子 链接:https://ac.nowcoder.com/acm/problem/19964 在一个热带雨林中生存着一群猴子,它们以树上的果子为生.昨天下了一场大雨,现在雨过天晴,但整个雨林的地 表还是被大水淹没着,部分植物的树冠露在水面上.猴子不会游泳,但跳跃能力比较强,它们仍然可以在露出水面 的不同树冠上来回穿梭,以找到喜欢吃的果实.现在,在这个地区露出水面的有N棵树,假设每棵树本身的直径都 很小,可以忽略不计.我们在这块区域上建立直角坐标系,则每一棵树的位置由其所对应的坐标表

数据结构之---C语言实现最小生成树之kruskal(克鲁斯卡尔)算法

//Kruskal(克鲁斯卡尔)算法 //杨鑫 #include <stdio.h> #include <stdlib.h> #define MAX 1000 #define MAXE MAX #define MAXV MAX typedef struct { int beginvex1; //边的起始顶点 int endvex2; //边的终止顶点 int weight; //边的权值 }Edge; void kruskal(Edge E[],int n,int e) { int

最小生成树之Kruskal(克鲁斯卡尔)算法

克鲁斯卡尔算法:       是在剩下的所有未选取的边中,找最小边,如果和已选取的边构成回路,则放弃,选取次小边. 先构造一个只含 n 个顶点.而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边, 若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取, 而应该取下一条权值最小的边再试之.依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止. 时间复杂度为为O(e^2)

kruskal算法求最小生成树(jungle roads的kruskal解法)

注意: 注意数组越界问题(提交出现runtimeError代表数组越界) 刚开始提交的时候,边集中边的数目和点集中点的数目用的同一个宏定义,但是宏定义是按照点的最大数定义的,所以提交的时候出现了数组越界问题,以后需要注意啦. Description The Head Elder of the tropical island of Lagrishan has a problem. A burst of foreign aid money was spent on extra roads betwe

Kruskal算法求最小生成树

求加权连通图的最小生成树的算法.kruskal算法总共选择n- 1条边,(共n条边)所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的具有最小耗费的边加入已选择的边的集合中.注意到所选取的边若产生环路则不可能形成一棵生成树.kruskal算法分e 步,其中e 是网络中边的数目.按耗费递增的顺序来考虑这e 条边,每次考虑一条边.当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入. 克鲁斯卡尔算法(Kruskal's algorithm)是两个经典的最小生成树算法的