数据结构之堆与优先队列
堆
堆必须是一个完全二叉树。除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列
堆中的每个节点的值必须大于等于(或者小于等于)其子树中每个节点的值或者说堆中每个节点的值都大于等于(或者小于等于)其左右子节点的值。这两种表述是等价的。
对于每个节点的值都大于等于子树中每个节点值的堆,我们叫作“大顶堆”。对于每个节点的值都小于等于子树中每个节点值的堆,我们叫作“小顶堆”。
在图中1和2是大顶堆,3是小顶堆,4不是堆(最后一层不是右子节点)。
实现
完全二叉树比较适合用数组来存储。用数组来存储完全二叉树是非常节省存储空间的。因为我们不需要存储左右子节点的指针,单纯地通过数组的下标,就可以找到一个节点的左右子节点和父节点。
数组中下标为 i 的节点的左子节点,就是下标为 $i$的节点,右子节点就是下标为$i*2+1$的节点,父节点就是下标为 的节点。
堆化
新插入的元素放到堆的最后,我们需要进行调整,让其重新满足堆的特性,这个过程就叫作堆化(heapify)。 堆化实际上有两种,从下往上和从上往下。
新插入的节点与父节点对比大小。如果不满足子节点小于等于父节点的大小关系,就互换两个节点。重复这个过程,直到父子节点之间满足刚说的那种大小关系。
删除堆顶元素
堆顶元素存储的其实是堆中数据中的最大值或者最小值。 如果我们构造的是大顶堆,堆顶元素就是最大的元素。当我们删除堆顶元素之后,就需要把第二大的元素放到堆顶,那第二大元素肯定会出现在左右子节点中。然后我们再迭代地删除第二大节点,以此类推,直到叶子节点被删除。
代码
复用原先的Array代码并且加以改造
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
public class Array<E> { private E[] data; private int size; // 构造函数,传入数组的容量capacity构造Array public Array(int capacity){ data = (E[])new Object[capacity]; size = 0; } // 无参数的构造函数,默认数组的容量capacity=10 public Array(){ this(10); } public Array(E[] arr){ data = (E[])new Object[arr.length]; for(int i = 0 ; i < arr.length ; i ++) data[i] = arr[i]; size = arr.length; } // 获取数组的容量 public int getCapacity(){ return data.length; } // 获取数组中的元素个数 public int getSize(){ return size; } // 返回数组是否为空 public boolean isEmpty(){ return size == 0; } // 在index索引的位置插入一个新元素e public void add(int index, E e){ if(index < 0 || index > size) throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size."); if(size == data.length) resize(2 * data.length); for(int i = size - 1; i >= index ; i --) data[i + 1] = data[i]; data[index] = e; size ++; } // 向所有元素后添加一个新元素 public void addLast(E e){ add(size, e); } // 在所有元素前添加一个新元素 public void addFirst(E e){ add(0, e); } // 获取index索引位置的元素 public E get(int index){ if(index < 0 || index >= size) throw new IllegalArgumentException("Get failed. Index is illegal."); return data[index]; } // 修改index索引位置的元素为e public void set(int index, E e){ if(index < 0 || index >= size) throw new IllegalArgumentException("Set failed. Index is illegal."); data[index] = e; } // 查找数组中是否有元素e public boolean contains(E e){ for(int i = 0 ; i < size ; i ++){ if(data[i].equals(e)) return true; } return false; } // 查找数组中元素e所在的索引,如果不存在元素e,则返回-1 public int find(E e){ for(int i = 0 ; i < size ; i ++){ if(data[i].equals(e)) return i; } return -1; } // 从数组中删除index位置的元素, 返回删除的元素 public E remove(int index){ if(index < 0 || index >= size) throw new IllegalArgumentException("Remove failed. Index is illegal."); E ret = data[index]; for(int i = index + 1 ; i < size ; i ++) data[i - 1] = data[i]; size --; data[size] = null; // loitering objects != memory leak if(size == data.length / 4 && data.length / 2 != 0) resize(data.length / 2); return ret; } // 从数组中删除第一个元素, 返回删除的元素 public E removeFirst(){ return remove(0); } // 从数组中删除最后一个元素, 返回删除的元素 public E removeLast(){ return remove(size - 1); } // 从数组中删除元素e public void removeElement(E e){ int index = find(e); if(index != -1) remove(index); } public void swap(int i, int j){ if(i < 0 || i >= size || j < 0 || j >= size) throw new IllegalArgumentException("Index is illegal."); E t = data[i]; data[i] = data[j]; data[j] = t; } @Override public String toString(){ StringBuilder res = new StringBuilder(); res.append(String.format("Array: size = %d , capacity = %d\n", size, data.length)); res.append('['); for(int i = 0 ; i < size ; i ++){ res.append(data[i]); if(i != size - 1) res.append(", "); } res.append(']'); return res.toString(); } // 将数组空间的容量变成newCapacity大小 private void resize(int newCapacity){ E[] newData = (E[])new Object[newCapacity]; for(int i = 0 ; i < size ; i ++) newData[i] = data[i]; data = newData; }} |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
public class MaxHeap<E extends Comparable<E>> { private Array<E> data; public MaxHeap(int capacity){ data = new Array<>(capacity); } public MaxHeap(){ data = new Array<>(); } public MaxHeap(E[] arr){ data = new Array<>(arr); for(int i = parent(arr.length - 1) ; i >= 0 ; i --) siftDown(i); } // 返回堆中的元素个数 public int size(){ return data.getSize(); } // 返回一个布尔值, 表示堆中是否为空 public boolean isEmpty(){ return data.isEmpty(); } // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引 private int parent(int index){ if(index == 0) throw new IllegalArgumentException("index-0 doesn't have parent."); return (index - 1) / 2; } // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引 private int leftChild(int index){ return index * 2 + 1; } // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引 private int rightChild(int index){ return index * 2 + 2; } // 向堆中添加元素 public void add(E e){ data.addLast(e); siftUp(data.getSize() - 1); } private void siftUp(int k){ while(k > 0 && data.get(parent(k)).compareTo(data.get(k)) < 0 ){ data.swap(k, parent(k)); k = parent(k); } } // 看堆中的最大元素 public E findMax(){ if(data.getSize() == 0) throw new IllegalArgumentException("Can not findMax when heap is empty."); return data.get(0); } // 取出堆中最大元素 public E extractMax(){ E ret = findMax(); data.swap(0, data.getSize() - 1); data.removeLast(); siftDown(0); return ret; } private void siftDown(int k){ while(leftChild(k) < data.getSize()){ int j = leftChild(k); // 在此轮循环中,data[k]和data[j]交换位置 if( j + 1 < data.getSize() && data.get(j + 1).compareTo(data.get(j)) > 0 ) j ++; // data[j] 是 leftChild 和 rightChild 中的最大值 if(data.get(k).compareTo(data.get(j)) >= 0 ) break; data.swap(k, j); k = j; } } // 取出堆中的最大元素,并且替换成元素e public E replace(E e){ E ret = findMax(); data.set(0, e); siftDown(0); return ret; }} |
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647 |
import java.util.Random; public class Main { private static double testHeap(Integer[] testData, boolean isHeapify){ long startTime = System.nanoTime(); MaxHeap<Integer> maxHeap; if(isHeapify) maxHeap = new MaxHeap<>(testData); else{ maxHeap = new MaxHeap<>(); for(int num: testData) maxHeap.add(num); } int[] arr = new int[testData.length]; for(int i = 0 ; i < testData.length ; i ++) arr[i] = maxHeap.extractMax(); for(int i = 1 ; i < testData.length ; i ++) if(arr[i-1] < arr[i]) throw new IllegalArgumentException("Error"); System.out.println("Test MaxHeap completed."); long endTime = System.nanoTime(); return (endTime - startTime) / 1000000000.0; } public static void main(String[] args) { int n = 1000000; Random random = new Random(); Integer[] testData = new Integer[n]; for(int i = 0 ; i < n ; i ++) testData[i] = random.nextInt(Integer.MAX_VALUE); double time1 = testHeap(testData, false); System.out.println("Without heapify: " + time1 + " s"); double time2 = testHeap(testData, true); System.out.println("With heapify: " + time2 + " s"); }} |
优先队列
普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用堆数据结构来实现。
1234567891011121314151617181920212223242526272829303132 |
public class PriorityQueue<E extends Comparable<E>> implements Queue<E> { private MaxHeap<E> maxHeap; public PriorityQueue(){ maxHeap = new MaxHeap<>(); } @Override public int getSize(){ return maxHeap.size(); } @Override public boolean isEmpty(){ return maxHeap.isEmpty(); } @Override public E getFront(){ return maxHeap.findMax(); } @Override public void enqueue(E e){ maxHeap.add(e); } @Override public E dequeue(){ return maxHeap.extractMax(); } |
12345678910111213141516171819202122 |
import java.util.Random; public class PriorityQueueMain { public static void main(String[] args) { PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(); Random random = new Random(); long startTime = System.nanoTime(); int size = 1000000; for (int i = 0; i < size; i++) { priorityQueue.enqueue(random.nextInt(Integer.MAX_VALUE)); if (i % 3 == 0) priorityQueue.dequeue(); } long endTime = System.nanoTime(); System.out.println("The priorityQueue size is :" + size +""+"\n After operation size is "+priorityQueue.getSize()+ "\n operation time is " + (((endTime - startTime) / 1000000000.0)) + "s"); }} |
参考资料
《大话数据结构》
《数据结构与算法之美》
《玩转数据结构》
数据结构之堆与优先队列相关推荐
- 数据结构与算法(4)——优先队列和堆
前言:题图无关,接下来开始简单学习学习优先队列和堆的相关数据结构的知识: 前序文章: 数据结构与算法(1)--数组与链表(https://www.jianshu.com/p/7b93b3570875) ...
- 【从蛋壳到满天飞】JS 数据结构解析和算法实现-堆和优先队列(一)
前言 [从蛋壳到满天飞]JS 数据结构解析和算法实现,全部文章大概的内容如下: Arrays(数组).Stacks(栈).Queues(队列).LinkedList(链表).Recursion(递归思 ...
- [重修数据结构0x03]并查集、堆、优先队列(2021.8.11)
前言 在做遍历的题目的时候,发现掌握一些特殊的数据结构和技巧有时对解决题目有着决定性的作用,不可不学.因此特地拿出来两天学习一下并查集.堆.优先队列.以后有更多思考和感悟再加补充吧.内容来自算法笔记, ...
- 【数据结构】堆,大根堆,小根堆,优先队列 详解
目录 堆 1.堆的数组实现 2.小根堆 3.大根堆 4.优先队列 例题 1.SP348 EXPEDI - Expedition(有趣的贪心思路,优先队列) 2.合并果子 堆 要了解堆之前,请先了解树, ...
- 数据结构-堆实现优先队列(java)
队列的特点是先进先出.通常都把队列比喻成排队买东西,大家都很守秩序,先排队的人就先买东西.但是优先队列有所不同,它不遵循先进先出的规则,而是根据队列中元素的优先权,优先权最大的先被取出.这就很像堆的特 ...
- 关于二叉堆(优先队列)的其他操作及其应用
[0]README 0.1)本文总结于 数据结构与算法分析:源代码均为原创, 旨在了解到我们学习了优先队列后,还能干些什么东西出来, 增加学习的interest: 0.2)以下列出了 关于二叉堆(优先 ...
- 堆与优先队列课内模板
全部数据结构.算法及应用课内模板请点击:https://blog.csdn.net/weixin_44077863/article/details/101691360 先补充两个概念 最大树(最小树) ...
- Python的堆与优先队列
Python的堆与优先队列 堆与优先队列 堆(英语:Heap)是计算机科学中的一种特别的树状数据结构.堆有如下特点:给定堆中任意节点P和C,若P是C的母节点,那么P的值会小于等于(或大于等于)C的值& ...
- 数据结构之堆(Heap)及其用途
本文采用图文码结合的方式介绍堆来实现优先队列 什么是优先队列? 队列是一种先进先出(FIFO)的数据结构.虽然,优先队列中含有队列两个字,但是,他一点也不像队列了.个人感觉,应该叫他优先群.怎么说那, ...
最新文章
- python17个常见问题_17个Python 常见错误的分析,你都遇到过哪些?
- SLAM综述-Lidar SLAM
- OpenCV_图像平滑
- 采样方法---吉布斯采样
- django学习之Model(四)MakingQuery
- centos 5 6安装本地yum源
- 孙叫兽进阶之路之如何进行情绪管理
- javascript:void(0) 含义
- 经典面试题(3):关于this指向的常见面试题
- python画roc曲线_使用Python画ROC曲线以及AUC值
- 梦断代码阅读笔记之二
- “开发者的面试完全是无稽之谈”
- 打算_20160604
- HTML5制作对联网页,经典的JS对联广告代码
- 优雅的处理Exception
- 【JS】秒杀倒计时制作
- C case和UVM TB的交互,tube_print, event_sync
- 向NS2中添加协议PING[转载]
- 基于idea-SSM的在线投稿审稿系统-稿件管理-作者管理-稿件审核(javaweb-php-asp.netC#-j2ee)
- ubc本科计算机雅思要求,加拿大各大学的雅思要求
热门文章
- php curl如何解决分页,一段PHP的分页程序,报错,该如何解决
- 2019手机号码正则表达式
- bzoj2819: Nim(博弈+树剖)
- Android视频录制从不入门到入门系列教程(一)————简介
- gitlab搭建之互备模式
- PostreSQL崩溃试验全记录
- 系统更新win10服务器失败,win10更新失败?使用这三招轻松解决,远离更新失败的烦恼!...
- 恩施市2021年高考成绩查询,2021年湖北恩施各高中中考分数线及录取时间结果查询安排...
- CCF201312-5 I’m stuck
- 触摸屏开发_Microchip推出新型电容触摸式控制器,加速汽车触摸屏EMI认证