二叉堆 ( 数据结构 )

0. 相关概念

二叉堆 : 用数组储存的完全二叉树
完全二叉树 : 二叉树,且除最后一层外,每一层都是填满了的,最后一层从第一个节点开始依次向右增加节点
最大堆 : 父节点值大于等于任意一个子节点的值
最小堆 : 父节点值小于等于任意一个子节点的值
堆化 : 使得任意子树都是堆

1. 结构

在这里插入图片描述

2. 遍历

  1. 根节点下标: 0

    节点i的:
    父节点下标: ( i - 1 ) / 2
    左孩子下标: i * 2 + 1
    右孩子下标: i * 2 + 2

  2. 根节点下标: 1 (常用)

    节点i的:
    父节点下标: i / 2
    左孩子下标: i * 2
    右孩子下标: i * 2 + 1

采用第二种方法的4种遍历:

    /**
     * 前序遍历
     * 
     * @param a     堆
     * @param index 当前节点下标
     */
    public static void preOrder(int[] a, int index) {
        if (index >= a.length)
            return;
        System.out.print(a[index] + " ");
        preOrder(a, index * 2);
        preOrder(a, index * 2 + 1);
    }

    /**
     * 中序遍历
     * 
     * @param a
     * @param index
     */
    public static void inOrder(int[] a, int index) {
        if (index >= a.length)
            return;
        inOrder(a, index * 2);
        System.out.print(a[index] + " ");
        inOrder(a, index * 2 + 1);
    }

    /**
     * 后序遍历
     * 
     * @param a
     * @param index
     */
    public static void postOrder(int[] a, int index) {
        if (index >= a.length)
            return;
        postOrder(a, index * 2);
        postOrder(a, index * 2 + 1);
        System.out.print(a[index] + " ");
    }

    /**
     * 层序遍历
     * 
     * @param a
     */
    public static void seqOrder(int[] a) {
        for (int i = 1; i < a.length; ++i)
            System.out.print(a[i] + " ");
    }
    
    public void testTriversal() {
        int[] a = range(10);
        System.out.println("前序遍历");
        preOrder(a, 1);
        System.out.println("\n中序遍历");
        inOrder(a, 1);
        System.out.println("\n后序遍历");
        postOrder(a, 1);
    }

3. 建堆

从后往前, 对数组的每一个元素做堆化操作
堆化: (大顶堆)自顶向下将比当前元素大的换上来, (小顶堆相反)
下面有递归和循环两种方式

3.1. 最大堆 ( 大顶堆 )

    /**
     * 建堆 O(logn)
     * 
     * 只有一个元素的堆是最大堆(最小堆)
     * 
     * @param a 堆
     */
    public static void makeMaxHeap(int[] a) {
        // 从后向前调用
        for (int i = a.length - 1 >> 1; i >= 1; i--)
            maxHeapifyLoop(a, i, a.length - 1);// 使用循环
        // maxHeapifyRecursion(a, i, a.length - 1);// 使用递归
    }

    /**
     * 将a调整为最大堆 循环 O(logn)
     * 
     * i的左右子树都满足大顶堆的性质
     * 
     * @param a      堆
     * @param index  起始下标(包括)
     * @param length 结束位置(包括)
     */
    private static void maxHeapifyLoop(int[] a, int index, int length) {
        while (index <= length) {// 不断向下调整
            int left = index * 2;
            int right = left + 1;
            int max = index;// 左右子节点中最大的,的下标
            // 找出max
            if (left <= length && greater(a[left], a[max]))// 左子树存在,且较大
                max = left;// 更新
            if (right <= length && greater(a[right], a[max]))
                max = right;
            if (max == index)// a[index]比左右子节点都大,后面就不用调整了
                break;
            swap(a, max, index);// 如果没有跳出就交换
            index = max;// 将i指向max继续向下调整
        }
    }

    /**
     * 堆化 -> 大顶堆 (递归)
     * 
     * @param a      堆
     * @param index  需要堆化处理的数据的索引
     * @param length 未排序的堆(数组)的长度
     */
    public static void maxHeapifyRecursion(int[] a, int index, int length) {
        int li = index << 1; // 左子节点索引
        int ri = li + 1; // 右子节点索引
        int iMax = li; // 子节点值最大索引,默认左子节点。
        if (li > length)
            return; // 左子节点索引超出计算范围,直接返回。
        if (ri <= length && a[ri] > a[li]) // 先判断左右子节点,哪个较大。
            iMax = ri;
        if (a[iMax] > a[index]) {
            swap(a, iMax, index); // 如果父节点被子节点调换,
            maxHeapifyRecursion(a, iMax, length); // 则需要继续判断换下后的父节点是否符合堆的特性。
        }
    
    /* 两个工具函数 */
    public static boolean greater(int a, int b) {
        return a > b;
    }
    public static void swap(int[] a, int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

3.2. 最小堆 ( 小顶堆 )

如法炮制

    /**
     * 建立小顶堆
     * 
     * @param a
     */
    public static void minHeapify(int[] a) {
        // 从后向前调用
        for (int i = a.length - 1; i >= 0; i--) {
            minHeapDown(a, i);
        }
    }

    private static void minHeapDown(int[] a, int i) {
        while (i < a.length) {// 不断向下调整
            int left = i * 2;
            int right = i * 2 + 1;
            int min = i;// 左右子节点中最小的,的下标
            // 找出min
            if (left < a.length && less(a[left], a[min]))// 左子树存在,且较小
                min = left;// 更新
            if (right < a.length && less(a[right], a[min]))
                min = right;
            if (min == i)// a[i]比左右子节点都小,后面就不用调整了
                break;
            // 如果没有跳出就交换
            int temp = a[i];
            a[i] = a[min];
            a[min] = temp;
            i = min;// 将i指向min继续向下调整
        }
    }

测试代码

    @Test
    public void test_makeMaxHeap() {
        int[] a = new int[11];
        for (int i = 1; i <= 10; i++) {
            a[i] = i - 1;
        }
        makeMaxHeap(a);
        seqOrder(a);
    }

    @Test
    public void test_minHeapify() {
        int[] a = new int[11];
        for (int i = 1; i <= 10; i++) {
            a[i] = 10 - i;
        }
        minHeapify(a);
        seqOrder(a);
    }

这里有个问题😅
为啥要自顶向下建堆而不是自底向上呢?
因为自底向上的化能会在交换的时候把本来在在正确位置的元素交换到下一层
或者下写个代码看看

    /**
     * 不用这个函数建立最小堆
     * 
     * 从上面换下来的元素可能比parent大
     * 
     * @param a
     * @param i
     */
    private static void heapUp(int[] a, int i) {
        while (i > 0) {
            int parent = i - 1 >> 1;
            if (a[i] < a[parent]) {
                int temp = a[i];
                a[i] = a[parent];
                a[parent] = temp;
                i = parent;
            } else {
                break;
            }
        }
    }

    /**
     * 不用这个
     * 
     * @param a
     */
    public static void makeMinHeapByUp(int[] a) {
        for (int i = a.length - 1; i >= 0; i--) {
            heapUp(a, i);
        }
        for (int i = a.length - 1; i >= 0; i--) {
            heapUp(a, i);
        }
    }

还有一个问题 🤣
最开始的时候这份代码就是用Java写的, 而且数组下标是从0开始的现在我把它改成了从1开始的,可能有些地方没有改到…😥

4. 堆排序

我们已经知道最大堆,堆排序就是先建堆,每次把堆的第一个元素取出来放到堆末尾
如果要从小到大排序的化就用大顶堆,每次取出最大的元素放到堆尾(不是数组末尾)

    /**
     * 堆排序的主要入口方法,共两步。
     */
    public static void heapSort(int[] a) {
        /*
         * 第一步:将数组堆化
         * beginIndex = 第一个非叶子节点。
         * 从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
         * 叶子节点可以看作已符合堆要求的节点,根节点就是它自己且自己以下值为最大。
         */
        int len = a.length - 1;
        int beginIndex = len >> 1;
        for (int i = beginIndex; i >= 1; i--) {
            maxHeapifyRecursion(a, i, len);
        }

        /*
         * 第二步:对堆化数据排序
         * 每次都是移出最顶层的根节点A[0],与最尾部节点位置调换,同时遍历长度 - 1。
         * 然后从新整理被换到根节点的末尾元素,使其符合堆的特性。
         * 直至未排序的堆长度为 0。
         */
        for (int i = len; i > 0; i--) {
            swap(a, 1, i);
            maxHeapifyRecursion(a, 1, i - 1);
        }
    }

    public static void heapSortLoop(int[] a) {
        int len = a.length - 1;
        int start = len >> 1;
        for (int i = start; i >= 1; i--)
            maxHeapifyLoop(a, i, len);
        for (int i = len; i >= 1; i--) {
            swap(a, 1, i);
            for (int j = start; j >= 1; j--)
                maxHeapifyLoop(a, 1, i - 1);
        }
    }

测试代码(非常简陋)

    @Test
    public void test_heapSort() {
        int[] a = new int[31];
        for (int i = 1; i <= 30; ++i)
            a[i] = (int) (Math.random() * 100);
        int[] b = a.clone();
        heapSort(a);
        seqOrder(a);
        System.out.println();

        heapSortLoop(b);
        seqOrder(b);
    }

5. PriorityQueue

在Java里面有着堆这种性质的数据结构就是PriorityQueue了

    /**
     * Class PriorityQueue<E>
     * boolean offer(E e) 添加元素<br/>
     * E poll() 删除元素<br/>
     * E peek() 返回队首元素<br/>
     * 
     * DEFAULT_INITIAL_CAPACITY = 11;
     * 
     */
    @Test
    public  void testPriorityQueue() {
        // 小顶堆
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        pq.offer(452);
        pq.offer(3);
        pq.offer(2352);
        pq.offer(4);
        pq.offer(34);
        pq.offer(2);
        System.out.println(pq.peek());
        while (!pq.isEmpty())
            System.out.println(pq.poll());

        // 大顶堆
        // PriorityQueue<Integer> pq1 = new PriorityQueue<>(new Comparator<Integer>() {
        // @Override
        // public int compare(Integer o1, Integer o2) {
        // return o2 - o1;
        // }
        // });
        System.out.println();
        PriorityQueue<Integer> pq1 = new PriorityQueue<>((a, b) -> b - a);
        // var p=new PriorityQueue<Integer>((a,b)->b-a);
        pq1.offer(452);
        pq1.offer(3);
        pq1.offer(2352);
        pq1.offer(4);
        pq1.offer(34);
        pq1.offer(2);
        System.out.println(pq1.peek());
        while (!pq1.isEmpty())
            System.out.println(pq1.poll());
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SVIP_Quanw

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

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

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

打赏作者

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

抵扣说明:

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

余额充值