数据结构——堆

概念

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质:

1.堆中某个节点的值总是不大于或不小于其父节点的值;
2.堆总是一棵完全二叉树。
性质一,就会使得堆产生两种形式“大根堆” “小根堆”,性质二呢,就使得堆是可以顺序存储的,即堆逻辑上表现为一棵二叉树,存储结构是顺序结构:
在这里插入图片描述

堆的创建

堆默认为小根堆,在我们使用堆时,会默认将我们提供的数组,调整为小根堆,采取的是向下调整,例如如下这一组数据,

在这里插入图片描述
对应的堆的逻辑结构是下面这颗二叉树
在这里插入图片描述

显然他不符合小根堆的定义,我们从最后一个元素开始调整,9下标和4下标的数进行比较,若4小标的大,则两者交换位置,若4下标小,我们开始调整3下标的子树,我们比较3下标节点的左右孩子,得到最小值,与3下标比较,若3下标的值大,3下标的值和左右节点的最小值交换位置,交换位置后,我们去比较7下标所在的子树,因为交换位置后,可能会产生子树不满足小根堆的条件。接下来我们来看代码:

public class MyHeap {

    public int[] elem;
    public int useSize;

    public MyHeap() {
        this.elem = new int[10];
    }

    public void init(int[] arr){
        for (int i = 0; i < arr.length; i++) {
            elem[i] = arr[i];
            useSize++;
        }
    }

    public void createHeap(){
        for (int parent = (useSize - 1 - 1)/2; parent >= 0; parent--) {
            shift(parent,useSize);
        }
    }

    public void shift(int parent,int len){
        int child = 2*parent + 1;

        while (child < len){

            if (child+1< useSize && elem[child] > elem[child + 1]){
                child++;
            }

            if (elem[parent] > elem[child]) {
                swap(parent,child);
                parent = child;
                child = 2*parent +1;
            }else {
                break;
            }
        }
    }

    public void swap(int parent,int child){
        int tmp = elem[parent];
        elem[parent] = elem[child];
        elem[child] = tmp;
    }
}

我们观察调整后的顺序
在这里插入图片描述
他的逻辑结构就是下图:
在这里插入图片描述
我们发现,调整后的堆满足小根堆的条件。

堆的插入

再上图中插入一个元素,那么应该向哪里插入呢?
首先我们得明确的一点是,堆是一个完全二叉树,那么堆只能在有效数据之后插入,即在4下标的有孩子处插入。
对于堆的插入,我们应当注意一下两点:
1.若此时数组空间已满,我们需要扩容,那么应当怎样扩容呢?
我们这里以数组长度的1.5进行扩容,而在优先级队列中的扩容策略是
将新元素插入数组的尾部、若数组已满、则进行扩容、小于64时2倍扩容、大于等于64、则1.5倍、若超过Integer-8则按、数组最大值来扩容。
2.插入时,旧的堆已经是一个小根堆,那么我们只需要,用新的元素与其父亲节点进行比较即可。

    public void offer(int val){
        if(isFull()){
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[useSize++] = val;
        
    }
    
    public void shiftUp(int child){
        int parent = (child - 1)/2;
        
        while (child > 0){
            if (elem[child] < elem[parent]) {
                swap(child,parent);
                child = parent;
                parent = (child - 1)/2; 
            }else {
                break;
            }
        }
    }
    public boolean isFull(){
        return useSize == elem.length;
    }

堆的删除

堆的删除思想就是,堆顶元素,和堆尾元素进行交换,交换完成后,堆的有效长度减一,除过堆尾元素外,其余元素,进行调整,调整尾小根堆。
我们来看代码:

public void Pop(){
        if (isEmpty()){
            return;
        }
        swap(useSize,0);
        useSize--;
        shift(0,useSize);
    }
    
    public boolean isEmpty(){
        return useSize == 0;
    }

堆的注意事项

堆的注意事项:
1.堆不能插入null对象、否则会抛出NullPointterException;
2.没有容量限制、可以自动扩容
3.删除时总是从根节点删除
4.默认为最小堆,
5.堆的最底层数据存储使用的是数组
6.插入和删除的时间发复杂度都是O(log2N)
7.PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值