数据结构笔记_最小生成树

一. 有权图

  • 边上的权值不一定是数值,而可以是各种类型

数据结构笔记_最小生成树_第1张图片

  • 有权图的邻接矩阵和邻接表
  • 抽象出一个Edge类,存放边的信息

数据结构笔记_最小生成树_第2张图片数据结构笔记_最小生成树_第3张图片

//Edge类, 表示边的信息
public class Edge implements Comparable {
    private int a, b;         //边的两个节点, 表示边 a->b
    private Weight weight;    //边的权重,不一定是数字类型

    public Edge(int a,int b, Weight weight){
        this.a = a;
        this.b = b;
        this.weight = weight;
    }
    public Edge(Edge e){
        this(e.a, e.b, e.weight);
    }

    //返回第一个顶点
    public int v(){
        return this.a;
    }


    //返回第二个顶点
    public int w(){
        return this.b;
    }


    //返回权重
    public Weight weight(){
        return this.weight;
    }


    //给定一个顶点返回另一个顶点
    public int otherV(int v){
        assert v == a || v == b;
        return v == a? b:a;
    }

    //输出边的信息
    public String toString(){
        return ""+a+"-"+b+":"+weight;
    }


    //边之间的比较
    @Override
    public int compareTo(Edge other){
        if(weight.compareTo(other.weight())>0){
            return 1;
        }else if(weight.compareTo(other.weight())<0){
            return -1;
        }else{
            return 0;
        }
    }
}
//有权稠密图 邻接矩阵表示
public class DenseGraph implements WeightGraph {
    private int n;               //节点数
    private int m;               //边数
    private boolean isDirected;  //是否为有向图
    private Edge[][] g;  //图的具体数据

    public DenseGraph(int n, boolean isDirected){
        assert n>=0;
        this.n = n;
        this.m = 0;
        this.isDirected = isDirected;

        // g初始化为n*n的矩阵, 每一个g[i][j]均为null, 表示没有任和边
        this.g = new Edge[n][n];
        for(int i=0; iw 是否存在
    @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 addEdge(Edge e){
        assert e.v()>=0 && e.v()=0 && e.w()> adjIterator(int v){
        assert v >= 0 && v < n;

        Vector> vector = new Vector<>();
        for(int i=0; i
//有权稀疏图 邻接表的表示
public class SpraseGraph implements WeightGraph {
    private int n;                      //节点数
    private int m;                      //边数
    private boolean isDirected;         //是否为有向图
    private Vector>[] g;   //图的具体数据

    public SpraseGraph(int n, boolean isDirected){
        assert n>=0;
        this.n = n;
        this.m = 0;
        this.isDirected = isDirected;
        this.g = (Vector>[]) new Vector[n];
        //初始化数组中的每个Vetor
        for(int i=0; i>();
        }
    }

    @Override
    public int V(){
        return this.n;
    }

    @Override
    public int E(){
        return this.m;
    }


    //检查边 v——>w 是否存在
    @Override
    public boolean hasEdge(int v, int w){
        assert v>=0 && v=0 && w e){
        assert e.v()>=0 && e.w()=0 && e.w()> adjIterator(int v){
        assert v >= 0 && v < n;
        return g[v];
    }

    @Override
    public void show(){
        for(int i = 0; i

 

二. 有权图的最小生成树问题

1.最小生成树

  • 生成树:一个连通图中,能连通所有顶点而又不产生回路的任何子图都是它的生成树(n个顶点+n-1条边)
  • 最小生成树:所有生成树中,各边的权重和最小的生成树
  • 最小生成树针对的是:带权无向图,连通图
  • 求最小生成树就是找v-1条边连接v个顶点,使得总权值最小

数据结构笔记_最小生成树_第4张图片

2.切分定理(Cut Property)

  • 把图中的节点分为两个部分,成为一个切分(Cut)
  • 如果一个边的两个端点,属于切分(Cut)不同的两边,这个边称为横切边(Crossing Edge)
  • 切分定理:给定任意切分,横切边中权值最小的边必然属于最小生成树
  • 若图中有多个相等的"横切边",则该图的最小生成树不唯一

数据结构笔记_最小生成树_第5张图片数据结构笔记_最小生成树_第6张图片

3. lazy-prim算法求最小生成树

  • 缺点:所有的边都要进入最小堆
  • 时间复杂度为:O(logE)
//lazy-prim算法带权无向图最小生成树
public class LazyPrimMST {
    private WeightGraph graph;           //用来生成最小生成树的图
    private boolean[] isVisited;         //标记节点是否被访问
    private Vector> mst;    //最小生成树中的边
    private Number mstWeight;            //最小生成树的权重和
    private PriorityQueue> pq;            //算法的辅助数据结构,用来选出权重最小的边

    public LazyPrimMST(WeightGraph graph){
        this.graph = graph;
        this.isVisited = new boolean[graph.V()];
        this.mst = new Vector<>();
        this.mstWeight = 0;
        this.pq = new PriorityQueue<>(graph.E(), new Comparator>() {
            @Override
            public int compare(Edge o1, Edge o2) {
                return o1.compareTo(o2);
            }
        });
    }


    //辅助方法,访问节点,并挑选该节点未被访问过的邻边加入优先队列
    private void visit(int v){
        //判断传入的节点v是否被访问过
        assert !isVisited[v];

        //访问节点v
        isVisited[v] = true;
        for(Edge e: graph.adjIterator(v)){
            if(!isVisited[e.otherV(v)]){
                pq.add(e);
            }
        }
    }


    //最小生成树实现  lazy-prim算法
    public void mst(){

        //初始化,先访问节点0
        visit(0);

        //lazy-prim
        while(!pq.isEmpty()){
            //从优先队列中取得当前队列中权重最小的边
            Edge e = pq.remove();

            //若边的两个端点均被访问过,则抛弃这条边,在从优先队列中取得下一个权重最小的边
            if(isVisited[e.v()] == isVisited[e.w()]){
                continue;
            }

            //若边的另一个端点没有被访问过,则该边为最小生成树中的一条边
            mst.add(e);

            //顺着这条边接着访问边中那个为被访问过的节点
            if(!isVisited[e.v()]){
                visit(e.v());
            }else{
                visit(e.w());
            }
        }

        //计算最小生成树中的权重和
        for(int i=0; i> mstEdges(){
        return this.mst;
    }


    // 返回最小生成树的权值
    public Number minWeight(){
        return this.mstWeight;
    }
}

4. prim算法

  • prim算法是贪婪算法的一个典型例子,有点类似于dijkstra算法。
  • 算法思想:
  1. 横切边:若一条边中有且只有一个节点被访问过(被标记过),则该边为一条横切边
  2. 根据切分定理,将节点切分已加入最小生成树的部分和未加入最小生成树的部分,从树节点(已标记的节点)出发寻找最短横切边,添加该最短横切边直到所有结点都加入到最小生成树。
  3. 从任意一个点开始选择,找出这个点连接的所有的边,然后找出最短的,选中这条边加入到生成树中,枚举每一个树顶点到每一个非树顶点的所有的边,然后找最短的边加入到生成树,一直加边n-1次, 直到所有的顶点都被加入到生成树中。
  • 辅助数据结构:利用一个最小索引堆,开辟V个空间,索引为节点,索引下存储的值是当前节点到树区的权重(距离)
  • 时间复杂度:O(logV)
//索引堆(构造时,可根据参数选择最大或者最小), 底层基于数组实现
public class IndexMinHeap {
    private boolean isMin; //是否为最小索引堆
    private E[] items;      //存放具体数据
    private int[] indexes; //索引堆的底层数组,数组内存储的是具体数据的索引,保持堆的排序
    private int[] reverse; //索引堆中的反向索引  reverse[indexes[i]]=i;
    private int size;      //堆中数据的个数
    private int capacity;  //堆的容量

    public IndexMinHeap(int capacity, boolean isMin){
        this.isMin = isMin;
        this.capacity = capacity;
        this.size = 0;
        this.indexes = new int[capacity];
        this.items = (E[])new Comparable[capacity];
        this.reverse = new int[capacity];
        for(int i=0; i=0 && i0;

        E ret = items[indexes[0]];
        indexes[0] = indexes[size-1];
        reverse[indexes[0]] = 0;
        size--;
        reverse[indexes[size]] = -1;
        shiftDown(0);

        return ret;
    }


    // 从索引堆中取出堆顶元素的索引
    public int removeIndex(){
        assert size>0;

        int ret = indexes[0];
        indexes[0] = indexes[size-1];
        reverse[indexes[0]] = 0;
        size--;
        reverse[indexes[size]] = -1;
        shiftDown(0);

        return ret;
    }


    // 获取索引堆中的堆顶元素
    public E get(){
        assert size>0;
        return items[indexes[0]];
    }


    // 获取索引堆中的堆顶元素的索引
    public int getIndex(){
        assert size>0;
        return indexes[0];
    }


    // 看索引i所在的位置是否存在元素
    public boolean contain(int i){
        assert i>=0 && i0; j=(j-1)/2){
                indexes[j] = indexes[(j-1)/2];
                reverse[indexes[j]] = j;
            }

        }else{
            for(j=i; e.compareTo(items[indexes[(j-1)/2]])>0 && j>0; j=(j-1)/2){
                indexes[j] = indexes[(j-1)/2];
                reverse[indexes[j]] = j;
            }
        }

        indexes[j] = ret;
        reverse[indexes[j]] = j;
    }



    // 索引堆中, 数据之间的比较根据data的大小进行比较, 但实际操作的是索引
    private void shiftDown(int i){
        while (2*i+10){
                    j = j+1;
                }

                if(items[indexes[i]].compareTo(items[indexes[j]])>0){
                    break;
                }
            }
            
            int temp = indexes[i];
            indexes[i] = indexes[j];
            reverse[indexes[i]] = i;
            indexes[j] = temp;
            reverse[indexes[j]] = j;

            i = j;
        }
    }
}
//prim算法 求带权无向图的最小生成树
public class PrimMST {
    private WeightGraph graph;    //待求最小生成树的图
    private Vector> mst;     //最小生成树的所有边
    private Number mstWeight;             //最小生成树的权重和
    private boolean[] isVisited;          //辅助数据结构,记录节点是否被访问过
    private Edge[] edgeTo;        //辅助数据结构,记录“横切边”, edgeTo[w] 表示已标记区域中某个节点到未标记节点w的边
    private IndexMinHeap imheap;  //辅助数据结构,最小索引堆,索引表示节点,对应的数据表示该节点到已标记区域的权重

    public PrimMST(WeightGraph graph){
        this.graph = graph;
        assert( graph.E() >= 1 );
        this.imheap = new IndexMinHeap<>(graph.V(), true);
        this.isVisited = new boolean[graph.V()];
        this.mst = new Vector>();
        this.mstWeight = 0;
        this.edgeTo = (Edge[]) new Edge[graph.V()];

        for(int i=0; i e: graph.adjIterator(v)){
            if(!isVisited[e.otherV(v)]){

                //判断边 v——otherV(v) 是否被标记为"横切边"
                //若未标记过,则进行标记,并加入最小堆
                //若标记过,则判断之前标记的“横切边”与当前由节点v发出的“横切边”v——otherV(v)那个更短,更新最小堆
                if(edgeTo[e.otherV(v)] == null){
                    edgeTo[e.otherV(v)] = e;
                    imheap.add(e.otherV(v), e.weight());
                }else if(edgeTo[e.otherV(v)].weight().compareTo(e.weight())>0){
                    //更新到节点e.otherV(v)的“横切边”
                    edgeTo[e.otherV(v)] = e;
                    imheap.set(e.otherV(v), e.weight());
                }
            }
        }
    }

    //prim算法, 生成最小生成树
    public void prim(){

        //prim
        visit(0);
        while (!imheap.isEmpty()){
            // 使用最小索引堆找出已经访问的边中权值最小的边
            // 最小索引堆中,索引表示节点,索引对应的值表示该节点到已标记区域的权重(距离)
            int v = imheap.removeIndex();
            //assert( edgeTo[v] != null );
            mst.add(edgeTo[v]);
            visit(v);
        }

        // 计算最小生成树的权值
        for (int i=0; i> mstEdges(){
        return this.mst;
    }

    //返回最小生成树的权重和
    public Number mstWeight(){
        return this.mstWeight;
    }
}

5. Kruslal算法

  • 算法思想:将图中所右边按照权重排序,从小到大依次试着将边加入到节点之间,若加入后不构成环,则该条边为最小生成树中的一条边,直至加构V-1条边;对于环的判断可以使用并查集实现
//并查集
public class UnionFind {
    private int[] parent;
    private int capacity;
    private int[] rank;

    public UnionFind(int capacity){
        this.capacity = capacity;
        this.parent = new int[capacity];
        this.rank = new int[capacity];
        for(int i=0; i rank[qRoot]){
            parent[qRoot] = pRoot;
        }else{
            parent[pRoot] = qRoot;
            rank[qRoot]++;
        }
    }
}
//kruskal算法
public class KruskalMST {
    private WeightGraph graph;
    private Vector> mst;
    private Number mstWeight;

    public KruskalMST(WeightGraph graph){
        this.graph = graph;
        this.mst = new Vector<>();
        this.mstWeight = 0;
    }

    public void kruskal(){

        //利用优先队列对图中各条边按照权重从小到大排序
        PriorityQueue> pq = new PriorityQueue<>(graph.E());
        for(int i=0; i e: graph.adjIterator(i)){
                //防止无向图中,同一条边两次入队
                if(e.v()<=e.w()){
                    pq.add(e);
                }
            }
        }


        //利用并查集检查加入一条边之前,该边的两个节点是否已经连接
        UnionFind uf = new UnionFind(graph.V());
        while(!pq.isEmpty() && mst.size() e = pq.remove();
            if(uf.isConnected(e.v(), e.w())){
                continue;
            }

            mst.add(e);
            uf.unionElements(e.v(), e.w());
        }

        //计算最小生成树权重
        for(int i=0; i> mstEdges(){
        return mst;
    }

    // 返回最小生成树的权值
    public Number mstWeight(){
        return mstWeight;
    }
}

6. 三种算法的时间复杂度

数据结构笔记_最小生成树_第7张图片

7. Vyssotsky算法

  • 将边逐渐添加到生成树中,一旦形成环,删除环中权值最大的边

 

你可能感兴趣的:(数据结构,算法基础)