zoukankan      html  css  js  c++  java
  • 最小生成树(二)Prim算法

    一、思想

    1.1 基本概念

    • 加权无向图的生成树:一棵含有其所有顶点无环连通子图。
    • 最小生成树(MST):一棵权值最小(树中所有边的权值之和)的生成树。

    1.2 算法原理

    1.2.1 切分定理

    • 切分定义:图的一种切分是将图的所有顶点分为两个非空且不重合的两个集合。横切边是一条连接两个属于不同集合的顶点的边。
    • 切分定理:在一幅加权图中,给定任意的切分,它的横切边中的权重最小者必然属于图的最小生成树。

    1.2.2 算法原理

    切分定理是解决最小生成树问题的所有算法的基础。切分定理再结合贪心算法思想,就可以最终落地实现最小生成树。

    Prim算法原理

    一开始树中只有一个顶点,向它添加v-1条边,每次总是将下一条连接 “树中的顶点” 与 “不在树中的顶点” 且权重最小的边,加入树中。如下图,当我们将顶点v添加到树中时,可能使得w到最小生成树的距离更近了(然后遍历顶点v的领接链表即可)。

    核心

    使用一个索引优先队列,保存每个非树顶点w的一条边(将它与树中顶点连接起来的权重最小的边)。优先队列(小顶堆)的最小键即是权重最小的横切边的权重,而和它相关联的顶点V就是下一个将被添加到树中的顶点

    二、实现

    2.1 无向边

      1 package study.algorithm.graph;
      2 
      3 import study.algorithm.base.StdOut;
      4 
      5 /***
      6  * @Description 无向边
      7  * @author denny.zhang
      8  * @date 2020/5/25 10:34 上午
      9  */
     10 public class Edge implements Comparable<Edge> {
     11 
     12     /**
     13      * 一个顶点
     14      */
     15     private final int v;
     16     /**
     17      * 另一个顶点
     18      */
     19     private final int w;
     20     /**
     21      * 权重
     22      */
     23     private final double weight;
     24 
     25     /**
     26      * Initializes an edge between vertices {@code v} and {@code w} of
     27      * the given {@code weight}.
     28      *
     29      * @param  v one vertex
     30      * @param  w the other vertex
     31      * @param  weight the weight of this edge
     32      * @throws IllegalArgumentException if either {@code v} or {@code w} 
     33      *         is a negative integer
     34      * @throws IllegalArgumentException if {@code weight} is {@code NaN}
     35      */
     36     public Edge(int v, int w, double weight) {
     37         if (v < 0) throw new IllegalArgumentException("vertex index must be a nonnegative integer");
     38         if (w < 0) throw new IllegalArgumentException("vertex index must be a nonnegative integer");
     39         if (Double.isNaN(weight)) throw new IllegalArgumentException("Weight is NaN");
     40         this.v = v;
     41         this.w = w;
     42         this.weight = weight;
     43     }
     44 
     45     /**
     46      * Returns the weight of this edge.
     47      *
     48      * @return the weight of this edge
     49      */
     50     public double weight() {
     51         return weight;
     52     }
     53 
     54     /**
     55      * 返回边的任意一个顶点
     56      *
     57      * @return either endpoint of this edge
     58      */
     59     public int either() {
     60         return v;
     61     }
     62 
     63     /**
     64      * 返回边的另一个顶点
     65      *
     66      * @param  vertex one endpoint of this edge
     67      * @return the other endpoint of this edge
     68      * @throws IllegalArgumentException if the vertex is not one of the
     69      *         endpoints of this edge
     70      */
     71     public int other(int vertex) {
     72         if      (vertex == v) return w;
     73         else if (vertex == w) return v;
     74         else throw new IllegalArgumentException("Illegal endpoint");
     75     }
     76 
     77     /**
     78      * Compares two edges by weight.
     79      * Note that {@code compareTo()} is not consistent with {@code equals()},
     80      * which uses the reference equality implementation inherited from {@code Object}.
     81      *
     82      * @param  that the other edge
     83      * @return a negative integer, zero, or positive integer depending on whether
     84      *         the weight of this is less than, equal to, or greater than the
     85      *         argument edge
     86      */
     87     @Override
     88     public int compareTo(Edge that) {
     89         return Double.compare(this.weight, that.weight);
     90     }
     91 
     92     /**
     93      * Returns a string representation of this edge.
     94      *
     95      * @return a string representation of this edge
     96      */
     97     public String toString() {
     98         return String.format("%d-%d %.5f", v, w, weight);
     99     }
    100 
    101     /**
    102      * Unit tests the {@code Edge} data type.
    103      *
    104      * @param args the command-line arguments
    105      */
    106     public static void main(String[] args) {
    107         Edge e = new Edge(12, 34, 5.67);
    108         StdOut.println(e);
    109         StdOut.println("任意一个顶点="+e.either());
    110         StdOut.println("另一个顶点="+e.other(12));
    111     }
    112 }

    如上图,初始化时构造了一个邻接表。Bag<Edge>[] adj,如下图。每条边有2个顶点,所以插入adj[]中2次。

    2.2.边加权无向图

      1 package study.algorithm.graph;
      2 
      3 import study.algorithm.base.*;
      4 
      5 import java.util.NoSuchElementException;
      6 
      7 /***
      8  * @Description 边权重无向图
      9  * @author denny.zhang
     10  * @date 2020/5/25 10:50 上午
     11  */
     12 public class EdgeWeightedGraph {
     13     private static final String NEWLINE = System.getProperty("line.separator");
     14 
     15     /**
     16      * 顶点数
     17      */
     18     private final int V;
     19     /**
     20      * 边数
     21      */
     22     private int E;
     23     /**
     24      * 顶点邻接表,每个元素Bag代表:由某个顶点关联的边数组,按顶点顺序排列
     25      */
     26     private Bag<Edge>[] adj;
     27     
     28     /**
     29      * Initializes an empty edge-weighted graph with {@code V} vertices and 0 edges.
     30      *
     31      * @param  V the number of vertices
     32      * @throws IllegalArgumentException if {@code V < 0}
     33      */
     34     public EdgeWeightedGraph(int V) {
     35         if (V < 0) throw new IllegalArgumentException("Number of vertices must be nonnegative");
     36         this.V = V;
     37         this.E = 0;
     38         adj = (Bag<Edge>[]) new Bag[V];
     39         for (int v = 0; v < V; v++) {
     40             adj[v] = new Bag<Edge>();
     41         }
     42     }
     43 
     44     /**
     45      * Initializes a random edge-weighted graph with {@code V} vertices and <em>E</em> edges.
     46      *
     47      * @param  V the number of vertices
     48      * @param  E the number of edges
     49      * @throws IllegalArgumentException if {@code V < 0}
     50      * @throws IllegalArgumentException if {@code E < 0}
     51      */
     52     public EdgeWeightedGraph(int V, int E) {
     53         this(V);
     54         if (E < 0) throw new IllegalArgumentException("Number of edges must be nonnegative");
     55         for (int i = 0; i < E; i++) {
     56             int v = StdRandom.uniform(V);
     57             int w = StdRandom.uniform(V);
     58             double weight = Math.round(100 * StdRandom.uniform()) / 100.0;
     59             Edge e = new Edge(v, w, weight);
     60             addEdge(e);
     61         }
     62     }
     63 
     64     /**  
     65      * Initializes an edge-weighted graph from an input stream.
     66      * The format is the number of vertices <em>V</em>,
     67      * followed by the number of edges <em>E</em>,
     68      * followed by <em>E</em> pairs of vertices and edge weights,
     69      * with each entry separated by whitespace.
     70      *
     71      * @param  in the input stream
     72      * @throws IllegalArgumentException if {@code in} is {@code null}
     73      * @throws IllegalArgumentException if the endpoints of any edge are not in prescribed range
     74      * @throws IllegalArgumentException if the number of vertices or edges is negative
     75      */
     76     public EdgeWeightedGraph(In in) {
     77         if (in == null) throw new IllegalArgumentException("argument is null");
     78 
     79         try {
     80             // 顶点数
     81             V = in.readInt();
     82             // 邻接表
     83             adj = (Bag<Edge>[]) new Bag[V];
     84             // 初始化邻接表,一个顶点对应一条链表
     85             for (int v = 0; v < V; v++) {
     86                 adj[v] = new Bag<Edge>();
     87             }
     88             // 边数
     89             int E = in.readInt();
     90             if (E < 0) throw new IllegalArgumentException("Number of edges must be nonnegative");
     91             // 遍历每一条边
     92             for (int i = 0; i < E; i++) {
     93                 // 一个顶点
     94                 int v = in.readInt();
     95                 // 另一个顶点
     96                 int w = in.readInt();
     97                 validateVertex(v);
     98                 validateVertex(w);
     99                 // 权重
    100                 double weight = in.readDouble();
    101                 // 构造边
    102                 Edge e = new Edge(v, w, weight);
    103                 // 添加边
    104                 addEdge(e);
    105             }
    106         }   
    107         catch (NoSuchElementException e) {
    108             throw new IllegalArgumentException("invalid input format in EdgeWeightedGraph constructor", e);
    109         }
    110 
    111     }
    112 
    113     /**
    114      * Initializes a new edge-weighted graph that is a deep copy of {@code G}.
    115      *
    116      * @param  G the edge-weighted graph to copy
    117      */
    118     public EdgeWeightedGraph(EdgeWeightedGraph G) {
    119         this(G.V());
    120         this.E = G.E();
    121         for (int v = 0; v < G.V(); v++) {
    122             // reverse so that adjacency list is in same order as original
    123             Stack<Edge> reverse = new Stack<Edge>();
    124             for (Edge e : G.adj[v]) {
    125                 reverse.push(e);
    126             }
    127             for (Edge e : reverse) {
    128                 adj[v].add(e);
    129             }
    130         }
    131     }
    132 
    133 
    134     /**
    135      * Returns the number of vertices in this edge-weighted graph.
    136      *
    137      * @return the number of vertices in this edge-weighted graph
    138      */
    139     public int V() {
    140         return V;
    141     }
    142 
    143     /**
    144      * Returns the number of edges in this edge-weighted graph.
    145      *
    146      * @return the number of edges in this edge-weighted graph
    147      */
    148     public int E() {
    149         return E;
    150     }
    151 
    152     // throw an IllegalArgumentException unless {@code 0 <= v < V}
    153     private void validateVertex(int v) {
    154         if (v < 0 || v >= V)
    155             throw new IllegalArgumentException("vertex " + v + " is not between 0 and " + (V-1));
    156     }
    157 
    158     /**
    159      * Adds the undirected edge {@code e} to this edge-weighted graph.
    160      *
    161      * @param  e the edge
    162      * @throws IllegalArgumentException unless both endpoints are between {@code 0} and {@code V-1}
    163      */
    164     public void addEdge(Edge e) {
    165         // 一个顶点
    166         int v = e.either();
    167         // 另一个顶点
    168         int w = e.other(v);
    169         validateVertex(v);
    170         validateVertex(w);
    171         // 追加进顶点v的邻接链表
    172         adj[v].add(e);
    173         // 追加进顶点w的领接链表
    174         adj[w].add(e);
    175         // 边数++
    176         E++;
    177     }
    178 
    179     /**
    180      * 顶点V关联的全部边
    181      *
    182      * @param  v the vertex
    183      * @return the edges incident on vertex {@code v} as an Iterable
    184      * @throws IllegalArgumentException unless {@code 0 <= v < V}
    185      */
    186     public Iterable<Edge> adj(int v) {
    187         validateVertex(v);
    188         return adj[v];
    189     }
    190 
    191     /**
    192      * Returns the degree of vertex {@code v}.
    193      *
    194      * @param  v the vertex
    195      * @return the degree of vertex {@code v}               
    196      * @throws IllegalArgumentException unless {@code 0 <= v < V}
    197      */
    198     public int degree(int v) {
    199         validateVertex(v);
    200         return adj[v].size();
    201     }
    202 
    203     /**
    204      * Returns all edges in this edge-weighted graph.
    205      * To iterate over the edges in this edge-weighted graph, use foreach notation:
    206      * {@code for (Edge e : G.edges())}.
    207      *
    208      * @return all edges in this edge-weighted graph, as an iterable
    209      */
    210     public Iterable<Edge> edges() {
    211         Bag<Edge> list = new Bag<Edge>();
    212         for (int v = 0; v < V; v++) {
    213             int selfLoops = 0;
    214             for (Edge e : adj(v)) {
    215                 if (e.other(v) > v) {
    216                     list.add(e);
    217                 }
    218                 // add only one copy of each self loop (self loops will be consecutive)
    219                 else if (e.other(v) == v) {
    220                     if (selfLoops % 2 == 0) list.add(e);
    221                     selfLoops++;
    222                 }
    223             }
    224         }
    225         return list;
    226     }
    227 
    228     /**
    229      * Returns a string representation of the edge-weighted graph.
    230      * This method takes time proportional to <em>E</em> + <em>V</em>.
    231      *
    232      * @return the number of vertices <em>V</em>, followed by the number of edges <em>E</em>,
    233      *         followed by the <em>V</em> adjacency lists of edges
    234      */
    235     public String toString() {
    236         StringBuilder s = new StringBuilder();
    237         s.append(V + " " + E + NEWLINE);
    238         for (int v = 0; v < V; v++) {
    239             s.append(v + ": ");
    240             for (Edge e : adj[v]) {
    241                 s.append(e + "  ");
    242             }
    243             s.append(NEWLINE);
    244         }
    245         return s.toString();
    246     }
    247 
    248     /**
    249      * Unit tests the {@code EdgeWeightedGraph} data type.
    250      *
    251      * @param args the command-line arguments
    252      */
    253     public static void main(String[] args) {
    254         In in = new In(args[0]);
    255         EdgeWeightedGraph G = new EdgeWeightedGraph(in);
    256         StdOut.println(G);
    257     }
    258 
    259 }

     2.3 索引小值优先队列

      1 package study.algorithm.base;
      2 
      3 import java.util.Iterator;
      4 import java.util.NoSuchElementException;
      5 
      6 /**
      7  * 索引(顶点)最小优先级队列
      8  *
      9  * @param <Key>
     10  */
     11 public class IndexMinPQ<Key extends Comparable<Key>> implements Iterable<Integer> {
     12     /**
     13      * 元素数量上限
     14      */
     15     private int maxN;
     16     /**
     17      * 元素数量
     18      */
     19     private int n;
     20     /**
     21      * 索引二叉堆(数组中每个元素都是顶点,顶点v,对应keys[v]):数组从pq[0]代表原点其它顶点从pq[1]开始插入
     22      */
     23     private int[] pq;
     24     /**
     25      * 标记索引为i的元素在二叉堆中的位置。pq的反转数组(qp[index]=i):qp[pq[i]] = pq[qp[i]] = i
     26      */
     27     private int[] qp;
     28 
     29     /**
     30      * 元素有序数组(按照pq的索引赋值)
     31      */
     32     public Key[] keys;
     33 
     34     /**
     35      * 初始化一个空索引优先队列,索引范围:0 ~ maxN-1
     36      *
     37      * @param  maxN the keys on this priority queue are index from {@code 0}
     38      *         {@code maxN - 1}
     39      * @throws IllegalArgumentException if {@code maxN < 0}
     40      */
     41     public IndexMinPQ(int maxN) {
     42         if (maxN < 0) throw new IllegalArgumentException();
     43         this.maxN = maxN;
     44         // 初始有0个元素
     45         n = 0;
     46         // 初始化键数组长度为maxN + 1
     47         keys = (Key[]) new Comparable[maxN + 1];
     48         // 初始化"键值对"数组长度为maxN + 1
     49         pq   = new int[maxN + 1];
     50         // 初始化"值键对"数组长度为maxN + 1
     51         qp   = new int[maxN + 1];
     52         // 遍历给"值键对"数组赋值-1,后续只要!=-1,即包含i
     53         for (int i = 0; i <= maxN; i++)
     54             qp[i] = -1;
     55     }
     56 
     57     /**
     58      * Returns true if this priority queue is empty.
     59      *
     60      * @return {@code true} if this priority queue is empty;
     61      *         {@code false} otherwise
     62      */
     63     public boolean isEmpty() {
     64         return n == 0;
     65     }
     66 
     67     /**
     68      * Is {@code i} an index on this priority queue?
     69      *
     70      * @param  i an index
     71      * @return {@code true} if {@code i} is an index on this priority queue;
     72      *         {@code false} otherwise
     73      * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
     74      */
     75     public boolean contains(int i) {
     76         validateIndex(i);
     77         return qp[i] != -1;
     78     }
     79 
     80     /**
     81      * Returns the number of keys on this priority queue.
     82      *
     83      * @return the number of keys on this priority queue
     84      */
     85     public int size() {
     86         return n;
     87     }
     88 
     89     /**
     90      * 插入一个元素,将元素key关联索引i
     91      *
     92      * @param  i an index
     93      * @param  key the key to associate with index {@code i}
     94      * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
     95      * @throws IllegalArgumentException if there already is an item associated
     96      *         with index {@code i}
     97      */
     98     public void insert(int i, Key key) {
     99         validateIndex(i);
    100         if (contains(i)) throw new IllegalArgumentException("index is already in the priority queue");
    101         // 元素个数+1
    102         n++;
    103         // 索引为i的二叉堆位置为n
    104         qp[i] = n;
    105         // 二叉堆底部插入新元素,值=i
    106         pq[n] = i;
    107         // 索引i对应的元素赋值
    108         keys[i] = key;
    109         // 二叉堆中,上浮最后一个元素(小值上浮)
    110         swim(n);
    111     }
    112 
    113     /**
    114      * 返回最小元素的索引
    115      *
    116      * @return an index associated with a minimum key
    117      * @throws NoSuchElementException if this priority queue is empty
    118      */
    119     public int minIndex() {
    120         if (n == 0) throw new NoSuchElementException("Priority queue underflow");
    121         return pq[1];
    122     }
    123 
    124     /**
    125      * 返回最小元素(key)
    126      *
    127      * @return a minimum key
    128      * @throws NoSuchElementException if this priority queue is empty
    129      */
    130     public Key minKey() {
    131         if (n == 0) throw new NoSuchElementException("Priority queue underflow");
    132         return keys[pq[1]];
    133     }
    134 
    135     /**
    136      * 删除最小值key,并返回最小值(优先队列索引)
    137      *
    138      * @return an index associated with a minimum key
    139      * @throws NoSuchElementException if this priority queue is empty
    140      */
    141     public int delMin() {
    142         if (n == 0) throw new NoSuchElementException("Priority queue underflow");
    143         // pq[1]即为索引最小值
    144         int min = pq[1];
    145         // 交换第一个元素和最后一个元素
    146         exch(1, n--);
    147         // 把新换来的第一个元素下沉
    148         sink(1);
    149         // 校验下沉后,最后一个元素是最小值
    150         assert min == pq[n+1];
    151         // 恢复初始值,-1即代表该元素已删除
    152         qp[min] = -1;        // delete
    153         // 方便垃圾回收
    154         keys[min] = null;
    155         // 最后一个元素(索引)赋值-1
    156         pq[n+1] = -1;        // not needed
    157         return min;
    158     }
    159 
    160     /**
    161      * Returns the key associated with index {@code i}.
    162      *
    163      * @param  i the index of the key to return
    164      * @return the key associated with index {@code i}
    165      * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
    166      * @throws NoSuchElementException no key is associated with index {@code i}
    167      */
    168     public Key keyOf(int i) {
    169         validateIndex(i);
    170         if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
    171         else return keys[i];
    172     }
    173 
    174     /**
    175      * Change the key associated with index {@code i} to the specified value.
    176      *
    177      * @param  i the index of the key to change
    178      * @param  key change the key associated with index {@code i} to this key
    179      * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
    180      * @throws NoSuchElementException no key is associated with index {@code i}
    181      */
    182     public void changeKey(int i, Key key) {
    183         validateIndex(i);
    184         if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
    185         keys[i] = key;
    186         swim(qp[i]);
    187         sink(qp[i]);
    188     }
    189 
    190     /**
    191      * Change the key associated with index {@code i} to the specified value.
    192      *
    193      * @param  i the index of the key to change
    194      * @param  key change the key associated with index {@code i} to this key
    195      * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
    196      * @deprecated Replaced by {@code changeKey(int, Key)}.
    197      */
    198     @Deprecated
    199     public void change(int i, Key key) {
    200         changeKey(i, key);
    201     }
    202 
    203     /**
    204      * 减小索引i对应的值为key
    205      * 更新:
    206      * 1.元素数组keys[]
    207      * 2.小顶二叉堆pq[]
    208      *
    209      * @param  i the index of the key to decrease
    210      * @param  key decrease the key associated with index {@code i} to this key
    211      * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
    212      * @throws IllegalArgumentException if {@code key >= keyOf(i)}
    213      * @throws NoSuchElementException no key is associated with index {@code i}
    214      */
    215     public void decreaseKey(int i, Key key) {
    216         validateIndex(i);
    217         if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
    218         // key 值一样,报错
    219         if (keys[i].compareTo(key) == 0)
    220             throw new IllegalArgumentException("Calling decreaseKey() with a key equal to the key in the priority queue");
    221         // key比当前值大,报错
    222         if (keys[i].compareTo(key) < 0)
    223             throw new IllegalArgumentException("Calling decreaseKey() with a key strictly greater than the key in the priority queue");
    224         // key比当前值小,把key赋值进去
    225         keys[i] = key;
    226         // 小值上浮(qp[i]=索引i在二叉堆pq[]中的位置)
    227         swim(qp[i]);
    228     }
    229 
    230     /**
    231      * Increase the key associated with index {@code i} to the specified value.
    232      *
    233      * @param  i the index of the key to increase
    234      * @param  key increase the key associated with index {@code i} to this key
    235      * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
    236      * @throws IllegalArgumentException if {@code key <= keyOf(i)}
    237      * @throws NoSuchElementException no key is associated with index {@code i}
    238      */
    239     public void increaseKey(int i, Key key) {
    240         validateIndex(i);
    241         if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
    242         if (keys[i].compareTo(key) == 0)
    243             throw new IllegalArgumentException("Calling increaseKey() with a key equal to the key in the priority queue");
    244         if (keys[i].compareTo(key) > 0)
    245             throw new IllegalArgumentException("Calling increaseKey() with a key strictly less than the key in the priority queue");
    246         keys[i] = key;
    247         sink(qp[i]);
    248     }
    249 
    250     /**
    251      * Remove the key associated with index {@code i}.
    252      *
    253      * @param  i the index of the key to remove
    254      * @throws IllegalArgumentException unless {@code 0 <= i < maxN}
    255      * @throws NoSuchElementException no key is associated with index {@code i}
    256      */
    257     public void delete(int i) {
    258         validateIndex(i);
    259         if (!contains(i)) throw new NoSuchElementException("index is not in the priority queue");
    260         int index = qp[i];
    261         exch(index, n--);
    262         swim(index);
    263         sink(index);
    264         keys[i] = null;
    265         qp[i] = -1;
    266     }
    267 
    268     // throw an IllegalArgumentException if i is an invalid index
    269     private void validateIndex(int i) {
    270         if (i < 0) throw new IllegalArgumentException("index is negative: " + i);
    271         if (i >= maxN) throw new IllegalArgumentException("index >= capacity: " + i);
    272     }
    273 
    274    /***************************************************************************
    275     * General helper functions.
    276     ***************************************************************************/
    277     private boolean greater(int i, int j) {
    278         return keys[pq[i]].compareTo(keys[pq[j]]) > 0;
    279     }
    280 
    281     private void exch(int i, int j) {
    282         int swap = pq[i];
    283         pq[i] = pq[j];
    284         pq[j] = swap;
    285         qp[pq[i]] = i;
    286         qp[pq[j]] = j;
    287     }
    288 
    289 
    290    /***************************************************************************
    291     * Heap helper functions. 上浮
    292     ***************************************************************************/
    293     private void swim(int k) {
    294         // 如果父节点值比当前节点值大,交换,父节点作为当前节点,轮询。即小值上浮。
    295         while (k > 1 && greater(k/2, k)) {
    296             exch(k, k/2);
    297             k = k/2;
    298         }
    299     }
    300      //下沉
    301     private void sink(int k) {
    302         while (2*k <= n) {
    303             int j = 2*k;
    304             if (j < n && greater(j, j+1)) j++;
    305             if (!greater(k, j)) break;
    306             exch(k, j);
    307             k = j;
    308         }
    309     }
    310 
    311 
    312    /***************************************************************************
    313     * Iterators.
    314     ***************************************************************************/
    315 
    316     /**
    317      * Returns an iterator that iterates over the keys on the
    318      * priority queue in ascending order.
    319      * The iterator doesn't implement {@code remove()} since it's optional.
    320      *
    321      * @return an iterator that iterates over the keys in ascending order
    322      */
    323     @Override
    324     public Iterator<Integer> iterator() { return new HeapIterator(); }
    325 
    326     private class HeapIterator implements Iterator<Integer> {
    327         // create a new pq
    328         private IndexMinPQ<Key> copy;
    329 
    330         // add all elements to copy of heap
    331         // takes linear time since already in heap order so no keys move
    332         public HeapIterator() {
    333             copy = new IndexMinPQ<Key>(pq.length - 1);
    334             for (int i = 1; i <= n; i++)
    335                 copy.insert(pq[i], keys[pq[i]]);
    336         }
    337 
    338         @Override
    339         public boolean hasNext()  { return !copy.isEmpty();                     }
    340         @Override
    341         public void remove()      { throw new UnsupportedOperationException();  }
    342 
    343         @Override
    344         public Integer next() {
    345             if (!hasNext()) throw new NoSuchElementException();
    346             return copy.delMin();
    347         }
    348     }
    349 
    350 
    351     /**
    352      * Unit tests the {@code IndexMinPQ} data type.
    353      *
    354      * @param args the command-line arguments
    355      */
    356     public static void main(String[] args) {
    357         // insert a bunch of strings
    358         String[] strings = { "it", "was", "the", "best", "of", "times", "it", "was", "the", "worst" };
    359 
    360         IndexMinPQ<String> pq = new IndexMinPQ<String>(strings.length);
    361         for (int i = 0; i < strings.length; i++) {
    362             pq.insert(i, strings[i]);
    363         }
    364 
    365         // delete and print each key
    366         while (!pq.isEmpty()) {
    367             int i = pq.delMin();
    368             StdOut.println(i + " " + strings[i]);
    369         }
    370         StdOut.println();
    371 
    372         // reinsert the same strings
    373         for (int i = 0; i < strings.length; i++) {
    374             pq.insert(i, strings[i]);
    375         }
    376 
    377         // print each key using the iterator
    378         for (int i : pq) {
    379             StdOut.println(i + " " + strings[i]);
    380         }
    381         while (!pq.isEmpty()) {
    382             pq.delMin();
    383         }
    384 
    385     }
    386 }

    2.4 Prim算法(基于二叉堆)

      1 package study.algorithm.graph;
      2 
      3 import study.algorithm.base.*;
      4 
      5 import java.util.Arrays;
      6 
      7 /***
      8  * @Description 使用Prim算法计算一棵最小生成树 27  *
     28  * @author denny.zhang
     29  * @date 2020/5/26 9:50 上午
     30  */
     31 public class PrimMST {
     32     private static final double FLOATING_POINT_EPSILON = 1E-12;
     33 
     34     /**
     35      * 顶点索引,树顶点到非树顶点的最短边(距离树最近的边)
     36      */
     37     private Edge[] edgeTo;
     38     /**
     39      * 顶点索引,最短边的权重
     40      */
     41     private double[] distTo;
     42     /**
     43      * 顶点索引,标记顶点是否在最小生成树中
     44      */
     45     private boolean[] marked;
     46     /**
     47      * 有效的横切边(索引最小优先队列,索引为顶点v,值pq[v]=edgeTo[v].weight()=distTo[v])
     48      */
     49     private IndexMinPQ<Double> pq;
     50 
     51     /**
     52      * 计算一个边加权图的最小生成树
     53      * @param G the edge-weighted graph
     54      */
     55     public PrimMST(EdgeWeightedGraph G) {
     56         // 初始化3个顶点索引数组
     57         edgeTo = new Edge[G.V()];
     58         distTo = new double[G.V()];
     59         marked = new boolean[G.V()];
     60         // 初始化:顶点索引最小优先队列
     61         pq = new IndexMinPQ<Double>(G.V());
     62         for (int v = 0; v < G.V(); v++) {
     63             // 初始化为无穷大
     64             distTo[v] = Double.POSITIVE_INFINITY;
     65         }
     66         // 遍历顶点数
     67         for (int v = 0; v < G.V(); v++)
     68         {
     69             // 如果顶点0开始能全进树,那就一次搞定
     70             StdOut.println("v="+ v+",marked[v]="+marked[v]);
     71             // 如果没进树
     72             if (!marked[v]) {
     73                 StdOut.println("v="+v+",执行prim");
     74                 // 最小生成树
     75                 prim(G, v);
     76             }
     77         }
     78 
     79         // 校验,可省略
     80         assert check(G);
     81     }
     82 
     83 
     84     /**
     85      * 从顶点s开始生成图G
     86      * @param G
     87      * @param s
     88      */
     89     private void prim(EdgeWeightedGraph G, int s) {
     90         // 顶点s的权重=0
     91         distTo[s] = 0.0;
     92         // 顶点s进队列,key为索引,value为边权重
     93         pq.insert(s, distTo[s]);
     94         StdOut.println("顶点s进队列:  s="+ s+",distTo[s]="+distTo[s]);
     95         StdOut.println("pq="+ Arrays.toString(pq.keys));
     96 
     97         // 循环
     98         while (!pq.isEmpty()) {
     99             // 取出最小权重边的顶点(最近的顶点)
    100             int v = pq.delMin();
    101             // 添加到树中
    102             scan(G, v);
    103         }
    104     }
    105 
    106     /**
    107      * 将顶点V添加到树中,更新数据
    108      * @param G
    109      * @param v
    110      */
    111     private void scan(EdgeWeightedGraph G, int v) {
    112         StdOut.println("v="+ v+",进树");
    113         // 标记 进树
    114         marked[v] = true;
    115         // 遍历顶点v的邻接边
    116         for (Edge e : G.adj(v)) {
    117             StdOut.println("遍历顶点v的邻接边:v="+ v+",e="+e.toString());
    118             // 另一个顶点
    119             int w = e.other(v);
    120             StdOut.println("w=" + w);
    121             // 如果w已进树,跳过(至少有一个点不在树中,计算才有意义)
    122             if (marked[w]) {
    123                 StdOut.println("已进树,跳过w=" + w);
    124                 continue;
    125             }
    126             // 如果边e的权重 < 当前到顶点w的权重
    127             if (e.weight() < distTo[w]) {
    128                 StdOut.println("e.weight()="+e.weight()+" ,distTo[w]=" + distTo[w]);
    129                 // 更新最小权重
    130                 distTo[w] = e.weight();
    131                 // 连接w和树的最佳边变为e
    132                 edgeTo[w] = e;
    133                 // 顶点w在pq队列中
    134                 if (pq.contains(w)) {
    135                     StdOut.println("顶点w在pq队列中:w="+w);
    136                     // 减小w索引对应的权重值,小值上浮
    137                     pq.decreaseKey(w, distTo[w]);
    138                  // 顶点w不在队列中
    139                 } else {
    140                     StdOut.println("顶点w不在pq队列中,插入队列前:w="+w+",pq="+ Arrays.toString(pq.keys));
    141                     // 插入队列
    142                     pq.insert(w, distTo[w]);
    143                     StdOut.println("顶点w不在pq队列中,插入队列后:w="+w+",pq="+Arrays.toString(pq.keys));
    144                 }
    145             }
    146         }
    147     }
    148 
    149     /**
    150      * Returns the edges in a minimum spanning tree (or forest).
    151      * @return the edges in a minimum spanning tree (or forest) as
    152      *    an iterable of edges
    153      */
    154     public Iterable<Edge> edges() {
    155         Queue<Edge> mst = new Queue<Edge>();
    156         for (int v = 0; v < edgeTo.length; v++) {
    157             Edge e = edgeTo[v];
    158             if (e != null) {
    159                 mst.enqueue(e);
    160             }
    161         }
    162         return mst;
    163     }
    164 
    165     /**
    166      * Returns the sum of the edge weights in a minimum spanning tree (or forest).
    167      * @return the sum of the edge weights in a minimum spanning tree (or forest)
    168      */
    169     public double weight() {
    170         double weight = 0.0;
    171         for (Edge e : edges()) {
    172             weight += e.weight();
    173         }
    174         return weight;
    175     }
    176 
    177 
    178     // check optimality conditions (takes time proportional to E V lg* V)
    179     private boolean check(EdgeWeightedGraph G) {
    180 
    181         // check weight
    182         double totalWeight = 0.0;
    183         for (Edge e : edges()) {
    184             totalWeight += e.weight();
    185         }
    186         if (Math.abs(totalWeight - weight()) > FLOATING_POINT_EPSILON) {
    187             System.err.printf("Weight of edges does not equal weight(): %f vs. %f
    ", totalWeight, weight());
    188             return false;
    189         }
    190 
    191         // check that it is acyclic
    192         UF uf = new UF(G.V());
    193         for (Edge e : edges()) {
    194             int v = e.either(), w = e.other(v);
    195             if (uf.find(v) == uf.find(w)) {
    196                 System.err.println("Not a forest");
    197                 return false;
    198             }
    199             uf.union(v, w);
    200         }
    201 
    202         // check that it is a spanning forest
    203         for (Edge e : G.edges()) {
    204             int v = e.either(), w = e.other(v);
    205             if (uf.find(v) != uf.find(w)) {
    206                 System.err.println("Not a spanning forest");
    207                 return false;
    208             }
    209         }
    210 
    211         // check that it is a minimal spanning forest (cut optimality conditions)
    212         for (Edge e : edges()) {
    213 
    214             // all edges in MST except e
    215             uf = new UF(G.V());
    216             for (Edge f : edges()) {
    217                 int x = f.either(), y = f.other(x);
    218                 if (f != e) {
    219                     uf.union(x, y);
    220                 }
    221             }
    222 
    223             // check that e is min weight edge in crossing cut
    224             for (Edge f : G.edges()) {
    225                 int x = f.either(), y = f.other(x);
    226                 if (uf.find(x) != uf.find(y)) {
    227                     if (f.weight() < e.weight()) {
    228                         System.err.println("Edge " + f + " violates cut optimality conditions");
    229                         return false;
    230                     }
    231                 }
    232             }
    233 
    234         }
    235 
    236         return true;
    237     }
    238 
    239     public static void main(String[] args) {
    240         // 读取图文件
    241         In in = new In(args[0]);
    242         // 初始化:边加权无向图
    243         EdgeWeightedGraph G = new EdgeWeightedGraph(in);
    244         // 核心算法Prim
    245         PrimMST mst = new PrimMST(G);
    246         // 打印全部边
    247         for (Edge e : mst.edges()) {
    248             StdOut.println(e);
    249         }
    250         // 打印总权重
    251         StdOut.printf("%.5f
    ", mst.weight());
    252     }
    253 
    254 
    255 }

     运行方法:

    使用导入文件的方式,后续只需要修改文件内容,即可执行,比较方便。8个顶点,16条边的边加权无向图,内容如下:

    8 16
    4 5 0.35
    4 7 0.37
    5 7 0.28
    0 7 0.16
    1 5 0.32
    0 4 0.38
    2 3 0.17
    1 7 0.19
    0 2 0.26
    1 2 0.36
    1 3 0.29
    2 7 0.34
    6 2 0.40
    3 6 0.52
    6 0 0.58
    6 4 0.93

    运行配置:

    运行:

    结合邻接链表来看结果,其实就是遍历了一遍邻接表。

    v=0,marked[v]=false----从顶点0作为原点,构建最小生成树,---begin!
    v=0,执行prim
    顶点s进队列:  s=0,distTo[s]=0.0
    pq=[0.0, null, null, null, null, null, null, null, null]
    v=0,进树---》顶点0开始  另一个顶点: 6 2 4 7 
    遍历顶点v的邻接边:v=0,e=6-0 0.58000
    w=6
    e.weight()=0.58 ,distTo[w]=Infinity
    顶点w不在pq队列中,插入队列前:w=6,pq=[null, null, null, null, null, null, null, null, null]
    顶点w不在pq队列中,插入队列后:w=6,pq=[null, null, null, null, null, null, 0.58, null, null]
    遍历顶点v的邻接边:v=0,e=0-2 0.26000
    w=2
    e.weight()=0.26 ,distTo[w]=Infinity
    顶点w不在pq队列中,插入队列前:w=2,pq=[null, null, null, null, null, null, 0.58, null, null]
    顶点w不在pq队列中,插入队列后:w=2,pq=[null, null, 0.26, null, null, null, 0.58, null, null]
    遍历顶点v的邻接边:v=0,e=0-4 0.38000
    w=4
    e.weight()=0.38 ,distTo[w]=Infinity
    顶点w不在pq队列中,插入队列前:w=4,pq=[null, null, 0.26, null, null, null, 0.58, null, null]
    顶点w不在pq队列中,插入队列后:w=4,pq=[null, null, 0.26, null, 0.38, null, 0.58, null, null]
    遍历顶点v的邻接边:v=0,e=0-7 0.16000
    w=7
    e.weight()=0.16 ,distTo[w]=Infinity
    顶点w不在pq队列中,插入队列前:w=7,pq=[null, null, 0.26, null, 0.38, null, 0.58, null, null]
    顶点w不在pq队列中,插入队列后:w=7,pq=[null, null, 0.26, null, 0.38, null, 0.58, 0.16, null]
    v=7,进树---》顶点7开始 另一个顶点: 2 1 0 5 4
    遍历顶点v的邻接边:v=7,e=2-7 0.34000
    w=2
    遍历顶点v的邻接边:v=7,e=1-7 0.19000
    w=1
    e.weight()=0.19 ,distTo[w]=Infinity
    顶点w不在pq队列中,插入队列前:w=1,pq=[null, null, 0.26, null, 0.38, null, 0.58, null, null]
    顶点w不在pq队列中,插入队列后:w=1,pq=[null, 0.19, 0.26, null, 0.38, null, 0.58, null, null]
    遍历顶点v的邻接边:v=7,e=0-7 0.16000
    w=0
    已进树,跳过w=0
    遍历顶点v的邻接边:v=7,e=5-7 0.28000
    w=5
    e.weight()=0.28 ,distTo[w]=Infinity
    顶点w不在pq队列中,插入队列前:w=5,pq=[null, 0.19, 0.26, null, 0.38, null, 0.58, null, null]
    顶点w不在pq队列中,插入队列后:w=5,pq=[null, 0.19, 0.26, null, 0.38, 0.28, 0.58, null, null]
    遍历顶点v的邻接边:v=7,e=4-7 0.37000
    w=4
    e.weight()=0.37 ,distTo[w]=0.38
    顶点w在pq队列中:w=4
    v=1,进树---》顶点1开始 另一个顶点: 3 2 7 5
    遍历顶点v的邻接边:v=1,e=1-3 0.29000
    w=3
    e.weight()=0.29 ,distTo[w]=Infinity
    顶点w不在pq队列中,插入队列前:w=3,pq=[null, null, 0.26, null, 0.37, 0.28, 0.58, null, null]
    顶点w不在pq队列中,插入队列后:w=3,pq=[null, null, 0.26, 0.29, 0.37, 0.28, 0.58, null, null]
    遍历顶点v的邻接边:v=1,e=1-2 0.36000
    w=2
    遍历顶点v的邻接边:v=1,e=1-7 0.19000
    w=7
    已进树,跳过w=7
    遍历顶点v的邻接边:v=1,e=1-5 0.32000
    w=5
    v=2,进树---》顶点2开始 另一个顶点: 6 7 1 0 3
    遍历顶点v的邻接边:v=2,e=6-2 0.40000
    w=6
    e.weight()=0.4 ,distTo[w]=0.58
    顶点w在pq队列中:w=6
    遍历顶点v的邻接边:v=2,e=2-7 0.34000
    w=7
    已进树,跳过w=7
    遍历顶点v的邻接边:v=2,e=1-2 0.36000
    w=1
    已进树,跳过w=1
    遍历顶点v的邻接边:v=2,e=0-2 0.26000
    w=0
    已进树,跳过w=0
    遍历顶点v的邻接边:v=2,e=2-3 0.17000
    w=3
    e.weight()=0.17 ,distTo[w]=0.29
    顶点w在pq队列中:w=3
    v=3,进树---》顶点3开始 另一个顶点: 6 1 2 5
    遍历顶点v的邻接边:v=3,e=3-6 0.52000
    w=6
    遍历顶点v的邻接边:v=3,e=1-3 0.29000
    w=1
    已进树,跳过w=1
    遍历顶点v的邻接边:v=3,e=2-3 0.17000
    w=2
    已进树,跳过w=2
    v=5,进树---》顶点5开始 另一个顶点: 1 7 4
    w=1
    已进树,跳过w=1
    遍历顶点v的邻接边:v=5,e=5-7 0.28000
    w=7
    已进树,跳过w=7
    遍历顶点v的邻接边:v=5,e=4-5 0.35000
    w=4
    e.weight()=0.35 ,distTo[w]=0.37
    顶点w在pq队列中:w=4
    v=4,进树---》顶点4开始 另一个顶点:6 0 7 5 
    遍历顶点v的邻接边:v=4,e=6-4 0.93000
    w=6
    遍历顶点v的邻接边:v=4,e=0-4 0.38000
    w=0
    已进树,跳过w=0
    遍历顶点v的邻接边:v=4,e=4-7 0.37000
    w=7
    已进树,跳过w=7
    遍历顶点v的邻接边:v=4,e=4-5 0.35000
    w=5
    已进树,跳过w=5
    v=6,进树---》顶点6开始 另一个顶点:4 0 3 2
    遍历顶点v的邻接边:v=6,e=6-4 0.93000
    w=4
    已进树,跳过w=4
    遍历顶点v的邻接边:v=6,e=6-0 0.58000
    w=0
    已进树,跳过w=0
    遍历顶点v的邻接边:v=6,e=3-6 0.52000
    w=3
    已进树,跳过w=3
    遍历顶点v的邻接边:v=6,e=6-2 0.40000
    w=2
    已进树,跳过w=2----从顶点0作为原点,构建最小生成树,---end!
    v=1,marked[v]=true----从顶点1作为原点,构建最小生成树,已经入树,跳过!后续节点都已进树,都跳过。
    v=2,marked[v]=true
    v=3,marked[v]=true
    v=4,marked[v]=true
    v=5,marked[v]=true
    v=6,marked[v]=true
    v=7,marked[v]=true
    1-7 0.19000---》打印最小生成树的边  权重
    0-2 0.26000
    2-3 0.17000
    4-5 0.35000
    5-7 0.28000
    6-2 0.40000
    0-7 0.16000
    1.810000---》打印最小生成树的总权重

    三、总结

    使用二叉堆(索引小值优先队列实现)优化的Prim算法,

    3.1 空间复杂度

    构造了几个顶点v索引的数组,所以和顶点数v成正比

    3.2 时间复杂度

    主要就在优先队列的操作

    • 1.PrimMST->prim中:共v个顶点,每次取出最小值pq.delMin() 取第一个最小值元素,并把最后一个元素填充,新上来的这个元素下沉sink()大值下沉,时间复杂度=logv, 共v个顶点,也就是vlogv。
    • 2.PrimMST->prim->scan  中 :遍历顶点的邻接表(邻接边链表),不管是 更新更小值 decreaseKey()、还是 插入新值 insert() ->都要执行swim()小值上浮, 时间复杂度=logv,一共E条边,所以Elogv.

    所以:O(vlgv+elgv)=ElgV

    3.3. 优化

    1.如果使用斐波那契堆也就是Fredman-Tarjan算法,稠密图的decreaseKey操作耗时可以降到O(1),所以总耗时=O(V*logV+E*O(1))=ElgV

    2.接近线性的算法Chazelle,但算法很复杂,就不再描述。

    下一节,我们分析Kruskal算法。

  • 相关阅读:
    CMD 常用命令
    CMD 删除脚本
    HAproxy 介绍
    HAproxy 配置参数详解
    HAproxy 源码包安装
    lvs keepalived 安装配置详解【转】
    linux下负载均衡(LVS安装与配置)【转】
    CentOS 6.3下部署LVS(NAT)+keepalived实现高性能高可用负载均衡【转】
    Linux负载均衡软件LVS之二(安装篇)[转]
    Mysql + keepalived 实现双主热备读写分离【转】
  • 原文地址:https://www.cnblogs.com/dennyzhangdd/p/12964549.html
Copyright © 2011-2022 走看看