堆
- 特点
- 堆是一棵完全二叉树,即除了最下边一层其余都是满的状态,最下边一层满足最后一个元素的左侧都是满状态。
- 堆有别于二叉树的一个特点就是其父节点是大于两个子节点的,两个子节点的左右分步没有硬性要求
- 堆通常以数组进行实现,常见的规律有 假设一个节点所在的索引为
k
,则其父节点所在的索引为k/2
,两个子节点的索引分别为2k
和2k+1
自己实现堆
public class Heap<T extends Comparable<T>> {
// 用来存储元素的数组
private T[] items;
// 用来存储堆中元素的个数
private int size;
public Heap(int capacity) {
this.items = (T[]) new Comparable[capacity + 1];
this.size = 0;
}
/**
* 交换数组索引i和索引j处的元素
* @param i 索引
* @param j 索引
*/
private void exchange(int i, int j) {
T temp = this.items[i];
this.items[i] = this.items[j];
this.items[j] = temp;
}
/**
* 删除堆中最大的元素,并返回
* @return 堆中最大的元素
*/
public T deleteMax() {
// 交换最大的元素和最大索引处的值
T maxItem = items[1];
exchange(1, size);
// 删除最大的元素,让完全二叉树中最右侧的元素变为临时的根节点
items[size] = null;
this.size --;
// 通过下沉算法调整堆,让根节点重新处于正确的位置,让堆重新有序
this.sink(1);
return maxItem;
}
/**
* 向堆中插入一个元素
* @param t 插入的元素
*/
public void insert(T t) {
items[++ size] = t;
swim(size);
}
/**
* 使用上浮算法,将索引index处的元素不断的与其父节点进行比较,最终能够在堆中处于正确的位置
* @param index 索引
*/
private void swim(int index) {
while (index > 1) {
// 与父节点进行比较
if (items[index].compareTo(items[index/2]) > 0) {
exchange(index, index/2);
}
index = index / 2;
}
}
/**
* 使用下沉算法,将索引index处的元素不断的
* @param index
*/
private void sink(int index) {
// 通过循环弊端的遍历当前索引的两个子节点
while (2*index <= size) {
// 获取两个子节点中较大的那个
int maxChildIndex;
if (2*index+1 <= size) {
maxChildIndex = items[2*index].compareTo(items[2*index+1]) > 0 ? 2*index : 2*index+1;
} else {
maxChildIndex = 2*index;
}
// 比较当前索引处的值和两个子节点最大的值的关系
if (items[index].compareTo(items[maxChildIndex]) < 0) {
// 当前索引比子节点的最大值小,就往两个子节点的最大者底下沉
exchange(index, maxChildIndex);
index = maxChildIndex;
} else {
break;
}
}
}
/**
* 将原始的数组进行堆排序并返回
* @param source 原始数组
*/
public static void sort(Comparable[] source) {
// 1. 构建堆
// 将source数组中的元素拷贝到堆数组中,形成一个无序待排序的堆
Comparable[] heap = new Comparable[source.length + 1];
System.arraycopy(source, 0, heap, 1, source.length);
// 对堆中的元素做下沉调整(从堆长度的一般开始,往索引1处进行扫描)
for (int i=(heap.length)/2; i>0; i--) {
sinkSort(heap, i, source.length);
}
// 2. 堆排序
// 记录未排序的数组最大索引
int maxIndex = source.length;
while (maxIndex != 1) {
// 交换1索引处和未排序的最大索引处的值
Comparable temp = heap[1];
heap[1] = heap[maxIndex];
heap[maxIndex] = temp;
maxIndex --;
// 对交换之后的索引1处元素进行下沉调整,调整的区域为未排序区域
sinkSort(heap, 1, maxIndex);
}
// 3. 将排序好的数组转到原数组中
System.arraycopy(heap, 1, source, 0, source.length);
}
/**
* 堆排序所需的下沉排序
* @param heap 堆数组
* @param targetIndex 需要做下沉的元素索引
* @param range 做下沉的范围,0-range
*/
private static void sinkSort(Comparable[] heap, int targetIndex, int range) {
while (2*targetIndex <= range) {
int maxChildIndex;
if (2*targetIndex+1 < range) {
maxChildIndex = heap[2*targetIndex].compareTo(heap[2*targetIndex+1])>0 ? 2*targetIndex : 2*targetIndex+1;
} else {
maxChildIndex = 2*targetIndex;
}
if (heap[targetIndex].compareTo(heap[maxChildIndex]) >= 0) break;
// 元素位置交换
Comparable temp = heap[targetIndex];
heap[targetIndex] = heap[maxChildIndex];
heap[maxChildIndex] = temp;
targetIndex = maxChildIndex;
}
}
}