heap java 数据结构,Java数据结构 - 堆

堆是一棵完全二叉树,如下图,也是一个优先队列。

小顶堆:所有节点的左右孩子节点都小于或者等于父节点

大顶堆:所有节点的左右孩子节点都大于或者等于父节点

84296b80f5d3

完全二叉树.png

完全二叉树

完全二叉树是除了最下面一层,上面所有层都满的二叉树,并且最下层叶子节点也是从左到右的。

完全二叉树如此有规律,以至于我们选择使用数组表示完全二叉树,而不是必须使用链。

!!!(本文将以数组第1位作为数组第0位进行讲解)

完全二叉树具有的性质

parent表示父节点的数组下标

leftChild表示左孩子节点的数组下标

rightChild表示右孩子节点的数组下标

leftChild = parent * 2

rightChild = parent * 2

parent = leftChild / 2 或者 rightChild / 2

堆的操作

插入操作(小顶堆)

插入时只能从堆的最后插入,也就是往数组中最后一个位置插入数据,例如插入 1 这个节点。

84296b80f5d3

插入 1 这个节点.png

插入 1 之后,此时不满足小顶堆的条件了,我们就需要对堆做出调整,使其重新满足条件。

首先,第一步,用temp记录下新插入节点的值。之后形成一个空穴。

84296b80f5d3

假装把 1 删除了.png

接下来,用temp与父节点(也就是20)进行比较。如果temp大于20,那么好,插入结束了,空穴重新填上1这个值。否则,将20移入空穴。变成下图。

84296b80f5d3

第一次调整.png

接下来,继续重复上述步骤,拿temp与10进行比较,结果发现temp小于10,之后把10移入空穴。如下图。

84296b80f5d3

第二次调整.png

最后,再继续上述方法进行调整。将3移入空穴,因为再往上没有父节点了,所以将temp也就是新节点移入空穴,至此插入过程结束,该过程叫做上滤。

84296b80f5d3

插入结束.png

代码如下

public void insert(T element){

if(currentSize == array.length - 1){

changBiggerArray(array.length * 2 + 1);

}

int hole = ++currentSize;

for( ; (hole>1)&&(compare.compare(array[hole/2], element) > 0); hole/=2){

array[hole] = array[hole/2];

}

array[hole] = element;

}

public void changBiggerArray(int size){

T[] arr1 = (T[])new Object[size];

System.arraycopy(array, 0, arr1, 0, array.length);

array = arr1;

}

插入过程总结:在数组最后插入新数据,也就知道了新节点的下标,之后除以2就知道了父亲节点的下标,之后逐层进行比较,直至父节点小于新插入节点或者没有父节点了。

删除操作(基于上面的小顶堆)

删除操作,其实就是出队操作,将小顶堆顶部的元素弹出。

例如上面的小顶堆,执行一次删除操作后变成:

84296b80f5d3

删除.png

此时在堆顶形成了空穴,为了保持堆的结构,又要对堆进行调整。

调整过程如下

从顶部空穴开始判断左右孩子节点哪个小,选出来较小的,之后与堆的最后一个节点(本堆中的20)比较大小,如果是最后一个节点小,那么将最后一个节点移入到空穴中,结束调整。如果最后一个节点大的话,那么将左右孩子节点中较小的移入空穴。

84296b80f5d3

第一次调整.png

重复上述的操作

84296b80f5d3

第二次调整.png

最后发现空穴左右孩子节点不全或者没有左右孩子节点,那么直接将20移入空穴,结束调整。

代码如下

public T delete(){

T root = array[1];

int current = 1;

int child = 2 * current;

while(currentSize > 2*current){

if((child+1 != currentSize) && (compare.compare(array[child], array[child+1]) > 0)) {

++child;

}

if(compare.compare(array[child], array[currentSize]) < 0){

array[current] = array[child];

current = child;

child = child << 1;

}else{

break;

}

}

array[current] = array[currentSize];

currentSize--;

return root;

}

删除总结:直接将数组第一位移除形成空穴,判断是否左右孩子都齐全,如果不全,则直接将末尾节点移入空穴。之后重复上面的操作(下滤)。

全部代码如下

* Created by ShouJingGuo on 2018/3/15.

* 二叉堆具有的性质:

* 1.是一棵完全二叉树,从数组arr[1]位置开始存储,满足左右孩子的数组下标/2 = 父亲节点的数组下标

* 树高h 含有节点数目大于等于2<

* 2.堆顶元素是数组最大值或者最小值,每个节点的子节点都大于等于或者小于等于该节点。

*/

public class BinaryHeap {

private int currentSize = 0;

private T[] array;

private Comparator compare;

BinaryHeap(int size, Comparator compare){

array = (T[])new Object[size];

this.compare = compare;

}

public void insert(T element){

if(currentSize == array.length - 1){

changBiggerArray(array.length * 2 + 1);

}

int hole = ++currentSize;

for( ; (hole>1)&&(compare.compare(array[hole/2], element) > 0); hole/=2){

array[hole] = array[hole/2];

}

array[hole] = element;

}

public void changBiggerArray(int size){

T[] arr1 = (T[])new Object[size];

System.arraycopy(array, 0, arr1, 0, array.length);

array = arr1;

}

public T delete(){

T root = array[1];

int current = 1;

int child = 2 * current;

while(currentSize > 2*current){

if((child+1 != currentSize) && (compare.compare(array[child], array[child+1]) > 0)) {

++child;

}

if(compare.compare(array[child], array[currentSize]) < 0){

array[current] = array[child];

current = child;

child = child << 1;

}else{

break;

}

}

array[current] = array[currentSize];

currentSize--;

return root;

}

public static void main(String[] args) {

BinaryHeap binaryHeap = new BinaryHeap<>(8, new IntegerComparator());

binaryHeap.insert(6);

binaryHeap.insert(5);

binaryHeap.insert(9);

binaryHeap.insert(11);

binaryHeap.insert(14);

binaryHeap.insert(10);

binaryHeap.insert(15);

binaryHeap.insert(20);

binaryHeap.insert(31);

binaryHeap.insert(27);

binaryHeap.insert(19);

System.out.println(Arrays.toString(binaryHeap.array));

binaryHeap.delete();

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值