二叉堆
一、实现的堆有如下特点
- 理论分析上为二叉堆
- 理论分析上为完全二叉树
- 自定义堆的优先级,创建堆对象时需要实现比较器
- 底层用线性数组存取元素。
- 支持heapify操作,将一个数组进行原地排序
二、如果索引从0开始开始编号,父子索引的关系如下
parent(i) = (i - 1) / 2
left child(i) = i * 2 + 1
right child(i) = i * 2 + 2
三、具体实现
import java.util.ArrayList;
import java.util.Comparator;
/**
* 实现一个自定义优先级的堆。
* 要求用户传输一个Lambda表达式指定是最大堆还是最小堆,或者其他定义!
* 很灵活是不是@_@
*/
public class MyHeap<E> {
private ArrayList<E> array;
private Comparator<E> compare;
// 构造函数
public MyHeap(Comparator<E> compare){
array = new ArrayList<>();
this.compare = compare;
}
// 返回堆的大小
public int size() {
return array.size();
}
// 判断堆是否为空
public boolean isEmpty() {
return this.size() == 0;
}
// swap交换函数
private void swap(int i, int j) {
E temp = array.get(i);
array.set(i, array.get(j));
array.set(j, temp);
}
// 计算父节点索引:parent(i) = (i - 1) / 2
private int parent(int i) {
return (i - 1) / 2;
}
// 计算左孩子节点:left child(i) = i * 2 + 1
private int leftChild(int i) {
return i * 2 + 1;
}
// 计算右孩子节点:right child(i) = i * 2 + 2
private int rightChild(int i) {
return i * 2 + 2;
}
// Shift Up操作,将末尾元素与父元素比较,按照规则进行交换!
private void shiftUp(int i) {
int parentIndex = parent(i);
while (i > 0 && compare.compare(array.get(i), array.get(parentIndex)) >= 0) {
swap(i, parentIndex);
i = parentIndex;
parentIndex = parent(parentIndex);
}
}
// 向堆中添加一个元素
public void add(E e) {
array.add(e);
shiftUp(array.size() - 1);
}
// 找到子节点优先级最高的节点的索引
// 为了实现原地排序法,需要边界参数 boundary
// 如果没有子节点则返回 -1
private int prioritySonIndex(int i, int boundary) {
int leftIndex = leftChild(i);
int rightIndex = rightChild(i);
if(leftIndex >= boundary)
return -1;
if(rightIndex >= boundary)
return leftIndex;
if(compare.compare(array.get(leftIndex), array.get(rightIndex)) > 0)
return leftIndex;
else
return rightIndex;
}
// Shift Down操作,从根节点开始与子节点比较,按照规则进行交换!
// 为了实现原地排序法,需要边界参数 boundary
private void shiftDown(int i, int boundary) {
int parent = i;
int son = prioritySonIndex(i, boundary);
while (son != -1) {
if(compare.compare(array.get(son), array.get(parent)) > 0) {
swap(parent, son);
parent = son;
son = prioritySonIndex(parent, boundary);
} else
return;
}
}
// 取出堆中的第一个元素,将最后一个元素
// 赋值到第一个元素的位置,然后进行shift down操作。
public E pop() {
try {
E temp = array.get(0);
array.set(0, array.get(array.size() - 1));
array.remove(array.size() - 1);
shiftDown(0, array.size());
return temp;
} catch (Exception e) {
throw new IndexOutOfBoundsException("数组索引越界错误!");
}
}
/** @Heapify排序,将一个数组进行排序
* 从最后一个含有子节点的节点开始一直到根节点,进行shift down操作
* 由于排序完毕之后是一个堆,需要进行原地排序法将数组整理一下
* 思路:【排序后是倒叙,所以在创建 堆 时,得设计好优先级!】
* 1. Heapify完成之后,将第一个元素与最后一个元素交换,进行shift down操作【最后一个数不参与】
* 2. 将第一个元素与倒数第二个交换,shift down操作【倒数第二个数之后的不参与】
* ...
* n. 直到第一个元素与交换的索引相等了,就终止
*/
public void heapify(ArrayList<E> arr){
this.array = arr;
int last = parent(arr.size() - 1);
for (int i = last; i >= 0; i--) {
shiftDown(i, array.size());
}
// 实现原地排序法
for (int i = array.size() - 1; i > 0; i --) {
swap(0, i);
shiftDown(0, i);
}
}
@Override
public String toString() {
return array.toString();
}
}
和堆相关的其他问题【以后探讨】
在N个元素中选出前M个元素O(NlogM)【已实现】
-
多路归并排序
多路并规排序.png d 叉堆 d-ary heap:每个节点有n个节点
最大堆,最大索引堆,最小堆,最小索引堆【已满足】
容量伸缩堆【已实现】
同时维护最大索引和最小索引
二项堆、斐波那契堆