1. 堆的特点
- 堆的逻辑结构是数组,内存结构是完全二叉树.完全二叉树即只有最后一层才有叶子节点.
- 堆又分为大顶堆与小顶堆. 大顶堆的特点是 : 父亲节点比孩子节点的都要大. 小顶堆的特点与其相反.
- Java的优先级队列(PriorityQueue)的底层实现即用到了小顶堆. 所以下文我们就用Java代码来实现小顶堆.
- 本文没有像实现栈或队列那样使用了泛型,为了方便省去了泛型的步骤.
2. 小顶堆的实现
(1). 主要操作 :
- 堆化:传入一个数组,从下到上,最后一个非叶子节点开始,再从上到下不断进行下沉操作.
- offer()添加元素:从数组尾部添加元素,不断上浮.
- poll()弹出堆首元素.将堆首元素与最后一个叶子节点互换,size--,再从堆首开始下沉操作即可.
- sort()堆排序.
(2). 代码实现
//代码实现小顶堆
//逻辑结构是数组
public class MyHeap {
private int size;
private int[] heap;
public MyHeap(int capacity) {
heap = new int[capacity];
}
//堆化
public void heapify(int[] arr) {
for (int i = 0; i < arr.length; i++) {
heap[i] = arr[i];
}
size = arr.length;
//第一步 : 找到最后一个非叶子节点
//第二步 : 从该节点出发, 从后往前, 以此堆化
//size - 1即最后一个叶子节点, 依据公式, 该则为叶子节点的父亲节点
for (int parent = (size - 1) / 2; parent >= 0; parent--) {
int leftChild = parent*2 + 1;
int rightChild = parent*2 + 2;
int min = parent;
//如果存在左孩子, 而且父亲节点比孩子节点要大
if (leftChild < size && heap[min] > heap[leftChild]) {
min = leftChild;
}
//如果存在右孩子, 而且右孩子的值比左孩子和父亲还要小
if (rightChild < size && heap[min] > heap[rightChild]) {
min = rightChild;
}
//如果最小值不是父亲, 那么需要下沉
if (min != parent) {
down(parent);
}
}
}
private void swap(int a, int b) {
int temp;
temp = heap[a];
heap[a] = heap[b];
heap[b] = temp;
}
private void down(int parent) {
int leftChild = parent*2 + 1;
int rightChild = parent*2 + 2;
int min = parent;
if (leftChild < size && heap[min] > heap[leftChild]) {
min = leftChild;
}
if (rightChild < size && heap[min] > heap[rightChild]) {
min = rightChild;
}
if (min != parent) {
swap(min, parent);
down(min);
}
}
//向堆中添加元素, 如果该添加的元素比父亲节点(如果有的话)要小,
//则需要上浮
public void offer(int a) {
if (size > heap.length) {
return;
}
heap[size] = a;
int child = size;
size++;
int parent;
while (child > 0) {
parent = (child - 1) / 2;
//如果添加的元素要比父亲要小
if (heap[parent] > heap[child]) {
swap(parent, child);
down(parent);
child = parent;
} else {
//如果添加后父亲节点仍然符合小顶堆, 那么退出循环, 无需再上浮
break;
}
}
}
//返回堆顶元素
public int peek() {
return heap[0];
}
//弹出堆顶元素
//第一步 : 将最后一个叶子节点与堆顶元素互换, size--
//第二步 : 从堆顶开始不断下沉
public int poll() throws Exception {
if (size == 0) {
throw new Exception();
}
int value = peek();
swap(0, size - 1);
size--;
//不断下沉
int parent = 0;
down(parent);
return value;
}
//删除指定索引的节点, 思路与poll类似
public int poll(int index) throws Exception {
//检验index索引的合法性
if (index < 0 || index >= size) {
throw new Exception();
}
int value = heap[index];
swap(index, size - 1);
size--;
int parent = index;
while (parent >= 0) {
down(parent);
//如果不加这个判断, 那么将parent为0的时候, 不断死循环
if (parent == 0) {
break;
}
parent = (parent - 1) / 2;
}
return value;
}
//堆排序
//第一步 : 将最后一个叶子节点与堆顶互换, size--
//第二步 : 开始下沉
int l;
public void sort() {
l = size;
while (size > 0) {
swap(0, size - 1);
size--;
down(0);
}
size = l;
int j = size - 1;
for (int i = 0; i < size/2; i++){
swap(i, j);
j--;
}
}
public boolean isEmpty() {
return size == 0;
}
}
3. 单元测试
public class MyHeapTest {
@Test
public void test1() throws Exception {
MyHeap heap = new MyHeap(10);
int[] arr = new int[]{9, 12, 4, 3, 1};
heap.heapify(arr);
System.out.println(heap.peek());
//1
System.out.println(heap.poll());
//1
System.out.println(heap.peek());
//3
heap.offer(0);
System.out.println(heap.peek());
//0
System.out.println(heap.poll(0));
}
@Test
public void test2() throws Exception {
MyHeap heap = new MyHeap(10);
int[] arr = new int[]{9, 12, 4, 3, 1, 10, 0};
heap.heapify(arr);
heap.offer(-1);
heap.offer(7);
heap.offer(-10);
while (!heap.isEmpty()){
System.out.print(heap.peek() + " ");
heap.poll();
}
}
}