(七) 数据结构 - 最大堆

最大堆

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

堆的常用场景:

  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, 1000000);
        Integer[] integers1 = SortTestHelper.generateRandomArray(10000, 0, 1000000);
        Integer[] integers2 = SortTestHelper.generateRandomArray(100000, 0, 1000000);
​
        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数据量为100000" + 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数据量为100000" + 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数据量为100000" + SortTestHelper.testSort(integers2, new QuickSortOptimize()));
​
​
        System.out.println("------------------------------近乎有序的数组--------------------------------");
        Integer[] integers00 = SortTestHelper.generateNearlyOrderedArray(100, 10);
        Integer[] integers11 = SortTestHelper.generateNearlyOrderedArray(10000, 100);
        Integer[] integers22 = SortTestHelper.generateNearlyOrderedArray(100000, 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数据量为100000" + 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数据量为100000" + 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数据量为100000" + SortTestHelper.testSort(integers22, new QuickSortOptimize()));
​
        Integer[] integers000 = SortTestHelper.generateRandomArray(100, 0, 10);
        Integer[] integers111 = SortTestHelper.generateRandomArray(10000, 0, 100);
        Integer[] integers222 = SortTestHelper.generateRandomArray(100000, 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数据量为100000" + 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数据量为100000" + 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数据量为100000" + 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数据量为100000排序时长为: 0.05s
快速排序优化测试1数据量为99排序时长为: 0.0s
快速排序优化测试2数据量为10000排序时长为: 0.011s
快速排序优化测试3数据量为100000排序时长为: 0.089s
堆排序测试1数据量为99排序时长为: 0.0s
堆排序测试2数据量为10000排序时长为: 0.001s
堆排序测试3数据量为100000排序时长为: 0.024s
------------------------------近乎有序的数组--------------------------------
归并排序优化测试1数据量为100排序时长为: 0.0s
归并排序优化测试2数据量为10000排序时长为: 0.001s
归并排序优化测试3数据量为100000排序时长为: 0.013s
快速排序优化测试1数据量为100排序时长为: 0.0s
快速排序优化测试2数据量为10000排序时长为: 0.001s
快速排序优化测试3数据量为100000排序时长为: 0.016s
堆排序测试1数据量为100排序时长为: 0.0s
堆排序测试2数据量为10000排序时长为: 0.001s
堆排序测试3数据量为100000排序时长为: 0.012s
------------------------------大量重复的数组--------------------------------
归并排序优化测试1数据量为100排序时长为: 0.0s
归并排序优化测试2数据量为10000排序时长为: 0.002s
归并排序优化测试3数据量为100000排序时长为: 0.023s
快速排序优化测试1数据量为100排序时长为: 0.0s
快速排序优化测试2数据量为10000排序时长为: 0.001s
快速排序优化测试3数据量为100000排序时长为: 0.011s
堆排序测试1数据量为100排序时长为: 0.0s
堆排序测试2数据量为10000排序时长为: 0.0s
堆排序测试3数据量为100000排序时长为: 0.009s

参考原文:https://blog.csdn.net/love905661433/article/details/82989404

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值