一个连通的无圈的无向图被称作树,将这个图记作T=<V,E>,n=|V|,m=|E|,
则m=n-1(数学归纳法易证之)。如果T是带权的,那么最小生成树便是带权路径总长为最小的一个树,这是最小生成树。
在图论算法中,Prime算法和Kruskal算法是两个不同的求一个连通的无向图中最小生成树的贪心算法,在使用贪心算法之前我们必须证明其可行性。下面分别介绍这两种算法,它们都在每一阶段选择价值最小的边。
1.Prime算法
这其实和求最短路径的Dijkstra算法差不多,只是每次更新和选择的是边的价值而不是带权路径长。首先随便选取图中的一个节点v作为开始,接下来的每一次迭代都更新邻接边的最小值并按最小边选取下一个节点,直到所有的节点都被选择。
例如,对于下列邻接矩阵所表示的无向图:
0,3,0,4,4,0,0,0,0,0
3,0,10,0,2,3,0,0,0,0
0,10,0,0,0,6,1,0,0,0
4,0,0,0,5,0,0,6,0,0
4,2,0,5,0,11,0,2,1,0
0,3,6,0,11,0,2,0,3,11
0,0,1,0,0,2,0,0,0,8
0,0,0,6,2,0,0,0,4,0
0,0,0,0,1,3,0,4,0,7
0,0,0,0,0,11,8,0,7,0
下面是Prime算法的示意:
我们设置两个数组来记录相应下标表示的节点的边长以及此边所关联另一节点对应的下标,还有一个布尔型数组,用于对相应下标表示的节点作标记,若节点被选入生成树中,则值为true,否则为false。下面的图为了简明而用下划线来标记。你可以反复地对比上下的图来了解这一过程。
/**
* 产尘最小生成树的Prime算法实现。这里处理的通常是无向图。
* 在具体实现上,Prime算法与Dijkstra算法几乎一样,只不过
* Prime算法不用计算每个节点的带权路径长,而只用比较邻接的
* 边的大小。
* 在这里,Prime算法的时间复杂度通常为O(|V|*|V|),但如果使用了优
* 先队列,时间复杂度将会降为O(|V|log|V|)。
* @param g 图的通用接口对象
* @param <V> 图中节点内容
* @return 包含边权重值及邻接节点的索引值的数组{len,pre},
* len是一个节点间权重值的数组,pre是节点的上一个邻接它的节
* 点的索引数组。
*/
public static <V> int[][] primeForMinSpanTree(Graph<V,Integer> g){
int n=g.getVerticesSize();//获取图g中的节点总数
boolean[] known=new boolean[n];//标记节点是否被选择
int[] len=new int[n];//与节点i(0<=i<n)关联的边的长度
int[] pre=new int[n];//与节点i邻接的上一个节点
for (int i = 0; i < n; i++) {
len[i]=Integer.MAX_VALUE;
}
len[0]=0;
int knownSum=0;
while(knownSum!=n){
/*下面寻找当前最短的边,也可以使用堆*/
int min=-1;
for(int i=0;i<len.length;i++){
if(!known[i]){
if(min==-1){
min=i;
}else if(len[min]>len[i]){
min=i;
}
}
}
known[min]=true;//选择该节点到生成树中
knownSum++;
/*下面是迭代更新与所选的节点关联的边*/
Iterator<Integer> itr = g.getAdjacentVertices(min);//获取与min邻接的所有节点的迭代器
int count=0;//由于邻接表可能是链表
while(itr.hasNext()){
int j=itr.next();
if(!known[j] && len[j]>g.getWeight(min,count)){
len[j]=g.getWeight(min,count);
pre[j]=min;
}
count++;
}
}
return new int[][] {len,pre};
}
2.运行代码分析
若要运行此代码,则要实现Graph接口,下面是是一个实现,但过于冗余和杂乱。如果你只是想大致知道以下过程,大可不必逐一查看和运行下面的代码,可以跳过这部分,进入第三部分——3.此贪心算法可行性分析
(1) data_structure.graphs2.Graph. 接口
package data_structure.graphs2;
import java.util.Iterator;
/**
* 数据结构之图的通用接口和表示。提供了一些操作图的必要函数。
* @param <V> 图中节点的详细信息的数据.
* @param <W> 图中边的权重值,在现实中通常代表距离、花费或成本等,
* 基于{@code Comparable}接口,它是可比较的。
*/
public interface Graph<V,W extends Comparable<? super W>> {
/**
* 判断图是否为空
*/
boolean isEmpty();
/**
* 判断图是否存在内容data为d的节点
* @return
*/
boolean hasVertex(String n);
boolean hasVertex(int i);
/**
* 判断是否存在边
* @param from
* @param to
* @param weight
* @return
*/
boolean hasEdge(String from,String to,W weight);
boolean hasEdge(int from,int to,W weight);
/**
* 判断是否存在边
* @param from
* @param to
* @return
*/
boolean hasEdge(String from,String to);
boolean hasEdge(int from,int to);
/**
* 获取一个节点的位置
* @param d
* @return
*/
int getVertexIndex(String d);
/**
* 获取节点的data内容
*/
V getVertexData(String n);
V getVertexData(int i);
/**
* 获取某边的权重
* @param from
* @param to
* @return
*/
W getWeight(String from,String to);
W getWeight(int from,int to);
/**
* 给出与一个节点邻接的所有邻接节点的位置
*/
Iterator<Integer> getAdjacentVertices(String n);
Iterator<Integer> getAdjacentVertices(int n);
/**
* 获取图中节点的总数
*/
int getVerticesSize();
}
(2)data_structure.graphs2.AdjacentList 类
package data_structure.graphs2;
import com.sun.scenario.animation.shared.ClipEnvelope;
import data_structure.graphs.Utils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Function;
/**
* 图——链接自身的邻接表的节点的容器
* @param <V>
* @param <W>
*/
public class AdjacentList<V,W extends Comparable<? super W>> extends AbstractGraph<V,W>{
Vector<WithEdgesVertex<V,W>> vertices;
private Map<String,Integer> namespace;
public AdjacentList(Vector<WithEdgesVertex<V, W>> vertices) {
this.vertices = vertices;
}
public static <V> AdjacentList<V,Integer> createAdjacentList(int n,String file,Function<Integer,V> toVertex) throws IOException {
return createAdjacentList(Utils.getIntMatrixFromFile(n,file),0,toVertex);
}
public static <V,W extends Comparable<? super W>> AdjacentList<V,W>
createAdjacentList(W[][] m, W notAEdgeMark, Function<Integer,V> toVertex){
AdjacentList<V, W> adjacentList = new AdjacentList<V, W>(new Vector<>());
for (int i = 0; i < m.length; i++) {
WithEdgesVertex<V, W> vertex = new WithEdgesVertex<>(toVertex.apply(i));
vertex.setIndex(i);
LinkedList<Edge<W>> edges = new LinkedList<>();
vertex.setEdges(edges);
adjacentList.vertices.add(vertex);
for(int j=0;j<m.length;j++){
if(i!=j && !m[i][j].equals(notAEdgeMark))//创建无环图
edges.add(new Edge<W>(m[i][j],j));
}
}
return adjacentList;
}
public static <W extends Comparable<? super W>> AdjacentList<Integer,W> createAdjacentList(W[][] m, W notAEdgeMark){
return createAdjacentList(m,notAEdgeMark,Function.identity());
}
public AdjacentList<V,W> createAdjacentList(Scanner sc){
throw new UnsupportedOperationException();
}
public void printAdjacentList(){
System.out.println("AdjacentList(");
for (int i = 0; i < vertices.size(); i++) {
if(i>0) System.out.println(",");
WithEdgesVertex<V, W> withEdgesVertex = vertices.get(i);
LinkedList<Edge<W>> edges = withEdgesVertex.getEdges();
System.out.print(withEdgesVertex.getData() + " -> {");
for (int i1 = 0; i1 < edges.size(); i1++) {
if(i1>0)System.out.print(",");
Edge<W> edge = edges.get(i1);
System.out.print(getVertexData(edge.getToVertexIndex())+"(weight:"+edge.getWeigth()+")");
}
System.out.print("}");
}
System.out.println("\n)");
}
@Override
public boolean isEmpty() {
return vertices.isEmpty();
}
@Override
public boolean hasVertex(String n) {
return hasVertex(namespace.getOrDefault(n,-1));
}
@Override
public boolean hasVertex(int i) {
return 0 <= i && i < vertices.size();
}
@Override
public boolean hasEdge(String from, String to, W weight) {
return hasEdge(getVertexIndex(from),getVertexIndex(to),weight);
}
@Override
public boolean hasEdge(int from, int to, W weight) {
if(hasVertex(from)){
Iterator<Integer> itr = getAdjacentVertices(from);
while(itr.hasNext()){
Integer next = itr.next();
if(next.equals(to) && vertices.get(from).getEdges().get(to).getWeigth().equals(weight))
return true;
}
}
return false;
}
@Override
public boolean hasEdge(String from, String to) {
return hasEdge(getVertexIndex(from),getVertexIndex(to));
}
@Override
public boolean hasEdge(int from, int to) {
if(hasVertex(from)){
Iterator<Integer> itr = getAdjacentVertices(from);
while(itr.hasNext()){
Integer next = itr.next();
if(next.equals(to))
return true;
}
}
return false;
}
@Override
public int getVertexIndex(String d) {
return namespace.getOrDefault(d,-1);
}
@Override
public V getVertexData(String n) {
return getVertexData(getVertexIndex(n));
}
@Override
public V getVertexData(int i) {
return vertices.get(i).getData();
}
@Override
public W getWeight(String from, String to) {
return getWeight(getVertexIndex(from),getVertexIndex(to));
}
@Override
public W getWeight(int from, int to) {
return vertices.get(from).getEdges().get(to).getWeigth();
/*
if(hasVertex(from)){
int count=0;
Iterator<Integer> itr = getAdjacentVertices(from);
while(itr.hasNext()){
Integer next = itr.next();
if(next.equals(to))
return vertices.get(from).getEdges().get(count).getWeigth();
count++;
}
}
return null;
*/
}
@Override
public Iterator<Integer> getAdjacentVertices(String n) {
return getAdjacentVertices(getVertexIndex(n));
}
@Override
public Iterator<Integer> getAdjacentVertices(int n) {
if(hasVertex(n)){
Iterator<Edge<W>> itr = vertices.get(n).getEdges().iterator();
return new Iterator<Integer>() {
@Override
public boolean hasNext() {
return itr.hasNext();
}
@Override
public Integer next() {
return itr.next().getToVertexIndex();
}
};
}
else return null;
}
@Override
List<? extends Vertex<V>> getVertices() {
return vertices;
}
@Override
Vector getExts(int i) {
return vertices.get(i).getExts();
}
@Override
Vector getExts(String n) {
return getExts(getVertexIndex(n));
}
@Override
void setExt(int i,int j, Object value) {
vertices.get(i).setExt(j,value,false);
}
@Override
void setExt(String n,int j, Object value) {
setExt(getVertexIndex(n),j,value);
}
@Override
Optional getExt(int i, int j) {
return Optional.ofNullable(vertices.get(i).getExt(j));
}
@Override
Optional getExt(String n, int j) {
return Optional.ofNullable(getExt(getVertexIndex(n),j));
}
@Override
void setExtIgnore(int i,int j, Object value) {
vertices.get(i).setExt(j,value,true);
}
@Override
void setExtIgnore(String n,int j, Object value) {
setExtIgnore(getVertexIndex(n),j,value);
}
@Override
public int getVerticesSize() {
return vertices.size();
}
public static void main(String[] args) throws IOException {
//创建一个图:
// AdjacentList<String, Integer> g = AdjacentList.createAdjacentList(
// 7,
// "adjacencyMatrix.txt",
// i->"v"+(i+1));
// g.printAdjacentList();
// System.out.println(g.getWeight(0, 1));
// System.out.println(g.vertices.get(0).getEdges().get(0).getWeigth());
// LinkedList<Integer> l = Graphs.zeroIndegree(g);
// System.out.println(l);
// System.out.println(Graphs.topSort(g));
// int[][] ints = Graphs.shortestPathsNoWeight(g, 0);
// for (int i = 0; i < ints.length; i++) {
// for (int j = 0; j < ints[0].length; j++) {
// System.out.print(ints[i][j]+" ");
// }
// System.out.println();
// }
// int[][] ints =Graphs.shortestPathsNoNagative(g,0);
// for (int i = 0; i < ints.length; i++) {
// for (int j = 0; j < ints[0].length; j++) {
// System.out.print(ints[i][j]+" ");
// }
// System.out.println();
// }
// int[][] ints =Graphs.shortestPaths(g,0);
// int[][] ints=Graphs.shortestPathsNoCircle(g,1);
Graph<String,Integer> g2=AdjacentList.createAdjacentList(
10,
"g2.txt",
i->"v"+(i+1));
// int[][] ints=Graphs.kruskalForMinSpanTree(g2);
// for (int i = 0; i < ints.length; i++) {
// for (int j = 0; j < ints[0].length; j++) {
// System.out.print(ints[i][j]+" ");
// }
// System.out.println();
// }
Graphs.dfs(g2);
}
}
(3)data_structure.graphs2.AbstractGraph 抽象类
package data_structure.graphs2;
import java.util.List;
import java.util.Optional;
import java.util.Vector;
public abstract class AbstractGraph<V,W extends Comparable<? super W>> implements Graph<V,W>{
/**
* 获得图中的所有节点
* @return
*/
abstract List<? extends Vertex<V>> getVertices();
/**
* 获取节点Vertex中ext变量列表
* @param i
* @return
*/
abstract Vector getExts(int i);
abstract Vector getExts(String n);
/**
* 获取节点iext变量中第j个(从0开始)变量的值
* @param i
* @param j
* @return
*/
abstract Optional getExt(int i, int j);
abstract Optional getExt(String n,int j);
/**
* 设置节点i的ext变量量列表中第j个(从0开始)变量的值
* @param i
* @param value
*/
abstract void setExt(int i,int j,Object value);
abstract void setExt(String n,int j,Object value);
/**
* 不管ext边量是否已被其它类型的变量赋值,都要强制赋值
* @param i
* @param j
* @param value
*/
abstract void setExtIgnore(int i,int j,Object value);
abstract void setExtIgnore(String n,int j,Object value);
}
(4)g2.txt
0,3,0,4,4,0,0,0,0,0
3,0,10,0,2,3,0,0,0,0
0,10,0,0,0,6,1,0,0,0
4,0,0,0,5,0,0,6,0,0
4,2,0,5,0,11,0,2,1,0
0,3,6,0,11,0,2,0,3,11
0,0,1,0,0,2,0,0,0,8
0,0,0,6,2,0,0,0,4,0
0,0,0,0,1,3,0,4,0,7
0,0,0,0,0,11,8,0,7,0
(5)data_structure.graphs.Utils 类
package data_structure.graphs;
import scala.Int;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* 辅助图结构的工具类,例如有从文件读取矩阵的方法
*/
public class Utils {
// public static <T,R> List<R> parseMatrixFromFile(String filePath,Function<String,? extends T> spliter,Function<T, R> parser) throws IOException {
// return Files.lines(Paths.get(filePath))
// .map(line -> {
// return parser.apply(spliter.apply(line));
// }).collect(
// Collectors.toList()
// );
// }
// public static Double[][] getMatrixFromFile(String filePath) throws IOException {
// List<Object> list = parseMatrixFromFile(
// filePath,
// l->l.split(","),
// arr->{
// ArrayList<Double> list1 = new ArrayList<Double>();
// for (String s : arr) {
// list1.add(Double.parseDouble(s));
// }
// return list1;
// });
// int n=list.size();
// int m=((List)list.get(0)).size();
// Double[][] mat=new Double[n][];
// for (int i = 0; i < n; i++) {
// for(int j=0;j<m;j++)
// mat[i][j]=(double)((List)list.get(i)).get(j);
// }
// return mat;
// }
public static Integer[][] getIntMatrixFromFile(int n,String filePath) throws IOException {
Integer[][] mat=new Integer[n][];
//从文件读取行形成流stream对象并加以处理
List<Integer[]> list = Files.lines(Paths.get(filePath))
.map(line -> {
String[] split = line.split(",");
Integer[] digs=new Integer[split.length];
for (int i = 0; i < digs.length; i++) {
digs[i]=Integer.parseInt(split[i]);
}
return digs;
}).collect(
Collectors.toList()
);
for (int i = 0; i < mat.length; i++) {
mat[i]=list.get(i);
}
return mat;
}
public static void main(String[] args) throws IOException {
Integer[][] m = getIntMatrixFromFile(5,"adjacencyMatrix.txt");
for (int i = 0; i < m.length; i++) {
System.out.println(Arrays.toString(m[i]));
}
}
}
(6)data_structure.graphs2.Vertex 类
package data_structure.graphs2;
import java.util.LinkedList;
import java.util.Vector;
/**
* 节点,是图的组成元素之一,代表图中的一个节点。
* @param <V>
*/
public class Vertex<V> {
private String name=null;
private V data=null;
/**
* 额外增加的属性,为了在需要的时候使用。
* 以便处理额外的需求,例如计算图的入度等。用{@code setExt(int j,Object obj,boolean ignoreClass)}或相关的多态方法可以更新此属性的
* 值,此方法对类型进行检查。
*/
private Vector exts=null;
public int index=-1;
Vertex(V data) {
this.data = data;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public V getData() {
return data;
}
public void setData(V data) {
this.data = data;
}
public Vector getExts() {
return exts;
}
/**
* 按照指定容量初始化或重置ext的Vector集合
* @param c
*/
public void setExts(int c){
if(this.exts==null){
Vector vector = new Vector(c);
for (int i = 0; i < c; i++) {
vector.add(null);
}
this.exts=vector;
}
for (int i = 0; i < c - this.exts.size(); i++) {
this.exts.add(null);
}
}
/**
* ext变量的Vector集合重置——给ext变量分配空间,并将其中的元素清空
*/
public void setExts() {
if(exts==null)
exts=new Vector<>();
}
/**
* 如果{@code pos}是合法的,则执行更新操作,否则旧值不会被更新。
* @param pos
* @param v
* @param ignoreClass
*/
public void setExt(int pos,Object v,boolean ignoreClass){
setExts(pos+1);
if(exts.size()>pos && pos >=0){
if(ignoreClass)
exts.set(pos,v);
else {
Object o = exts.get(pos);
if(o==null || o.getClass().isInstance(v)){
exts.set(pos,v);
}
else
throw new IllegalArgumentException("该变量已有其它类型的值,参数Object v 不是该变量类型的实例");
}
}
}
public Object getExt(int pos){
setExts(pos+1);
return exts.get(pos);
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public static void main(String[] args) {
Vertex<Integer> v = new Vertex<Integer>(0);
v.setExt(0,"abc",false);
v.setExt(0,123,true);
System.out.println(v.getExts());
}
}
(7)data_structure.graphs2.WithEdgesVertex 类
package data_structure.graphs2;
import java.util.LinkedList;
import java.util.Vector;
public class WithEdgesVertex<V,W extends Comparable<? super W>> extends Vertex<V>{
private LinkedList<Edge<W>> edges;
WithEdgesVertex(V data) {
super(data);
}
public LinkedList<Edge<W>> getEdges() {
return edges;
}
public void setEdges(LinkedList<Edge<W>> edges) {
this.edges = edges;
}
}
3.此贪心算法可行性分析
当采用贪心算法的策略进行算法设计时,必须小心,因为在算法每一阶段的最优选择的总和未产生问题的最优解。要证明一个贪心算法是否是正确的,其实可以做做减法,让最小生成树的总价值减去图中其他所有生成树的总价值,若小于或等于零,则说明按此种算法生成的最小生成树是最小的。其实,其他贪心算法也可以用减法,如操作系统让作业平均运行最短的调度算法。
给定一个连通的无向图G,图G中一定有很多生成树T,它们都连通且无圈,只要增加原图G中存在的一条边e到生成树T上,T中便产生了一个包含此边e的一个圈,可以从此圈中去掉其中的一条边来产生另外一个不同的生成树。设T0是按Prime算法生成的一个最小生成树,通过上述的加边并替换的方法,能够将T0变成G中的任意一个生成树T,反之亦然,因为对于G中的任意一个T,若不存在边e,而边e在T0中,则可以按上述方法增加边e,并从T中替换T0中不存在的边e',让这个圈消失,这样一直进行下去,直到T含有T0中的所有的边,这时T就成为了T0了。记cost(T)为生成树T的总价值,weight(e)为边e的价值,下面来说明cost(T0)-cost(T)<=0。将G中的一条边e添加到G的所生成的最小生成树T0,则在包含e的圈中,weight(e)大于或等于圈中的其他边的价值,不会小于圈中其他的边,因为Prime算法每次选择与当前所选的节点所关联的价值最短的边,对边e,设它所关联的连个节点为ui和uj,不能同时选择这两个节点ui和uj,若先选择了ui(此时ui和uj并不连通),则必更新与之关联的边e的价值(如果存在其他价值更小的边,待处理完后就会更新),若weight(e)是最小的,则e必在T0中,与先前的条件不符。于是,cost(T0)-cost(T)由于将大边替换了小的,该差总是小于或等于零的。
由此便证明了Prime算法是行得通的。同样若每次选择最小的边,让这些边逐渐地组成一个树,这样的算法也是成立的,这就是下面要向你介绍的另一种算法——Kruskal算法。
4.Kruskal算法
每次选择一条最短的边即可。如下图:
算法进行当中注意不能成圈。
/**
* Kruskal算法——另一种实现最小生成树的生成的贪心算法。
* 这里使用的是无向图。这里使用了并查集这一数据结构来判
* 断节点间的连通性,其实还可以通过使用更加灵巧的并查集
* 来降低O(|E|*|E|)的时间复杂度。
* @param g 图的通用接口对象
* @param <V> 图中节点内容
* @return 这里的生成树用邻接矩阵来表示,也可以用向上
* 面的方法那样的int[][] {len,pre}来表示,只不过要
* 进行深度优先遍历以确定树根的方向并加以调整(如果对于
* 边(u,v),遇到pre[u]!=-1 && pre[v]!=-1的情况
* )。根据树的特性,可以证明,任何一个树,都可以用
* “len,pre”来表示。
*/
public static <V> int[][] kruskalForMinSpanTree(Graph<V,Integer> g){
int n=g.getVerticesSize();
int[][] edges=new int[n][n];//邻接矩阵
int[] disjset=new int[n];//并查集
for(int i=0;i<n;i++){
disjset[i]=i;//根
}
int u=-1,v=-1;//边所关联的那两个节点
int selectedSum=0;//对所选的边计数
/*选择|V|-1条边的迭代过程*/
while(selectedSum < n-1){
/*寻找最短边的过程*/
u=v=-1;
int min=-1;
for(int i=0;i<n;i++){
Iterator<Integer> itr = g.getAdjacentVertices(i);
int count=0;
while(itr.hasNext()){
int j=itr.next();
if(disjset[i] != disjset[j]){//若两个节点不等价(连通)
if(min==-1){
u=i;
v=j;
min=g.getWeight(u,count);
}else if(min > g.getWeight(i,count)){
u=i;
v=j;
min=g.getWeight(u,count);
}
}
count++;
}
}
/*合并两个节点所在对等价类,选择边*/
int name=disjset[v];//以免中途改变(若t==v)
for(int t=0;t<disjset.length;t++){
if(disjset[t] == name)
disjset[t]=disjset[u];
}
edges[u][v]=edges[v][u]=min;
selectedSum++;
}
return edges;
}