二叉堆可以看作是完全二叉树的数组对象,需要满足两个条件:完全二叉树;子节点不能大于父节点。
什么是完全二叉树?
二叉树的特点是每个节点的度都不能大于2,完全二叉树指的是深度为k的二叉树,除了k层以外,1--k-1层节点均达到了最大。
图1、2就是完全二叉树,图3不是
(重点)二叉堆的规律:假设当前父节点的索引为k,则它的两个子节点,左边的索引为k/2,右边为k/2+1
堆的基础代码
首先,堆内装的数据是不一定的,所以我们用使用泛型,同时因为后续需要比较,这个泛型必须要继承Comparable。其次堆的本质是一个数组,所以我们要准备一个数组来装元素,这里使用的是data[],同时我们需要一个元素来记录数组内元素的个数,这里使用多的count。
堆的初始化:
在堆的初始化前我们要先了解一个函数:shiftDown(int i)
shiftDown的作用是将i位置的元素放到正确的位置,过程为:(假设除了i位置,其他元素原本位置是正确的)首先获取子节点中元素最大的,然后与i位置的元素作比较,如果i位置的元素大,此时i位置的元素就是正确的,如果i位置的元素小,就交换i位置和子节点中最大元素的位置。依次循环,将元素放到正确位置上。
如果图所示:
此时i=2
子节点中最大元素为17,3<17交换位置
再寻找子节点最大的元素,为6,6>3,所以交换位置
此时完成了排序。
该函数的代码如下:
public void shiftDown(int i){
while (2*i <= count){
int k = 2*i;
if (k+1 <= count && data[k].compareTo(data[k+1]) < 0){//找出子节点最大的
k++;
}
if (data[i].compareTo(data[k]) > 0){//如果子节点最大的仍然比该节点小,则完成了归位
break;
}
swap(i, k);//如果最大子节点大于父节点,则交换位置
i = k;//获取新的位置,继续比较
}
}
现在我们再来学习初始化:
首先要传入一个打乱的数组,我们 将这个数组进行排序,获得堆。
假设传入的数组为:{12,33,42,44,65,28,79,88,4,5,2}
当前数组情况为:
从下面开始往上做比较,第一个是11/2,也就是5位置开始使用shiftDown,65大于5所以不用换。然后4位置开始使用,因为88>44所以要交换位置,如图
再对3位置使用,42<79,所以要交换位置:
再对2位置使用,因为33<88所以交换位置:
因为33<44,所以继续交换位置:
最后对1使用,因为12<88,所以交换位置:
因为12<65,继续交换:完成排序。
代码如下:
public BasicHeapify(T[] arr) {
int length = arr.length;
this.capacity = length;
count = length;
data = (T[]) new Comparable[capacity + 1];//+1的原因是0位不装元素,从1位开始装
for (int i = 0; i <length; i++){
data[i+1] = arr[i];
}
for (int i = count / 2; i > 0 ; i--){
shiftDown(i);//从最底部开始将元素归到正确位置,先把大的数找出,再比较
}
}
堆的插入:
堆的插入思路是,首先把要插入的元素放到堆的末端(要保证还有位置可以插入),然后把将该节点与父节点比较大小,如果该节点内元素大,则交换位置,如果小,则该位置是正确的。
在这之前我们同时要认识一个函数shifUp(int i)也就是上面描述的比较过程:
如现在插入一个元素42
42>28所以与之交换位置:
42<79,所以结束,已找到正确位置。
代码展示:
public void shiftUp(int i){
while (i > 1 && data[i].compareTo(data[i/2]) > 0){//如果元素比父节点大,则交换位置
swap(i, i/2);
i /= 2;
}
}
insert的代码展示:
public void insert(T t) throws Exception {
if (count == capacity) throw new Exception("堆已满,无法再添加");
data[count] = t;
shiftUp(count);//将元素放到正确位置
count ++;
}
整体代码展示:
package CSDN;
public class BasicHeapify<T extends Comparable> {
private T[] data;
private int count;
private int capacity;
public BasicHeapify(T[] arr) {
int length = arr.length;
this.capacity = length;
count = length;
data = (T[]) new Comparable[capacity + 1];//+1的原因是0位不装元素,从1位开始装
for (int i = 0; i <length; i++){
data[i+1] = arr[i];
}
for (int i = count / 2; i > 0 ; i--){
shiftDown(i);//从最底部开始将元素归到正确位置,先把大的数找出,再比较
}
}
public void insert(T t) throws Exception {
if (count == capacity) throw new Exception("堆已满,无法再添加");
data[count] = t;
shiftUp(count);//将元素放到正确位置
count ++;
}
public void shiftUp(int i){
while (i > 1 && data[i].compareTo(data[i/2]) > 0){//如果元素比父节点大,则交换位置
swap(i, i/2);
i /= 2;
}
}
public T extractMax(){//获取最大元素
T t = data[1];
swap(1,count);
count--;
shiftDown(1);
return t;
}
public T delete(int i){//删除元素
T t = data[i];
swap(i,count);
count--;
shiftDown(i);
return t;
}
public void shiftDown(int i){
while (2*i <= count){
int k = 2*i;
if (k+1 <= count && data[k].compareTo(data[k+1]) < 0){//找出子节点最大的
k++;
}
if (data[i].compareTo(data[k]) > 0){//如果子节点最大的仍然比该节点小,则完成了归位
break;
}
swap(i, k);//如果最大子节点大于父节点,则交换位置
i = k;//获取新的位置,继续比较
}
}
public void swap(int i, int j){//交换位置的函数
T a = data[i];
data[i] = data[j];
data[j] = a;
}
public int getSize(){//获得大小
return count;
}
public boolean isEmpty(){//判断是否为空
return count == 0;
}
public static void main(String[] args) {
Integer[] a = {12,32,44,54,234,432,66,44,94,23};
BasicHeapify<Integer> heapify = new BasicHeapify<Integer>(a);
for (int i = 1; i < heapify.capacity+1; i++) {
System.out.println(heapify.extractMax());
}
}
}