数据结构——堆(带图详解)
目录
堆
堆的概念
堆的性质
堆的创建
1、堆向下调整
2、堆的创建
3、建堆的时间复杂度
堆的插入和删除
1、堆的插入
2、堆的删除
堆的应用
1、优先级队列的实现
2、堆排序
3、Top-k问题
堆 (Heap)
堆的概念
前面介绍的优先级队列在JDK1.8中其底层使用了堆的数据结构,而堆实际就是在完全二叉树的基础之上进行了一些元素的调整。
堆的性质
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
下面来看一下堆的可视化操作堆的可视化操作https://visualgo.net/zh/heap
堆的创建
1、堆向下调整
![](/assets/blank.gif)
- parent右孩子是否存在,存在找到左右孩子中最小的孩子,让child进行标记
- 将parent与较小的孩子child比较,如果:parent小于较小的孩子child,调整结束。否则:交换parent与较小的孩子child,交换完成之后,parent中大的元素向下移动,可能导致子树不满足对的性质,因此需要继续向下调整,即parent = child;child = parent*2+1; 然后继续2。
public void shiftDown(int[] array, int parent) {// child先标记parent的左孩子,因为parent可能右左没有右int child = 2 * parent + 1;int size = array.length;while (child < size) {// 如果右孩子存在,找到左右孩子中较小的孩子,用child进行标记if(child+1 < size && array[child+1] < array[child]){child += 1;}// 如果双亲比其最小的孩子还小,说明该结构已经满足堆的特性了if (array[parent] <= array[child]) {break;}else{// 将双亲与较小的孩子交换int t = array[parent];array[parent] = array[child];array[child] = t;// parent中大的元素往下移动,可能会造成子树不满足堆的性质,因此需要继续向下调整parent = child;child = parent * 2 + 1;}}
}
2、堆的创建
![](/assets/blank.gif)
public static void createHeap(int[] array) {// 找倒数第一个非叶子节点,从该节点位置开始往前一直到根节点,遇到一个节点,应用向下调整for(int root = (array.length-2)/2; root >= 0; root--){shiftDown(array, array.length, root);}
}
3、建堆的时间复杂度
![](/assets/blank.gif)
因此:建堆的时间复杂度为O(N)
堆的插入和删除
1、堆的插入
- 先将元素放入到底层空间中(注意:空间不够时需要扩容)
- 将最后新插入的节点向上调整,直到满足堆的性质
public void shiftUp(int child) {// 找到child的双亲int parent = (child - 1) / 2;while (child > 0) {// 如果双亲比孩子大,parent满足堆的性质,调整结束if (array[parent] > array[child]) {break;}else{// 将双亲与孩子节点进行交换int t = array[parent];array[parent] = array[child];array[child] = t;// 小的元素向下移动,可能到值子树不满足对的性质,因此需要继续向上调增child = parent;parent = (child - 1) / 2;}}
}
2、堆的删除
- 将堆顶元素对堆中最后一个元素交换
- 将堆中有效数据个数减少一个
- 对堆顶元素进行向下调整
public static void shiftDown(int[] array, int size, int parent){int child = parent*2+1;while(child < size){// 找左右孩子中较大的孩子if(child+1 < size && array[child+1] > array[child]){child += 1;}// 双亲小于交大的孩子if(array[parent] < array[child]){swap(array, parent, child);parent = child;child = parent*2+1;}else{return;}}
}
堆的应用
1、优先级队列的实现
public class MyPriorityQueue {Integer[] array;int size; // 有效元素的个数public MyPriorityQueue(){array = new Integer[11];size = 0;}public MyPriorityQueue(int initCapacity){if(initCapacity < 1){throw new IllegalArgumentException("初始容量小于1");}array = new Integer[initCapacity];size = 0;}public MyPriorityQueue(Integer[] arr){// 1. 将arr中的元素拷贝到数组中array = new Integer[arr.length];for(int i = 0; i < arr.length; ++i){array[i] = arr[i];}size = arr.length;// 2. 找当前完全二叉树中倒数第一个叶子节点// 注意:倒数第一个叶子节点刚好是最后一个节点的双亲// 最后一个节点的编号size-1 倒数第一个非叶子节点的下标为(size-1-1)/2int lastLeafParent = (size-2)/2;// 3. 从倒数第一个叶子节点位置开始,一直到根节点的位置,使用向下调整for(int root = lastLeafParent; root >= 0; root--){shiftDown(root);}}boolean offer(Integer e){if(e == null){throw new NullPointerException("插入时候元素为null");}ensureCapacity();array[size++] = e;// 注意:当新元素插入之后,可能会破坏对的性质---需要向上调整shiftUp(size-1);return true;}// 将堆顶的元素删除掉public Integer poll(){if(isEmpty()){return null;}Integer ret = array[0];// 1. 将堆顶元素与堆中最后一个元素交换swap(0, size-1);// 2. 将堆中有效元素个数减少一个size--; // size -= 1;// 3. 将堆顶元素往下调整到合适位置shiftDown(0);return ret;}public int size(){return size;}public boolean isEmpty(){return size == 0;}public void clear(){size = 0;}// 功能:调整以parent为根的二叉树// 前提:必须要保证parent的左右子树已经满足堆的特性// 时间复杂度:O(logN)private void shiftDown(int parent){// 默认让child先标记左孩子---因为:parent可能有左没有右int child = parent*2 + 1;// while循环条件可以保证:parent的左孩子一定存在// 但是不能保证parent的右孩子是否存在while(child < size){// 1. 找到左右孩子中较小的孩子if(child+1 < size && array[child+1] < array[child]){child += 1;}// 2. 较小的孩子已经找到了// 检测双亲和孩子间是否满足堆的特性if(array[parent] > array[child]){swap(parent, child);// 大的双亲往下走了,可能会导致子树又不满足堆的特性// 因此需要继续往下调整parent = child;child = parent*2 + 1;}else{// 以parent为根的二叉树已经是堆了return;}}}private void shiftUp(int child){int parent = (child-1)/2;while(child != 0){if(array[child] < array[parent]){swap(child, parent);child = parent;parent = (child-1)/2;}else{return;}}}private void ensureCapacity(){if(array.length == size){int newCapacity = array.length*2;array = Arrays.copyOf(array, newCapacity);}}// 注意:left和right是数组的下标private void swap(int left, int right){int temp = array[left];array[left] = array[right];array[right] = temp;}
2、堆排序
- 升序:建大堆
- 降序:建小堆
![](/assets/blank.gif)
public static void swap(int[] array, int left, int right){int temp = array[left];array[left] = array[right];array[right] = temp;}public static void shiftDown(int[] array, int size, int parent){int child = parent*2+1;while(child < size){// 找左右孩子中较大的孩子if(child+1 < size && array[child+1] > array[child]){child += 1;}// 双亲小于交大的孩子if(array[parent] < array[child]){swap(array, parent, child);parent = child;child = parent*2+1;}else{return;}}}// 假设:升序public static void heapSort(int[] array){// 1. 建堆----升序:大堆 降序:小堆---向下调整for(int root = (array.length-2)/2; root >= 0; root--){shiftDown(array, array.length, root);}// 2. 利用堆删除的思想来排序---向下调整int end = array.length-1; // end标记最后一个元素while(end != 0){swap(array,0,end);shiftDown(array, end,0);end--;}}
3、Top-k问题
- 前k个最大的元素,则建小堆
- 前k个最小的元素,则建大堆
Top-k问题
class Solution {public int[] smallestK(int[] arr, int k) {int[] vec = new int[k];if (k == 0) { // 排除 0 的情况return vec;}PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {public int compare(Integer num1, Integer num2) {return num2 - num1;}});for (int i = 0; i < k; ++i) {queue.offer(arr[i]);}for (int i = k; i < arr.length; ++i) {if (queue.peek() > arr[i]) {queue.poll();queue.offer(arr[i]);}}for (int i = 0; i < k; ++i) {vec[i] = queue.poll();}return vec;}
}
复杂度分析
时间复杂度:O(nlog k),其中 n 是数组 arr 的长度。由于大根堆实时维护前 k 小值,所以插入删除都是O(logk) 的时间复杂度,最坏情况下数组里 n 个数都会插入,所以一共需要 O(nlogk) 的时间复杂度。
空间复杂度:O(k),因为大根堆里最多 k 个数
数据结构——堆(带图详解)相关推荐
- VMware里建立虚拟机快照(带图详解)
VMware里建立虚拟机快照(带图详解) 创建快照 如下图所示,想要创建快照有两种方法 可以自行修改名称和描述,修改好之后电机"拍摄快照"即可 此时大家可以看一下自己的快照是否建立 ...
- 【数据结构】动图详解二叉树——堆及堆排序
目录 一.树的概念 1.树的特征 2.树的相关名词 二.树的表示方法 1.左孩子右兄弟表示法 2.双亲表示法 三.特殊二叉树 四.堆的向上调整算法建堆及排序 1.向上调整建堆O(N*logN) 2.向 ...
- 数据结构:二叉树(带图详解)
目录 树的概念和结构 树的概念 树的表示形式 二叉树 二叉树的概念 两种特殊的二叉树 1.满二叉树 2.完全二叉树 二叉树的性质 二叉树的存储 二叉树的遍历 1. 前中后序遍历 还原二叉树 2.层序遍 ...
- Iconfont矢量图图标怎么使用带图详解
我们经常在网上看到的一些好看的小图标,很多其实它不是img.png图片,而是矢量图.那什么是矢量图呢?先简单普及一下: 计算机中显示的图形一般可以分为两大类--矢量图和位图. 矢量图使用直线和曲线来描 ...
- Dijkstra最短路径算法C++带图详解
一.问题定义 求解单元点的最短路径问题:给定带权有向图G和源点v,求v到G中其他顶点的最短路径 限制条件:图G中不存在负权值的边 二.思想 划重点,迪杰斯特拉最最朴素的思想就是按长度递增的次序产生最短 ...
- uniapp微信授权+获取手机号+解密手机号(带图详解)
那废话不多说,开始今天的教程,我会在尾部添加全部代码,如果你懒的自己写,可以复制我的代码,但是里面的一些数据你需要更换,我会标注出来 1.获取微信信息(我的写法是自动获取,而不需要点击触发事件获取). ...
- 广度优先搜索算法带图详解
1.前言 广度优先搜索https://so.csdn.net/so/search?q=%E5%B9%BF%E5%BA%A6%E4%BC%98%E5%85%88%E6%90%9C%E7%B4%A2&am ...
- Linux常用基础指令、Linux常用工具(软件包)使用带图详解
目录 目录相关指令: 文件相关指令: 压缩解压缩指令: 匹配查找指令: 权限相关指令: Linux常用工具: 指令使用规则:指令 [该指令的详细操作选项] [操作对象(通常有路径)],后面两可不加. ...
- ibd文件结构组成(带图详解)
ibd文件结构组成 页数据格式 InnodDB采用Btree作为存储结构,当用户创建一个Table的时候,就会根据显示或隐式定义的主键构建了一棵Btree,而构成Btree的叶子节点被称为Page,默 ...
最新文章
- syntax error near unexpected token
- Solaris ALOM1.6 SC Password Reset
- Linux入门基础思维导图
- 《短文本数据理解(1)》一1.3 短文本理解框架
- mysql 集群 qps_MySQL Cluster:如何通过扩展为MySQL带来2亿QPS
- 无法在只读编辑器中编辑_Mol Plant中国农科院作物科学研究所夏兰琴课题组成功利用 优化的引导基因编辑器在水稻中实现高效精准基因编辑...
- KP-ABE基于属性的加密加解密算法及Access Tree构建
- python日期对照表_2020年日期表-python实现
- mysql分组查询 having,MYSQL-分组查询-where和having的区别
- git安装 苹果笔记本_自己挖的坑自己填,无光驱安装苹果笔记本双系统
- 2020法研杯比赛阅读理解任务冠军参赛总结
- SaaS应用架构师所面临的最大挑战
- Eclipse配置反编译问题
- 前端学习(六)——HTML5中通过CSS设置超链接及鼠标形状
- oracle集群如何搭建,Oracle集群搭建步骤.docx
- 软件工程—01可行性研究报告
- 学习之苦也正是学习之甜------知识的本质
- Windows 7 安装VS2008 SP1 失败 1
- static 函数和变量
- linux中Swap分区是做什么的?