(七) 数据结构 – 最大堆

(七) 数据结构 – 最大堆本文深入探讨了最大堆数据结构的原理与应用 包括其作为完全二叉树的特性 如何构建优先队列 支持堆排序及快速查找集合中的最大值

大家好,欢迎来到IT知识分享网。

最大堆

堆是一种数据结构,一种叫做完全二叉树的数据结构。排序根据“堆属性”,其决定了树中节点的位置。

堆的常用场景:

  1. 构建优先队列
  2. 支持堆排序
  3. 快速找出一个集合中的最小值(或者最大值)

二叉堆(Binary Heap)

  • 二叉堆是一颗完全二叉树
  • 堆中某个节点的值总是大于等于(或小于等于)其子节点, 对应的就是最大堆和最小堆

可以用数组存储二叉堆, 数组下标以1开始,可以如下展示 :

(七) 数据结构 - 最大堆

 

数组从0开始, 展示如下 :

(七) 数据结构 - 最大堆

 

常见操作

添加元素

  • 在数组的最后一个位置添加一个新元素
  • 新的元素进行上浮(Sift Up), 上浮操作如下图:

(七) 数据结构 - 最大堆

 

取出元素

堆每次只能取出最大的元素, 具体取出元素的步骤如下 :

  • 将堆的根节点与最后一个元素交换位置
  • 删除最后的一个元素, 此时最后一个元素位于根节点
  • 如果左右子节点不小于现在的根节点, 则将现有根节点和左右子节点中较大的那个交换位置
  • 重复上一步, 直到不再需要最后的那个节点找到它应该存放的位置, 示意图如下

(七) 数据结构 - 最大堆

 

替换元素

取出堆中的最大元素, 再放入新元素, 步骤如下:

  • 直接将堆顶元素替换成新元素
  • 对新元素执行Sift Down操作

将任意数组整理成堆(heapify)

  • 首先将数组当成一个完全二叉树
  • 找到最后一个非叶子节点, 方法是根据最后一个元素计算其父节点
  • 从最后一个非叶子节点之前的节点都执行Sift Down操作

(七) 数据结构 - 最大堆

 

最大堆代码实现

最大堆:

package com.paal.demo.heap; ​ / * <p/> * <li>title: 最大堆</li> * <li>@author: li.pan</li> * <li>Date: 2019/12/24 9:01 下午</li> * <li>Version: V1.0</li> * <li>Description: * <p> * 数组下标以1开始时,父节点parent(i)=i/2, left child (i)= 2*i, right child = 2*i +1 * <p> * 在堆的有关操作中,需要比较堆中元素的大小,所以E需要extends Comparable接口 * <p> * </li> */ public class MaxHeap<E extends Comparable> { ​    protected E[] data;    protected int count;    protected int capacity; ​ ​    /     * 构造函数, 构造一个空堆, 可容纳capacity个元素     *     * @param capacity     */    public MaxHeap(int capacity) {        data = (E[]) new Comparable[capacity + 1];        count = 0;        this.capacity = capacity;   } ​    /     * 构造函数, 通过一个给定数组创建一个最大堆     * Heapify方式将集合中的元素添加到最大堆中 该构造堆的过程, 时间复杂度为O(n)     *     * @param arr     */    public MaxHeap(E[] arr) {        int n = arr.length;        data = (E[]) new Comparable[n + 1];        capacity = n; ​        for (int i = 0; i < n; i++)            data[i + 1] = arr[i];        count = n; ​        for (int i = count / 2; i >= 1; i--)            shiftDown(i);   } ​    /     * 返回堆中元素个数     *     * @return     */    public int size() {        return count;   } ​    /     * 返回堆是否为空     *     * @return     */    public boolean isEmpty() {        return count == 0;   } ​    /     * 像最大堆中插入一个新的元素     *     * @param item 元素E     */    public void insert(E item) {        assert count + 1 <= capacity;        data[count + 1] = item;        count++;        shiftUp(count);   } ​ ​    /     * 从最大堆中取出堆顶元素, 即堆中所存储的最大数据     *     * @return     */    public E extractMax() {        assert count > 0;        //获取最大值        E ret = data[1];        //将根结点和最后一个叶子节点交换位置,即用最后一个叶子结点替换根结点        swap(1, count);        //删除最后一个节点        count--;        // 根结点下沉        shiftDown(1);        return ret;   } ​    /     * 节点上浮     *     * @param k 索引     */    private void shiftUp(int k) {        //不是根结点,且当前节点大于父节点时,和父节点交换位置        while (k > 1 && data[k / 2].compareTo(data[k]) < 0) {            swap(k, k / 2);            k /= 2;       }   } ​    /     * 节点下沉     *     * @param k     */    private void shiftDown(int k) {        // 防止数组越界        while (2 * k <= count) {            int j = 2 * k;            // 在此轮循环中,data[k]和data[j]交换位置            // data[j] 是 data[2*k]和data[2*k+1]中的最大值            if (j + 1 <= count && data[j + 1].compareTo(data[j]) > 0)                j++;            if (data[k].compareTo(data[j]) >= 0)                break;            swap(k, j);            k = j;       }   } ​    /     * 获取最大堆中的堆顶元素     *     * @return     */    public E getMax() {        assert (count > 0);        return data[1];   } ​ ​    /     * 交换堆中索引为i和j的两个元素     *     * @param i 索引i     * @param j 索引j     */    private void swap(int i, int j) {        E t = data[i];        data[i] = data[j];        data[j] = t;   } }

打印最大堆工具类:

package com.paal.demo.heap; ​ / * <p/> * <li>title: 打印最大堆</li> * <li>@author: li.pan</li> * <li>Date: 2019/12/28 18:01 下午</li> * <li>Version: V1.0</li> * <li>Description: * <p> * PrintableMaxHeap只能处理整数信息,所以继承的是MaxHeap<Comparable<Integer>> * <p> * </li> */ public class PrintableMaxHeap extends MaxHeap<Comparable<Integer>> { ​    public PrintableMaxHeap(int capacity) {        super(capacity);   } ​    public PrintableMaxHeap(Integer[] e) {        super(e);   } ​    /     * 以树状打印整个堆结构     */    public void treePrint() { ​        if (size() >= 100) {            System.out.println("This print function can only work for less than 100 integer");            return;       } ​        System.out.println("The max heap size is: " + size());        System.out.println("最大堆图示结构如下: ");        System.out.println();        System.out.println(); ​        int n = size();        int maxLevel = 0;        int numberPerLevel = 1;        while (n > 0) {            maxLevel += 1;            n -= numberPerLevel;            numberPerLevel *= 2;       } ​        int maxLevelNumber = (int) Math.pow(2, maxLevel - 1);        int curTreeMaxLevelNumber = maxLevelNumber;        int index = 1;        for (int level = 0; level < maxLevel; level++) { ​            String line1 = new String(new char[maxLevelNumber * 3 - 1]).replace('\0', ' '); ​            int curLevelNumber = Math.min(count - (int) Math.pow(2, level) + 1, (int) Math.pow(2, level));            boolean isLeft = true;            for (int indexCurLevel = 0; indexCurLevel < curLevelNumber; index++, indexCurLevel++) {                line1 = putNumberInLine((Integer) data[index], line1, indexCurLevel, curTreeMaxLevelNumber * 3 - 1, isLeft);                isLeft = !isLeft;           }            System.out.println(line1); ​            if (level == maxLevel - 1)                break; ​            String line2 = new String(new char[maxLevelNumber * 3 - 1]).replace('\0', ' ');            for (int indexCurLevel = 0; indexCurLevel < curLevelNumber; indexCurLevel++)                line2 = putBranchInLine(line2, indexCurLevel, curTreeMaxLevelNumber * 3 - 1);            System.out.println(line2); ​            curTreeMaxLevelNumber /= 2;       }   } ​    private String putNumberInLine(Integer num, String line, int indexCurLevel, int curTreeWidth, boolean isLeft) { ​        int subTreeWidth = (curTreeWidth - 1) / 2;        int offset = indexCurLevel * (curTreeWidth + 1) + subTreeWidth;        assert offset + 1 < line.length();        if (num >= 10)            line = line.substring(0, offset + 0) + num.toString()                    + line.substring(offset + 2);        else {            if (isLeft)                line = line.substring(0, offset + 0) + num.toString()                        + line.substring(offset + 1);            else                line = line.substring(0, offset + 1) + num.toString()                        + line.substring(offset + 2);       }        return line;   } ​    private String putBranchInLine(String line, int indexCurLevel, int curTreeWidth) { ​        int subTreeWidth = (curTreeWidth - 1) / 2;        int subSubTreeWidth = (subTreeWidth - 1) / 2;        int offsetLeft = indexCurLevel * (curTreeWidth + 1) + subSubTreeWidth;        assert offsetLeft + 1 < line.length();        int offsetRight = indexCurLevel * (curTreeWidth + 1) + subTreeWidth + 1 + subSubTreeWidth;        assert offsetRight < line.length(); ​        line = line.substring(0, offsetLeft + 1) + "/" + line.substring(offsetLeft + 2);        line = line.substring(0, offsetRight) + "\\" + line.substring(offsetRight + 1); ​        return line;   } ​    // 测试 PrintableMaxHeap //   public static void main(String[] args) { // //       PrintableMaxHeap maxHeap = new PrintableMaxHeap(100); //       int N = 99; // 堆中元素个数 //       int M = 100; // 堆中元素取值范围[0, M) //       for (int i = 0; i < N; i++) //           maxHeap.insert(new Integer((int) (Math.random() * M))); //       maxHeap.treePrint(); // //   } }

测试方法:​

 /     * 堆排序测试     */    @Test    public void heapSortTest() {        Integer[] integers = SortTestHelper.generateRandomArray(50, 0, 100);        Integer[] integers0 = SortTestHelper.generateRandomArray(99, 0, );        Integer[] integers1 = SortTestHelper.generateRandomArray(10000, 0, );        Integer[] integers2 = SortTestHelper.generateRandomArray(, 0, ); ​        System.out.println("------------------------------最大堆结构--------------------------------");        new PrintableMaxHeap(integers).treePrint();        System.out.println("------------------------------随机数组--------------------------------");        System.out.println("归并排序优化测试1数据量为99" + SortTestHelper.testSort(integers0, new MergeSortOptimize()));        System.out.println("归并排序优化测试2数据量为10000" + SortTestHelper.testSort(integers1, new MergeSortOptimize()));        System.out.println("归并排序优化测试3数据量为" + SortTestHelper.testSort(integers2, new MergeSortOptimize()));        System.out.println("快速排序优化测试1数据量为99" + SortTestHelper.testSort(integers0, new QuickSortOptimize()));        System.out.println("快速排序优化测试2数据量为10000" + SortTestHelper.testSort(integers1, new QuickSortOptimize()));        System.out.println("快速排序优化测试3数据量为" + SortTestHelper.testSort(integers2, new QuickSortOptimize()));        System.out.println("堆排序测试1数据量为99" + SortTestHelper.testSort(integers0, new HeapSort()));        System.out.println("堆排序测试2数据量为10000" + SortTestHelper.testSort(integers1, new QuickSortOptimize()));        System.out.println("堆排序测试3数据量为" + SortTestHelper.testSort(integers2, new QuickSortOptimize())); ​ ​        System.out.println("------------------------------近乎有序的数组--------------------------------");        Integer[] integers00 = SortTestHelper.generateNearlyOrderedArray(100, 10);        Integer[] integers11 = SortTestHelper.generateNearlyOrderedArray(10000, 100);        Integer[] integers22 = SortTestHelper.generateNearlyOrderedArray(, 1000);        System.out.println("归并排序优化测试1数据量为100" + SortTestHelper.testSort(integers00, new MergeSortOptimize()));        System.out.println("归并排序优化测试2数据量为10000" + SortTestHelper.testSort(integers11, new MergeSortOptimize()));        System.out.println("归并排序优化测试3数据量为" + SortTestHelper.testSort(integers22, new MergeSortOptimize()));        System.out.println("快速排序优化测试1数据量为100" + SortTestHelper.testSort(integers00, new QuickSortOptimize()));        System.out.println("快速排序优化测试2数据量为10000" + SortTestHelper.testSort(integers11, new QuickSortOptimize()));        System.out.println("快速排序优化测试3数据量为" + SortTestHelper.testSort(integers22, new QuickSortOptimize()));        System.out.println("堆排序测试1数据量为100" + SortTestHelper.testSort(integers00, new HeapSort()));        System.out.println("堆排序测试2数据量为10000" + SortTestHelper.testSort(integers11, new QuickSortOptimize()));        System.out.println("堆排序测试3数据量为" + SortTestHelper.testSort(integers22, new QuickSortOptimize())); ​        Integer[] integers000 = SortTestHelper.generateRandomArray(100, 0, 10);        Integer[] integers111 = SortTestHelper.generateRandomArray(10000, 0, 100);        Integer[] integers222 = SortTestHelper.generateRandomArray(, 0, 1000);        System.out.println("------------------------------大量重复的数组--------------------------------");        System.out.println("归并排序优化测试1数据量为100" + SortTestHelper.testSort(integers000, new MergeSortOptimize()));        System.out.println("归并排序优化测试2数据量为10000" + SortTestHelper.testSort(integers111, new MergeSortOptimize()));        System.out.println("归并排序优化测试3数据量为" + SortTestHelper.testSort(integers222, new MergeSortOptimize()));        System.out.println("快速排序优化测试1数据量为100" + SortTestHelper.testSort(integers000, new QuickSortOptimize()));        System.out.println("快速排序优化测试2数据量为10000" + SortTestHelper.testSort(integers111, new QuickSortOptimize()));        System.out.println("快速排序优化测试3数据量为" + SortTestHelper.testSort(integers222, new QuickSortOptimize()));        System.out.println("堆排序测试1数据量为100" + SortTestHelper.testSort(integers000, new HeapSort()));        System.out.println("堆排序测试2数据量为10000" + SortTestHelper.testSort(integers111, new QuickSortOptimize()));        System.out.println("堆排序测试3数据量为" + SortTestHelper.testSort(integers222, new QuickSortOptimize())); ​   }

运行结果:

------------------------------最大堆结构-------------------------------- The max heap size is: 50 最大堆图示结构如下: ​ ​                                               99                                                                     /                                             \                                             99                                             98                                 /                     \                       /                     \                     98                     88                     94                     45               /         \           /         \           /         \           /         \         95         86         84         85         78         87         32         25       /   \     /   \     /   \     /   \     /   \     /   \     /   \     /   \   92   85   53   36   81   53   30   47   60   35   65   70   15     6   12   12 / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \   / \ 78 63 35 28 12 19 17 17 68 57 36 38 23 9 12 5 37 21 17                                       ------------------------------随机数组-------------------------------- 归并排序优化测试1数据量为99排序时长为: 0.0s 归并排序优化测试2数据量为10000排序时长为: 0.007s 归并排序优化测试3数据量为排序时长为: 0.05s 快速排序优化测试1数据量为99排序时长为: 0.0s 快速排序优化测试2数据量为10000排序时长为: 0.011s 快速排序优化测试3数据量为排序时长为: 0.089s 堆排序测试1数据量为99排序时长为: 0.0s 堆排序测试2数据量为10000排序时长为: 0.001s 堆排序测试3数据量为排序时长为: 0.024s ------------------------------近乎有序的数组-------------------------------- 归并排序优化测试1数据量为100排序时长为: 0.0s 归并排序优化测试2数据量为10000排序时长为: 0.001s 归并排序优化测试3数据量为排序时长为: 0.013s 快速排序优化测试1数据量为100排序时长为: 0.0s 快速排序优化测试2数据量为10000排序时长为: 0.001s 快速排序优化测试3数据量为排序时长为: 0.016s 堆排序测试1数据量为100排序时长为: 0.0s 堆排序测试2数据量为10000排序时长为: 0.001s 堆排序测试3数据量为排序时长为: 0.012s ------------------------------大量重复的数组-------------------------------- 归并排序优化测试1数据量为100排序时长为: 0.0s 归并排序优化测试2数据量为10000排序时长为: 0.002s 归并排序优化测试3数据量为排序时长为: 0.023s 快速排序优化测试1数据量为100排序时长为: 0.0s 快速排序优化测试2数据量为10000排序时长为: 0.001s 快速排序优化测试3数据量为排序时长为: 0.011s 堆排序测试1数据量为100排序时长为: 0.0s 堆排序测试2数据量为10000排序时长为: 0.0s 堆排序测试3数据量为排序时长为: 0.009s

参考原文:(七) 数据结构 – 最大堆

 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/114543.html

(0)
上一篇 2025-12-08 13:15
下一篇 2025-12-08 13:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信