美文网首页
最小生成树的Prim算法

最小生成树的Prim算法

作者: SharryChoo | 来源:发表于2018-04-17 11:08 被阅读63次

    Prim算法

    1. 特点: Prim 算法能够得到任意加权图的最小生成树
    2. 使用到的数据结构
      • 延时版本: 优先队列
      • 即时版本: 索引优先队列

    优先队列

    1. 定义: 许多应用程序都需要处理有序的元素, 但不一定要求它们全部有序, 或是不一定要一次就将它们排序, 很多情况下, 我们会收集一些元素, 处理当前键值最大的元素, 然后再收集一些元素, 再处理当前键值最大的元素
      在这种情况下, 一个适合的数据结构应该支持两种操作: 删除最大/最小元素插入元素, 这种数据类型叫做优先队列

    2. 实现方案

      • 数组: 有序, 无序
      • 链表: 有序, 无序
      • 二叉堆: 通过堆有序可以很容易的获得最大元素, 并且元素也不是绝对有序的, 最适合实现优先队列
    3. 具体实现

    /**
     * 优先队列(二叉堆实现)
     * @author Frank
     */
    @SuppressWarnings("unchecked")
    public class PriorityQueue<Key extends Comparable<Key>> implements Iterable<Key>{
        
        public enum Type{
            Maximum,// 最大优先
            Minimum // 最小优先
        }
        private Type type;
        private Key[] heap;
        private int N = 0;// 存储于 heap[1...N] 中, heap[0] 没有使用
        
        public PriorityQueue(Type type, int maxN) {
            this.type = type; 
            heap = (Key[]) new Comparable[maxN + 1];
        }
        
        /**
         * 向优先队列中插入一个元素
         */
        public void insert(Key v){
            heap[++N] = v;
            // 对新添加的元素进行上浮排序
            swim(N);
        }
    
        /**
         * 获取堆顶元素(最大/或者最小的元素)
         */
        public Key top(){
            return heap[1];
        }
        
        /**
         * 删除并返回(最小/最大)元素
         */
        public Key delTop(){
            // 从根结点获取堆顶元素
            Key min = heap[1];
            // 将其和最后一个结点元素互换
            exch(1, N--);
            // 将刚才互换到底部的元素置空
            heap[N+1] = null;
            // 下沉到合适的位置
            sink(1);
            return min;
        }
        
        /**
         * 返回队列是否为空
         */
        public boolean isEmpty(){
            return N == 0;
        }
        
        /**
         * 返回优先队列中元素的个数
         */
        public int size(){
            return N;
        }
        
        /**
         * 比较元素大小
         */
        private boolean less(int i, int j) {
            return heap[i].compareTo(heap[j]) < 0;
        }
        
        /**
         * 交换元素位置
         */
        private void exch(int i, int j) {
            Key t = heap[i];
            heap[i] = heap[j];
            heap[j] = t;
        }
        
        /**
         * 元素上浮, 实现堆序列
         */
        private void swim(int k) {
            while (k > 1 && (type == Type.Maximum 
                    ? less(k/2, k) : !less(k/2, k))) {
                exch(k/2, k);
                k = k/2;
            }
        }
        
        /**
         * 元素下沉, 实现堆序列
         */
        private void sink(int k) {
            while (2 * k <= N) {
                int j = 2 * k;
                if (j < N &&(type == Type.Maximum 
                        ? less(j, j + 1) : !less(j, j + 1))) j++;
                if (type == Type.Maximum 
                        ? !less(k, j) : less(k, j)) break;
                exch(k, j);
                k = j;
            }
        }
        
    
        @Override
        public Iterator<Key> iterator() {
            return new Iterator<Key>() {
                
                int index = 0;
                
                @Override
                public boolean hasNext() {
                    return index < N;
                }
    
                @Override
                public Key next() {
                    return heap[++index];
                }
            };
        }
    
    }
    
    

    索引优先队列

    1.优先队列存在的问题: 优先队列有一个缺点,就是不能直接访问已存在于优先队列中的对象,并更新它们。这个问题在 Prim 算法的及时实现中就有明显的体现。

    2. 定义: 索引优先队用一个整数和对象进行关联,当我们需要更新该对象的值时,可以通这个整数进行快速索引,然后对对象的值进行更新。当然更新后的对象在优先队列中的位置可能发生变化,这样以保证整个队列还是一个优先队列。

    3.1 使用纯数组实现

    互逆数组实现索引优先队列.jpg
    /**
     * 索引最优先队列(数组实现)
     * @author Frank
     */
    @SuppressWarnings("unchecked")
    public class IndexArrayPriorityQueue<Element extends Comparable<Element>> {
    
        public enum Type{
            Maximum,// 最大优先
            Minimum // 最小优先
        }
        private Type type;
        private int N;
        private Element[] elements;// 值的二叉堆, 索引为对应的 k值, 不连续存放
        private int[] pq;// Key 的数组, 连续存放存放对应的 key
        private int[] qp;// 索引的数组, 存放 key 在 pq 中的索引
        
        public IndexArrayPriorityQueue(Type type, int maxN) {
            this.type = type; 
            elements = (Element[])new Comparable[maxN + 1];
            pq = new int[maxN + 1];
            qp = new int[maxN + 1];
            // 初始化索引数组, 索引默认为 -1
            for (int i = 0; i <= maxN; i++) qp[i] = -1; 
        }
        
        public boolean isEmpty() {
            return N == 0;
        }
        
        public boolean contains(int k) {
            return qp[k] != -1;
        }
        
        public void insert(int key, Element element) {
            N++;
            pq[N] = key;// pq 的第 N 个位置存放  k
            qp[key] = N;// qp 记录 key 的索引
            elements[key] = element;
            // 因为是新插入的, 所以默认在最底部, 
            // 执行上浮操作即可保证堆序列
            swim(N);
        }
        
        public int topKey() {
            return pq[1];
        }
        
        public Element topElement() {
            return elements[pq[1]];
        }
        
        public int delTop() {
            int key = pq[1];
            delete(key);
            return key;
        }
    
        public void change(int k, Element key) {
            elements[k] = key;
            // 重构堆序列
            swim(qp[k]);
            sink(qp[k]);
        }
        
        public void delete(int key) {
            int keyIndex = qp[key];
            // 与最后一个元素互换
            exch(keyIndex, N--);
            // 重构堆序列
            swim(keyIndex);
            sink(keyIndex);
            // 释放数组对应位置废弃的值
            elements[key] = null;
            qp[key] = -1;
        }
        
        /**
         * 元素上浮, 实现堆序列
         */
        private void swim(int k) {
            while (k > 1 && (type == Type.Maximum 
                    ? less(k/2, k) : !less(k/2, k))) {
                exch(k/2, k);
                k = k/2;
            }
        }
        
        /**
         * 元素下沉, 实现堆序列
         */
        private void sink(int k) {
            while (2 * k <= N) {
                int j = 2 * k;
                if (j < N &&(type == Type.Maximum 
                        ? less(j, j + 1) : !less(j, j + 1))) j++;
                if (type == Type.Maximum 
                        ? !less(k, j) : less(k, j)) break;
                exch(k, j);
                k = j;
            }
        }
        
        /**
         * 比较大小(堆序列的位置)
         */
        private boolean less(int i, int j) {
            return elements[pq[i]].compareTo(elements[pq[j]]) < 0;
        }
        
        
        /**
         * 交换元素位置(堆序列的位置)
         */
        private void exch(int i, int j) {
            // exchange key array
            int tempKey = pq[i];
            pq[i] = pq[j];
            pq[j] = tempKey;
            // exchange keyIndex array
            qp[pq[i]] = i;
            qp[pq[j]] = j;
        }
        
    }
    

    3.2 定义 Entry 实体类实现

    /**
     * 索引最优先队列(使用Entry实现)
     * @author Frank
     */
    @SuppressWarnings("unchecked")
    public class IndexEntryPriorityQueue<Element extends Comparable<Element>> {
    
        public enum Type{
            Maximum,// 最大优先
            Minimum // 最小优先
        }
        private Type type;
        private int N;
        private int[] qp;// 记录key在堆中的索引
        private Entry<Integer, Element>[] heap;// 堆序列
        
        public IndexEntryPriorityQueue(Type type, int maxN) {
            this.type = type; 
            heap = new Entry[maxN + 1];
            qp = new int[maxN + 1];
            // 初始化索引数组, 索引默认为 -1
            for (int i = 0; i <= maxN; i++) qp[i] = -1; 
        }
        
        public boolean isEmpty() {
            return N == 0;
        }
        
        public boolean contains(int k) {
            return qp[k] != -1;
        }
        
        public void insert(int key, Element element) {
            N++;
            heap[N] = new Entry<Integer, Element>(key, element);
            qp[key] = N;// qp 记录 key 的索引
            // 因为是新插入的, 所以默认在最底部, 
            // 执行上浮操作即可保证堆序列
            swim(N);
        }
    
        public int topKey() {
            return heap[1].key;
        }
        
        public Element topElement() {
            return heap[1].element;
        }
        
        public int delTop() {
            int key = heap[1].key;
            delete(key);
            return key;
        }
    
        public void change(int key, Element element) {
            int keyIndex = qp[key];
            heap[keyIndex].element = element;
            // 恢复堆序列
            swim(qp[key]);
            sink(qp[key]);
        }
    
        public void delete(int key) {
            int keyIndex = qp[key];
            // 与最后一个元素互换
            exch(keyIndex, N--);
            // 重构堆序列
            swim(keyIndex);
            sink(keyIndex);
            // 释放数组对应位置废弃的值
            heap[N+1] = null;
            qp[key] = -1;
        }
        
        /**
         * 元素上浮, 实现堆序列
         */
        private void swim(int k) {
            while (k > 1 && (type == Type.Maximum 
                    ? less(k/2, k) : !less(k/2, k))) {
                exch(k/2, k);
                k = k/2;
            }
        }
        
        /**
         * 元素下沉, 实现堆序列
         */
        private void sink(int k) {
            while (2 * k <= N) {
                int j = 2 * k;
                if (j < N &&(type == Type.Maximum 
                        ? less(j, j + 1) : !less(j, j + 1))) j++;
                if (type == Type.Maximum 
                        ? !less(k, j) : less(k, j)) break;
                exch(k, j);
                k = j;
            }
        }
        
        /**
         * 比较大小
         */
        private boolean less(int i, int j) {
            return heap[i].compareTo(heap[j]) < 0;
        }
        
        /**
         * 交换元素位置
         */
        private void exch(int i, int j) {
            // 交换元素位置
            Entry<Integer, Element> temp = heap[i];
            heap[i] = heap[j];
            heap[j] = temp;
            // 交换存放  key的索引数组的位置
            qp[heap[i].key] = i;
            qp[heap[j].key] = j;
        }
        
        /**
         * 存放键值对
         */
        public static class Entry<Key, Element extends Comparable<Element>> implements Comparable<Entry<Key, Element>>{
    
            Key key;
            Element element;
            
            public Entry(Key key, Element element) {
                this.key = key;
                this.element = element;
            }
            
            @Override
            public int compareTo(Entry<Key, Element> other) {
                return element.compareTo(other.element);
            }
            
        }
        
    }
    
    

    Prim算法的延时版本

    /**
     * 最小生成树 Prim 算法的延时实现
     */
    public class LazyPrimMST {
    
        private boolean[] marked;// 顶点索引
        private Queue<Edge> mst;// 用于存放最小生成树的边
        private PriorityQueue<Edge> pq;// 用于存放横切边
        
        public LazyPrimMST(EdgeWeightedGraph G) {
            marked = new boolean[G.V()];
            mst = new ConcurrentLinkedQueue <>();
            pq = new PriorityQueue<Edge>(PriorityQueue.Type.Minimum, G.V());
            // 用 0 初始化横切边
            visit(G, 0);
            // 开始寻找最小生成树
            find(G);
        }
    
        /**
         * 将第 v 个顶点的边加入最小优先队列, 
         * 这样就可以使用 delMin() 来获取横切边的最小值
         * @param G
         * @param v
         */
        private void visit(EdgeWeightedGraph G, int v) {
            marked[v] = true;
            for (Edge e: G.adj(v)) {
                pq.insert(e);
            }
        }
    
        /**
         * 开始寻找
         * @param G
         */
        private void find(EdgeWeightedGraph G) {
            while(!pq.isEmpty()) {
                // 1. 获取最小的横切边
                Edge e = pq.delTop();
                // 2. 处理获取到的横切边
                int v = e.either();
                int w = e.other(v);
                if (marked[v] && marked[w]) continue;// 跳过失效的边
                mst.offer(e);// 将边添加到生成树中
                if (!marked[v]) visit(G, v);// 将顶点(v 或 w)添加到队列中
                if (!marked[w]) visit(G, w);
            }
        }
        
        public Iterable<Edge> edges() {
            return mst;
        }
        
        public double weight() {
            double sumWeight = 0;
            for (Edge edge: mst) {
                sumWeight += edge.weight();
            }
            return sumWeight;
        }
    }
    

    Prim算法的及时版本

    /**
     * 最小生成树 Prim 算法的及时实现
     */
    public class InstantlyPrimMST {
    
        private boolean marked[];
        private Edge[] edgeTo;// 距离树最近的边
        private double[] distTo;// distTo[w] = edgeTo[w].weight();
        private IndexEntryPriorityQueue<Double> pq;// 有效的横切边
        
        public InstantlyPrimMST(EdgeWeightedGraph G) {
            marked = new boolean[G.V()];
            edgeTo = new Edge[G.V()];
            // 初始化边的 distTo 每个点之间的距离为最大值 
            distTo = new double[G.V()];
            for (int i = 0; i < G.V(); i++) {
                distTo[i] = Double.MAX_VALUE;
            }
            pq = new IndexEntryPriorityQueue<>(IndexEntryPriorityQueue.
                    Type.Minimum, G.V());
            find(G);
        }
    
        private void find(EdgeWeightedGraph G) {
            // 用顶点 0 来初始化 distTo
            distTo[0] = 0.0;
            // 用顶点 0 和权重 0 来初始化 pq
            pq.insert(0, 0.0);
            // 遍历
            while(!pq.isEmpty()) {
                visit(G, pq.delTop());// 将最近的顶点添加到生成树中
            }
        }
    
        private void visit(EdgeWeightedGraph G, int v) {
            marked[v] = true;
            for (Edge e: G.adj(v)) {
                int w = e.other(v);
                if (marked[w]) continue;
                if (e.weight() < distTo[w]) {
                    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() {
            List<Edge> mst = new ArrayList<>();
            for (int v = 1; v < edgeTo.length; v++) {
                if (edgeTo[v] == null) continue;
                mst.add(edgeTo[v]);
            }
            return mst;
        }
        
    }
    

    调用

    public class Main {
    
        public static void main(String[] args) {
            // 最小生成树
            EdgeWeightedGraph graph = new EdgeWeightedGraph(10);
            graph.addEdge(new Edge(0, 1, 0.5));
            graph.addEdge(new Edge(0, 2, 0.3));
            graph.addEdge(new Edge(1, 3, 0.1));
            graph.addEdge(new Edge(3, 0, 0.9));
            graph.addEdge(new Edge(3, 6, 0.7));
            graph.addEdge(new Edge(6, 7, 0.3));
            graph.addEdge(new Edge(7, 8, 0.3));
            graph.addEdge(new Edge(7, 3, 0.3));
            // 延时 Prim 算法
            System.out.println("--------------------LazyPrimMST--------------------");          
            LazyPrimMST mst = new LazyPrimMST(graph);
            for (Edge e: mst.edges()) {
                System.out.println(e.toString());           
            }
            // 及时 Prim 算法
            System.out.println("--------------------InstantlyPrimMST--------------------");         
            InstantlyPrimMST inmst = new InstantlyPrimMST(graph);
            for (Edge e: inmst.edges()) {
                if (e != null) {
                    System.out.println(e.toString());       
                }   
            }
        }
    }
    
    image.png

    相关文章

      网友评论

          本文标题:最小生成树的Prim算法

          本文链接:https://www.haomeiwen.com/subject/njsukftx.html