二叉堆是一个数组,它可以被看成一个近似的完全二叉树,树上的每一个节点对应数组中的一个元素。除了最底层外,该树是完全充满的,而且是从左向右填充。二叉堆可以有两种形式:最大堆和最小堆,这里我主要讲解最大堆。最大堆的定义是:堆中某个节点的值总是不大于其父节点的值。
62
/ \
41 30
/ \ / \
28 16 22 13
/ \ /
19 17 15
0 1 2 3 4 5 6 7 8 9
62 41 30 28 16 22 13 19 17 15
当我们用二叉堆表示上面的数组的时候,我们可以知道
父节点:parent(i) = (i - 1)/2
左节点:left child (i) = 2 i + 1
右节点:right child(i) = 2i + 2
首先我们实现一下堆的交换方法swap和父子节点的方法
public void swap(int i,int j){
E t = data[i];
data[i] = data[j];
data[j] = t;
}
// 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
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;
}
二叉堆的核心是”添加节点”和”删除节点”,理解这两个算法,二叉堆也就基本掌握了。下面对它们进行介绍。
向堆中插入一个元素
从最后一个节点的地方插入元素,然后和父节点比较并进行交换位置。如果堆的有序状态因为某个节点变得比它的父节点更大而被打破,那么我们就需要通过交换它和它的父节点来修复堆。
比如向堆中插入52:
62
/ \
41 30
/ \ / \
28 16 22 13
/ \ / \
19 17 15 52
在最后添加节点52,发现52大于16,于是和16交换位置
----->
62
/ \
41 30
/ \ / \
28 52 22 13
/ \ / \
19 17 15 16
52再和自己的父节点比较,发现52大于41,再和41交换位置
----->
62
/ \
52 30
/ \ / \
28 41 22 13
/ \ / \
19 17 15 16
当52发现小于自己的父节点的时候,停止交换,插入完成
----->
62
/ \
52 30
/ \ / \
28 41 22 13
/ \ / \
19 17 15 16
我们用代码来表示上面的过程
// 向堆中添加元素
public void add(E e){
data.addLast(e);
siftUp(data.getSize() - 1);
}
private void siftUp(int k){
while(k > 0 && data[parent(k)].compareTo(data[k)] < 0 ){
swap(k, parent(k));
k = parent(k);
}
}
删除堆中最大元素
我们从数组顶端删去最大的元素并将数组的最后一个元素放到顶端,减小堆的大小并让这个元素下沉到合适的位置。
62
/ \
52 30
/ \ / \
28 41 22 13
/ \ / \
19 17 15 16
首先将最后一个元素放到顶端
16
/ \
52 30
/ \ / \
28 41 22 13
/ \ /
19 17 15
然后和左右两个子节点比较,和并和子节点中较大的节点交换位置,16和52交换
52
/ \
16 30
/ \ / \
28 41 22 13
/ \ /
19 17 15
然后一直交换,16和41交换,然后发现16比它子节点15大,交换完毕
52
/ \
41 30
/ \ / \
28 16 22 13
/ \ /
19 17 15
下面我们用代码实现一下这个过程
// 看堆中的最大元素
public E findMax(){
if(data.getSize() == 0)
throw new IllegalArgumentException("Can not findMax when heap is empty.");
return data[0];
}
// 取出堆中最大元素
public E extractMax(){
E ret = findMax();
swap(0, data.getSize() - 1);
data[data.getSize() - 1]= null;
size --;
siftDown(0);
return ret;
}
private void siftDown(int k){
while(leftChild(k) < getSize()){
int j = leftChild(k); // 在此轮循环中,data[k]和data[j]交换位置
if( j + 1 < getSize() &&
data[j + 1].compareTo(data[j]) > 0 )
j ++;
// data[j] 是 leftChild 和 rightChild 中的最大值
if(data[k].compareTo(data[j]) >= 0 )
break;
swap(k, j);
k = j;
}
}
由于数组的大小固定之后就不能改变,所以我们这里使用动态数组ArrayList来代替数组的实现。下面放出完整代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* @author luozhiyun on 2019-04-07.
*/
public class MaxHeap> {
private List list;
public MaxHeap(){
list = new ArrayList<>();
}
public MaxHeap(int cap) {
list = new ArrayList<>(cap);
}
public MaxHeap(E[] es) {
list = new ArrayList<>(es.length);
for (int i = 0; i < es.length; i++) {
list.add(es[i]);
}
for (int i = parent(list.size()-1); i >=0; i--) {
siftUp(i);
}
}
public int size() {
return list.size();
}
public boolean isEmpty() {
return list.isEmpty();
}
private int parent(int index) {
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) {
list.add(e);
siftUp(list.size() - 1);
}
private void siftUp(int index) {
while (index > 0 &&
list.get(parent(index)).compareTo(list.get(index)) < 0) {
swap(index, parent(index));
index = parent(index);
}
}
private void swap(int i,int j) {
E e = list.get(i);
list.set(i, list.get(j));
list.set(j, e);
}
public E findMax() {
return list.get(0);
}
//取出堆中最大的元素
public E extractMax() {
E max = findMax();
swap(0, list.size() - 1);
list.remove(list.size() - 1);
siftDown(0);
return max;
}
private void siftDown(int index) {
while (leftChild(index) < list.size()) {
int l = leftChild(index);
if (l + 1 < list.size() &&
list.get(l).compareTo(list.get(l + 1) )< 0) {
l++;
}
if (list.get(index).compareTo(list.get(l)) >= 0) {
break;
}
swap(index, l);
index = l;
}
}
public E replace(E e) {
E max = findMax();
list.set(0, e);
siftDown(0);
return max;
}
public static void main(String[] args) {
int n = 100000 ;
MaxHeap integerMaxHeap = new MaxHeap<>();
Random random = new Random();
for (int i = 0; i < n; i++) {
integerMaxHeap.add(random.nextInt(Integer.MAX_VALUE));
}
int[] ints = new int[n];
for (int i = 0; i < n; i++) {
ints[i] = integerMaxHeap.extractMax();
}
for (int i = 1; i < n; i++) {
if (ints[i] > ints[i - 1]) {
throw new IllegalArgumentException("Error");
}
}
System.out.println("Test MaxHeap completed;");
}
}