堆的一个经典的实现是彻底二叉树(complete binary tree),这样实现的堆称为二叉堆(binary heap)。数组
这里来讲明一下满二叉树的概念与彻底二叉树的概念以及完满二叉树概念。数据结构
堆的特性:ide
必须是彻底二叉树函数
任一结点的值是其子树全部结点的最大值或最小值动画
最大值时,称为“最大堆”,也称大顶堆;spa
最小值时,称为“最小堆”,也称小顶堆。3d
只要谨记堆的定义特性,实现起来实际上是很容易的。code
特性1. 维持彻底二叉树 blog
特性2. 子类数字老是大于父类数字 排序
1public class MinHeap <E extends Comparable<E>> { 2 private Array<E> data; 3 4 public MinHeap(int capacity){ 5 data = new Array<>(capacity); 6 } 7 8 public MinHeap(){ 9 data = new Array<>(); 10 } 11 12 // 返回堆中的元素个数 13 public int size(){ 14 return data.getSize(); 15 } 16 17 // 返回一个布尔值, 表示堆中是否为空 18 public boolean isEmpty(){ 19 return data.isEmpty(); 20 } 21 22 // 返回彻底二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引 23 private int parent(int index){ 24 return (index - 1) / 2; 25 } 26 27 // 返回彻底二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引 28 private int leftChild(int index){ 29 return index * 2 + 1; 30 } 31 32 // 返回彻底二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引 33 private int rightChild(int index){ 34 return index * 2 + 2; 35 } 36}
最小堆的插入(ADD)
假设现有元素 5 须要插入,为了维持彻底二叉树的特性,新插入的元素必定是放在结点 6 的右子树;同时为了知足任一结点的值要小于左右子树的值这 一特性,新插入的元素要和其父结点做比较,若是比父结点小,就要把父结点拉下来顶替当前结点的位置,本身则依次不断向上寻找,找到比本身大的父结点就拉下来,直到没有符合条件的值为止。
动画讲解
在这里先将元素 5 插入到末尾,即放在结点 6 的右子树。
而后与父类比较, 6 > 5 ,父类数字大于子类数字,子类与父类交换。
重复此操做,直到不发生替换。
Show me the code:
添加一个辅助函数,用来交换传入的索引两个位置的元素值
1/** 2 * 交换传入的索引两个位置的元素值 3 * 4 * @param i 5 * @param j 6 */ 7 public void swap(int i, int j) { 8 if (i < 0 || i >= size || j < 0 || j >= size) 9 throw new IllegalArgumentException("Index is illegal."); 10 11 E temp = data[i]; 12 data[i] = data[j]; 13 data[j] = temp; 14 }
数组中添加交换两元素位置的方法,注意下面代码中注释的描述特性位置。
1 /** 2 * 堆中添加元素方法。 3 * 4 * @param e 5 */ 6 public void add(E e) { 7 //特性1:新插入的元素首先放在数组最后,保持彻底二叉树的特性 8 data.addLast(e); 9 siftUp(data.getSize() - 1); 10 } 11 12 /** 13 * index 为i位置元素上浮。 14 * 15 * @param i 16 */ 17 private void siftUp(int i) { 18 //特性2:比较插入值和其父结点的大小关系,小于父结点则用父结点替换当前值,index位置上升为父结点 19 // 当上浮元素大于父亲,继续上浮。而且不能上浮到0之上 20 // 直到i 等于 0 或 比 父亲节点小了。 21 while (i > 0 && data.get(i).compareTo(data.get(parent(i))) > 0) { 22 // 数组Array中添加方法swap 23 data.swap(i, parent(i)); 24 i = parent(i); // 这句话让i来到新的位置,使得循环能够查看新的位置是否还要大。 25 } 26 }
最小堆的删除(DELETE)
核心点:将最后一个元素填充到堆顶,而后不断的下沉这个元素。
假设要从节点 1 ,也能够称为取出节点 1 ,为了维持彻底二叉树的特性 ,咱们将最后一个元素 6 去替代这个 1 ;而后比较 1 和其子树的大小关系,若是比左右子树大(若是存在的话),就要从左右子树中找一个较小的值替换它,而它能本身就要跑到对应子树的位置,再次循环这种操做,直到没有子树比它小。
经过这样的操做,堆依然是堆,总结一下:
找到要删除的节点(取出的节点)在数组中的位置
用数组中最后一个元素替代这个位置的元素
当前位置和其左右子树比较,保证符合最小堆的节点间规则
删除最后一个元素
Show me the code:
1 public E findMin() { 2 return data.get(0); 3 } 4 5 public E extractMin() { 6 7 E ret = findMin(); 8 9 data.swap(0, data.getSize() - 1); // 0位置元素和最后一个元素互换。 10 data.removeLast(); // 删除此时的最后一个元素(最小值) 11 siftDown(0); // 对于0处进行siftDown操做 12 13 return ret; 14 } 15 16 /** 17 * k位置元素下移 18 * 19 * @param k 20 */ 21 private void siftDown(int k) { 22 23 while(leftChild(k) < data.getSize()){ 24 int j = leftChild(k); // 在此轮循环中,data[k]和data[j]交换位置 25 if( j + 1 < data.getSize() && 26 data.get(j + 1).compareTo(data.get(j)) < 0 ) 27 j ++; 28 // data[j] 是 leftChild 和 rightChild 中的最小值 29 30 if(data.get(k).compareTo(data.get(j)) >= 0 ) 31 break; 32 33 data.swap(k, j); 34 k = j; 35 } 36 }
对于有 n 个节点的堆来讲,其高度 d = log2n + 1
。 根为第 0 层,则第 i 层结点个数为 2i,
考虑一个元素在堆中向下移动的距离。
大约一半的结点深度为 d-1 ,不移动(叶)。
四分之一的结点深度为 d-2 ,而它们至多能向下移动一层。
树中每向上一层,结点的数目为前一层的一半,而子树高度加一
堆有logn
层深,因此插入删除的平均时间和最差时间都是O(logN)
普通队列是一种先进先出的数据结构,先放进队列的元素取值时优先被取出来。而优先队列是一种具备最高优先级元素先出的数据结构,好比每次取值都取最大的元素。
优先队列支持下面的操做:
a. 找出优先级最高的元素(最大或最小元素);
b. 删除一个具备最高优先级的元素;
c. 添加一个元素到集合中。
1public class PriorityQueue<E extends Comparable<E>> implements Queue<E> { 2 3 private MaxHeap<E> maxHeap; 4 5 public PriorityQueue(){ 6 maxHeap = new MaxHeap<>(); 7 } 8 9 @Override 10 public int getSize(){ 11 return maxHeap.size(); 12 } 13 14 @Override 15 public boolean isEmpty(){ 16 return maxHeap.isEmpty(); 17 } 18 19 @Override 20 public E getFront(){ 21 return maxHeap.findMax(); 22 } 23 24 @Override 25 public void enqueue(E e){ 26 maxHeap.add(e); 27 } 28 29 @Override 30 public E dequeue(){ 31 return maxHeap.extractMax(); 32 } 33}
理解了优先队列,堆排序的逻辑十分简单。