数据结构:树形结构之堆

欲望以提升热忱,毅力以磨平高山。

什么是堆?

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可被看做一棵树的数组对象。这里介绍的是最为常见的二叉堆。

二叉堆(Binary Heap)是一种特殊的堆,二叉堆是完全二元树并且总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

二叉堆有两种:最大堆和最小堆。

  • 最大堆:父结点的键值总是大于或等于任何一个子节点的键值;
  • 最小堆:父结点的键值总是小于或等于任何一个子节点的键值。

最大堆

二叉堆的数组表示法

如果对于一个二叉堆从上到下、从左至右依次从1开始编号,并将每个节点的编号作为数组的下标,那么就能得到一个用数组表示的二叉堆。采用数组表示法有以下优点:

  • 存储密度高
  • 能够轻易获得某个节点的双亲和左右孩子结点

根据二叉树的性质:下标为 i(i>1)处节点的双亲节点下标为 i/2(整除),下标为 i 处节点的做孩子节点下标为 2 * i ;右孩子节点下标为 2 * i + 1。
在这里插入图片描述
为了充分利用数组空间,我们可以从 0 开始编号,那么下标为 i(i>0)处节点的双亲节点下标为 ( i - 1 ) / 2(整除),下标为 i 处节点的做孩子节点下标为 2 * i + 1 ;右孩子节点下标为 2 * i + 2。
在这里插入图片描述

大顶堆实现

基于以前实现的动态数组,这里直接复用SeqList实现大顶堆
数据结构:线性数据结构

大顶堆的向上调整siftUp和向下调整siftDown
siftUp

向大顶堆中添加元素时,由于我们是基于数组实现的,因此可直接在数组末尾添加该元素。但是添加完元素后的堆并不一定满足大顶堆的性质,因此还需要进行大顶堆的调整(siftUp)。
下面展示了添加元素的过程:
在这里插入图片描述
在这里插入图片描述
在数组尾部添加52
在这里插入图片描述
向上调整,52 大于 16
在这里插入图片描述
向上调整,52 大于 41
在这里插入图片描述
52小于62 因此不需要再调整,此时就满足大顶堆的性质了。

向大顶堆中添加元素:

 
//从index处向上调整
    private void siftUp(int index) {

        //index > 0 并且 index处的值大于双亲的值 则进行向上调整为大顶堆
        while (index > 0 && data.getIndexOf(index).compareTo(data.getIndexOf(parent(index))) > 0) {

            data.swap(index, parent(index));
            index = parent(index);
        }
    }

    //向大顶堆中添加元素
    public void add(E e){
        data.addLast(e);
        siftUp(data.getSize() - 1);
    }

由于需要交换数组中的值,因此在SeqList中添加swap方法:

//交换下标为 i 和 j 下标的值
    public void swap(int i, int j) {
    
        if (i < 0 || i >= size) {
            throw new IllegalArgumentException(" index is illegal !");
        }
        if (j < 0 || j >= size) {
            throw new IllegalArgumentException(" index is illegal !");
        }
        T temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }
siftDown

需要取出大顶堆的最大元素时,我们可以将数组中最后一个元素放到堆顶,然后进行堆的向下调整。下面展示了取出大顶堆的最大元素的过程:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200212230457965.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMDgwODM5,size_16,color_FFFFFF,t_70
在这里插入图片描述
将数组最后一个元素放到堆顶。
在这里插入图片描述
如果待调整的元素小于它的孩子节点中的最大元素,那么就和最大元素交换。
16 小于 52 ,因此 16 和 52 发生了交换。
在这里插入图片描述
16 小于41 所以交换。
在这里插入图片描述
16 大于 15 因此不用再向下调整了,此时的堆满足大顶堆的性质。

取出最大元素

//从index处向下调整大顶堆
    private void siftDown(int index) {

        while (index * 2 + 1 < data.getSize()) {

            int maxIndex = leftChild(index);

            if (maxIndex + 1 < data.getSize() && data.getIndexOf(rightChild(index)).compareTo(data.getIndexOf(leftChild(index))) > 0) {
                maxIndex = rightChild(index);
            }//此时maxIndex为孩子元素中最大的元素

            //若index处的元素小于maIndex的元素则交换 否则结束
            if (data.getIndexOf(index).compareTo(data.getIndexOf(maxIndex)) < 0) {
                data.swap(index, maxIndex);
                index = maxIndex;
            }else {
                break;
            }
        }
    }

    //取出最大元素
    public E extractMax() {

        E ret = getMax();
        //将最后一个元素放到堆顶 向下调整大顶堆
        data.set(0, data.removeLast());
        siftDown(0);

        return ret;
    }
    
 //取出最大元素,并用e替换
    public E replace(E e){

        E ret = getMax();
        data.set(0, e);
        siftDown(0);

        return ret;
    }
将数组转化为大顶堆Heapify

将数组转化为大顶堆可以在遍历数组的时候调用上面的add方法,但是如果在原来数组上进行调整可以减少近半的次数,因为我们可以从第一个非叶子结点开始向下调整。

  //构造方法:Heapify 将一个数组转化为大顶堆
    public MaxBinaryHeap(E[] arr) {

        data = new SeqList<E>(arr);
        //从第一个非叶子结点开始,依次向下调整大顶堆
        for (int i = parent(arr.length - 1); i >=0 ; i--) {
            siftDown(i);
        }
    }

同时由于我们底层是用的自己实现的SeqList,因此还需在SeqList添加构造方法

//将数组转化为动态数组
public SeqList(T[] arr) {

        data = (T[]) new Object[arr.length];
        for (int i = 0; i < arr.length; i++) {
            data[i] = arr[i];
        }
        size = arr.length;
    }
完整代码

到此,二叉大顶堆基本就实现了,需要注意的是,在实现大顶堆的相关操作时,对以前实现的SeqList增添了一个swap方法和一个构造方法。

MaxBinaryHeap

package cn.boom.heap;

public class MaxBinaryHeap<E extends Comparable<E>> {

    private SeqList<E> data;

    public MaxBinaryHeap(int capacity) {
        this.data = new SeqList<E>(capacity);
    }

    public MaxBinaryHeap() {
        this.data = new SeqList<E>();
    }

    //Heapify 将一个数组转化为大顶堆
    public MaxBinaryHeap(E[] arr) {

        data = new SeqList<E>(arr);

        //从第一个非叶子结点开始,依次向下调整大顶堆
        for (int i = parent(arr.length - 1); i >=0 ; i--) {
            siftDown(i);
        }
    }

    //获取元素个数
    public int getSize() {
        return data.getSize();
    }

    //非空判断
    public boolean isEmpty() {
        return data.isEmpty();
    }

    //获取参数下标对应的双亲下标
    private int parent(int index) {

        if (index < 0 || index >= data.getSize()) {
            throw new IllegalArgumentException("index = " + index + " , index is illegal !");
        }

        if (index == 0) {
            throw new IllegalArgumentException(" index = 0 , no parent !");
        }

        return ( index - 1 ) / 2;
    }

    //获取参数下标对应的左孩子下标
    private int leftChild(int index) {

        if (index < 0 || index >= data.getSize()) {
            throw new IllegalArgumentException("index = " + index + " , index is illegal !");
        }

        if ( 2 * index + 1 >= data.getSize()  ) {
            throw new IllegalArgumentException(" index = " + index +" , no leftChild !");
        }

        return 2 * index + 1;
    }

    //获取参数下标对应的右孩子下标
    private int rightChild(int index) {

        if (index < 0 || index >= data.getSize()) {
            throw new IllegalArgumentException("index = " + index + " , index is illegal !");
        }

        if ( 2 * index + 2 >= data.getSize()  ) {
            throw new IllegalArgumentException(" index = " + index +" , no rightChild !");
        }

        return 2 * index + 2;
    }

    //从index处向上调整
    private void siftUp(int index) {

        //index > 0 并且 index处的值大于双亲的值 则进行向上调整为大顶堆
        while (index > 0 && data.getIndexOf(index).compareTo(data.getIndexOf(parent(index))) > 0) {

            data.swap(index, parent(index));
            index = parent(index);
        }
    }

    //向大顶堆中添加元素
    public void add(E e){
        data.addLast(e);
        siftUp(data.getSize() - 1);
    }

    //获取最大元素
    public E getMax() {

        if (data.getSize() == 0) {
            throw new IllegalArgumentException("No data , No MaxValue ! ");
        }

        return data.getIndexOf(0);
    }

    //从index处向下调整大顶堆
    private void siftDown(int index) {

        while (index * 2 + 1 < data.getSize()) {

            int maxIndex = leftChild(index);

            if (maxIndex + 1 < data.getSize() && data.getIndexOf(rightChild(index)).compareTo(data.getIndexOf(leftChild(index))) > 0) {
                maxIndex = rightChild(index);
            }//此时maxIndex为孩子元素中最大的元素

            //若index处的元素小于maIndex的元素则交换 否则结束
            if (data.getIndexOf(index).compareTo(data.getIndexOf(maxIndex)) < 0) {
                data.swap(index, maxIndex);
                index = maxIndex;
            }else {
                break;
            }
        }
    }

    //取出最大元素
    public E extractMax() {

        E ret = getMax();

        //将最后一个元素放到堆顶 向下调整大顶堆
        data.set(0, data.removeLast());
        siftDown(0);

        return ret;
    }

    //取出最大元素,并用e替换
    public E replace(E e){

        E ret = getMax();

        data.set(0, e);
        siftDown(0);

        return ret;
    }

    @Override
    public String toString() {
        return "MaxBinaryHeap{" +
                "data=" + data +
                '}';
    }
}

修改后的SeqList

package cn.boom.heap;


public class SeqList<T> {

    private T[] data;
    private int size;

    //无参构造
    public SeqList(){
        data = (T[]) new Object[10];
        size = 0;
    }

    //带参构造 :参数 容量
    public SeqList(int capacity) {
        data = (T[]) new Object[capacity];
        size = 0;
    }


    public SeqList(T[] arr) {

        data = (T[]) new Object[arr.length];

        for (int i = 0; i < arr.length; i++) {
            data[i] = arr[i];
        }

        size = arr.length;
    }

    /**
     * 获取真实长度(数据个数)
     * @return size
     */
    public int getSize(){
        return this.size;
    }

    /**
     * 获取index索引位置的元素
     * @param index
     * @return data[index]
     * @throws IllegalArgumentException 参数不合法异常
     */
    public T getIndexOf(int index) throws IllegalArgumentException{
        //参数合法性校验
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("Illegal Index !");
        }
        return this.data[index];
    }

    //修改值
    public void set(int index, T elem) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("Illegal Index !");
        }
        data[index] = elem;
    }

    /**
     * 按值查找 返回elem第一次出现的下标 未找到返回-1
     * @param elem
     * @return index
     */
    public int locationElem(T elem){

        for (int i = 0; i < this.size; i++) {

            if (elem.equals(this.data[i])) {
                return i;
            }
        }
        return -1;
    }

    //是否存在元素
    public boolean contains(T elem){
        return locationElem(elem) != -1;
    }

    //表是否为空
    public boolean isEmpty(){
        return (size == 0);
    }

    //表是否为空
    public boolean isFull(){
        return (size == data.length);
    }

    //获取容量
    public int getCapacity(){
        return data.length;
    }

    //在 index处插入一个元素
    public void add(int index, T elem) {

        //参数合法性校验

        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Illegal Index ! index is " + index);
        }

        if (isFull()) {
            updateCapacity(data.length * 2);
        }

        //在 index 后的数据后移
        for (int i = size; i > index; i--) {
            this.data[i] = this.data[i - 1];
        }

        this.data[index] = elem;

        size++;
    }

    //在数组首部添加元素
    public void addFirst(T elem) {
        add(0, elem);
    }

    //在数组尾部添加元素
    public void addLast( T elem){
        add(this.size,elem);
    }


    //更新数组容量
    public void updateCapacity(int newCapacity) {

        T[] newArray = (T[]) new Object[newCapacity];

        for (int i = 0; i < size; i++) { // copy原数组中的数据
            newArray[i] = data[i];
        }

        this.data = newArray;

    }

    //删除下标为index的元素并返回
    public T remove(int index){

        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Illegal Index ! index is " + index);
        }

        T elem = data[index];

        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }

        size--;

        if (size < data.length / 2) { //缩容
            updateCapacity(data.length / 2);
        }

        return elem;
    }

    //删除第一个元素
    public T removeFirst() {
        return remove(0);
    }

    //删除最后一个元素
    public T removeLast() {
        return remove(size - 1);
    }

    //交换下标为 i 和 j 下标的值
    public void swap(int i, int j) {

        if (i < 0 || i >= size) {
            throw new IllegalArgumentException(" index is illegal !");
        }

        if (j < 0 || j >= size) {
            throw new IllegalArgumentException(" index is illegal !");
        }

        T temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

    @Override
    public String toString() {

        StringBuilder res = new StringBuilder();

        res.append("SeqList{");

        res.append(" data=[");

        for (int i = 0; i < this.size; i++) {

            res.append(this.data[i].toString());

            if (i != size - 1) {
                res.append(',');
            }
        }
        res.append("], size=" + size +  ", capacity=" + getCapacity() + " }");

        return res.toString();
    }
}

基于二叉堆实现优先队列

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。

由于二叉大顶堆的堆顶是堆中最大的元素,基于这个特点,我们很方便的就能基于二叉堆来实现优先队列这种数据结构。

package cn.boom.queue;

/**
 * 优先队列
 * @param <E>
 */
public class priorityQueue<E extends Comparable<E>> implements Queue<E> {

    private MaxBinaryHeap<E> maxBinaryHeap;

    public priorityQueue() {
        this.maxBinaryHeap = new MaxBinaryHeap<E>();
    }

    @Override
    public int getSize() {
        return maxBinaryHeap.getSize();
    }

    @Override
    public boolean isEmpty() {
        return maxBinaryHeap.isEmpty();
    }

    @Override
    public void enQueue(E e) {
        maxBinaryHeap.add(e);
    }

    @Override
    public E deQueue() {
        return maxBinaryHeap.extractMax();
    }

    @Override
    public E getFront() {
        return maxBinaryHeap.getMax();
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值