最大二叉堆的Java实现 :
/**
* @ADT abstract data type 抽象数据类型
* @PriorityQueue 优先队列
*
* @KeyMethod :
* insert(enqueue) , deleteMax(extractMax,dequeue)
*
* @最大二叉堆实现核心算法(2-堆) :
* 1. 向上过滤 : 用在插入方法。将元素放在最后,通过上浮的方式确定其位置
* 2. 向下过滤 : 用在删除最大值或堆排序中。
* 1)删除最大值后,将最后一个节点置于堆顶,通过和两个子节点的最大值比较,依次下沉,确定其位置
* 2)对于堆排序,从第n/2个节点到根节点,依次向下过滤每个元素,最后得到堆。
*
* @TODO 应用扩展
*
* 1. d-堆 每个节点有d个孩子
* 2. 最大最小堆
* 3. 两个堆的合并(merge)
* 4. 左式堆(leftist heap)
* 5. 斜堆(skew heap)
* 6. 二项队列(binomial queue)
*/
public class BinaryHeap<AnyType extends Comparable super AnyType>> {
private static final int DEFAULT_CAPACITY = 10;
private int currentSize;
private AnyType[] array;
public BinaryHeap() {
currentSize = 0;
array = (AnyType[]) new Comparable[DEFAULT_CAPACITY];
}
public BinaryHeap(int capacity) {
currentSize = 0;
array = (AnyType[]) new Comparable[capacity];
}
/**
* 递归方式初始化
*
* @param items
*/
public BinaryHeap(AnyType[] items) {
this(items, true);
}
/**
* 采用递归方式或者迭代(循环加动态步长)方式初始化
*
* @param items
* @param useRecurse
*/
public BinaryHeap(AnyType[] items, boolean useRecurse) {
currentSize = items.length;
array = (AnyType[]) new Comparable[items.length + DEFAULT_CAPACITY];
int i = 1;
for (AnyType item : items) {
array[i++] = item;
}
if (useRecurse) {
for (i = currentSize / 2; i > 0; i--) {
percolateDownByRecurse(i);
}
} else {
for (i = currentSize / 2; i > 0; i--) {
percolateDown(i);
}
}
}
/**
* 采用递归方式
*
* @param hole
*/
private void percolateDownByRecurse(int hole) {
int childLeft = hole * 2;
if (childLeft > currentSize) {
return;
}
// 先和左节点比,再和右节点比,一下实际上是取左节点,右节点和自身节点得最大值放入自身节点,左右节点再往下比
// 更好的做法是现将左节点和右节点比较,然后选取较大的和自身节点比较。这样只递归一个分支就可以了,可以用循环实现,见percolateDown的实现
if (array[hole].compareTo(array[childLeft]) < 0) {
AnyType temp = array[hole];
array[hole] = array[childLeft];
array[childLeft] = temp;
percolateDownByRecurse(childLeft);
}
//
int childRight = childLeft + 1;
if (childRight > currentSize) {
return;
}
if (array[hole].compareTo(array[childRight]) < 0) {
AnyType temp = array[hole];
array[hole] = array[childRight];
array[childRight] = temp;
percolateDownByRecurse(childRight);
}
}
/**
* (最优解) 采用迭代(循环加动态步长)方式(下滤)
*
* @param hole
*/
private void percolateDown(int hole) {
while (hole*2 <= currentSize) {
int child = hole * 2;
// 取左右节点的最大节点的索引
if (child != currentSize && array[child + 1].compareTo(array[child]) > 0) {
child++;
}
// 过滤下沉方式确定每个元素位置
if (array[hole].compareTo(array[child]) < 0) {
AnyType temp = array[hole];
array[hole] = array[child];
array[child] = temp;
hole = child;
} else {
break;
}
}
}
public void insert(AnyType[] items) {
for (AnyType item : items) {
insert(item);
}
}
/**
* (最优解)采用迭代(循环加动态步长)的方式实现插入
*
* @param x
*/
public void insert(AnyType x) {
// 自动增长
autoEnlargeArray();
// 将新插入元素放入最后(这里只是拿到新加入的最后一个位置,把这个位置想象成洞,并没有实际放入该元素)
int hole = ++currentSize;
// 这个循环的逻辑是每次和父节点比较,如果比父节点大,将父节点放入洞中,将洞上浮到父节点位置,然后继续循环,直到根节点退出
// 这个循环的步长为动态的 hole/2
while (hole > 1) {
int parent = hole / 2;
if (x.compareTo(array[parent]) > 0) {
array[hole] = array[parent];
hole = parent;
} else {
break;
}
}
// 最后将待插入元素插入选好的洞中
array[hole] = x;
}
/**
* 采用递归的形式实现插入
*
* @param x
*/
public void insertByRecurse(AnyType x) {
autoEnlargeArray();
array[++currentSize] = x;
percolateUp(currentSize);
}
/**
* 将插入元素保持或上浮到合适位置(上滤)
* @param index
*/
private void percolateUp(int index) {
int parent = index / 2;
if (parent == 0) {
return;
}
if (array[index].compareTo(array[parent]) > 0) {
AnyType temp = array[index];
array[index] = array[parent];
array[parent] = temp;
percolateUp(parent);
}
}
public AnyType findMax() {
return array[1];
}
// extract max
public AnyType deleteMax() {
if(isEmpty()) {
return null;
}
//提出根节点元素
AnyType max= findMax();
//将最后一个元素置于根节点,并将size减一
array[1]=array[currentSize--];
//
percolateDown(1);
return max;
}
public boolean isEmpty() {
return currentSize==0;
}
public void makeEmpty() {
currentSize=0;
array=null;
}
private void autoEnlargeArray() {
if (currentSize >= array.length + 1) {
AnyType[] temp = (AnyType[]) new Comparable[array.length * 2 + 1];
for (int i = 1; i < array.length; i++) {
temp[i] = array[i];
}
array = temp;
}
}
/*
* 降低某节点优先级
* 简单设计,仅供参考,当AnyType为Integer时有效
*/
public void decreaseKey(int index,int value){
if(index>currentSize) {
return ;
}
if(array[index] instanceof Integer) {
Integer current = (Integer) array[index];
Integer now= current-value;
array[index]= (AnyType) now;
}
percolateDown(index);
}
/*
* 增加某节点优先级
* 简单设计,仅供参考,当AnyType为Integer时有效
*/
public void increaseKey(int index,int value){
if(index>currentSize) {
return ;
}
if(array[index] instanceof Integer) {
Integer current = (Integer) array[index];
Integer now= current+value;
array[index]= (AnyType) now;
}
percolateUp(index);
}
/**
* 删除某节点
* 简单设计,仅供参考,当AnyType为Integer时有效
* @param index
*/
public void delete(int index) {
increaseKey(index,Integer.MAX_VALUE/2);
deleteMax();
}
@Override
public String toString() {
String value="[";
for(int i=1;i<=currentSize;i++) {
if(i!=1) {
value+=", ";
}
value+= array[i];
}
value+="]";
return value;
}
}
测试代码 :
import java.util.List;
import java.util.PriorityQueue;
public class BinaryHeapTest {
public static void main(String[] args) {
BinaryHeapTest test = new BinaryHeapTest();
log("----------测试插入方法开始----------");
test.testinsert();
log("----------测试插入方法结束----------");
log("----------测试数据初始化方法开始----------");
test.testInitialize();
log("----------测试数据初始化方法结束----------");
log("----------测试提取并删除最大节点方法开始----------");
test.testDeleteMax();
log("----------测试提取并删除最大节点方法结束----------");
log("----------测试调整优先级相关方法开始----------");
test.testChangeKey();
log("----------测试调整优先级相关方法结束----------");
log("----------测试Java自带优先队列PriorityQueue开始----------");
test.testJavaDefault();
log("----------测试Java自带优先队列PriorityQueue结束----------");
}
public void testinsert() {
BinaryHeap queue1 = new BinaryHeap<>();
BinaryHeap queue2 = new BinaryHeap<>();
int[] data = { 10, 3, 5, 12, 44, 9 };
for (int value : data) {
queue1.insert(value);
queue2.insertByRecurse(value);
}
log("循环加动态步长的插入算法 : " + queue1);
log("递归的插入算法 : " + queue2);
}
public void testInitialize() {
Integer[] data = { 10, 3, 5, 12, 44, 9 };
BinaryHeap queue1 = new BinaryHeap<>(data, false);
BinaryHeap queue2 = new BinaryHeap<>(data, true);
BinaryHeap queue3 = new BinaryHeap<>();
queue3.insert(data);
log("循环加动态步长方式,先填充数组,然后将 size/2 ~ 1 的顶点元素逐一过滤下沉 : " + queue1);
log("递归方式,先填充数组,然后将 size/2 ~ 1 的顶点元素逐一过滤下沉 : " + queue2);
log("先置空数组,然后逐个插入 : " + queue3);
}
public void testDeleteMax() {
Integer[] data = { 10, 3, 5, 12, 44, 9 };
BinaryHeap queue = new BinaryHeap<>(data, false);
log("初始堆 : " + queue);
while (!queue.isEmpty()) {
int item = queue.deleteMax();
log("当前删除的最大值 : " + item + " , 剩余堆 : " + queue);
}
}
public void testJavaDefault() {
Integer[] data = { 10, 3, 5, 12, 44, 9 };
//此默认为最小堆
PriorityQueue queue= new PriorityQueue();
queue.addAll(List.of(data));
while (!queue.isEmpty()) {
int item = queue.poll();
log("当前删除的最小值 : " + item + " , 剩余堆 : " + queue);
}
}
public void testChangeKey() {
Integer[] data = { 10, 3, 5, 12, 44, 9 };
BinaryHeap queue = new BinaryHeap<>(data, false);
log("初始堆 : " + queue);
//提高最后一个元素优先级
queue.increaseKey(6, 100);
log("将最后元素(5)优先级增加100 : " + queue);
queue.decreaseKey(3, 100);
log("将第三个元素(44)优先级降低100 : " + queue);
queue.delete(2);
log("删除第三个元素(12) : " + queue);
}
public static void log(String message) {
System.out.println(message);
}
}
程序输出 :
———-测试插入方法开始———-
循环加动态步长的插入算法 : [44, 12, 9, 3, 10, 5]
递归的插入算法 : [44, 12, 9, 3, 10, 5]
———-测试插入方法结束———-
———-测试数据初始化方法开始———-
循环加动态步长方式,先填充数组,然后将 size/2 ~ 1 的顶点元素逐一过滤下沉 : [44, 12, 9, 10, 3, 5]
递归方式,先填充数组,然后将 size/2 ~ 1 的顶点元素逐一过滤下沉 : [44, 12, 9, 3, 10, 5]
先置空数组,然后逐个插入 : [44, 12, 9, 3, 10, 5]
———-测试数据初始化方法结束———-
———-测试提取并删除最大节点方法开始———-
初始堆 : [44, 12, 9, 10, 3, 5]
当前删除的最大值 : 44 , 剩余堆 : [12, 10, 9, 5, 3]
当前删除的最大值 : 12 , 剩余堆 : [10, 5, 9, 3]
当前删除的最大值 : 10 , 剩余堆 : [9, 5, 3]
当前删除的最大值 : 9 , 剩余堆 : [5, 3]
当前删除的最大值 : 5 , 剩余堆 : [3]
当前删除的最大值 : 3 , 剩余堆 : []
———-测试提取并删除最大节点方法结束———-
———-测试调整优先级相关方法开始———-
初始堆 : [44, 12, 9, 10, 3, 5]
将最后元素(5)优先级增加100 : [105, 12, 44, 10, 3, 9]
将第三个元素(44)优先级降低100 : [105, 12, 9, 10, 3, -56]
删除第三个元素(12) : [105, 10, 9, -56, 3]
———-测试调整优先级相关方法结束———-
———-测试Java自带优先队列PriorityQueue开始———-
当前删除的最小值 : 3 , 剩余堆 : [5, 10, 9, 12, 44]
当前删除的最小值 : 5 , 剩余堆 : [9, 10, 44, 12]
当前删除的最小值 : 9 , 剩余堆 : [10, 12, 44]
当前删除的最小值 : 10 , 剩余堆 : [12, 44]
当前删除的最小值 : 12 , 剩余堆 : [44]
当前删除的最小值 : 44 , 剩余堆 : []
———-测试Java自带优先队列PriorityQueue结束———-
GitHub 源码:
https://github.com/wodong/java-learning-stack/tree/master/algorithm/src/main/java/com/guyuesoft/structure/priorityqueue
堆算法的动态可视化展示:
http://zh.visualgo.net/en/heap