有向边
public class Edge<E extends Number & Comparable> implements Comparable<Edge>{
//定义改变的起始节点和终止节点
private int from;
private int to;
//该边的权值
private E weight;
public Edge(int v, int w, E weight)
{
this.from = v;
this.to = w;
this.weight = weight;
}
public Edge(Edge<E> e)
{
this.from = e.from;
this.to = e.to;
this.weight = e.weight;
}
//获取起点
public int v(){
return from;
}
//获取终点
public int w(){
return to;
}
//获取权值
public E wt(){
return weight;
}
// 给定一个顶点, 返回另一个顶点
public int other(int x){
assert x == from || x == to;
return x == from ? to : from;
}
//输出边的具体信息
@Override
public String toString() {
return "" + from + "-" + to + ": " + weight;
}
@Override
public int compareTo(Edge that) {
int num=weight.compareTo(that.wt());
return num;
}
}
带权图
// 带权图的接口
public interface WeightedGraph<E extends Number & Comparable> {
public int V();
public int E();
public void addEdge(Edge<E> edge);
boolean hasEdge(int v, int w);
void show();
public Iterable<Edge<E>> adj(int v);
}
稠密图(带权图)
public class DenseWeightedGraph<E extends Number & Comparable> implements WeightedGraph<E>{
private int n; // 节点数
private int m; // 边数
private boolean directed; // 是否为有向图
private Edge[][] g; // 图的具体数据
// 构造函数
public DenseWeightedGraph(int n , boolean directed ){
assert n >= 0;
this.n = n;
this.m = 0; // 初始化没有任何边
//false表示无向图
this.directed = directed;
// g初始化为n*n的有向边矩阵
g = new Edge[n][n];
}
@Override
public int V(){ return n;} // 返回节点个数
@Override
public int E(){ return m;} // 返回边的个数
// 向图中添加一个边
@Override
public void addEdge(Edge edge){
assert edge.v() >= 0 && edge.v() < n ;
assert edge.w() >= 0 && edge.w() < n ;
if( hasEdge( edge.v() , edge.w() ) ){
return;
}
g[edge.v()][edge.w()] = new Edge(edge);
if( edge.v() != edge.w() && !directed ){
g[edge.w()][edge.v()] = new Edge(edge.w(), edge.v(), edge.wt());
}
m ++;
}
// 验证图中是否有从v到w的边
@Override
public boolean hasEdge( int v , int w ){
assert v >= 0 && v < n ;
assert w >= 0 && w < n ;
return g[v][w]!=null;
}
@Override
public void show() {
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(g[i][j]!=null){
System.out.print(g[i][j].wt()+"\t");
}else{
System.out.print("NULL\t");
}
}
System.out.println();
}
}
// 返回图中一个顶点的所有邻边
// 由于java使用引用机制,返回一个Vector不会带来额外开销
@Override
public Iterable<Edge<E>> adj(int v) {
assert v >= 0 && v < n;
Vector<Edge<E>> adjV = new Vector<>();
for (int i = 0; i < n; i++) {
if (g[v][i]!=null) {
adjV.add(g[v][i]);
}
}
return adjV;
}
}
稀疏图(带权图)
public class SparseWeightedGraph<E extends Number & Comparable> implements WeightedGraph<E>{
private int n; // 节点数
private int m; // 边数
private boolean directed; // 是否为有向图
private Vector<Edge<E>>[] g; // 图的具体数据
// 构造函数
public SparseWeightedGraph(int n , boolean directed ){
assert n >= 0;
this.n = n;
this.m = 0; // 初始化没有任何边
this.directed = directed;
// g初始化为n个空的vector, 表示每一个g[i]都为空, 即没有任和边
g = (Vector<Edge<E>>[])new Vector[n];
for(int i = 0 ; i < n ; i ++){
g[i] = new Vector<Edge<E>>();
}
}
@Override
public int V(){ return n;} // 返回节点个数
@Override
public int E(){ return m;} // 返回边的个数
// 向图中添加一个边
@Override
public void addEdge(Edge edge){
assert edge.v() >= 0 && edge.v() < n ;
assert edge.w() >= 0 && edge.w() < n ;
g[edge.v()].add(edge);
if( edge.v() != edge.w() && !directed ){
g[edge.w()].add(new Edge(edge.w(),edge.v(),edge.wt()));
}
m ++;
}
// 验证图中是否有从v到w的边 -->时间复杂度:O(n)
@Override
public boolean hasEdge(int v, int w){
assert v >= 0 && v < n ;
assert w >= 0 && w < n ;
for( int i = 0 ; i < g[v].size() ; i ++ ){
if( g[v].elementAt(i).other(v) == w ){
return true;
}
}
return false;
}
@Override
public void show() {
for( int i = 0 ; i < n ; i ++ ){
System.out.print("vertex " + i + ":\t");
for( int j = 0 ; j < g[i].size() ; j ++ ){
Edge e = g[i].elementAt(j);
System.out.print( "( to:" + e.other(i) + ",wt:" + e.wt() + ")\t");
}
System.out.println();
}
}
// 返回图中一个顶点的所有邻边
// 由于java使用引用机制,返回一个Vector不会带来额外开销
@Override
public Iterable<Edge<E>> adj(int v) {
assert v >= 0 && v < n;
return g[v];
}
}
最小生成树
最小生成树问题针对的是带权无向图,并且该图是一个连通图。
切分定理
1.切分
把图中的的顶点分成两部分,成为一个切分。
其中图中顶点被分为蓝色部分和红色部分,这就是一个切分。
2.横切边
如果一个边的两个端点,分别属于不同的切分,则这个边就被成为横切边
其中使用绿色标出了横切边。
3.切分定理
给定任意切分,横切边中权值最小边必然属于最小生成树
在这个切分中横切边中权值最小的是0.16,已使用红色标出。
在这个切分中横切边中权值最小的是0.4,已使用红色标出。
Lazy Prim
public class LazyPrimMST<E extends Number & Comparable> {
private WeightedGraph<E> G; // 图的引用
private PriorityQueue<Edge<E>> pq; // 最小堆, -->Java默认使用小根堆
private boolean[] marked; // 标记数组, 在算法运行过程中标记节点i是否被访问
private Vector<Edge<E>> mst; // 最小生成树所包含的所有边
private Number mstWeight; // 最小生成树的权值
public LazyPrimMST(WeightedGraph graph){
this.G=graph;
pq=new PriorityQueue<>();
marked=new boolean[graph.V()];
mst=new Vector<>();
//从0节点开始访问
visit(0);
while(!pq.isEmpty()){
//获取最小边
Edge<E> e=pq.poll();
//看看这条最小边是否是横切边
if(marked[e.v()]==marked[e.w()]){
continue;
}
//是横切边,说明就是MST的边
mst.add(e);
// 访问和这条边连接的还没有被访问过的节点
if(! marked[e.v()]){
visit(e.v());
}else{
visit(e.w());
}
//计算最小生成树的权值
mstWeight=mst.elementAt(0).wt();
for(int i=1;i<mst.size();i++){
mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
}
}
}
//v顶点是未访问过的
private void visit(int v){
assert !marked[v];
marked[v]=true;
//将与v节点相连的顶点边加入到堆中
for(Edge e:G.adj(v)){
if(!marked[e.other(v)]){
pq.add(e);
}
}
}
// 返回最小生成树的所有边
public Vector<Edge<E>> mstEdges(){
return mst;
}
// 返回最小生成树的权值
public Number result(){
return mstWeight;
}
}
Prim 算法的优化
/**
* 优化的Prim算法求图的最小生成树
*/
public class PrimMST<E extends Number & Comparable> {
private WeightedGraph G; // 图的引用
private IndexMinHeap<E> ipq; // 最小索引堆, 算法辅助数据结构
private Edge<E>[] edgeTo; // 访问的点所对应的边, 算法辅助数据结构
private boolean[] marked; // 标记数组, 在算法运行过程中标记节点i是否被访问
private Vector<Edge<E>> mst; // 最小生成树所包含的所有边
private Number mstWeight; // 最小生成树的权值
public PrimMST(WeightedGraph graph){
G = graph;
assert( graph.E() >= 1 );
ipq = new IndexMinHeap<E>(graph.V());
// 算法初始化
marked = new boolean[G.V()];
edgeTo = new Edge[G.V()];
mst=new Vector<>();
//Lazy Prim算法的改进
visit(0);
while (!ipq.isEmpty()){
// 使用最小索引堆找出已经访问的边中权值最小的边
// 最小索引堆中存储的是点的索引, 通过点的索引找到相对应的边
int v=ipq.extractMinIndex();
assert( edgeTo[v] != null );
mst.add(edgeTo[v]);
visit(v);
}
//计算最小生成树的权值
mstWeight=mst.elementAt(0).wt();
for(int i=1;i<mst.size();i++){
mstWeight=mstWeight.doubleValue()+mst.elementAt(i).wt().doubleValue();
}
}
private void visit(int v){
assert !marked[v];
marked[v] = true;
// 将和节点v相连接的未访问的另一端点, 和与之相连接的边, 放入最小堆中
for(Object edge:G.adj(v)){
Edge<E> e=(Edge<E>)edge;
int w=e.other(v);
// 如果边的另一端点未被访问
if(!marked[w]){
if(edgeTo[w]==null){
//如果从没有考虑过这个端点, 直接将这个端点和与之相连接的边加入索引堆
//edgeTo[w] 表示访问w定点所对应的边<v,w>
edgeTo[w]=e;
ipq.insert(w,e.wt());
}else if(e.wt().compareTo(edgeTo[w].wt())<0){
//如果曾经考虑这个端点, 但现在的边比之前考虑的边更短, 则进行替换
edgeTo[w]=e;
ipq.change(w,e.wt());
}
}
}
}
// 返回最小生成树的所有边
Vector<Edge<E>> mstEdges(){
return mst;
}
// 返回最小生成树的权值
Number result(){
return mstWeight;
}
}
Kruskal
public class KruskalMST<E extends Number & Comparable> {
private Vector<Edge<E>> mst; // 最小生成树所包含的所有边
private Number mstWeight; // 最小生成树的权值
public KruskalMST(WeightedGraph graph){
mst=new Vector<>();
// 将图中的所有边存放到一个最小堆中
PriorityQueue<Edge<E>> pq=new PriorityQueue<>(graph.E());
for(int i=0;i<graph.V();i++){
for(Object item:graph.adj(i)){
Edge<E> e=(Edge<E>)item;
if(e.w() <e.v()){ //由于是无向图,只要加入一条边就好了
pq.add(e);
}
}
}
//创建一个并查集, 来查看已经访问的节点的联通情况
UnionFind uf = new UnionFind(graph.V());
while (!pq.isEmpty() && mst.size()<graph.V()-1){
// 从最小堆中依次从小到大取出所有的边
Edge<E> e = pq.poll();
//如果该边的两个端点是联通的, 说明加入这条边将产生环, 扔掉这条边
if(uf.isConnected(e.w(),e.v())){
continue;
}
// 否则, 将这条边添加进最小生成树, 同时标记边的两个端点联通
mst.add(e);
uf.unionElements(e.w(),e.v());
}
// 计算最小生成树的权值
mstWeight = mst.elementAt(0).wt();
for( int i = 1 ; i < mst.size() ; i ++ ){
mstWeight = mstWeight.doubleValue() + mst.elementAt(i).wt().doubleValue();
}
}
// 返回最小生成树的所有边
Vector<Edge<E>> mstEdges(){
return mst;
}
// 返回最小生成树的权值
Number result(){
return mstWeight;
}
}
时间复杂度
算法 | 时间复杂度 |
---|---|
Lazy Prim | O(ElogE) |
Prim | O(ElogV) |
Kruskal | O(ElogE) |