数据结构之堆

简介

堆是一颗完全二叉树,分为两种,最大堆和最小堆,两者的区别在于排序方式上。最大(小)堆是值在一颗完全二叉树中,根节点的值不小于(不大于)树中其他节点的值。这里要注意的是,堆中的任一子树也是堆,但不要求一颗子树中的父节点的值不小于(不大于)另一颗子树中节点值。

在这里插入图片描述

最大堆

在本小节中,我们创建一个最大堆,包含建堆、增加、删除、替换4中操作。最小堆与最大堆类似,本节不再讲述最小堆。在文章最后会附上最小堆的代码实现。

建堆

在这里,我们采用数组来实现完全二叉树,并提供一个构造函数,可以将一个数组转换为最大堆。

我们先看下如何用一颗存储一颗完全二叉树。从下面的数组表示可以看出,如果父节点的索引为n,那么左子节点的索引值为2n+1,右子节点的索引值为2n+2。如果子节点的索引值为x,那么父节点的索引值为x/2。不懂的话,建议去看下树的层序遍历。
在这里插入图片描述

基本定义

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

	//使用数组来存储数据
    private E[] data;
    
	//堆中元素的个数
    private int size;

	//堆的容量
    private int capacity;

    public MaxHeap(int capacity) {
        this.capacity = capacity;
        this.data = (E[]) new Object[capacity];
        this.size = 0;
    }

    public MaxHeap(E[] elements) {
        //将一个数组构成最大堆
        if (elements == null || elements.length == 0) {
            throw new IllegalArgumentException("elements is empty.");
        }
        this.capacity = elements.length;
        this.size = elements.length;
        this.data = Arrays.copyOf(elements, size);
        //heapify:从非叶子节点不断的执行shift down操作
        for (int i = data.length - 1; i >= 0; i--) {
            shiftDown(getParent(i));
        }
    }
    
    private int getParent(int child) {
        //以数组下标0开始存储元素,父亲节点的下标是(child - 1) / 2
        //以数组下标1开始存储元素,父亲节点的下标是child / 2
        return (child - 1) / 2;
  	}
	
	 private int getLeftChild(int parent) {
        //以数组下标0开始存储元素,左孩子节点的下标是2 * parent + 1
        //以数组下标1开始存储元素,左孩子节点的下标是2 * parent
        return 2 * parent + 1;
    }
}

shiftDown

在上面的构造函数的代码中,提到shiftDown(下沉)操作。所谓的shiftDown操作就是不断的同子节点进行比较交互,最后找到元素位置的操作。如下图所示,不断对值为6的节点进行shiftDown操作,直到节点满足最大堆的性质(不小于左右子节点的值)。

在这里插入图片描述

因此,在上述的构造函数中,只要循环对树中的非叶子节点进行shiftDown操作,就可以完成建堆。

    private void shiftDown(int index) {
        int leftChildIdx = getLeftChild(index);
        //不必判断有右子节点是否存在,因为完全二叉树若左子节点不存在,右子节点就一定不存在,一定是叶子节点
        if (index != leftChildIdx && leftChildIdx < size) {
            //如果有左右子节点,则选择值最大的节点进行比较
            int maxIndex = leftChildIdx + 1 < size && data[leftChildIdx + 1].compareTo(data[leftChildIdx]) > 0
                    ? leftChildIdx + 1 : leftChildIdx;
            if (data[index].compareTo(data[maxIndex]) < 0) {
                swap(index, maxIndex);
                shiftDown(maxIndex);
            }
        }
    }

删除

在堆中,只允许删除根节点的元素。因为在最大(小)堆中,删除的值是堆中最大(小)值。

删除操作,只要将堆中的根节点和最后一个节点交换位置,再将最后一个节点指向null。将根节点不断的执行shiftDown操作即可。如下图所示,我们删除一个值为10的根节点。

在这里插入图片描述

  public E remove() {
        if (isEmpty()) {
            return null;
        }
        E maxValue = data[0];
        //将第一元素与最后一个交换位置,然后不断的将跟节点执行shift down操作
        swap(0, --size);
        data[size] = null;
        shiftDown(0);
        return maxValue;
    }

添加

当我们向堆中添加节点后,要求新堆也要满足最大堆的性质(节点值不大于父节点值),也就是说,新添加的节点在整个堆中的位置是固定的。我们可以将新节点添加到堆的最后,然后不断的通过shiftUp(上浮)操作,来交换父子节点。

在这里插入图片描述

    public void add(E e) {
        if (e == null) {
            throw new IllegalArgumentException("element is null..");
        }
        if (size + 1 == capacity) {
            throw new ArrayIndexOutOfBoundsException("add error. array is full.");
        }
        //添加到元素末尾
        data[size++] = e;
        //将元素添加到堆的最后一个元素,不断的将最后一个节点执行shift up操作
        shiftUp(size - 1);
    }
    
    private void shiftUp(int index) {
        int parent = getParent(index);
        if (index != parent && data[parent].compareTo(data[index]) < 0) {
            //子节点比父节点大,交换元素位置
            swap(index, parent);
            //交换元素后,再次与父节点进行比较,直到比父节点的值小
            shiftUp(parent);
        }
    }

替换

替换元素比较简单,这里不在过多描述了,代码如下。

    public E replace(E e) {
        E old = data[0];
        data[0] = e;
        shiftDown(0);
        return old;
    }

实现

最小堆

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

    //使用数组来存储数据
    private E[] data;

    //堆中元素的个数
    private int size;

    //堆的容量
    private int capacity;

    public MaxHeap(int capacity) {
        this.capacity = capacity;
        this.data = (E[]) new Object[capacity];
        this.size = 0;
    }

    public MaxHeap(E[] elements) {
        //将一个数组构成最大堆
        if (elements == null || elements.length == 0) {
            throw new IllegalArgumentException("elements is empty.");
        }
        this.capacity = elements.length;
        this.size = elements.length;
        this.data = Arrays.copyOf(elements, size);
        //heapify:从非叶子节点不断的执行shift down操作
        for (int i = data.length - 1; i >= 0; i--) {
            shiftDown(getParent(i));
        }
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public int getSize() {
        return size;
    }

    public E remove() {
        if (isEmpty()) {
            return null;
        }
        E maxValue = data[0];
        //将第一元素与最后一个交换位置,然后不断的将跟节点执行shift down操作
        swap(0, --size);
        data[size] = null;
        shiftDown(0);
        return maxValue;
    }


    public void add(E e) {
        if (e == null) {
            throw new IllegalArgumentException("element is null..");
        }
        if (size + 1 == capacity) {
            throw new ArrayIndexOutOfBoundsException("add error. array is full.");
        }
        //添加到元素末尾
        data[size++] = e;
        //将元素添加到堆的最后一个元素,不断的将最后一个节点执行shift up操作
        shiftUp(size - 1);
    }

    public E replace(E e) {
        E old = data[0];
        data[0] = e;
        shiftDown(0);
        return old;
    }

    private void shiftDown(int index) {
        int leftChildIdx = getLeftChild(index);
        //不必判断有右子节点是否存在,因为完全二叉树若左子节点不存在,右子节点就一定不存在,一定是叶子节点
        if (index != leftChildIdx && leftChildIdx < size) {
            //如果有左右子节点,则选择值最大的节点进行比较
            int maxIndex = leftChildIdx + 1 < size && data[leftChildIdx + 1].compareTo(data[leftChildIdx]) > 0
                    ? leftChildIdx + 1 : leftChildIdx;
            if (data[index].compareTo(data[maxIndex]) < 0) {
                swap(index, maxIndex);
                shiftDown(maxIndex);
            }
        }
    }


    private void shiftUp(int index) {
        int parent = getParent(index);
        if (index != parent && data[parent].compareTo(data[index]) < 0) {
            //子节点比父节点大,交换元素位置
            swap(index, parent);
            //交换元素后,再次与父节点进行比较,直到比父节点的值小
            shiftUp(parent);
        }
    }


    private int getParent(int child) {
        //以数组下标0开始存储元素,父亲节点的下标是(child - 1) / 2
        //以数组下标1开始存储元素,父亲节点的下标是child / 2
        return (child - 1) / 2;
    }

    private int getLeftChild(int parent) {
        //以数组下标0开始存储元素,左孩子节点的下标是2 * parent + 1
        //以数组下标1开始存储元素,左孩子节点的下标是2 * parent
        return 2 * parent + 1;
    }

    private void swap(int a, int b) {
        E temp = data[a];
        data[a] = data[b];
        data[b] = temp;
    }
}

最小堆

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

    //使用数组来存储数据
    private E[] data;

    //堆中元素的个数
    private int size;

    //堆的容量
    private int capacity;

    public MinHeap(int capacity) {
        this.capacity = capacity;
        this.data = (E[]) new Object[capacity];
        this.size = 0;
    }

    public MinHeap(E[] elements) {
        //将一个数组构成最大堆
        if (elements == null || elements.length == 0) {
            throw new IllegalArgumentException("elements is empty.");
        }
        this.capacity = elements.length;
        this.size = elements.length;
        this.data = Arrays.copyOf(elements, size);
        //heapify:从非叶子节点不断的执行shift down操作
        for (int i = data.length - 1; i >= 0; i--) {
            shiftDown(getParent(i));
        }
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public int getSize() {
        return size;
    }

    public E remove() {
        if (isEmpty()) {
            return null;
        }
        E minValue = data[0];
        //将第一元素与最后一个交换位置,然后不断的将跟节点执行shift down操作
        swap(0, --size);
        data[size] = null;
        shiftDown(0);
        return minValue;
    }


    public void add(E e) {
        if (e == null) {
            throw new IllegalArgumentException("element is null.");
        }
        if (size + 1 == capacity) {
            throw new ArrayIndexOutOfBoundsException("add error. array is full.");
        }
        //添加到元素末尾
        data[size++] = e;
        //将元素添加到堆的最后一个元素,不断的将最后一个节点执行shift up操作
        shiftUp(size - 1);
    }

    public E replace(E e) {
        E old = data[0];
        data[0] = e;
        shiftDown(0);
        return old;
    }

    private void shiftDown(int index) {
        int leftChildIdx = getLeftChild(index);
        //非叶子节点
        if (index != leftChildIdx && leftChildIdx < size) {
            //如果有左右子节点,则选择值最小的节点进行比较
            int minIndex = leftChildIdx + 1 < size && data[leftChildIdx + 1].compareTo(data[leftChildIdx]) < 0
                    ? leftChildIdx + 1 : leftChildIdx;
            if (data[index].compareTo(data[minIndex]) > 0) {
                swap(index, minIndex);
                shiftDown(minIndex);
            }
        }
    }


    private void shiftUp(int index) {
        int parent = getParent(index);
        if (index != parent && data[parent].compareTo(data[index]) > 0) {
            //子节点比父节点大,交换元素位置
            swap(index, parent);
            //交换元素后,再次与父节点进行比较,直到比父节点的值小
            shiftUp(parent);
        }
    }


    private int getParent(int child) {
        //以数组下标0开始存储元素,父亲节点的下标是(child - 1) / 2
        //以数组下标1开始存储元素,父亲节点的下标是child / 2
        return (child - 1) / 2;
    }

    private int getLeftChild(int parent) {
        //以数组下标0开始存储元素,左孩子节点的下标是2 * parent + 1
        //以数组下标1开始存储元素,左孩子节点的下标是2 * parent
        return 2 * parent + 1;
    }

    private void swap(int a, int b) {
        E temp = data[a];
        data[a] = data[b];
        data[b] = temp;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值