能带你起飞的【数据结构】成王第十一篇:堆

前言

二叉树的顺序存储

之前写的二叉树都是以链式存储的,左孩子,右孩子存地址,每个节点通过left和right串联起来.

今天写顺序存储一棵二叉树 

存储方式 

使用数组保存二叉树结构,方式即将二叉树用层序遍历方式放入数组中。

一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费。

这种方式的主要用法就是堆的表示。

 下标关系 :

已知双亲(parent)的下标,则:

左孩子(left)下标 = 2 * parent + 1;

右孩子(right)下标 = 2 * parent + 2;

已知孩子(不区分左右)(child)下标,则: 双亲(parent)下标 = (child - 1) / 2;

堆(heap) 

1. 堆逻辑上是一棵完全二叉树

2. 堆物理上是保存在数组中

3. 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆

4. 反之,则是小堆,或者小根堆,或者最小堆

5.堆的基本作用是,快速找集合中的最值

实现一个堆 

堆分为大根堆和小根堆

我们首先要把这棵二叉树调整为大根堆

要变成大根堆,每棵子树也必须是大根堆

首先比较左右孩子,左右孩子大的再和根节点比较大小,孩子比根大就互换,小就不变.

 只要变成大根堆一定能保证最上面的根节点是最大的元素

大根堆不是说就是有序的

总结:

1.调整是从最后一棵子树出发的

2.每棵子树的调整都是向下调整的(根和左右孩子去比较,根小根就下来了,这就叫做向下调整)

问题:

1.如何找到最后一棵子树?

整棵树是一个数组,那么最后一个元素就是len - 1

得到父亲节点parent = ((len-1)-1)/2

2.parent--就可以遍历完每棵子树

3.写一个向下调整的函数

4.每棵树的调整结束位置,如何判定?

每棵树调整的结束位置实际上都是一样的len

public class TestHeap {
    public int elem[];
    public int usedSize;

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

    /**
     * 向下调整函数的实现
     * @param parent 每棵树的根节点
     * @param len 每棵树的调整的结束位置
     */
    public void shiftDown(int parent ,int len){
        int child = 2 * parent + 1;
        //1.最起码是有左孩子的,至少是有一个孩子的
        while(child < len){
            if(child + 1 < len && elem[child] > elem[child + 1]){
                child++;//保证当前左右孩子最大值的下标
            }
            if(elem[child] > elem[parent]){
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                parent = child;
                child = 2 * parent + 1;

                
            }else{
                break;
            }

        }

    }
   
    //创建一个大根堆
    public void createBigHeap(int[] array ){
        for (int i = 0; i < array.length ; i++) {
            //将数据放到 elem 当中
            elem[i] = array[i];
            usedSize++;
        }
        //parent到 < 0 的时候就不能进行向下调整了
        for (int parent = (usedSize - 1 -1)/2; parent >= 0 ; parent--) {
            //调整
            shiftDown(parent,usedSize);
        }

    }
}

 此时我们就有了一个堆

大堆还是小堆需要验证:

 打印结果

 小堆 

入队操作(向上调整):

 假设在没满的情况下,要放一个80到里面,如何放

 把80放到最后一个,此时80的下标就是10 

那么此时是大根堆吗?不是,需要进行调整

直接让80跟37比较(因为在插入80之前已经是大根堆了)

交换

这就是向上调整

出队列(向下调整):

每次出队列都得保证出最大的或者最小的 

第一步:交换0下标和最后一个元素

每次出都相当于usedSize都会减减 

此时80就出出去了 

此时我们就可以发现只有一棵树不是大根堆(0下标这棵树)

第二步:调整0下标这棵树就可以了(进行向下调整)

完整代码

public class TestHeap {
    public int elem[];
    public int usedSize;

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

    /**
     * 向下调整函数的实现
     * @param parent 每棵树的根节点
     * @param len 每棵树的调整的结束位置
     */
    public void shiftDown(int parent ,int len){
        int child = 2 * parent + 1;
        //1.最起码是有左孩子的,至少是有一个孩子的
        while(child < len){
            if(child + 1 < len && elem[child] > elem[child + 1]){
                child++;//保证当前左右孩子最大值的下标
            }
            if(elem[child] > elem[parent]){
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                parent = child;
                child = 2 * parent + 1;

                
            }else{
                break;
            }

        }

    }
   
    //创建一个大根堆
    public void createBigHeap(int[] array ){
        for (int i = 0; i < array.length ; i++) {
            //将数据放到 elem 当中
            elem[i] = array[i];
            usedSize++;
        }
        //parent到 < 0 的时候就不能进行向下调整了
        for (int parent = (usedSize - 1 -1)/2; parent >= 0 ; parent--) {
            //调整
            shiftDown(parent,usedSize);
        }

    }
    //向上调整
    private void shiftup(int child){
        int parent = (child - 1)/2;
        while(child > 0){
            if(elem[child] > elem[parent]){
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                child = parent;
                parent = (child - 1)/2;
            }
        }

    }
     //堆建好了往里面放元素
    public void offer( int val){
        if(isFull()){
            //扩容
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize++] = val;
        shiftup(usedSize-1);

    }
    //判满不满
    public boolean isFull(){

        return usedSize == elem.length;
    }
      //出队列
    public int poll(){
        if(isEmpty()){
          throw new RuntimeException("优先级队列为空");
        }
        int tmp = elem[0];
        elem[0] = elem[usedSize - 1];
        elem[usedSize - 1] = tmp;
        shiftDown(0,usedSize);
        return tmp;
    }
    public boolean isEmpty(){
        return usedSize == 0;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

K稳重

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值