无向图API
public class Graph{
Graph(int V);//构造一个含有V个顶点但不含有边的图
Graph(In in);//从标准输入读入一幅图,先是顶点数,然后是边的个数,然后是用顶点对表示的边,
int V();//获取顶点数
int E();//获取边数
void addEdge(int v,int w);//添加一个v-w的边
Iterable<Integer> adj(int v);//遍历输出和v相邻的所有顶点
String toString();//对象的字符串表示
}
最常用的图处理代码:
计算v的度数:
public static int degree(Graph G,int v){
int degree = 0;
for(int w:G.adj(v)){
degree++;
}
return degree;
}
计算所有顶点的最大度数:
public static int maxDegree(Graph G){
int max=0;
for(int v=0;v<G.V();v++){
if(degree(G,v)>max){
max = degree(G,v);
}
}
return max;
}
计算所有顶点的平均度数:
public static double avgDegree(Graph G){
return 2*G.E()/G.V();
}
计算自环的个数:
public static int numberOfSelfLoops(Graph G){
int count = 0;
for(int v=0;v<G.V();v++){
for(int w :G.adj(v)){
if(v == w);count++;
}
}
return count/2;
}
图的邻接表的字符串表示:
public String toString(){
String s= V+"顶点,"+E+"边\n";
for(int v=0;v<V;v++){
s+=v+":";
for(int w:this.adj(v)){
s+=w+" ";
}
s+="\n";
}
return s;
}
使用的数据结构:邻接表数组
以顶点为索引的列表数组,其中的每个元素都是 和该顶点相邻的顶点列表。
特点:
Graph数据类型
package bag_queue_stack;
import java.util.Scanner;
public class Graph{
private final int V;
private int E;
private Bag<Integer>[] adj;
public Graph(int V){
this.V = V;this.E = 0;
adj = (Bag<Integer>[]) new Bag[V];//创建邻接表
for(int v=0;v<V;v++){
adj[v] = new Bag<Integer>();
}
}
public Graph(Scanner in){
this(in.nextInt());
int E = in.nextInt();
for(int i=0;i<E;i++){
int o = in.nextInt();
int p = in.nextInt();
addEdge(o,p);
}
}
public int V(){return V;}
public int E(){return E;}
public void addEdge(int v,int w){
adj[v].add(w);
adj[w].add(v);
E++;
}
public Iterable<Integer> adj(int v){
return (Iterable<Integer>) adj[v];
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Graph G = new Graph(scanner);
DepthFirstSearch searchs = new DepthFirstSearch(G,0);
// for(int v=0;v
// if(searchs.marked(v)){
// System.out.print(v+" ");
// }
// }
// for(int i:G.adj(1)){
// System.out.println(i);
// }
DepthFirstPaths paths = new DepthFirstPaths(G,0);
for(int i:paths.pathTo(3)){
System.out.println(i);
}
}
}
判断一个顶点与其他顶点是否连通
public class Graph{
Search(Graph G,int s);//找到和起点s连通的点
boolean marked(int v);//v和s是连通的吗
int count();//与s连通的点的个数
}
这个方法是用来判断一个图是不是连通图(从一个顶点出发,可以到达任何节点),其中marked()方法是关键,判断两个顶点是否连通,将通过深度优先搜索算法求解。
package bag_queue_stack;
public class DepthFirstSearch{
private boolean[] marked;
private int count;
public DepthFirstSearch(Graph G,int s){
marked = new boolean[G.V()];
dfs(G,s);
}
private void dfs(Graph G,int v){
marked[v] = true;
count++;
for(int w:G.adj(v)){
if(!marked[w]){
dfs(G,w);
}
}
}
public boolean marked(int w){
return marked[w];
}
public int count(){
return count;
}
}
深度优先搜索算法可以用来解决“两个给定的顶点是否连通”,“图中有多少个连通子图”等问题。
public class Paths{
Paths(Graph G,int s);//在G中找出所有起点为s的路径
boolean hasPathTo(int v);//是否存在从s到v的路径
Iterable<Integer> pathTo(int v);//s到v的路径,如果不存在则返回null
}
使用深度优先搜索寻找路径
import java.util.Stack;
public class DepthFirstPaths {
private boolean[] marked;//这个顶点上调用过dfs()了吗
private int[] edgeTo;//从起点到一个顶点的已知路径上的最后一个顶点
private final int s;//起点
public DepthFirstPaths(Graph G, int s){
marked = new boolean[G.V()];
edgeTo = new int[G.V()];
this.s = s;
dfs(G,s);
}
private void dfs(Graph G,int v){
marked[v] = true;
for(int w:G.adj(v)){
if(!marked[w]){
edgeTo[w] = v;//表示v-w是第一次访问w时经过的边。是以顶点编号为索引的数组
dfs(G,w);
}
}
}
public boolean hasPathTo(int v){
return marked[v];
}
public Iterable<Integer> pathTo(int v){
if(!hasPathTo(v)){
return null;
}
Stack<Integer> path = new Stack<Integer>();
for(int x = v;x!=s;x=edgeTo[x]){//从后往前查找,查找第一次到达v时经过了哪个节点,再查这个节点,一直查到起点为止。
path.push(x);
}
path.push(s);
return path;
}
}
深度优先搜索得到从给定起点到任意标记顶点的路径所需要的时间与路径的长度成正比。
解决的问题:
要找到从s到v的最短路径,从s开始,在所有由一条边就可以到达的顶点中寻找v,如果找不到就继续在与s距离两条边的所有顶点中查找
package bag_queue_stack;
import java.util.Stack;
public class BreadthFirstPaths {
private boolean[] marked;//到达该顶点的最短路径已知吗
private int[] edgeTo;
private final int s;//起点
public BreadthFirstPaths(Graph G,int s){
marked = new boolean[G.V()];
edgeTo = new int[G.V()];
this.s = s;
bfs(G,s);
}
private void bfs(Graph G,int s){
Queue<Integer> queue = new Queue<Integer>();
marked[s] = true;//标记起点
queue.enqueue(s);//将它加入队列
while (!queue.isEmpty()){
int v = queue.dequeue();//队列中删除一个顶点
for(int w:G.adj(v)){
if(!marked[w]){
edgeTo[w] = v;//w是从v来的,且是第一次从v来,即从v来最近。
marked[w] = true;
queue.enqueue(w);//将它加入队列中
}
}
}
}
public boolean hasPathTo(int v){
return marked[v];
}
public Iterable<Integer> pathTo(int v){
if(!hasPathTo(v)){
return null;
}
Stack<Integer> path = new Stack<Integer>();
for(int x = v;x!=s;x=edgeTo[x]){
path.push(x);
}
path.push(s);
return path;
}
}
广度优先搜索最坏情况下所需要的时间和V+E成正比。
连通图:若G中任意两个不同的顶点v和w都连通,则G为连通图
连通分量:无向图G的极大连通子图称为G的最强连通分量,不相交的连通子图称为图的连通分量
API
public class CC{
CC(Graph G);
boolean connected(int v,int w);//v和w连通吗
int count();//连通分量数
int id(int v);//v所在的连通分量的标识符
}
使用深度优先搜索找出图中的所有连通分量
package bag_queue_stack;
public class CC {
private boolean[] marked;
private int[] id;//以顶点作为索引的数组id,将同一个连通分量中的顶点和连通分量的标识符关联起来
//如果v属于第i个连通分量,则id[v]=i;
private int count;//表示第几个连通分量,最后的数就代表了连通分量的个数
public CC(Graph G){
marked = new boolean[G.V()];
id = new int[G.V()];
for(int s=0;s<G.V();s++){
if(!marked[s]){
//s的一次递归调用,能访问到所有与它连通的顶点
dfs(G,s);
//到这里说明v的连通顶点已经访问完毕
count++;
}
}
}
private void dfs(Graph G,int v){
marked[v] = true;
id[v] = count; //如果v属于第i个连通分量,则id[v]=i;
for(int w:G.adj(v)){
if(!marked[w]){
dfs(G,w);
}
}
}
public boolean connected(int v,int w){
return id[v] == id[w];
}
public int id(int v){
return id[v];
}
public int count(){
return count;
}
}
检测环:给定的图有环吗?
package bag_queue_stack;
public class Cycle {
private boolean[] marked;
private boolean hasCycle;
public Cycle(Graph G){
marked = new boolean[G.V()];
for(int s=0;s<G.V();s++){
if(!marked[s]){
dfs(G,s,s);
}
}
}
//第一个参数v表示当前检测的点,u表示是从u点来的,如果检测出v相邻的点中有已经被标记过的,并且还不是u的话
//说明有环存在
private void dfs(Graph G,int v,int u){
marked[v] = true;
for(int w:G.adj(v)){
if(!marked[w]){
dfs(G,w,v);
}else if(w!=u){//w点被访问过了,并且它还不是从w-v边上的点,说明现在v点与链上的两个点相邻,就形成了环
hasCycle = true;
}
}
}
public boolean hasCycle(){
return hasCycle;
}
}
双色问题:能够用两种颜色将图中所有点着色,使得任意一条边的两个端点的颜色都不相同。等价与二分图
package bag_queue_stack;
public class TwoColor {
private boolean[] marked;
private boolean[] color;
private boolean isTwoColorable = true;
public TwoColor(Graph G){
marked = new boolean[G.V()];
color = new boolean[G.V()];
for(int s=0;s<G.V();s++){
if(!marked[s]){
dfs(G,s);
}
}
}
private void dfs(Graph G,int v){
marked[v] = true;
for(int w:G.adj(v)){
if(!marked[w]){
color[w] = !color[v];
dfs(G,w);
}else if(color[w]==color[v]){
isTwoColorable = false;
}
}
}
public boolean isBiparite(){
return isTwoColorable;
}
}
有向图数据结构:
public class Digraph{
Digraph(int V);
Digraph(In in);
int V();
int E();
void addEdge(int v,int w);
Iterable<Integer> adj(int v);
Digraph reverse();//该图的反向图
String toString();
}
package bag_queue_stack;
public class Digraph {
private final int V;
private int E;
private Bag<Integer>[] adj;
public Digraph(int V){
this.V = V;
this.E = 0;
adj = (Bag<Integer>[]) new Bag[V];
for(int v=0;v<V;v++){
adj[v] = new Bag<Integer>();
}
}
public Digraph(Scanner in){
this(in.nextInt());
int E = in.nextInt();
for(int i=0;i<E;i++){
int o = in.nextInt();
int p = in.nextInt();
addEdge(o,p);
}
}
public int V(){
return V;
}
public int E(){
return E;
}
public void addEdge(int v,int w){
adj[v].add(w);
E++;
}
public Iterable<Integer> adj(int v){
return adj[v];
}
public Digraph reverse(){
Digraph R = new Digraph(V);
for(int v=0;v<V;v++){
for(int w:adj(v)){
R.addEdge(w,v);
}
}
return R;
}
}
#### 有向图的可达性
package bag_queue_stack;
import java.util.Scanner;
public class DirectedDFS {
private boolean[] marked;
public DirectedDFS(Digraph G,int s){//在G中找到从s可到达的所有顶点
marked = new boolean[G.V()];
dfs(G,s);
}
public DirectedDFS(Digraph G,Iterable<Integer> sources){//在G中找到从sources中的所有顶点可以到达的所有顶点
marked = new boolean[G.V()];
for(int s:sources){
if(!marked[s]){
dfs(G,s);
}
}
}
private void dfs(Digraph G,int v){
marked[v] = true;
for (int w:G.adj(v)){
if(!marked[w]){
dfs(G,w);
}
}
}
public boolean marked(int v){
return marked[v];
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Digraph G = new Digraph(scanner);
DirectedDFS directedDFS = new DirectedDFS(G,0);
Bag<Integer> sources = new Bag<Integer>();
sources.add(1);
sources.add(2);
DirectedDFS directedDFS2 = new DirectedDFS(G,sources);
// for(int v=0;v
// if(directedDFS.marked(v)){
// System.out.print(v+" ");
// }
// }
for(int v=0;v<G.V();v++){
if(directedDFS2.marked(v)){
System.out.print(v+" ");
}
}
}
}
在JVM中的垃圾回收的可达性分析算法就是类似的。
拓扑排序:给定一副有向图,将所有的顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素。
判断有没有环:
package bag_queue_stack;
import java.util.Stack;
public class DirectedCycle {
private boolean[] marked;
private int[] edgeTo;
private Stack<Integer> cycle;//有向环中的所有顶点
private boolean[] onStack;//递归调用栈上的所有顶点
public DirectedCycle(Digraph G){
onStack = new boolean[G.V()];
edgeTo = new int[G.V()];
marked = new boolean[G.V()];
for(int v=0;v<G.V();v++){
if(!marked[v]){
dfs(G,v);
}
}
}
private void dfs(Digraph G,int v){
onStack[v] = true;
marked[v] = true;
for(int w:G.adj(v)){
if(this.hasCycle())return;
else if(!marked[w]){
edgeTo[w] = v;
dfs(G,w);
}else if(onStack[w]){
cycle = new Stack<Integer>();
for(int x=v;x!=w;x=edgeTo[x]){
cycle.push(x);
}
cycle.push(w);
cycle.push(v);
}
}
onStack[v] = false;
}
public boolean hasCycle(){
return cycle!=null;
}
public Iterable<Integer> cycle(){
return cycle;
}
}
前序:在递归调用之前将顶点加入队列
后序:在递归调用之后将顶点加入队列
逆后序:在递归调用之后将顶点压入栈
package bag_queue_stack;
import java.util.Stack;
public class DepthFirstOrder {
private boolean[] marked;
private Queue<Integer> pre;
private Queue<Integer> post;
private Stack<Integer> reversePost;
public DepthFirstOrder(Digraph G){
pre = new Queue<Integer>();
post = new Queue<Integer>();
reversePost = new Stack<Integer>();
marked = new boolean[G.V()];
for(int v=0;v<G.V();v++){
if(!marked[v]){
dfs(G,v);
}
}
}
private void dfs(Digraph G,int v){
pre.enqueue(v);
marked[v] = true;
for(int w:G.adj(v)){
if(!marked[w]){
dfs(G,w);
}
}
post.enqueue(v);
reversePost.push(v);
}
public Iterable<Integer> pre(){
return pre;
}
public Iterable<Integer> post(){
return post;
}
public Iterable<Integer> reversPost(){
return reversePost;
}
}
一副有向图的拓扑排序即为所有顶点的逆后序排序。
使用深度优先搜索对有向图进行拓扑排序所需要的时间和V+E成正比
package bag_queue_stack;
//拓扑排序
public class Topological {
private Iterable<Integer> order;//顶点的拓扑顺序
public Topological(Digraph G){
DirectedCycle cyclefinder = new DirectedCycle(G);
if(!cyclefinder.hasCycle()){
DepthFirstOrder dfs = new DepthFirstOrder(G);
order = dfs.reversPost();
}
}
public Iterable<Integer> order(){
return order;
}
public boolean isDAG(){
return order!=null;
}
}
强连通性:如果两个顶点v和w是互相可达的,则称他们为强连通的。如果任意两个顶点都是强连通的,则这幅图也是强连通的。
强连通分量API
public class SCC{
SCC(Digraph G);
boolean stronglyConnected(int v,int w);//v和w是强连通的吗
int count();//强连通分量总数
int id(int v);//v所在的强连通分量标识符
}
Kosaraju算法:
package bag_queue_stack;
public class KosarajuSCC {
private boolean[] marked;
private int[] id;
private int count;
public KosarajuSCC(Digraph G){
marked = new boolean[G.V()];
DepthFirstOrder order = new DepthFirstOrder(G.reverse());
for(int s:order.reversPost()){
if(!marked[s]){
dfs(G,s) ;
count++;
}
}
}
private void dfs(Digraph G,int v){
marked[v] = true;
id[v] = count;
for(int w:G.adj(v)){
if(!marked[w]){
dfs(G,w);
}
}
}
public boolean stronglyConnected(int v,int w){
return id[v] == id[w];
}
public int id(int v){
return id[v];
}
public int count(){
return count;
}
}
Kosaraju算法的预处理所需要的时间和空间与V+E成正比且支持常数时间的有向图强连通性的查询。
闭包:有向图G的传递传递闭包是由相同的一组顶点组成的另一副有向图,在传递闭包中存在一条从v指向w的边,当且仅当在G中w是从v可达的。
顶点对的可达性:
package bag_queue_stack;
public class TransitiveClosure {
private DirectedDFS[] all;
public TransitiveClosure(Digraph G){
all = new DirectedDFS[G.V()];
for(int v=0;v<G.V();v++){
all[v] = new DirectedDFS(G,v);
}
}
public boolean reachable(int v,int w){
return all[v].marked(w);
}
}
图的生成树是它的一颗含有其所有顶点的无环连通子图,一副加权无向图的最小生成树是它的一颗权值和最小的生成树。
原理:
加权边API: Edge
package 最小生成树;
public class Edge implements Comparable<Edge>{
private final int v;//顶点之一
private final int w;//另一个顶点
private final double weight;//边的权重
public Edge(int v,int w,double weight){
this.v = v;
this.w = w;
this.weight = weight;
}
public double weight(){
return weight;
}
public int either(){
return v;
}
public int other(int vertex){
if(vertex==v){
return w;
}else {
return v;
}
}
@Override
public int compareTo(Edge that) {
if(this.weight()<that.weight()){
return -1;
}else if(this.weight()>that.weight()){
return 1;
}else {
return 0;
}
}
public String toString(){
return String.format("%d-%d %.2f",v,w,weight);
}
}
只需要将无向图Graph中的顶点Integer类型改为Edge类型。
package 最小生成树;
import bag_queue_stack.Bag;
import java.util.Scanner;
public class EdgeWeightedGraph {
private final int V;//顶点总数
private int E;//边的总数
private Bag<Edge>[] adj;//邻接表
public EdgeWeightedGraph(int V){
this.V = V;
this.E = 0;
adj = new Bag[V];
for(int v=0;v<V;v++){
adj[v] = new Bag<Edge>();
}
}
public EdgeWeightedGraph(Scanner in){
this(in.nextInt());//这是调用public EdgeWeighted(int V)
int E = in.nextInt();
for(int i=0;i<E;i++){
int o = in.nextInt();
int p = in.nextInt();
double d = in.nextDouble();
Edge e = new Edge(o,p,d);
addEdge(e);
}
}
public int V(){
return V;
}
public int E(){
return E;
}
public void addEdge(Edge e){
int v = e.either();
int w = e.other(v);
adj[v].add(e);
adj[w].add(e);
E++;
}
public Iterable<Edge> adj(int v){
return adj[v];
}
}
最小生成数API:
public class MST{
MST(EdgeWeightedGraph G);
Iterable<Edge> edges();//最小生成树的所有边
double weight();//最小生成树的权重
}
该算法一开始只有一个顶点,然后每次总是将下一条连接树中的顶点与不在树中的顶点且权重最小的边加入树中。
数据结构:
算法过程:
使用一个私有方法visit()来为树添加一个顶点,将它标记为已访问,并将与它关联的所有未失效的边加入优先队列,以保证队列含有所有连接树顶点和非树顶点的边。代码的内循环是算法的具体实现:从优先队列中取出一条边并将它添加到树中,并把这条边的另一个顶点也添加到树中,然后用新的顶点作为参数调用visit方法来更新横切边的集合。weight()方法可以遍历树的所有边并得到它们的权重和(延迟实现),或者用一个运行时的中间变量统计总权重(即时实现)
prim算法延迟实现空间与E成正比:因为优先队列中最多有E条边。所需时间与ElogE成正比:因为优先队列的插入一次需要logE,删除最小的需要2logE,最多插入E次。
最小生成树的Prim算法的延迟实现
package 最小生成树;
import bag_queue_stack.Queue;
import 排序.MinPQ;
public class LazyPrimMST {
private boolean[] marked;//最小生成树的顶点
private Queue<Edge> mst;//最小生成树的边
private MinPQ<Edge> pq;//横切边(包括失效的边)
public LazyPrimMST(EdgeWeightedGraph G){
pq = new MinPQ<Edge>(G.E());
marked = new boolean[G.V()];
mst = new Queue<Edge>();
visit(G,0);//假设G是连通的
while (!pq.isEmpty()){
Edge e = pq.delMin();//从pq中得到权重最小的边
int v = e.either(),w = e.other(v);
//如果这条边的两个顶点都在树中了,则为失效边,跳过失效的边
if(marked[v]&&marked[w]){
continue;
}
mst.enqueue(e);//将边添加到树中
if(!marked[v]){//将顶点添加到树中
visit(G,v);
}
if(!marked[w]){
visit(G,w);
}
}
}
private void visit(EdgeWeightedGraph G,int v){
//标记顶点v并将所有链接v和未被标记的顶点的边加入pq
marked[v] = true;
for(Edge e:G.adj(v)){
if(!marked[e.other(v)]){
pq.insert(e);
}
}
}
public Iterable<Edge> edges(){
return mst;
}
public double weight(){
double w = 0.0;
for(Edge e:mst){
w+=e.weight();
}
return w;
}
}
Prim算法的即时实现:
我们只会在优先队列中保存每个非树顶点w的一条边,将它和树中的顶点连接起来权重最小的那条边。
它将懒实现中的marked[] 和mst[]换成了两个顶点索引的数组,edgeTo[]和distTo[]。
最小生成树的Prim算法(即时版本)
package 最小生成树;
import bag_queue_stack.Queue;
import 排序.IndexMinPQ;
//最小生成树Prim算法即时版本
import java.util.Scanner;
public class PrimMST {
private static final double FLOATING_POINT_EPSILON = 1E-12;
private Edge[] edgeTo; // 距离树最近的边
private double[] distTo; // edgeTo对应的权重
private boolean[] marked; // marked[v] = true if v on tree, false otherwise
private IndexMinPQ<Double> pq;
public PrimMST(EdgeWeightedGraph G) {
edgeTo = new Edge[G.V()];
distTo = new double[G.V()];
marked = new boolean[G.V()];
for (int v = 0; v < G.V(); v++)
distTo[v] = Double.POSITIVE_INFINITY;
pq = new IndexMinPQ<Double>(G.V());
distTo[0] = 0.0;
pq.insert(0,0.0);
while (!pq.isEmpty()){
visit(G,pq.delMin());//将最近的顶点添加到树中
}
}
private void visit(EdgeWeightedGraph G,int v){
//将顶点v添加到树中,更新数据
marked[v] = true;
for(Edge e:G.adj(v)){
int w = e.other(v);
if(marked[w]){
continue;
}
if(e.weight()<distTo[w]){
//链接w和树的最佳边变为e
edgeTo[w] = e;
distTo[w] = e.weight();
if(pq.contains(w)){
pq.change(w,distTo[w]);
}else {
pq.insert(w,distTo[w]);
}
}
}
}
public Iterable<Edge> edges() {
Queue<Edge> mst = new Queue<Edge>();
for (int v = 0; v < edgeTo.length; v++) {
Edge e = edgeTo[v];
if (e != null) {
mst.enqueue(e);
}
}
return mst;
}
public double weight() {
double weight = 0.0;
for (Edge e : edges())
weight += e.weight();
return weight;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
EdgeWeightedGraph G = new EdgeWeightedGraph(scanner);
PrimMST mst = new PrimMST(G);
for (Edge e : mst.edges()) {
System.out.println(e);
}
System.out.printf("%.5f\n", mst.weight());
}
}
即时算法的时间与ElogV成正比:算法会进行V次插入操作,V次删除最小元素的操作,和E次改变优先级的操作。
所需的空间和V成正比:优先队列中边数最多为V,且使用了三个由顶点索引的数组。
按照边的权重顺序,将边加入最小生成树中,加入的边不会与已经加入的边构成环,直到树中含有V-1条边为止。
KrusKal算法空间与E成正比,时间与ElogE成正比。
KrusKal算法一般比Prim算法要慢,因为在处理每条边时除了两种算法都要完成的优先队列操作之外,还需要进行一次connect操作。
最小生成树的Kruskal算法
package 最小生成树;
import bag_queue_stack.Queue;
import unionfind.UF;
import 排序.MinPQ;
import java.util.Scanner;
public class KruskalMST {
private static final double FLOATING_POINT_EPSILON = 1E-12;
private double weight; // weight of MST
private Queue<Edge> mst = new Queue<Edge>(); // edges in MST
/**
* Compute a minimum spanning tree (or forest) of an edge-weighted graph.
* @param G the edge-weighted graph
*/
public KruskalMST(EdgeWeightedGraph G) {
// more efficient to build heap by passing array of edges
MinPQ<Edge> pq = new MinPQ<Edge>(G.E());
for (Edge e : G.edges()) {
pq.insert(e);
}
// run greedy algorithm
UF uf = new UF(G.V());
while (!pq.isEmpty() && mst.size() < G.V() - 1) {
Edge e = pq.delMin();//获取权重最小的边
int v = e.either();
int w = e.other(v);
if (uf.find(v) != uf.find(w)) { // v-w does not create a cycle
uf.union(v, w); // merge v and w components
mst.enqueue(e); // add edge e to mst
weight += e.weight();
}
}
// check optimality conditions
assert check(G);
}
/**
* Returns the edges in a minimum spanning tree (or forest).
* @return the edges in a minimum spanning tree (or forest) as
* an iterable of edges
*/
public Iterable<Edge> edges() {
return mst;
}
/**
* Returns the sum of the edge weights in a minimum spanning tree (or forest).
* @return the sum of the edge weights in a minimum spanning tree (or forest)
*/
public double weight() {
return weight;
}
// check optimality conditions (takes time proportional to E V lg* V)
private boolean check(EdgeWeightedGraph G) {
// check total weight
double total = 0.0;
for (Edge e : edges()) {
total += e.weight();
}
if (Math.abs(total - weight()) > FLOATING_POINT_EPSILON) {
System.err.printf("Weight of edges does not equal weight(): %f vs. %f\n", total, weight());
return false;
}
// check that it is acyclic
UF uf = new UF(G.V());
for (Edge e : edges()) {
int v = e.either(), w = e.other(v);
if (uf.find(v) == uf.find(w)) {
System.err.println("Not a forest");
return false;
}
uf.union(v, w);
}
// check that it is a spanning forest
for (Edge e : G.edges()) {
int v = e.either(), w = e.other(v);
if (uf.find(v) != uf.find(w)) {
System.err.println("Not a spanning forest");
return false;
}
}
// check that it is a minimal spanning forest (cut optimality conditions)
for (Edge e : edges()) {
// all edges in MST except e
uf = new UF(G.V());
for (Edge f : mst) {
int x = f.either(), y = f.other(x);
if (f != e) uf.union(x, y);
}
// check that e is min weight edge in crossing cut
for (Edge f : G.edges()) {
int x = f.either(), y = f.other(x);
if (uf.find(x) != uf.find(y)) {
if (f.weight() < e.weight()) {
System.err.println("Edge " + f + " violates cut optimality conditions");
return false;
}
}
}
}
return true;
}
/**
* Unit tests the {@code KruskalMST} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// int in = scanner.nextInt();
EdgeWeightedGraph G = new EdgeWeightedGraph(scanner);
KruskalMST mst = new KruskalMST(G);
for (Edge e : mst.edges()) {
System.out.println(e);
}
System.out.printf("%.5f\n", mst.weight());
}
}