Java实现最小堆

       堆,其实是一种优先队列,分为最大堆和最小堆。在最小堆中,它首先是一颗完全二叉树,并且根节点的值,要比左右孩子的值都要小,同时,左右子树也是最小堆。本文包含堆的操作如下:

(1)插入一个节点

(2)删除堆顶元素,也就是删除最小值

(3)通过给定的一组数据,构建最小堆

 

由于用数组表示完全二叉树十分方便,所以本文采用的是数组存储数据。

 

1.准备

    private int[] data;    // 利用数组和完全二叉树的性质,存储数据
    private int size;      // 当前大小
    private int maxSize;   // 最大容量

    public MinHeapSort(){
        // 默认大小
        this(16);
    }

    public MinHeapSort(int maxSize){
        // 下标为0的位置,不存储实际值,只存储哨兵
        data = new int[maxSize+1];
        data[0] = Integer.MIN_VALUE;   // 哨兵,最小值
        this.size = 0;
        this.maxSize = maxSize;
    }

定义了一个数组data存储数据,并且提供了两个构造函数。

 

2.插入一个数据,并调整为最小堆

// 向最小堆插入一个值
    public boolean insert(int value){
        int index = ++size;  // 当前要插入的下标
        // 判断是否满了
        if (size == maxSize){
            System.out.println("堆已满,无法插入 "+value);
            return false;
        }
        // 沿着祖先路径,查找待插入值的合适位置
        // 当父节点大于自己时
        while (value < data[index/2]){
            // 将父节点放在自己当前的位置
            data[index] = data[index/2];
            // 然后再继续和上一级比较
            index = index/2;
        }
        // 循环结束之后,index就是待插入值value的最终位置了,因为data[index] < (data[0] = Integer.MIN_VALUE) 是不可能成立的
        // 这也是哨兵的作用,不用每次都判断index > 0
        data[index] = value;
        return true;
    }

一个比较关键的点是,如何找到待插入值的最终下标:

(1)破坏最小堆规则的,只有当前要插入的节点及其祖先节点

(2)方法则是,不断得和父节点比较,直到找到最终存放的下标

 

3. 删除根结点

// 删除根结点,也就是最小值
    public int deleteMinValue(){
        // 判断是否为空
        if (size == 0){
            System.out.println("堆空");
            return -1;
        }
        // 如果只有一个结点
        if (size == 1){
            size = 0;
            return data[1];
        }
        // 要删除的值
        int delValue = data[1];
        // 用最后一个叶子节点值,替换根结点,然后再调整为最小堆
        int lastValue = data[size--];
        int index = 1;  // lastValue最终存放的位置,一开始是放在根结点的位置
        // 向下和孩子节点比较,找到lastValue最终存放的位置
        for (int child = index*2; child <= size;){
            // 如果存在右孩子,并且右孩子小于左孩子
            if (child < size && data[child] >= data[child+1]){
                child++;
            }
            // 如果根结点,比最小的孩子节点值还要小,那么就结束比较,当前位置就是要插入的位置了
            if (lastValue < data[child]){
                break;
            }else {
                // 孩子节点往上移动一层
                data[index] = data[child];
                index = child;
                child = child*2;
            }
        }
        data[index] = lastValue;
        return delValue;
    }

删除节点,会将最后一个节点替换根结点,然后再逐步向下调整

 

4.构建最小堆

(1)提供一个新的构造函数

// 通过完全输入一个完全二叉树的数组来构建最小堆
    public MinHeapSort(int[] initData){
        if (initData == null || initData.length < 0){
            System.out.println("初始化数组参数不正确");
            return;
        }
        data = new int[initData.length+1];
        data[0] = Integer.MIN_VALUE;
        for (int i=0;i<initData.length;i++){
            data[i+1] = initData[i];
        }
        this.maxSize = initData.length;
        this.size = initData.length;
        // 构建最小堆
        buildMinHeap();
    }

(2)buildMinHeap()

    // 构建最小堆
    private void buildMinHeap(){
        if (size == 1){
            // 只有一个节点
            return;
        }

        // 从最后一个非叶子节点开始,逐渐向上调整,直到根结点
        for (int parent = size/2;parent > 0;parent--){
            fixMinHead(parent);
        }
    }

 

(3)fixMinHeap(),这一步,其实是和删除那部分的调整逻辑是一样的

    // 将以root为根的树,调整为最小堆
    private void fixMinHead(int root){
        int index = root;
        int tmp = data[root];
        // 向下和孩子节点比较,找到tmp最终存放的位置
        for (int child = index*2; child <= size;child = child*2){
            // 如果存在右孩子,并且右孩子小于左孩子
            if (child < size && data[child] >= data[child+1]){
                child++;
            }
            // 如果根结点,比最小的孩子节点值还要小,那么就结束比较,当前位置就是要插入的位置了
            if (tmp < data[child]){
                break;
            }else {
                // 孩子节点往上移动一层
                data[index] = data[child];
                index = child;
            }
        }
        data[index] = tmp;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值