• 数据结构之优先队列


    1、优先队列的底层实现可以使用最大堆进行实现,由于优先队列本身就是一个队列,所以可以复用队列的接口。

    2、首先,将定义好的Queue接口,创建好,可以让优先队列实现该接口之后,实现这些接口的方法。

     1 package com.queue;
     2 
     3 /**
     4  *
     5  */
     6 public interface Queue<E> {
     7 
     8     /**
     9      * 向队列中添加一个元素,入队。对应栈的入栈。
    10      *
    11      * @param e
    12      */
    13     public void enqueue(E e);
    14 
    15     /**
    16      * 从队列中取出一个元素,出队。对应栈的出栈。
    17      *
    18      * @return
    19      */
    20     public E dequeue();
    21 
    22     /**
    23      * 获取到队列中队首的元素。对应栈的查看栈顶的元素。
    24      *
    25      * @return
    26      */
    27     public E getFront();
    28 
    29     /**
    30      * 获取到队列的大小
    31      *
    32      * @return
    33      */
    34     public int getSize();
    35 
    36     /**
    37      * 判断队列是否为空
    38      *
    39      * @return
    40      */
    41     public boolean isEmpty();
    42 
    43 }

    3、由于优先队列可以使用很多底层的数据结构实现,这里使用的是最大堆这种数据结构。

      1 package com.maxHeap;
      2 
      3 import com.company.Array;
      4 
      5 import java.util.Random;
      6 
      7 /**
      8  * 最大堆,由于规定了每个节点的值都要大于等于左右孩子节点的值,所以要具有可比较性
      9  */
     10 public class MaxHeap<E extends Comparable<E>> {
     11 
     12     // 动态数组
     13     public Array<E> data;
     14 
     15     // ctrl + f12查看方法,变量等等
     16     // fn + alt + insert 构造方法
     17 
     18     /**
     19      * 如果直到动态数组的大小,可以直接创建
     20      *
     21      * @param capacity
     22      */
     23     public MaxHeap(int capacity) {
     24         data = new Array<>(capacity);
     25     }
     26 
     27 
     28     /**
     29      * 传递的数组参数转换为堆的形状,放到data的数组中。
     30      * <p>
     31      * <p>
     32      * Heapify的算法复杂度,将n个元素逐个插入到一个空堆中,算法复杂度是O(nlogn)级别的。
     33      * <p>
     34      * <p>
     35      * 如果使用的是heapify的过程,算法复杂度是O(n)级别的。
     36      *
     37      * @param arr
     38      */
     39     public MaxHeap(E[] arr) {
     40         // 首先拷贝一份用户传递的数组元素,放入到动态数组中。
     41         // 根据传递的数组,生成一个新的动态数组
     42         data = new Array<>(arr);
     43         // 此时data已经存放了用户传递的arr数组元素的值
     44 
     45         // 从最后一个非叶子节点开始向前依次遍历,进行元素的下沉
     46         // 从最后一个非叶子节点开始,最后一个节点的索引的父亲节点的索引。
     47         for (int k = this.parent(arr.length - 1); k >= 0; k--) {
     48             this.siftDown(k);
     49         }
     50     }
     51 
     52     /**
     53      * 无参构造函数,如果不知道动态数组的大小,默认大小长度是10
     54      */
     55     public MaxHeap() {
     56         data = new Array<>();
     57     }
     58 
     59     /**
     60      * 返回堆中的元素个数
     61      *
     62      * @return
     63      */
     64     public int size() {
     65         return data.getSize();
     66     }
     67 
     68     /**
     69      * 返回一个布尔值,表示堆中是否为空
     70      *
     71      * @return
     72      */
     73     public boolean isEmpty() {
     74         return data.isEmpty();
     75     }
     76 
     77     /**
     78      * 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
     79      *
     80      * @param index
     81      * @return
     82      */
     83     private int parent(int index) {
     84         if (index == 0) {
     85             throw new IllegalArgumentException("index-0 doesn't hava parent.");
     86         }
     87         return (index - 1) / 2;
     88     }
     89 
     90     /**
     91      * 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引。
     92      *
     93      * @param index
     94      * @return
     95      */
     96     private int leftChild(int index) {
     97         return index * 2 + 1;
     98     }
     99 
    100     /**
    101      * 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引。
    102      *
    103      * @param index
    104      * @return
    105      */
    106     private int rightChild(int index) {
    107         return index * 2 + 2;
    108     }
    109 
    110 
    111     /**
    112      * 向堆中添加元素。时间复杂度是O(logn)
    113      *
    114      * @param e
    115      */
    116     public void add(E e) {
    117         // 向动态数组中添加一个元素
    118         data.addLast(e);
    119 
    120         // 开始维护堆的性质
    121         // 参数是,希望上浮的那个元素所对应的索引是多少,即最后一个元素的索引
    122         siftUp(data.getSize() - 1);
    123     }
    124 
    125     /**
    126      * 元素上浮过程
    127      *
    128      * @param k
    129      */
    130     private void siftUp(int k) {
    131         // 首先,不能已经达到了根节点,所以k必须大于零的。
    132         // 如果该节点的父亲节点小于当前节点就进行上浮操作
    133         while (k > 0 && data.get(this.parent(k)).compareTo(data.get(k)) < 0) {
    134             // 将当前节点所处的元素和父亲节点的元素进行比较
    135             // 如果当前节点所处的元素大于父亲节点的元素的值,将它们进行交换,进行上浮的动作。
    136             data.swap(k, this.parent(k));
    137             // 此时,让该节点元素的父亲节点的索引赋值给当前元素,
    138             // 下一轮循环的时候,可以看当前的k已经来到了新的位置,
    139             // 对于新的位置,是不是依然不满足堆的性质,就是它在新的位置
    140             // 上的父亲节点所在的元素还要大,还要大就进行交换,依次类推。
    141             // 直到走到了索引为0或者该节点小于父亲节点的元素。
    142             k = parent(k);
    143         }
    144     }
    145 
    146     /**
    147      * 看堆中的最大元素
    148      *
    149      * @return
    150      */
    151     public E findMax() {
    152         if (data.getSize() == 0) {
    153             throw new IllegalArgumentException("Can not findMax when heap is empty.");
    154         }
    155         return data.get(0);
    156     }
    157 
    158     /**
    159      * 取出堆中最大元素。时间复杂度是O(logn)
    160      *
    161      * @return
    162      */
    163     public E extractMax() {
    164         // 首先,保存堆中的最大元素,暂存最大元素
    165         E ret = this.findMax();
    166         // 将堆顶元素和最后一个元素交换位置
    167         data.swap(0, data.getSize() - 1);
    168         // 交换完位置之后,此时已经将堆顶的元素放到数组的末尾了,数组末尾元素放在了堆顶位置
    169 
    170         // 删除最大元素,删除处于数组末尾的堆顶元素
    171         data.removeLast();
    172 
    173         // 开始进行元素的下沉,参数是索引位置为0
    174         siftDown(0);
    175 
    176         // 将最大元素返回,然后将最大元素从堆中删除
    177         return ret;
    178     }
    179 
    180     /**
    181      * @param k 索引K
    182      */
    183     private void siftDown(int k) {
    184         // 循环遍历,什么时候结束呢,
    185         // 最极端的情况,k这个位置所处的节点,已经没有孩子了,当下沉到叶子节点的时候就结束了,
    186         // 即,如果对于k这个节点的左孩子所在的索引小于数组的长度的时候,
    187         // 此时k的左孩子都已经越界了,肯定一个孩子都没有了,此时右孩子的索引比左孩子的索引大。
    188         // 循环的意思是,如果左孩子小于数组长度,就一直循环,否则就终止循环。
    189         while (leftChild(k) < data.getSize()) {
    190             // 比较k这个节点和k的左右孩子中最大的那个节点,看看k和这个最大的节点到底那个比较大,
    191             // 先找到k左右孩子中最大的节点。
    192             // 左孩子肯定是存在的,因为leftChild(k)小于数组的长度。
    193             int j = this.leftChild(k);
    194             // 但是右孩子不一定存在,所以此时进行判断,j + 1就是k的右孩子所在的索引,
    195             // 如果K的右孩子小于数组的长度,说明有右孩子。
    196             // 如果k的右孩子大于k的左孩子
    197             if (j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0) {
    198                 // data[j]是leftChild和rightChild中的最大值
    199                 // j自增。此时j存储的是右孩子所对应的索引了。
    200                 // j++;
    201                 j = rightChild(k);
    202                 // 如果k没有右孩子,或者k的左孩子大于右孩子,那么j存储的就是左孩子所对应的索引。
    203             }
    204 
    205 
    206             // 此时,将k节点和左右孩子最大的节点进行比较,如果发现了就终止本次循环。
    207             // 因为此时,父亲节点的值大于左右孩子的值了,下沉操作结束了。
    208             if (data.get(k).compareTo(data.get(j)) >= 0) {
    209                 break;
    210             }
    211 
    212             // 否则,就开始交换k这个索引位置和j这个索引位置的值。
    213             data.swap(k, j);
    214             // 交换结束之后,将j的赋值给k。进行下一轮循环,对于新的一轮k来说,是否需要继续下沉。
    215             k = j;
    216         }
    217 
    218     }
    219 
    220 
    221     /**
    222      * 取出堆中的最大元素,并且替换成元素e。
    223      * <p>
    224      * <p>
    225      * Replace是取出最大元素后,放入一个新元素。此时,堆中元素的总数未发生改变,
    226      * 此时可以使用这样方法来实现,首先可以先extractMax,然后再add操作,
    227      * 两次O(logn)的操作。但是呢,可以直接将堆顶元素替换以后Sift Down(元素进行下沉操作),
    228      * 这样的话是一次O(logn)的操作。
    229      *
    230      * @param e
    231      * @return
    232      */
    233     public E replace(E e) {
    234         // 取出最大元素的值
    235         E ret = findMax();
    236         // 此时,将0索引位置的元素替换成e
    237         data.set(0, e);
    238         // 然后使用下沉元素,使得根节点元素符合堆的性质
    239         siftDown(0);
    240 
    241         // 将最大元素的值返回
    242         return ret;
    243     }
    244 
    245 
    246     /**
    247      * 对使用heapify或者不使用的时候进行性能测试
    248      * <p>
    249      * 使用heapify的方式进行元素添加,或者自己将元素添加到空堆中。
    250      *
    251      * @param data
    252      * @param isHeapify 是否使用heapify
    253      * @return
    254      */
    255     private static double performanceTesting(Integer[] data, boolean isHeapify) {
    256         // 创建一个变量存储开始时间
    257         long startTime = System.nanoTime();
    258 
    259         // 创建一个变量
    260         MaxHeap<Integer> maxHeap;
    261         // 判断是否使用heapify
    262         if (isHeapify) {
    263             // true是使用heapify,此时将数组传入到构造函数中就实现了使用heapify方法
    264             maxHeap = new MaxHeap<>(data);
    265         } else {
    266             // 创建一个maxHeap对象
    267             maxHeap = new MaxHeap<>();
    268             // 循环遍历,将所有的元素值都新增到堆中
    269             for (int i = 0; i < data.length; i++) {
    270                 maxHeap.add(data[i]);
    271             }
    272         }
    273 
    274         // 验证堆的正确性
    275         // 创建一个一百万长度的数组arr
    276         int[] arr = new int[data.length];
    277         // 取出最大元素。此时是循环一百万次,是对堆进行了一遍排序,从大到小进行排序的。
    278         for (int i = 0; i < data.length; i++) {
    279             // 将堆中最大的元素依次放入到数组中
    280             arr[i] = maxHeap.extractMax();
    281         }
    282 
    283         // 循环判断,如果下一个元素的值小于这个元素,就抛出异常
    284         // 即前一个数小于后一个数,此时不是降序的序列了。
    285         for (int i = 1; i < data.length; i++) {
    286             if (arr[i - 1] < arr[i]) {
    287                 throw new IllegalArgumentException("Error");
    288             }
    289         }
    290 
    291         // 如果是降序数组,此时正常执行这句话
    292         System.out.println("Test MaxHeap completed.");
    293 
    294         // 创建一个变量存储结束时间
    295         long endTime = System.nanoTime();
    296         // 将纳秒转换为秒返回即可
    297         return (endTime - startTime) / 1000000000.0;
    298     }
    299 
    300 
    301     public static void main(String[] args) {
    302 //        // 初始化一百万
    303 //        int n = 1000000;
    304 //        // 初始化堆
    305 //        MaxHeap<Integer> maxHeap = new MaxHeap<>();
    306 //        // 创建一个随机对象
    307 //        Random random = new Random();
    308 //        // 将一百万个随机数扔到堆中
    309 //        for (int i = 0; i < n; i++) {
    310 //            // 0-到Integet最大值
    311 //            maxHeap.add(random.nextInt(Integer.MAX_VALUE));
    312 //        }
    313 //
    314 //        // 创建一个一百万长度的数组arr
    315 //        int[] arr = new int[n];
    316 //        // 取出最大元素。此时是循环一百万次,是对堆进行了一遍排序,从大到小进行排序的。
    317 //        for (int i = 0; i < n; i++) {
    318 //            // 将堆中最大的元素依次放入到数组中
    319 //            arr[i] = maxHeap.extractMax();
    320 //        }
    321 //
    322 //        // 循环判断,如果下一个元素的值小于这个元素,就抛出异常
    323 //        // 即前一个数小于后一个数,此时不是降序的序列了。
    324 //        for (int i = 1; i < n; i++) {
    325 //            if (arr[i - 1] < arr[i]) {
    326 //                throw new IllegalArgumentException("Error");
    327 //            }
    328 //        }
    329 //
    330 //        System.out.println("Test MaxHeap completed.");
    331 
    332 
    333         // 初始化一百万
    334         int n = 1000000;
    335         // 初始化数组
    336         Integer[] data = new Integer[n];
    337         // 创建一个随机对象
    338         Random random = new Random();
    339         // 将一百万个随机数扔到堆中
    340         for (int i = 0; i < n; i++) {
    341             // 0-到Integet最大值
    342             data[i] = random.nextInt(Integer.MAX_VALUE);
    343         }
    344 
    345         // 第一次使用的是一个一个元素新增到堆中
    346         double time1 = MaxHeap.performanceTesting(data, false);
    347         System.out.println("Without heapify time1 : " + time1);
    348 
    349         // 第二次使用heapify的方式将元素添加到堆中
    350         double time2 = MaxHeap.performanceTesting(data, true);
    351         System.out.println("with heapify time2 : " + time2);
    352 
    353     }
    354 
    355 }

    4、优先队列的实现代码,如下所示:

     1 package com.queue;
     2 
     3 import com.maxHeap.MaxHeap;
     4 
     5 /**
     6  * 优先队列的底层实现可以使用最大堆进行实现,
     7  * 由于优先队列本身就是一个队列,所以可以复用队列的接口。
     8  *
     9  * @param <E> 由于优先队列要具有可比较性,所以要进行继承Comparable
    10  */
    11 public class PriorityQueue<E extends Comparable<E>> implements Queue<E> {
    12 
    13     // 使用最大堆实现优先队列
    14     private MaxHeap<E> maxHeap;
    15 
    16     /**
    17      * 无参构造函数,直接创建一个MaxHeap即可
    18      */
    19     public PriorityQueue() {
    20         maxHeap = new MaxHeap<>();
    21     }
    22 
    23     @Override
    24     public void enqueue(E e) {
    25         // 入队操作,直接调用最大堆的add方法
    26         maxHeap.add(e);
    27     }
    28 
    29     @Override
    30     public E dequeue() {
    31         // 出队操作,将最大元素提取出来
    32         return maxHeap.extractMax();
    33     }
    34 
    35     @Override
    36     public E getFront() {
    37         // 查看队首的元素是谁
    38         // 其实对应的是最大堆对应的堆顶的元素
    39         E maxHeapMax = maxHeap.findMax();
    40         // 获取到队列中队首的元素。对应栈的查看栈顶的元素。
    41         return maxHeapMax;
    42     }
    43 
    44     @Override
    45     public int getSize() {
    46         // 直接返回最大堆中的大小
    47         return maxHeap.size();
    48     }
    49 
    50     @Override
    51     public boolean isEmpty() {
    52         // 直接返回最大堆中的是否为空
    53         return maxHeap.isEmpty();
    54     }
    55 
    56 
    57     public static void main(String[] args) {
    58         PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
    59         // 优先队列入队操作
    60         for (int i = 0; i < 1000; i++) {
    61             priorityQueue.enqueue(i);
    62         }
    63 
    64 
    65         // 优先队列出队操作
    66         for (int i = 0; i < 1000; i++) {
    67             if (i % 30 == 0) {
    68                 System.out.println();
    69             }
    70             System.out.print(priorityQueue.dequeue() + " ");
    71         }
    72 
    73 
    74     }
    75 
    76 }

     

  • 相关阅读:
    学习Ember遇到的一些问题
    angular学习资源
    电话号码验证(正则)
    使用 Bitbucket Pipelines 持续交付托管项目
    zookeepercli
    如何用 JIRA REST API 创建 Issue
    Maven如何传递系统属性变量到TestNG
    基于WebDriver&TestNG 实现自己的Annotation @TakeScreenshotOnFailure
    25+ Useful Selenium Web driver Code Snippets For GUI Testing Automation
    Selenium WebDriver 之 PageObjects 模式 by Example
  • 原文地址:https://www.cnblogs.com/biehongli/p/12515486.html
Copyright © 2020-2023  润新知