本文介绍了堆的概念,包括最大堆和最小堆,并通过示例详细阐述了堆的创建过程,如数组元素的顺序变化。接着讲解了堆中添加元素和删除元素的方法,涉及向上调整和向下调整的策略。最后,文章通过一个堆排序的实例展示了堆在排序中的应用。
摘要由CSDN通过智能技术生成

堆的定义

通常情况下我们把堆看成是一棵完全二叉树。

堆一般分为两种,一种是最大堆,一种是最小堆。
最大堆要求根节点的值即大于左子树的值,又大于右子树的值。也就是说最大堆根节点的值是堆中最大的。最小堆根节点的值是堆中最小的,以最小堆为例子,如下图
在这里插入图片描述
他是数组结构,结点中的数字是数组元素的下标,不是数组元素的值。所以如果我们知道父节点的下标我们就可以知道他的两个子节点(如果有子节点),如果知道子节点的下标也一定能找到父节点的下标,他们的关系是:

父节点的下标 =(子节点下标 - 1)>>1;

左子节点下标 = 父节点下标 * 2 + 1;

右子节点下标 = 父节点下标 * 2 + 2 ;

堆的创建

堆有两种,一种是最大堆,一种是最小堆。顾名思义,最大堆就是堆顶的元素是最大的,最小堆就是堆顶的元素是最小的。原理都差不多,我们只分析一个就行了,我们就以数组【10,4,8,3,5,1】为例来画个图分析一下最小堆是怎么创建的。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这就是堆的创建,把元素加入到堆中的时候,都要和父节点进行比对,在最小堆中,如果比父节点小,就要和父节点交换,交换之后还要继续和再和新的父节点对比……。同理在最大堆中,如果比父节点大,就要和父节点交换。

我们看到上面堆创建之后数组的元素顺序变为【1,4,3,10,5,8】

堆添加元素方法代码

public boolean add(E e) {
    if (e == null) // e 不能为空
        throw new IllegalArgumentException();
    // 如果堆的空间不够了就扩容,这里是扩大2倍
    if (size >= data.length)
        data = Arrays.copyOf(data, data.length << 1);
    //如果堆是空的,直接添加就可以了,不需要调整,因为就一个没发调整
    if (size == 0)
        data[0] = e;
    else // 如果堆不是空的,就要往上调整。
        siftUp(e); // 向上调整的方法,详细请看完整代码
    size++;
    return true;
}

堆的删除

堆的添加会往上调整,堆的删除不仅会往下调整而且还有可能往上调整。堆中元素的删除我们可以从堆顶删除,也可以从中间某个位置删除,如果从堆顶删除的话我们只需要往下调整即可,因为堆顶没有父节点,不需要往上调整。如果从中间删除的话,我们先要往下调整,如果往下调整没有成功(比如在最小堆中,他比两个子节点都小),我们还要进行往上的调整。调整的原理就是把数组最后一个元素放到要删除的元素位置上,然后在删除元素的位置上进行上下调整,原理其实都差不多,我们来看一下最小堆顶部元素删除之后的调整。
在这里插入图片描述
上面是我们把堆顶元素1删除之后堆的调整,如果我们再把堆顶元素3删除之后,只需要用数组的最后一个元素5替换3,然后再往下调整即可……

堆删除元素的方法代码

public E remove() {
    if (size == 0)
        return null;
    size--;
    E result = (E) data[0]; // 获取堆顶元素
    E x = (E) data[size]; // 取出数组的最后一个元素
    data[size] = null; // 数组最后一个元素置空
    // 这里实际上是把数组的最后一个元素取出放到栈顶,然后再往下调整。
    if (size != 0) 
        siftDown(x); // 向下调整的方法,详细请看完整代码
    return result;
}

完整代码

import java.util.Arrays;
import java.util.Comparator;

public class MyHeap<E> {
    private Object [] data;   // 数据存放
    private int size;      // 堆的大小
    private Comparator<? super E> comparator;    //比较器

    public MyHeap(int initialCapacity) {
        this(initialCapacity, null);
    }

    public MyHeap(int initialCapacity, Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException("堆的大小必须大于0");
        this.data = new Object[initialCapacity];
        this.comparator = comparator;
    }

    // 向堆中添加元素
    public boolean add(E e) {
        if (e == null) // e 不能为空
            throw new IllegalArgumentException();
        // 如果堆的空间不够了就扩容,这里是扩大2倍
        if (size >= data.length)
            data = Arrays.copyOf(data, data.length << 1);
        //如果堆是空的,直接添加就可以了,不需要调整,因为就一个没发调整
        if (size == 0)
            data[0] = e;
        else // 如果堆不是空的,就要往上调整。
            siftUp(e);
        size++;
        return true;
    }

    // 返回堆的大小
    public int getSize() {
        return size;
    }

    // 删除堆顶元素
    public E remove() {
        if (size == 0)
            return null;
        size--;
        E result = (E) data[0]; // 获取堆顶元素
        E x = (E) data[size]; // 取出数组的最后一个元素
        data[size] = null; // 数组最后一个元素置空
        // 这里实际上是把数组的最后一个元素取出放到栈顶,然后再往下调整。
        if (size != 0) 
            siftDown(x);
        return result;
    }

    // 访问堆顶元素,不删除
    public E peek() {
        return size == 0 ? null : (E) data[0];
    }

    // 返回数组的值
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            return (T[]) Arrays.copyOf(data, size, a.getClass());
        System.arraycopy(data, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    //  往上调整,往上调整只需要和父节点比较即可,如果比父节点大就不需要在调整
    private void siftUp(E e) {
        int s = size;
        while (s > 0) {
        	// 通过子节点的位置找到父节点的位置
            int parent = (s-1) >>> 1; 
            Object pData = data[parent];
            //和父节点比较,如果比父节点大就退出循环不再调整
            if (comparator != null && comparator.compare(e, (E)pData) >= 0)
                break;
            else if (comparator == null && ((Comparable<? super E>) e).compareTo((E) pData) >= 0)
                break;
            // 如果比父节点小,就和父节点交换,然后再继续往上调整
            data[s] = pData;
            s = parent;
        }
        // 通过上面的往上调整,找到合适的位置,再把e放进去
        data[s] = e;
    }

    // 往下调整,往下调整需要和他的两个子节点(如果有两个子节点)都要比较,
    // 哪个最小就和哪个交换,如果比两个子节点都小就不用再交换
    private void siftDown(E e) {
        int half = size >>> 1; // 非终端结点的个数
        int index = 0;
        while (index < half) {
            // 根据父节点的位置可以找到左子节点的位置
            int min = (index<<1) + 1;
            Object minChild = data[min];
            // 根据左子节点找到右子节点的位置
            int right = min + 1;
            if (right < size) { // 如果有右子节点时
                // 如果有右子节点,肯定会有左子节点。那么就需要左右两个子节点比较,把小的赋值给minChild
                if (comparator != null) {
                    if (comparator.compare((E) minChild, (E) data[right]) > 0)
                        minChild = data[min = right];
                } else {
                    if (((Comparable<? super E>) minChild).compareTo((E)data[right]) > 0)
                        minChild = data[min = right];
                }
            }
            // 用节点e和他的最小的子节点比较,如果小于他最小的子节点就退出循环,不再往下调整了。
            if (comparator != null) {
                if (comparator.compare(e, (E) minChild) <= 0)
                    break;
            } else {
                if (((Comparable<? super E>) e).compareTo((E) minChild) <= 0)
                    break;
            }
            // 如果e比它的最小的子节点小,就用最小的子节点和e交换位置,然后再继续往下调整。
            data[index] = minChild;
            index = min;
        }
        data[index] = e;
    }
}

堆排序

使用堆排序测试一下

public static void main(String[] args) {
    int[] arr = {10, 4, 8, 3, 5, 1};
    System.out.print("数组原始的值:");
    for (int i = 0; i < arr.length; i++) {
        System.out.printf(arr[i] + "\t");
    }
    System.out.println();

    MyHeap myHeap = new MyHeap(10, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return (o1 - o2 > 0) ? 1 : -1;
        }
    });
    for (int i = 0; i < arr.length; i++) {
        myHeap.add(arr[i]);
    }

    System.out.printf("堆中元素的值:");
    Object[] temp = myHeap.toArray(new Object[myHeap.getSize()]);
    for (int i = 0; i < temp.length; i++) {
        System.out.printf((int)temp[i] + "\t");
    }
    System.out.println();

    System.out.print("排序之后的值:");
    for (int i = 0, length = myHeap.size; i < length; i++) {
        System.out.printf(myHeap.remove() + "\t");
    }
}

运行结果

数组原始的值:10	4	8	3	5	1	
堆中元素的值:1	4	3	10	5	8	
排序之后的值:1	3	4	5	8	10
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值