在看本文之前最好先看看
算法分析之最小生成树
在介绍两种算法之前,我需要先介绍一个推论
首先介绍一个概念, 连通分量:其实就是一棵树(某些树也可能是一个节点)
在所有的连接两颗树的边里找到权重最小的边(u,v),设C1,C2分别为u,v所连接的两个连通分量由于(u,v)一定是连接两个连通分量C1,C2的轻量级边,由推论可知(u,v)是安全的
算法伪代码:
A=null
对G中的边E按权重w对每条边安卓小到大的顺序进行排序
Prim算法
算法的每一步都寻找一条横跨切割(A,V-A)的轻量级边作为A的安全边
设v.key为v到A中每个结点的边中最小边的权重
伪代码:
A=null
Q=G.V
任意确定一个点作为root加入到A中
while(Q!=null){
从Q找出结点u,u.key是Q中的所有结点中最小的
将u加入到A并从Q中删除
更新与u相连的结点的v.key
}
用下面的一系列图来说明:
prim模板如下:
在介绍两种算法之前,我需要先介绍一个推论
首先介绍一个概念, 连通分量:其实就是一棵树(某些树也可能是一个节点)
推论:对于无向连通图G=(V,E)。设A是G的某棵最小生成树的子集,并设C=(Vc,Ec)为森林Ga=(V,A)的一个连通分量,如果边(u,v)是连接C和Ga中某个其他连通分量的一条轻量级边,则(u,v)对于A来说是安全的。
在所有的连接两颗树的边里找到权重最小的边(u,v),设C1,C2分别为u,v所连接的两个连通分量由于(u,v)一定是连接两个连通分量C1,C2的轻量级边,由推论可知(u,v)是安全的
算法伪代码:
A=null
对G中的边E按权重w对每条边安卓小到大的顺序进行排序
for each (u,v)∈G.E{
如果u,v不属于同一颗树{
A=A∪{(u,v)}
将u,v所在的树合并成一棵树
}
}
具体过程可以用下图来说明
算法模板:
public class Mian
{
public static void main(String[] args)
{
/**
* 对fatherV初始化
**/
for (int i = 0; i < fatherV.length; i++)
{
fatherV[i]=i;
}
/**
* 给e赋值
**/
//TODO
/**
* 给V赋值
**/
//TODO
kruskal();
}
static int V;//结点的个数
static int[] fatherV=new int[100];//farher[i]=j表示结点i的父节点为j
static Edge[] e=new Edge[100];
static class Edge{
int u,v,w;
Edge(int u,int v,int w){
this.u=u;
this.v=v;
this.w=w;
}
}
/**
* 寻找结点x所在树的根节点
**/
static int findRoot(int x)
{
return (x==fatherV[x]) ? x : findRoot(fatherV[x]);
}
/**
* 判断结点u,v是否属于同一棵树,如果不属于同一棵树就将u,v所在的树合并为一棵树
**/
static boolean judgeIsSameTree(int u,int v)
{
int root1=findRoot(u);
int root2=findRoot(v);
if(root1==root2)//u,v在同一棵树中
{
return true;
}
else {
fatherV[root1]=root2;//将u,v所在的两颗树合并
}
return false;
}
static void kruskal()
{
boolean flag=false;
int numEgdeInTree=0;
quickSort(0, e.length);
for(int i=0;i<e.length;i++)
{
if(!judgeIsSameTree(e[i].u, e[i].v))
{
numEgdeInTree++;
if(numEgdeInTree==V-1)//找到了一棵最小生成树
{
flag=true;
break;
}
}
}
if(flag)
{
//TODO
}
else {
System.out.println("can not find a minimal spanning tree");
}
}
/**
* 快排对e按w进行升序排序,这里排序可以随意选择方法,也可以用语言给定好的排序函数
**/
static void quickSort(int l,int r){
if(l>=r){
return;
}
int q=partition(l, r);
quickSort(l, q-1);
quickSort(q+1, r);
}
static int partition(int l,int r){
int i=l;int j=r;int x=e[i].w;
while(i<j){
while(i<j && e[j].w>=x) j--;
if(i<j){
e[i].w=e[j].w;
i++;
}else {
break;
}
while(i<j && e[i].w<=x) i++;
if(i<j){
e[j].w=e[i].w;
j--;
}else {
break;
}
}
//显然退出时i=j
e[i].w=x;
return i;
}
}
Prim算法
算法的每一步都寻找一条横跨切割(A,V-A)的轻量级边作为A的安全边
设v.key为v到A中每个结点的边中最小边的权重
伪代码:
A=null
Q=G.V
任意确定一个点作为root加入到A中
while(Q!=null){
从Q找出结点u,u.key是Q中的所有结点中最小的
将u加入到A并从Q中删除
更新与u相连的结点的v.key
}
用下面的一系列图来说明:
prim模板如下:
public class Main
{
public static void main(String[] args)
{
prim();
}
static int[] low=new int[100];//low[i]=d表示结点i到A中的结点的最小边的权值为d
static int used[]=new int[100];//used[i]=1,0分别表示i结点在集合A中和i结点不在集合A中
static int n;//图中结点的数目
static int[][] w=new int[100][100];//w[i][j]结点i到j的边的权值
static void prim(){
//初始化所有结点的low值全部设为无限大
for(int i=0;i<low.length;i++)
{
low[i]=2147483647;
}
//以结点0为初始结点,标记该结点
int pos=0;used[pos]=1;
for(int i=1;i<n;i++)
{
low[i]=w[pos][i];
}
for(int i=1;i<n;i++)
{
/**
* 找出集合A到A以外的结点的边的权值最小的结点
**/
int min_low=1000000;
for(int j=1;j<n;j++)
{
if(used[j]==0 && low[j]<min_low)
{
min_low=low[j];
pos=j;
}
}
/**
* 将找到的结点加入到集合A,并更新与该结点相连的结点的low值
**/
used[pos]=1;
for(int j=1;j<n;j++)
{
if(used[j]==0 && w[pos][j]<low[j])
{
low[j]=w[pos][j];
//TODO,结点j的父节点为pos
}
}
}
}
}