热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

堆和堆排序

一、什么是优先队列?普通队列:先进先出,后进后出优先队列:出队顺序和入队顺序无关,和优先级相关。优先队列的实现:二、堆的基本实现二叉堆的特点:这很重要!!!是核心任意节点小于其父节

一、什么是优先队列?

  普通队列:先进先出,后进后出

  优先队列:出队顺序和入队顺序无关,和优先级相关。

  优先队列的实现:

  入队 出队
普通数组 O(1) O(n)
顺序数组 O(N) O(1)
O(logN) O(logN)

二、堆的基本实现

  二叉堆的特点:这很重要!!! 是核心

    任意节点小于其父节点

    除了最后一层叶子节点外,其他层的元素个数必须是最大值 ,叶子节点虽然可以不是最大值,但必须靠左排列(最大堆)

    堆是一棵完全二叉树

  用数组存储二叉堆

  

  这样用数组存储的堆中元素和数组下标有以下规律:  这很重要!!! 是核心

父节点下标:parent (i) = ( i - 1) / 2

左子节点: left child (i)  = (i + 1) * 2 

右子节点:right child (i) = (i + 1) * 2 + 1 

  最大堆代码实现 (逐步的实现,下面只是简单的定义,各种操作的方法后续依次加入) :

 1 public class MaxHeap {
 2 
 3     // 存储元素
 4     private int[] data;
 5     // 记录堆中节点个数
 6     private int size;
 7     // 堆的容量
 8     private int capacity;
 9 
10     public MaxHeap(int capacity) {
11 
12         this.capacity = capacity;
13         data = new int[capacity]; // 堆的第一个元素索引为 0;
14         this.size = 0;
15     }
16 
17     public int size() {
18         return size;
19     }
20 
21     public boolean isEmpty() {
22         return size == 0;
23     }
24 
25 }

 

  往最大堆中添加元素(shiftUp)

                                    

   根据前面对最大堆的定义(任意子节点小于其父节点) 以及元素下标之间的关系,我们不断交换父子节点的位置,知道满足最大堆的原则,就完成了元素插入。下面是代码实现:

    /**
     * 往最大堆中加入一个元素
     * @param e 
     */
    public void insert(int e) {

        if (size - 1 < capacity) {
            data[size] = e;
            shiftUp(size);
            size++;
        }
    }

    /**
     * 根据堆的定义,交换父子节点的位置,直到满足最大堆的原则
     * @param k
     */
    private void shiftUp(int k) {

        while (k > 0 && data[k] > data[(k - 1) / 2]) {
            SortedHandler.swap(data, k, (k - 1) / 2);
            k = (k - 1) / 2;
        }
    }

 

   删除最大堆中的元素(shiftDown)

            

  根据优先队列的定义,元素的出顺序按照优先级,而在最大堆中,根节点的优先级就是最大的,因此我们删除的时候,总是从根节点开始。

  具体的思路是,首先交换根节点和最后一个节点的位置,然后删除掉交换后的根节点,也就是最大值,然后根据堆的定义交换节点位置维护最大堆的原则,最后返回删除了的最大值即可。代码实现如下:

    /**
     * 交换根节点和最后一个节点的位置,再将移除的根节点的值返回,并维护最大堆的原则
     * @return 原堆中的最大值
     */
    public int extractMax() {

        if (size > 0) {
            int res = data[0];

            // 交换第一个和最后一个的位置
            SortedHandler.swap(data, 0, size - 1);
            size--;
            shiftDown(0);
            return res;
        }

        return -1;
    }

    /**
     * 交换节点的位置  维护最大堆的定义
     * @param k 开始的节点位置
     */
    private void shiftDown(int k) {

        while (2 * k + 1 < size) {
            int j = 2 * k + 1; // 左子点的下标
            if (j + 1  data[j]) {
                j += 1;
            }
            if (data[k] < data[j]) {
                SortedHandler.swap(data, k, j);
                k = j;
            } else {
                break;
            }
        }

    }

 

 

 基本的堆排序

  通过上面的努力,我们实现了一个基本操作的最大堆。如果前面的明白了的话,那么实现一个堆排序就是小问题了,因为我们的最大堆的输出顺序就是由大到小的,那么排序的问题不过是将数组的顺序反过来 .

    public static void heapSorted1(int arr[]) {

        int n = arr.length;
        MaxHeap heap = new MaxHeap(n);

        for (int i = 0; i ) {
            heap.insert(arr[i]);
        }
        for (int i = n - 1; i >= 0; i--) {
            arr[i] = heap.extractMax();
        }
    }

 

 最大堆的另外一种构造方法 —— Heapify

  在前面构造最大堆的实现中,我们都是首先构造一个初始化容量的数组,然后依次加入数组的每个元素。现在我们考虑一个情况,因为最大堆的存储本身就是数组实现的,那么当我们对数组需要排序的时候,是否可以直接将这个数组构造成为最大堆呢,而无需逐个的复制元素并shiftUp?答案是肯定的。

  具体的思路是:将待排序的数组本身看成是一棵二叉树,在这课二叉树中,所有不同的非叶子节点就是不用的最大堆。那么我们就从这棵二叉树的第一个非叶子节点开始执行shiftDown操作,直到整棵二叉树满足最大堆的原则。那么问题又来了?第一个非叶子是多少呢,这里又有一个规律:完全二叉树的排列中,第一个非叶子节点 i 等于数组的长度 (n - 1) / 2.代码实现如下:

    // heapify 的过程
    public MaxHeap(int arr[]) {

        int n = arr.length;
        data = new int[n];
        capacity = n;
        for (int i = 0; i ) {
            data[i] = arr[i];
        }
        size = n;
        // 第一个非叶子节点的下标 (n-1) / 2
        for (int i = (n - 1) / 2; i >= 0; i--) {
            shiftDown(i);
        }

    }

    public static void heapSorted2(int arr[]) {

        int n = arr.length;
        MaxHeap heap = new MaxHeap(arr);

        for (int i = n - 1; i >= 0; i--) {
            arr[i] = heap.extractMax();
        }

    }

 

 

堆排序的优化——原地堆排序

    public static void heapSorted3(int arr[]) {

        int n = arr.length;
        
        // heapify ,将数组转换为堆
        for (int i = (n - 1) / 2; i >= 0; i--) {
            __shiftDown(arr, n, i);
        }

        // 
        for (int i = n - 1; i > 0; i--) {
            int tmp = arr[i];
            arr[i] = arr[0];
            arr[0] = tmp;

            __shiftDown(arr, i, 0);
        }

    }

    /**
     *  原地堆排序的shiftDown操作
     * @param arr
     * @param n
     * @param i
     */
    private static void __shiftDown(int[] arr, int n, int i) {

        while (2 * i + 1 < n) {
            int j = 2 * i + 1;
            if (j + 1 ]) {
                j += 1;
            }
            if (arr[j] > arr[i]) {
                int tmp = arr[j];
                arr[j] = arr[i];
                arr[i] = tmp;

                i = j;
            } else
                break;
        }

    }

推荐阅读
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • JVM:33 如何查看JVM的Full GC日志
    1.示例代码packagecom.webcode;publicclassDemo4{publicstaticvoidmain(String[]args){byte[]arr ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了logistic回归(线性和非线性)相关的知识,包括线性logistic回归的代码和数据集的分布情况。希望对你有一定的参考价值。 ... [详细]
  • LeetCode笔记:剑指Offer 41. 数据流中的中位数(Java、堆、优先队列、知识点)
    本文介绍了LeetCode剑指Offer 41题的解题思路和代码实现,主要涉及了Java中的优先队列和堆排序的知识点。优先队列是Queue接口的实现,可以对其中的元素进行排序,采用小顶堆的方式进行排序。本文还介绍了Java中queue的offer、poll、add、remove、element、peek等方法的区别和用法。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文介绍了解决Facebook脸书面试题中插入区间的方法,通过模拟遍历的方式判断当前元素与要插入元素的关系,找到插入点并将新区间插入。同时对算法的时间复杂度和空间复杂度进行了分析。 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • 本文介绍了一道经典的状态压缩题目——关灯问题2,并提供了解决该问题的算法思路。通过使用二进制表示灯的状态,并枚举所有可能的状态,可以求解出最少按按钮的次数,从而将所有灯关掉。本文还对状压和位运算进行了解释,并指出了该方法的适用性和局限性。 ... [详细]
  • 本文介绍了如何通过维持两个堆来获取一个数据流中的中位数。通过使用最大堆和最小堆,分别保存数据流中较小的一半和较大的一半数值,可以保证两个堆的大小差距为1或0。如果数据流中的数量为奇数,则中位数为较大堆的最大值;如果数量为偶数,则中位数为较大堆的最大值和较小堆的最小值的平均值。可以使用优先队列来实现堆的功能。本文还提供了相应的Java代码实现。 ... [详细]
  • 生产环境下JVM调优参数的设置实例
     正文前先来一波福利推荐: 福利一:百万年薪架构师视频,该视频可以学到很多东西,是本人花钱买的VIP课程,学习消化了一年,为了支持一下女朋友公众号也方便大家学习,共享给大家。福利二 ... [详细]
  • 初识java关于JDK、JRE、JVM 了解一下 ... [详细]
author-avatar
戴晓珊_340
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有