本文将首先介绍什么是堆,而后介绍了堆的插入和删除操做,最后给出了堆的代码实现,并进行了测试。java
堆是一颗彻底二叉树,堆中某个节点的值老是不大于或不小于其父节点的值。根节点最大的堆叫作大根堆,根节点最小的堆叫作小根堆。
首先解释下什么是彻底二叉树,设一颗二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层全部的结点都连续集中在最左边,这就是彻底二叉树。以下图所示,左侧的二叉树知足彻底二叉树的定义,而右侧的不知足。
上图左侧即是一个小根堆,知足任意一个节点的值老是不小于其父节点的值。算法
通常的二叉树表示时须要首先定义节点结构,节点中包含指向父节点的指针,以下所示:编程
class Node<E>{ E e;//节点储存的值 Node left,right;//左右子节点 public Node(E e){ this.e = e; this.left = this.right = null; } }
可是堆并非像树同样存储,其中没有使用父指针或者子指针,而是用数组来实现。怎么用数组来实现呢?先看一张图,以下:
咱们从0开始对节点进行编号,寻找其中父子节点之间索引的对应关系。
首先,经过子节点的索引来找父节点的索引,设子节点的索引为i,则其父节点的索引为数组
int parentIndex = (i - 1) / 2;
而后,经过父节点的索引来找子节点的索引,设父节点的索引为p,则其孩子节点的索引为安全
int leftChildIndex = 2 * p + 1;//左子节点 int rightChildIndex = 2 * p + 2;//右子节点
这样,经过子节点与父节点之间的索引关系,便至关于创建了父节点和子节点之间的指针,实现了用数组来存储堆这种数据结构。数据结构
对于堆来讲,只有插入和删除两种操做,先谈一下堆的插入操做,此处以小根堆为例。
如上图1所示,在小根堆中插入元素0,首先将元素放置在二叉树最后一行的末尾,此时依然是彻底二叉树;而后将该元素与父节点的值比较,若改节点的值小于父节点,则进行交换,如图3所示;以后再次与父节点进行对比交换,直至该节点的值大于等于父节点的值或者已是根节点为止,如图4 所示。此时堆依然知足定义。多线程
对于堆来讲,删除元素是指移除根节点。以小根堆为例,是指移除根中最小值的节点,也就是根节点。移除很简单,以后咱们要经过操做来使得堆依然知足定义。
首先删除堆中索引为0,也就是根节点,对于小根堆来讲也就是最小值。而后将堆中最后一个元素填充至根节点的位置,如图3所示;以后比较该节点与左右子节点,若该节点大于左右子节点中较小的节点的值,则与该节点进行交换(小根堆中父节点永远与左右子节点中较小的那个子节点交换),如图四、5所示;直至知足该节点的值小于其左右子节点的值或者该节点左右子节点均为空。此时堆依然知足定义。测试
下面给出堆的代码实现,以下所示,实现了堆的插入和删除操做,并进行了测试。this
package datastructures; public class Heap { private int[] data;//存储堆的数组 private int size;//堆中元素的数量 public Heap(int capacity){ data = new int[capacity];//初始化数组 size = 0;//初始化数量 } /** * 插入元素 */ public void insert(int value) throws Exception{ if(size == data.length) throw new Exception("堆已满"); else{ data[size] = value;//将新插入的元素放在堆的末尾 int i = size; size ++; while(i > 0){//对堆进行调整,直至知足条件 int p = (i - 1) / 2; if(data[i] < data[p]){ int temp = data[i]; data[i] = data[p]; data[p] = temp; i = p; } else break; } } } /** * 删除堆中的元素 * @return * @throws Exception */ public int delMin() throws Exception{ int res; if(size == 0) throw new Exception("为空"); else{ res = data[0];//返回索引为0的元素 size -- ; data[0] = data[size];//将堆中最后一个元素填充至索引为0的位置 int i = 0; while(2 * i + 1 < size){//对堆进行调整 int left = 2 * i + 1; int right = 2 * i + 2; if(right < size && data[right] < data[left] && data[right] < data[i]){ int temp = data[i]; data[i] = data[right]; data[right] = temp; i = right; } else if(data[left] < data[i] && (right >= size || data[right] >= data[left])){ int temp = data[i]; data[i] = data[left]; data[left] = temp; i = left; } else break; } } return res; } //测试 public static void main(String[] args) throws Exception { Heap heap = new Heap(10); heap.insert(1); heap.insert(5); heap.insert(4); heap.insert(3); heap.insert(6); heap.insert(2); System.out.println(heap.delMin()); System.out.println(heap.delMin()); System.out.println(heap.delMin()); System.out.println(heap.delMin()); System.out.println(heap.delMin()); System.out.println(heap.delMin()); } }
推荐阅读
为何有红黑树?什么是红黑树?看完这篇你就明白了
《深刻浅出话数据结构》系列之什么是B树、B+树?为何二叉查找树不行?
都2020年了,据说你还不会归并排序?手把手教你手写归并排序算法
为何会有多线程?什么是线程安全?如何保证线程安全?spa
以为文章有用的话, 点赞+ 关注呗,好让更多的人看到这篇文章,也激励博主写出更多的好文章。
更多关于 算法、数据结构和计算机基础知识的内容,欢迎扫码关注个人原创公众号「<font color=#005AB5> 超悦编程</font>」。