【数据结构】堆

堆的定义

(最大)堆是一个可以被看成一棵树的数组对象,满足如下性质:

  • 堆中的父亲结点总大于或等于其左右孩子结点的值
  • 总是一棵完全二叉树
完全二叉树
  • 若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
    在这里插入图片描述
    在这里插入图片描述
  • 根据堆的性质可以得出如下结论
    • 根节点没有父亲结点
    • 除根节点之外的任意结点(i)的父亲结点的索引为: parent = i/2
    • 任意结点的左孩子结点的索引为: leftIndex = 2 * i
    • 任意结点的右孩子结点的索引为: rightIndex = 2 * i +1
      上面的结论是根结点存储在索引为1的位置,如果根结点存储在索引为0的位置时,会得到:
    • parent = (i- 1)/2;
    • leftIndex = 2 * i + 1
    • rghtIndex = 2 * I + 2
向堆中添加元素(上浮 float up)

1、添加元素52,添加到数组中索引为size的位置,然后更新size
在这里插入图片描述

2、从最后一个结点开始与父亲结节进行(优先级)比较,如果父亲结点的优先级低于当前结点,
在这里插入图片描述
则进行交换
在这里插入图片描述
3、重复第二步操作,
在这里插入图片描述
在这里插入图片描述

4、直至根节点或父亲结点的优先级高于当前结点
在这里插入图片描述

取出堆中优先级最高的元素(下沉 swim)

最大堆中优先级最高的元素是索引为0的元素
在这里插入图片描述

1、使用最后一个元素替换索引为0元素,更新size
在这里插入图片描述

2、从索引为0的位置开始进行下沉操作
下沉操作:
1>找到当前结点左右孩子结点中优先级较高的结点
在这里插入图片描述

2>如果当前结点的优先级小于左右孩子结点中优先较高的结点,则进行交换
在这里插入图片描述

3>重复第2步操作
在这里插入图片描述
在这里插入图片描述

3、直至叶子结点或左右孩子结点中优先级较高结点小于当前结点的优先级
在这里插入图片描述

堆的时间复杂度分析

从上面的分析图中,可以得出:无论进行上浮还是下沉操作,最多交换的次数为整棵树的高度h
O(h) = O(logn)

Heapify和Replace

replace: 取出最大元素后,放入一个新元素
实现方式:直接将堆顶元素替换成新元素,然后进行Sift down操作
heapify: 将任意数组整理成堆的形状
实现方式:从最后一个元素的父亲结点开始进行调整(sift down),直到根结点
在这里插入图片描述
1、找到最后一个元素的父亲结点。 (size-1-1)/2在这里插入图片描述
2、循环进行下沉操作,直至根结点
在这里插入图片描述
在这里插入图片描述

上面分析得知,将一个元素插入到堆中,复杂度为O(logn),所以,将n个元素逐个插入
到一个空堆中,算法复杂堆为O(nlogn)

heapify的过程:

在这里插入图片描述

堆的代码实现

import java.security.PublicKey;
import java.util.Arrays;
import java.util.Random;

/**
 * 最大堆 优先级-> 数值大的优先级高
 * 最小堆 优先级-> 数值小的优先级高
 */
public class MaximumHeap<T extends Comparable<T>> {
    private T[] data; // 保存树的结点
    private int size; //保存树中结点的个数

    public MaximumHeap() {
        this.data = (T[]) new Comparable[500];
        this.size = 0;
//        Arrays.fill(data, Integer.MIN_VALUE);
    }

    //重写构造  构建一棵完全二叉树
    public MaximumHeap(T[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }
        this.data = (T[]) new Comparable[arr.length + 1];
        for (int i = 0; i < arr.length; i++) {
            this.data[i + 1] = arr[i];
        }
        this.size = arr.length;
        this.heapify(); //构建堆
    }

    //判断堆是否为空
    public boolean isEmpty() {
        return this.size == 0;
    }

    //获取堆中元素的个数
    public int getSize() {
        return this.size;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        int cnt = 0;
        while (cnt < this.size) {
            sb.append(this.data[cnt + 1]);
            if (cnt != this.size - 1) {
                sb.append(",");
            }
            cnt++;
        }
        sb.append("]");
        return sb.toString();
    }

    //根据当前结点所在数组的索引 得到 父结点的索引
    public int getParentIndex(int index) {
        if (index == 1) { // 处理根节点
            return -1;
        }
        if (index < 0) {
            throw new IllegalArgumentException("index is invalid!");
        }
        return index / 2;
    }

    //根据当前结点所在的数组的索引获取左孩子的结点
    public int getLeftChildIndex(int index) {
        return 2 * index;
    }

    //向树中添加元素(构建堆)
    public void add(T val) {
        //1、将size++
        this.size++;
        //2、将val添加到数组中,索引为size 的位置
        this.data[this.size] = val;
        //3、上浮操作
//        floatUp();
        floatUp2(val);
    }

    // 上浮操作
    private void floatUp() {
        //1、从最后一个结点开始进行上浮操作
        int curIndex = this.size;
        int parentIndex = getParentIndex(curIndex);

        //2、使用当前结点与父结点的优先级做比较,当前结点的优先级>父结点的优先级,就进行交换
        //3、直到根节点或当前结点的优先级<=父结点的优先级
        while (curIndex > 1 && this.data[curIndex].compareTo(this.data[parentIndex]) > 0) {
            swap(curIndex, parentIndex);
            // 更新当前结点的索引,继续判断并交换
            curIndex = parentIndex;
            // 重新得到父亲结点
            parentIndex = getParentIndex(curIndex);
        }
    } // 用交换的策略

    private void floatUp2(T ele) { // 用替换的策略
        //1、从最后一个结点开始进行上浮操作
        int curIndex = this.size;
        int parentIndex = getParentIndex(curIndex);

        while (curIndex > 1 && this.data[parentIndex].compareTo(ele) < 0) {
            //使用当前结点和父结点优先级进行比较,当当前结点的优先级>父结点优先级 父结点替换当前结点
            this.data[curIndex] = this.data[parentIndex];
            curIndex = parentIndex;
            parentIndex = getParentIndex(curIndex);
        }
        this.data[curIndex] = ele;
    }

    //下沉操作
    private void siftDown() {
        //下沉操作 <1>记录下沉结点的索引
        int curIndex = 1;
        //<2>获取下沉结点左右孩子的索引
        int leftChildIndex = getLeftChildIndex(curIndex);
        int changeIndex = leftChildIndex; // 用来保存左右孩子中优先级较高的结点索引
        //判断左孩子是否存在
        while (leftChildIndex < this.size) { //此时存在左孩子
            int rightChildIndex = leftChildIndex + 1;
            // 判断右孩子是否存在 并且 右孩子结点的优先级大于左孩子结点的优先级
            if (rightChildIndex <= this.size && this.data[leftChildIndex].compareTo(this.data[rightChildIndex]) < 0) {
                changeIndex = rightChildIndex;
            }
            //比较changeIndex所在的优先级是否比curIndex所在结点的优先级高,如果高,就交换
            if (this.data[changeIndex].compareTo(this.data[curIndex]) > 0) {
                swap(changeIndex, curIndex);
                curIndex = changeIndex;
                leftChildIndex = getLeftChildIndex(curIndex);
                changeIndex = leftChildIndex;
            } else {
                break;
            }
        }
    }

    //从堆中取出优先级最高的的元素
    public T removeMaxValueFromHeap() {
        if (isEmpty()) {
            return null;
        }
        // 1、先取出根元素
        T result = this.data[1];

        //2、让树中最后一个元素替换根节点
        this.data[1] = this.data[this.size];
        //更新size
        this.size--;
        siftDown();
        return result;
    }

    //采取交换的方式
    public T removeMaxValueFromHeap2() {
        if (isEmpty()) {
            return null;
        }
        // 1、先取出根元素
        T result = this.data[1];
        //2、获取树中最后一个结点
        T val = this.data[this.size];//(比较结点)
        //更新size
        this.size--;
        //下沉操作 <1>记录下沉结点的索引
        int curIndex = 1;
        //<2>获取下沉结点左右孩子的索引
        int leftChildIndex = getLeftChildIndex(curIndex);
        int changeIndex = leftChildIndex; // 用来保存左右孩子中优先级较高的结点索引
        //判断左孩子是否存在
        while (leftChildIndex <= this.size) { //此时存在左孩子
            int rightChildIndex = leftChildIndex + 1;
            // 判断右孩子是否存在 并且 右孩子结点的优先级大于左孩子结点的优先级
            if (rightChildIndex <= this.size && this.data[leftChildIndex].compareTo(this.data[rightChildIndex]) < 0) {
                changeIndex = rightChildIndex;
            }
            //比较changeIndex所在的优先级是否比curIndex所在结点的优先级高,如果高,就交换
            if (this.data[changeIndex].compareTo(val) > 0) {
                this.data[curIndex] = this.data[changeIndex];
                curIndex = changeIndex;
                leftChildIndex = getLeftChildIndex(curIndex);
                changeIndex = leftChildIndex;
            } else {
                break;
            }
        }
        this.data[curIndex] = val;
        return result;
    }

    //元素替换操作 replace 将对顶元素替换成新元素
    public T replace(T newVal) {
        if (isEmpty()) {
            return null;
        }
        T result = this.data[1];
        this.data[1] = newVal;
        //进行下沉操作
        siftDown();

        return result;
    }

    //heapify操作 构建堆操作
    private void heapify() {
        if (this.isEmpty()) {
            return;
        }
        // 将完全二叉树整理成堆
        //1、找到最后一个元素的父亲结点
        int parentIndex = getParentIndex(this.size);

        //2、从最后一个元素的父亲结点开始进行下沉操作,直到根节点
        for (; parentIndex >= 1; parentIndex--) {
            siftDown(parentIndex);
        }
    }

    // 从目标位置进行下沉操作
    private void siftDown(int curIndex) {
        int leftChildIndex = getLeftChildIndex(curIndex);
        int changeIndex = leftChildIndex;
        while (leftChildIndex <= this.size) {
            int rightChildIndex = leftChildIndex + 1;
            if (rightChildIndex <= this.size && this.data[rightChildIndex].compareTo(this.data[leftChildIndex]) < 0) {
                changeIndex = rightChildIndex;
            }
            if (this.data[changeIndex].compareTo(this.data[curIndex]) < 0) {
                //交换操作
                swap(curIndex, changeIndex);
                curIndex = changeIndex;
                leftChildIndex = getLeftChildIndex(curIndex);
                changeIndex = leftChildIndex;
            } else {
                break;
            }
        }
    }

    //交换
    private void swap(int curIndex, int changeIndex) {
        T temp = this.data[curIndex];
        this.data[curIndex] = this.data[changeIndex];
        this.data[changeIndex] = temp;
    }

    //获取最大元素
    public T getMaxValueFromHeap() {
        if (isEmpty()) {
            return null;
        }
        return this.data[1];
    }

    public static void main(String[] args) {
//        Random random = new Random();
//        MaximumHeap<Integer> maximumHeap = new MaximumHeap<>();
//        for (int i = 0; i < 10; i++) {
//            maximumHeap.add(random.nextInt(100));
//        }
//        System.out.println(maximumHeap);
//        int max = maximumHeap.removeMaxValueFromHeap2();
//        System.out.println(max);
//        System.out.println(maximumHeap);
        Integer [] arr = {23, 4, 7, 9, 12, 27, 19, 10, 54, 67, 45};
        System.out.println(Arrays.toString(arr));
        MaximumHeap<Integer> maximumHeap = new MaximumHeap<>(arr);
        System.out.println(maximumHeap);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

勇者六花i

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

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

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

打赏作者

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

抵扣说明:

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

余额充值