常用的求最小代价生成树的方法有两种:Prim算法和Kruskal算法,这两种算法都是贪心算法的应用,
Prim算法
在一个无向带权图中求得最小生成树得思路是:从a顶点出发,将a放入u集合(表示已选),找与u集合中所有点连接的权值最小的边(此时只有a结点),假设该边连接a和b结点,b结点被选定放入u集合,查找与u集合中所有点连接的权值最小的边(此时有a,b结点),通过改变找到c结点,c被选定放入u集合中…当所有结点都被选中时,最小生成树建成了。故Prim可看做为‘加点’的算法。
辅助结构
typedef struct
{
int adjvex;//相连接的顶点
int lowcost;//表示权值
}st;
st closeEdge[maxSize];
closeEdge[i].adjvex=k;//k为在集合中的顶点
closeEdge[i[.lowcost=0;
i顶点与在集合中的k顶点相邻接。i顶点的lowcost为0,表示将i顶点被选中加入u集合
代码如下
用邻接矩阵存储图,将边上的权值存入矩阵中,图中不连接的边在矩阵中为INT_MAX,自身之间的连接也存为INT_MAX
void miniSpanTree(mGraph& G, int u)
{
//从顶点u出发构造G的最小代价生成树
int i, j, min;
for (i = 0; i < G.vexnum; i++)
if (i != u)
closeEdge[i]= { u, G.arc[i][u]};//初始化与u相连所有顶点间的权值
closeEdge[u].lowcost = 0;//将u加入到已选集合中
for (j = 1; j < G.vexnum; j++)//仅起到循环G.vexnum-1次的作用
{
int minValue =INT_MAX;
for (i = 0; i < G.vexnum; i++)
if (closeEdge[i].lowcost != 0)
{
if (minValue > closeEdge[i].lowcost)
{
minValue = closeEdge[i].lowcost;
min = i;
}
}
std::cout << "选定结点: "<<G.vex[min] << " 权值: " << closeEdge[min].lowcost << '\n';
closeEdge[min].lowcost = 0;//将min顶点加入已选集合中
for (i = 0; i < G.vexnum; i++)
{
if (closeEdge[i].lowcost != 0)
{
if (closeEdge[i].lowcost>G.arc[min][i])
closeEdge[i] = { min, G.arc[min][i] };//i与集合中的min相连,因为它们的权值更小
}
}
}
}
Java代码如下(后来补充)
public class Solution {
PriorityQueue<int[]> p;
boolean[] in;
int sum;
public int prim(List<int[]>[] graph){ //计算由graph得到的最小生成树的权重。
int n=graph.length;// 节点数目
p=new PriorityQueue<>((a,b)->{
return a[1]-b[1]; // int[] {to,weight}
});
in=new boolean[n];
in[0]=true;//将节点 0 加入已选集合。
sum=0;
getEdge(0,graph);
while(!p.isEmpty()){
int[] edge=p.poll();
int to=edge[0],weight=edge[1];
if(in[to])
continue;
sum+=weight;
in[to]=true;
getEdge(to,graph);
}
return sum;
}
private void getEdge(int cur,List<int[]>[] graph){
for(int[] edge:graph[cur]){
int to=edge[0];
if(in[to])
continue;
p.offer(edge);
}
}
}
完整示例
输入图如下:6个顶点,10条边
对该图用prim算法进行最小代价生成树构造代码如下:
#include<iostream>
//#include<climits>
#define maxSize 6//顶点数目
#define MAX 10//边的个数
//邻接矩阵
typedef struct
{
int vexnum, arcnum;//顶点数和边数
char vex[maxSize];//顶点信息(字符)
int arc[maxSize][maxSize];//二维数组(存储边上的信息)
}mGraph;
typedef struct
{
int adjvex;//相连接的顶点
int lowcost;//表示权值
}st;
st closeEdge[maxSize];
void miniSpanTree(mGraph& G, int u);//u顶点的序号
int main()
{
using namespace std;
mGraph G;//邻接矩阵存储图并进行初始化
G.vexnum = maxSize;
G.arcnum = MAX;
char vexData[maxSize] = { 'a', 'b', 'c', 'd', 'e','f'};//顶点信息
int arcData[MAX][3] = { { 0, 1, 6 }, { 0, 2, 1 }, { 0, 3, 5 }, { 1, 2, 5 }, { 2, 3, 5 },
{ 1, 4, 3 }, { 2, 4, 6 }, { 2, 5, 4 }, { 3, 5, 2 }, { 4, 5, 6 } };//连接的边及权值
int i, j;
for (i = 0; i < G.vexnum; ++i)
{
G.vex[i] = vexData[i];
for (j = 0; j < G.vexnum; j++)
G.arc[i][j] = INT_MAX;
}
for (i = 0; i < G.arcnum; i++)
G.arc[arcData[i][0]][arcData[i][1]] = G.arc[arcData[i][1]][arcData[i][0]] = arcData[i][2];
//初始化完毕
cout << "Prim 最小代价生成树: " << endl;
cout << "自顶点" << G.vex[0] << "开始:\n";
miniSpanTree(G,0);//从第一个顶点构造最小生成树
cout << endl;
return 0;
}
void miniSpanTree(mGraph& G, int u)
{
//从顶点u出发构造G的最小代价生成树
int i, j, min;
for (i = 0; i < G.vexnum; i++)
if (i != u)
closeEdge[i]= { u, G.arc[i][u]};//初始化与u相连所有顶点间的权值
closeEdge[u].lowcost = 0;//将u加入到已选集合中
for (j = 1; j < G.vexnum; j++)
{
int minValue =INT_MAX;
for (i = 0; i < G.vexnum; i++)
if (closeEdge[i].lowcost != 0)
{
if (minValue > closeEdge[i].lowcost)
{
minValue = closeEdge[i].lowcost;
min = i;
}
}
std::cout << "选定结点: "<<G.vex[min] << " 权值: " << closeEdge[min].lowcost << '\n';
closeEdge[min].lowcost = 0;//将min顶点加入已选集合中
for (i = 0; i < G.vexnum; i++)
{
if (closeEdge[i].lowcost != 0)
{
if (closeEdge[i].lowcost>G.arc[min][i])
closeEdge[i] = { min, G.arc[min][i] };//i与集合中的min相连,因为它们的权值更小
}
}
}
}
输出结果如下:
Kruskal算法
Kruskal算法可看作为‘加边’来得到最小生成树。它的思路为:
1,找到所有边中代价(权值)最小的边,加入u集合中
2,判定该边所关联的两个顶点是否在同一个分量中(可以用DFS在集合u中来判断,当一次DFS可以成功查找到,那么两个顶点在同一个分量中)如果两个顶点已经在同一个分量中,那么该边被舍弃,否则该边被加入到所选集合u中
3,再找代价最小的边,再判定…
对于下图:
先找到a-c边,然后是d-f边,再是b-e边,接下来是c-f边,如果当找到a-d边时,因为在集合u中a顶点与d顶点已经在同一个连通分量中了,舍弃a-d边,重新查找…