三、二叉堆(以最小堆为例)

1. 定义与基本特性

二叉堆是用数组描述的完全二叉树。下面对定义中的名词作简单解释。

1.1 完全二叉树

即树上全部的结点都按照从上到下从左到右的顺序依次填满树。如下图就是一颗完全二叉树。如果将第6个元素(也就是圆圈9)从树中删去,则树未能填满,不是完全二叉树。

1.2 本质是数组

当我们在写代码时,二叉堆其实就是一个数组a[]。只不过我们给这个数组赋予了特殊的意义,即:

  • a[2*i]是a[i]的左子节点
  • a[2*i+1]是a[i]的右子节点
  • a[i/2]是a[i]的父节点(注意:i/2是整除运算)

所以,就不要问“欢哥,子节点的指针在哪呀?”之类的问题,数组怎么会有指针呢?在这里插入图片描述

在这里插入图片描述

1.3 二叉堆的成员变量

class MinHeap{
    // data 是二叉堆的数组
    private double[] data;
    // size 是二叉堆的大小。显然有 size <= data.length
    private int size;
    
    public double[] getData(){
        return data;
    }
    public MinHeap(double[] data) {
        this.data = data;
        size = data.length;
    }
}

2. 二叉堆的基本操作及其实现

2.0 核心思想(十分重要)

  • 最小堆的性质:即父节点的元素比子节点小。最大堆与之相反。
  • 最小堆操作的核心思想:最小堆的所有操作,都是为了维护和保证上述最小堆的性质。理解了核心思想,就容易理解最小堆的操作了。

2.1 最小堆化 Min_Heapify(int index)

  • 输入参数index
    public void minHeapify(int index) {
        int l = 2*index+1;//左子节点
        int r = 2*index+2;//右子节点
        int minIndex = index;//这个变量用于记录比index小的子节点
        
        // 下面三个连续的if代码,用于判断左右子节点是否比index节点小。
        if (l >= size){
            return;
        }
        if (data[l] < data[index]){
            minIndex = l;
        }
        if (r < size){
            if (data[r] < data[minIndex]){
                minIndex = r;
            }
        }
        
        
        // 如果存在左右子节点比index节点小,则交换该节点与index节点,并对该节点递归minHeapify
		// 这样做维护了最小堆的性质
        if(minIndex != index) {
            double t = data[index];
            data[index] = data[minIndex];
            data[minIndex] = t;
            minHeapify(minIndex);
        }
    }

2.2 构建最小堆 Build_MinHeap()

    public void buildMinHeap(){
        //自底向上构建最小堆
        for (int i = size/2-1; i >= 0; i--){
            minHeapify(i);
        }
    }

自底向上构建最小堆,即从叶子节点 --> 根节点。

循环过程中,每次执行完minHeapfiy(i) 后,以i为根节点的堆都是最小堆。因此当i=0时,整个堆就是最小堆。这个思想称为循环不变式( Loop invariant)。(这句话肯定不考)

2.3 堆排序 Heap_Sort()

    public void sort() {
        //首先,构建最小堆
        buildMinHeap();

        while (size > 0){
            /*
             * 每次将堆中的首个元素(也就是最小元素)与堆的结尾元素交换,
             * 交换后缩小堆的size,并完成最小堆化(为了维护最小堆的性质)
             */
            double t = data[size - 1];
            data[size - 1] = data[0];
            data[0] = t;
            size--;
            minHeapify(0);
        }
    }
  • 上述操作可以用一句话概括:依次找到堆中的最小元素、第二小元素、第三小元素…。
  • 最小堆完成上述操作后,堆的数组就是一个降序排列的数组。使用最大堆可以获得升序排列的数组
  • 时间复杂度:O(n·logn)
  • 空间复杂度:O(1) 原地算法

2.4 抽取最大值 Heap_Extract_Max()

  • 抽取:先获得最小值,再将该值从堆中删除。
	public double heapExtractMax(){
        // 第0个元素就是最大值
        double res = data[0];
        
        // 删除第0个元素,并维护最小堆
        data[0] = data[--size];
        minHeapify(0);
        
        return res;
    }

2.5 获取最大值 Heap_Max()

  • 获取:只是获取最大值,不需要删除。
	public double heapMax(){
        return data[0];
    }

2.6 最小堆降低键值 MinHeap_Decrease_Key(int index, double key)

  • int index: 被降低键值的元素的下标
  • double key: 键值降低为 key
    void minHeapDecreaseKey(int index, double key){
        // 若key值大于index元素的值,则是提高键值,滚粗,不许调用
        if (key > data[index]){
            System.out.println("method call error");
            return;
        }
        
        // 将index中的元素,键值设置为key
        data[index] = key;
        // 自底向上进行,将节点元素与父节点进行比较,判断它是否满足最小堆的性质
        while (index > 0 && data[index/2] > data[index]){
            // 若不满足,则维护最小堆的性质,交换父节点与子节点的值
            double t = data[index/2];
            data[index/2] = data[index];
            data[index] = t;
            index /= 2;
        }
    }

2.7 最小堆尾插入元素 MinHeap_Insert(double key)

  • double key: 插入元素的键值
    void minHeapInsert(double key){
        /*
         * 扩大堆的容量(size),并令新扩增位置的元素为正无穷
         */
        size ++;
        data[size - 1] = Double.POSITIVE_INFINITY;//POSITIVE:正数,INFINITY:无穷大
        
        /*
         * 将正无穷的键值降至key
         * 借助该方法,维护最小堆的性质
         */
        minHeapDecreaseKey(size, key);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值