图
-
生成树
定义:连通图的⽣成树是⼀个极⼩的连通⼦图,它含有图中全部的n个顶点,但只⾜以构成⼀颗树的n-1条边。
满⾜以下3个条件则为连通图的⽣成树:
1. 图是连通图; 2. 图中包含了N个顶点; 3. 图中边的数量等于N-1条边(没有闭环)。
-
最小生成树
定义:把构成连通网的最小代价的生成树称为最小生成树。
-
题目
假设⽬前有N 个顶点,每个顶点连接的路径不⼀样.请你设计⼀个算法,快速找出能覆盖所有顶点的路径.
-
普⾥姆(Prim)算法
思路:
1. 定义2个数组; adjvex ⽤来保存相关顶点下标; lowcost 保存顶点之间的权值 2. 初始化2个数组, 从v0开始寻找最⼩⽣成树, 默认v0是最⼩⽣成树上第⼀个顶点 3. 循环lowcost 数组,根据权值,找到顶点 k; 4. 更新lowcost 数组 5. 循环所有顶点,找到与顶点k 有关系的顶点. 并更新lowcost 数组与adjvex 数组;
注意:
更新lowcost 数组与adjvex 数组的条件: 1. 与顶点k 之间有连接 2. 当前结点 j 没有加⼊过最⼩⽣成树; 3. 顶点 k 与 当前顶点 j 之间的权值 ⼩于 顶点j 与其他顶点 k 之前的权值. 则更新. 简单说就是要⽐较之前存储的值要⼩,则更新
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXVEX 20//定点数 #define INFINITYC 65535//假定无穷 typedef int Status;//Status是函数的类型,其值是函数结果状态代码,如OK等 typedef struct { int arc[MAXVEX][MAXVEX]; int numVertexes, numEdges;//numEdges边数;numVertexes顶点数 } MGraph;
//创建邻接矩阵 void CreateMGraph(MGraph *G) { int i, j; /* printf("请输入边数和顶点数:"); */ G->numEdges = 15; G->numVertexes = 9; //初始化图 for (i = 0; i < G->numVertexes; i++) { for ( j = 0; j < G->numVertexes; j++) { if (i==j) G->arc[i][j]=0; else G->arc[i][j] = G->arc[j][i] = INFINITYC; } } G->arc[0][1]=10; G->arc[0][5]=11; G->arc[1][2]=18; G->arc[1][8]=12; G->arc[1][6]=16; G->arc[2][8]=8; G->arc[2][3]=22; G->arc[3][8]=21; G->arc[3][6]=24; G->arc[3][7]=16; G->arc[3][4]=20; G->arc[4][7]=7; G->arc[4][5]=26; G->arc[5][6]=17; G->arc[6][7]=19; for(i = 0; i < G->numVertexes; i++) { for(j = i; j < G->numVertexes; j++) { G->arc[j][i] =G->arc[i][j]; } } }
/* Prim算法生成最小生成树 */(以下算法以V0为初始顶点) void MiniSpanTree_Prim(MGraph G) { int i, j;//用来遍历 int min;//min用来记录最小权值 int k;//k用来记录最小权值对应的下标; int sum = 0;//用来累计权值 int adjvex[MAXVEX];//保存相关顶点下标(数组下标为顶点,数组值为当前顶点在最小生成树中的前一个顶点,即通过哪个顶点到达当前顶点的权值最小时,记录哪个顶点)。 int lowcost[MAXVEX];//保存相关顶点间边的权值(数组下标为顶点;数组值为顶点与顶点之间的权值,即邻接矩阵的数值),已加入最小生成树时,下标对应的权值置0。 /* 初始化第一个权值为0,即v0加入生成树 */ /* lowcost的值为0,在这里就是此下标的顶点已经加入生成树 */ lowcost[0] = 0; /* 初始化第一个顶点下标为0 */ adjvex[0] = 0; //1. 初始化 //循环除下标为0外的全部顶点 for(i = 1; i < G.numVertexes; i++) { lowcost[i] = G.arc[0][i];//将v0顶点与之有边的权值存入数组 adjvex[i] = 0;//初始化都为v0的下标 } //2. 循环除了下标为0以外的全部顶点, 找到lowcost数组中最小的顶点k for(i = 1; i < G.numVertexes; i++) { /* 初始化最小权值为∞, */ /* 通常设置为不可能的大数字如32767、65535等 */ min = INFINITYC; j = 1;k = 0; //循环全部顶点 while (j < G.numVertexes) { /* 如果权值不为0且权值小于min */ if (lowcost[j] != 0 && lowcost[j] < min) { /* 则让当前权值成为最小值,更新min */ min = lowcost[j]; /* 将当前最小值的下标存入k */ k = j; } j++; } /* 打印当前顶点边中权值最小的边 */ printf("(V%d, V%d) = %d\n", adjvex[k], k ,G.arc[adjvex[k]][k]); sum+=G.arc[adjvex[k]][k]; /* 3.将当前顶点的权值设置为0,表示此顶点已经完成任务 */ lowcost[k] = 0; /* 循环所有顶点,找到与顶点k 相连接的顶点 1. 与顶点k 之间连接; 2. 该结点没有被加入到生成树; 3. 顶点k 与 顶点j 之间的权值 < 顶点j与其他顶点的权值,则更新lowcost 数组; */ for (j = 1; j < G.numVertexes; j++) { /* 如果下标为k顶点各边权值小于此前这些顶点未被加入生成树权值 */ //lowcost[j] 如果为0说明已加入最小生成树 if (lowcost[j] != 0 && G.arc[k][j] < lowcost[j]) { /* 将较小的权值存入lowcost相应位置 */ lowcost[j] = G.arc[k][j]; /* 将下标为k的顶点存入adjvex */ adjvex[j] = k; } } } printf("sum = %d\n",sum); }
-
克鲁斯卡尔(Kruskal)算法
思路
1. 将邻接矩阵 转化成 边表数组; 2. 对边表数组根据权值按照从⼩到⼤的顺序排序; 3. 遍历所有的边, 通过parent 数组找到边的连接信息; 避免闭环问题; 4. 如果不存在闭环问题,则加⼊到最⼩⽣成树中. 并且修改parent 数组
举例:
当遇到闭环时:
#define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXEDGE 20 #define MAXVEX 20 #define INFINITYC 65535 typedef int Status; typedef struct { int arc[MAXVEX][MAXVEX]; int numVertexes, numEdges; } MGraph; /* 对边集数组Edge结构的定义 */ typedef struct { int begin; int end; int weight; } Edge ;
/* 交换权值以及头和尾 */ void Swapn(Edge *edges,int i, int j) { int tempValue; //交换edges[i].begin 和 edges[j].begin 的值 tempValue = edges[i].begin; edges[i].begin = edges[j].begin; edges[j].begin = tempValue; //交换edges[i].end 和 edges[j].end 的值 tempValue = edges[i].end; edges[i].end = edges[j].end; edges[j].end = tempValue; //交换edges[i].weight 和 edges[j].weight 的值 tempValue = edges[i].weight; edges[i].weight = edges[j].weight; edges[j].weight = tempValue; } /* 对权值进行排序 */ void sort(Edge edges[],MGraph *G) { //对权值进行排序(从小到大) int i, j; for ( i = 0; i < G->numEdges; i++) { for ( j = i + 1; j < G->numEdges; j++) { if (edges[i].weight > edges[j].weight) { Swapn(edges, i, j); } } } printf("边集数组根据权值排序之后的为:\n"); for (i = 0; i < G->numEdges; i++) { printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight); } } /* 查找连线顶点的尾部下标 */ //根据顶点f以及parent 数组,可以找到当前顶点的尾部下标; 帮助我们判断2点之间是否存在闭环问题; int Find (int *parent, int f) { while ( parent[f] > 0) { f = parent[f]; } return f; } /* 生成最小生成树 */ void MiniSpanTree_Kruskal(MGraph G) { int i, j, n, m; int sum = 0; int k = 0; /* 定义一数组用来判断边与边是否形成环路,用来记录顶点间的连接关系. 通过它来防止最小生成树产生闭环;*/ int parent[MAXVEX]; /* 定义边集数组,edge的结构为begin,end,weight,均为整型 */ Edge edges[MAXEDGE]; /*1. 用来构建边集数组*/ for ( i = 0; i < G.numVertexes-1; i++) { for (j = i + 1; j < G.numVertexes; j++) { //如果当前路径权值 != ∞ if (G.arc[i][j]<INFINITYC) { //将路径对应的begin,end,weight 存储到edges 边集数组中. edges[k].begin = i; edges[k].end = j; edges[k].weight = G.arc[i][j]; //边集数组计算器k++; k++; } } } //2. 对边集数组排序 sort(edges, &G); //3.初始化parent 数组为0. 9个顶点; // for (i = 0; i < G.numVertexes; i++) for (i = 0; i < MAXVEX; i++) parent[i] = 0; //4. 计算最小生成树 printf("打印最小生成树:\n"); /* 循环每一条边 G.numEdges 有15条边*/ for (i = 0; i < G.numEdges; i++) { //获取begin,end 在parent 数组中的信息; //如果n = m ,将begin 和 end 连接,就会产生闭合的环. n = Find(parent,edges[i].begin); m = Find(parent,edges[i].end); //printf("n = %d,m = %d\n",n,m); /* 假如n与m不等,说明此边没有与现有的生成树形成环路 */ if (n != m) { /* 将此边的结尾顶点放入下标为起点的parent中。 */ /* 表示此顶点已经在生成树集合中 */ parent[n] = m; /*打印最小生成树路径*/ printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight); sum += edges[i].weight; } } printf("sum = %d\n",sum); }