堆是一种特殊的完全二叉树;
在完全二叉树的基础上,堆的双亲结点一定大于(小于)或者等于对应子节点的值,因此堆的根节点一定是堆中最大或者最小的结点,分别称这两种情况下的堆为大根堆或小根堆;
堆的性质和完全二叉树很类似,例如按照层序遍历的方式为堆中的每个节点依次编号,那么1号位置的结点必定是根节点,i大于1时,双亲结点就时i/2,子结点就是2i和2i+1,通过这种条件使得可以很方便的访问到对应下标结点的双亲结点与子节点。
在向堆中添加元素时,需要保持堆的性质,所以每一次的添加和删除都要重新对堆进行向上调整或者向下调整。
以小根堆为例:
private void shitUp(int index, T value) {
heap[index] = value;//将待添加结点放入最后一个位置上(叶子结点)
while (index > 0) {
int parent = (index - 1) >> 2;//通过while循环遍历每一个有孩子的结点(双亲结点),调整其左右子树
Comparable <T> v1 = (Comparable <T>) value;
if (v1.compareTo((T) heap[parent]) < 0) {//如果双亲结点的值大于待添加结点,就进行交换
heap[index] = heap[parent];
index = parent;
} else {
//双亲节点小于等于孩子节点,不用调整
break;
}
}
//index == 0 不用调整
heap[index] = value;
删除时向下调整:
private void shitDown(int index, T Value) {
int half = size >>> 1;//遍历所有非叶子节点
while (index < half) {
//向下调整
int child = 2 * index + 1;
int right = child + 1;
Comparable <T> ch = (Comparable <T>) heap[child];
if (right < size && (ch.compareTo((T) heap[right]) > 0)) {
child = right;
ch = (Comparable <T>) heap[right];
}
if (ch.compareTo(Value) < 0) {
//最小的孩子节点小于父节点
heap[index] = heap[child];
index = child;
} else {
break;
}
}
heap[index] = Value;
}
利用堆的这种数据结构同样可以进行数据排序:
堆排序算法就是通过将待排序的序列构建为一个大根堆或者小根堆,这样根节点就是最大值或者最小值,将根节点和末尾结点进行交换,再重新对堆进行调整,循环往复,得到一个有序的序列。
以小根堆为例,
将待排序序列构建为一棵完全二叉树
将下标(n/2)-1的下标位置作为根结点开始构建堆
找出当前结点中两个孩子的最小值,并与双亲结点作比较,若大于双亲结点,就break,否则交换
以交换后得到的子结点为新的双亲结点,向下继续调整,直至发生越界(没有子结点)跳出循环,再进行值覆盖
public void heapSort(T arr[]){
for (int i = arr.length/2-1;i>=0;i--){
heapAdjust(arr,i,arr.length);
}
for (int i = arr.length-1;i>0;i--){
T temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapAdjust(arr,0,i);
}
}
private void heapAdjust(T arr[],int low ,int high) {
int i = low;
int j = 2*i+1;//左孩子
T temp = arr[i];
while (j<high){
if (j<high-1 && (arr[j].compareTo(arr[j+1])>0)){
j++;
}
if ((temp.compareTo(arr[j])>0)){
arr[i] = arr[j];
i = j;
j = 2*i +1;
}
else {
break;
}
}
arr[i] = temp;
}