Java版大顶堆的实现

堆的概念

堆是一棵完全二叉树,一般使用数组来存储。通俗来讲堆其实就是利用数组来维护一个完全二叉树

按照堆的特点可以把堆分为大顶堆和小顶堆

  • 大顶堆:堆的每个结点的值都大于或等于其左右孩子结点的值

  • 小顶堆:堆的每个结点的值都小于或等于其左右孩子结点的值

根据堆的概念(利用数组维护的完全二叉树),可以推导出:
假设 节点A 在数组 tree 的索引为 i

(1)A节点的左节点索引:leftIdx = (i+1)*2 -1
(2)A节点的右节点索引:rightIdx = (i+1)*2
(3)A的父节点索引:parentIdx = (i-1)/2

堆的构建

堆的构建可以看成是空堆中逐渐插入数据,因此构建堆,应该先实现堆的插入方法。

往堆中插入节点

往堆中插入数据时,可能会破坏大顶堆(或小顶堆),根节点大于左右节点的性质,因此需要做出调整。

堆的插入流程如下:

  1. 将插入的数组置于数组的尾部
  2. 从尾部开始比较当前节点(一开始为插入的节点)与父节点是否满足大顶堆(或小顶堆)的性质,不满足则交换父子节点。
  3. 重复2步骤,直到满足大顶堆性质或到达堆顶

示例代码:

	/**
     * 插入的流程:
     * 1.先把元素放到数组最后
     * 2.与父元素比较,若父元素小于子元素则交换父子元素,直到父元素大于等于子元素
     * @param n
     */
       public void add(int n){
        if(size>=capacity){
            throw new RuntimeException("堆达到最大容量");
        }
		
		//新节点的索引
        int curIdx = size;
        //将新节点插入数组尾部
        table[curIdx] = n;
        
        //获得父节点索引,具体代码在参考后文的完整示例
        //父节点索引 pIdx = (curIdx-1)/2
        int pIdx = getParent(curIdx);

        /**
         * 调整堆,使其符合大顶堆的性质,时间复杂度O(logn)
         * 与父元素比较,若父元素小于子元素则交换父子元素,直到父元素大于等于子元素
         */
        while(table[curIdx]>table[pIdx]){
        	//子节点值大于父节点,交换父子节点
            int t = table[pIdx];
            table[pIdx] = table[curIdx];
            table[curIdx] = t;
            curIdx = pIdx;
            pIdx = getParent(curIdx);
        }
        size++;
    }

在清楚堆的插入过程之后,堆的构建就是直接调用插入方法,往堆中存放数据。

同时由上述可知,往堆中添加元素的时间复杂度为 O(logN),因此构建堆的时间复杂度为n次插入,即 O(NlogN)

删除堆顶节点

删除堆顶元素的流程:

  1. 用堆的最后一个元素(数组中的最后一个元素),代替堆顶元素(数组的第一个元素)
  2. 判断当前元素(一开始为堆顶),是否小于左右子元素,小于则用左右子元素中较大的元素和当前元素进行交换
  3. 重复步骤2,直接节点大于左右元素或没有左右元素

示例代码:

/**
     * 删除并返回堆顶元素
     * 删除流程:
     * 1.把最后一个元素代替删除位置的元素
     * 2.与子元素比较,把较大的子元素与当前元素交换,直到当前元素大于左右子元素
     * @return 堆顶元素
     */
    public int remove(){
        //待删除的堆顶元素
        int v = table[0];

        //用堆的最后一个元素(数组中的最后一个元素),代替堆顶元素(数组的第一个元素)
        table[0] = table[size-1];
        size--;

        //调正堆,使堆满足大顶堆的性质
        //当前元素索引,一开始为堆顶元素
        int curIdx = 0;
        while(true){
        	//获得当前元素的左右节点索引,具体代码参考后面完整示例
            int lf = getLeftChild(curIdx);
            int rt = getRightChild(curIdx);
            
            if(lf==-1&&rt==-1){
                //没有左右元素,无需调整
                break;
            }

            //较大的子元素索引
            int swapIdx;
            if(lf==-1||rt==-1){
                swapIdx = lf==-1?rt:lf;
            }else{
                swapIdx = table[rt]>table[lf]?rt:lf;
            }

            if(table[curIdx]>table[swapIdx]){
                //父元素大于左右子元素,结束交换
                break;
            }

            //用较大的子元素代替父元素
            int t = table[curIdx];
            table[curIdx] = table[swapIdx];
            table[swapIdx] = t;

            curIdx = swapIdx;
        }
		
		//返回被删除的元素
        return v;
    }

完整代码

/**
 * @author Darren
 * @date 2021/6/24 15:54
 * 简单大顶堆的实现
 */
public class Heap {
    //堆的容量
    int capacity;
    //已存放的节点数
    int size;
    //存放完全二叉树结构的数组
    int[] table;

    Heap(int capacity){
        this.capacity = capacity;
        table = new int[capacity];
    }

    /**
     * 插入的流程:
     * 1.先把元素放到数组最后
     * 2.与父元素比较,若父元素小于子元素则交换父子元素,直到父元素大于等于子元素
     * @param n
     */
    public void add(int n){
        if(size>=capacity){
            throw new RuntimeException("堆达到最大容量");
        }

        //当前元素的索引
        int curIdx = size;
        //将新节点插入数组尾部
        table[curIdx] = n;
        int pIdx = getParent(curIdx);

        /**
         * 调整堆,使其符合大顶堆的性质,时间复杂度O(logn)
         * 与父元素比较,若父元素小于子元素则交换父子元素,直到父元素大于等于子元素
         */
        while(table[curIdx]>table[pIdx]){
            int t = table[pIdx];
            table[pIdx] = table[curIdx];
            table[curIdx] = t;
            curIdx = pIdx;
            pIdx = getParent(curIdx);
        }
        size++;
    }

    /**
     * 删除堆顶元素
     * 删除流程:
     * 1.把最后一个元素代替删除位置的元素
     * 2.与子元素比较,把较大的子元素与当前元素交换,直到当前元素大于左右子元素
     * @return
     */
    public int remove(){
        //待删除的堆顶元素
        int v = table[0];

        //用堆的最后一个元素(数组中的最后一个元素),代替堆顶元素(数组的第一个元素)
        table[0] = table[size-1];
        size--;

        //调正堆,使堆满足大顶堆的性质
        //当前元素索引,一开始为堆顶元素
        int curIdx = 0;
        while(true){
            int lf = getLeftChild(curIdx);
            int rt = getRightChild(curIdx);
            if(lf==-1&&rt==-1){
                //没有左右元素,无需调整
                break;
            }

            //较大的子元素索引
            int swapIdx;
            if(lf==-1||rt==-1){
                swapIdx = lf==-1?rt:lf;
            }else{
                swapIdx = table[rt]>table[lf]?rt:lf;
            }

            if(table[curIdx]>table[swapIdx]){
                //父元素大于左右子元素,结束交换
                break;
            }

            //用较大的子元素代替父元素
            int t = table[curIdx];
            table[curIdx] = table[swapIdx];
            table[swapIdx] = t;

            curIdx = swapIdx;
        }

        return v;
    }

    public int getLeftChild(int i){
        int index = (i+1)*2-1;
        //index>=size 说明没有左子节点
        return index<size?index:-1;
    }

    public int getRightChild(int i){
        int index = (i+1)*2;
        //index>=size 说明没有右子节点
        return index<size?index:-1;
    }

    public int getParent(int i){
        return (i-1)/2;
    }
}

参考

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值