浅谈对顶堆

对顶堆详解

我们知道,堆是一种极有用的数据结构。它能在短时间内将数据维护成单调递增/递减的序列。但是这种“朴素堆”对于问题求解起到的效果毕竟是有限的。所以我们在朴素堆的基础上,进行深入思考和适当变形,使之能解决一些其他的用朴素堆解决不了的问题,并使思路变得简洁有效。

这篇随笔就堆中的一个分支——对顶堆进行讲解,读者在阅读前应该掌握堆的基本概念,以便于更好地理解对顶堆。

如果把堆中的大根堆想成一个上宽下窄的三角形,把小根堆想成一个上窄下宽的三角形,那么对顶堆就可以具体地被想象成一个“陀螺”或者一个“沙漏”,通过这两个堆的上下组合,我们可以把一组数据分别加入到对顶堆中的大根堆和小根堆,以维护我们不同的需要。

那么对顶堆是干嘛用的呢?

举一个例题:

给定\(N\)个数字,求其前\(i\)个元素中第\(K\)小的那个元素。

我们很容易想到用堆来维护这个单调递增的序列,如果使用数组实现的话,我们直接输入数组下标为\(K\)的元素即可。但我们使用的是堆,它的实现方式——优先队列是不支持任意点访问的,那么我们就无法进行单点查询。引申对顶堆的概念,我们可以这样解决问题:

优先队列虽然不支持任意点访问,但可以用\(O(1)\)的时间查询出堆顶元素,也就是说,我们只能通过维护对顶堆中两个堆的堆顶元素来进行单调性的维护。怎么办呢?

原理很简单:根据数学中不等式的传递原理,假如一个集合\(A\)中的最小元素比另一个集合\(B\)中的最大元素还要大,那么就可以断定:\(A\)中的所有元素都比\(B\)中元素大。所以,我们把小根堆“放在”大根堆“上面”,如果小根堆的堆顶元素比大根堆的堆顶元素大,那么小根堆的所有元素要比大根堆的所有元素大。

回到这个问题:我们要求第\(K\)小的元素,那么我们把大根堆的元素个数限制成\(K\)个,前\(K\)个元素入队之后,每个元素在入队之前先与堆顶元素比较,如果比堆顶元素大,就加入小根堆,如果没有的话,把大根堆的堆顶弹出,将新元素加入大根堆。这样就维护出一个对顶堆。它的作用在于找出第\(K\)小的元素

同理,对顶堆还可以用于解决其他“第\(K\)小”的变形问题:比如求前\(i\)个元素的中位数等。

原文地址:https://www.cnblogs.com/fusiwei/p/11432323.html

时间: 2024-10-30 02:31:07

浅谈对顶堆的相关文章

[转]浅谈dijkstra堆优化

众所周知的,dijkstra是图论算法中求单源最短路的一种简单求法.可能有人会说SPFA比dijkstra要实用,而且可以用于求存在负边权的情况,但是dijkstra有着他的优点——其运行速度上优于SPFA. (PS.需要堆进行优化.) 我们先看一道经典(水)题: 平面上有n个点(n<=100),每个点的坐标均在-10000~10000之间.其中的一些点之间有连线. 若有连线,则表示可从一个点到达另一个点,即两点之间有通路,通路的距离为两点之间的直线距离.现在的任务是找出从入点到出点之间的最短路

浅谈数据结构-堆

在数据结构的世界中有一个叫堆的玩意,这玩意有什么用呢?无用,都去pq了 堆,其实就是一棵完全二叉树. “若设二叉树的深度为h,除第 h 层外,其它各层 (1-h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树”  by 谋财害命公司 百度 ↑清真的 完全二叉树 ↓ 啊那么为什么会闲的无聊出现这种奇怪的数据结构呢? 因为我们的某些算法可能需要堆来进行优化,如dj,prim. 堆可以在O(1)的时间取出最优值,但是需要O(logn)的时间修改和O(nlogn)

浅谈Java堆内存分代回收

概述 与C++不同的是:在Java中我们无需关心对象占用空间的释放,这主要得益于Java中的垃圾处理器(简称GC)帮助我们自动的进行对象占用空间的释放. 下面我们带着几个问题来学习: 堆内存是如何分代的? 各分代之间是如何配合工作的? 1.堆内存是如何分代的? 用一张图片来描述(面积大小不代表实际占用空间大小) 堆内存分为:年轻代(Young) + 老年代(Old),年轻代又分为:Eden区 + Survivor区 * 2. 通常年轻代中的各区比值为:Eden区:Survivor0 :Survi

浅谈C#中堆和栈的区别(附上图解)

线程堆栈:简称栈 Stack 托管堆: 简称堆 Heap 使用.Net框架开发程序的时候,我们无需关心内存分配问题,因为有GC这个大管家给我们料理一切.如果我们写出如下两段代码: 代码段1: public int AddFive(int pValue) { int result; result = pValue + 5; return result; } 代码段2: public class MyInt { public int MyValue; } public MyInt AddFive(i

浅谈Java中的栈和堆

人们常说堆栈堆栈,堆和栈是内存中两处不一样的地方,什么样的数据存在栈,又是什么样的数据存在堆中? 这里浅谈Java中的栈和堆 首先,将结论写在前面,后面再用例子加以验证. Java的栈中存储以下类型数据,栈对应的英文单词是Stack 基本类型 引用类型变量 方法 Java的堆中存储以下类型数据,堆对应的英文单词是Heap 实例对象 在函数中定义的一些基本类型的变量(8种)和对象的引用变量都是在函数的栈Stack内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当

浅谈C中的malloc和free

在C语言的学习中,对内存管理这部分的知识掌握尤其重要!之前对C中的malloc()和free()两个函数的了解甚少,只知道大概该怎么用——就是malloc然后free就一切OK了.当然现在对这两个函数的体会也不见得多,不过对于本文章第三部分的内容倒是有了转折性的认识,所以 写下这篇文章作为一个对知识的总结.这篇文章之所以命名中有个“浅谈”的字眼,也就是这个意思了!希望对大家有一点帮助! 如果不扯得太远的话(比如说操作系统中虚拟内存和物理内存如何运做如何管理之类的知识等),我感觉这篇文章应该是比较

浅谈算法和数据结构

: 一 栈和队列 http://www.cnblogs.com/yangecnu/p/Introduction-Stack-and-Queue.html 最近晚上在家里看Algorithems,4th Edition,我买的英文版,觉得这本书写的比较浅显易懂,而且“图码并茂”,趁着这次机会打算好好学习做做笔记,这样也会印象深刻,这也是写这一系列文章的原因.另外普林斯顿大学在Coursera 上也有这本书同步的公开课,还有另外一门算法分析课,这门课程的作者也是这本书的作者,两门课都挺不错的. 计算

【内存类操作】浅谈内存拷贝异常

结合本人在实际项目中所积累的经验,以及曾经犯过的错误,堆内存操作类函数做一个简单的剖析,抛砖引玉,欢迎大家吐槽. 首先,讲一下内存使用异常发生的几种场景. 1.野指针的使用,使用已经释放的指针,如果向野指针中写内容,就极有可能导致设备重启或任务挂死.因为,正在运行的任务的地址被意外的改写. [避免策略]函数入参要判空,指针使用(包括释放)之前一定要释放. 2.内存函数的错误使用: void *memset(void *s, int ch, size_t n); c语言中在<memory.h>或

浅谈一下缓冲区溢出

0x01 缓冲出溢出概念 缓冲区是用户为程序运行时在计算机中申请的一段连续内存,它保存了给定类型的数据. 缓冲区溢出就是在向缓冲区写入数据时,由于没有做边界检查,导致写入缓冲区的数据超过预先分配的边界,从而使溢出数据覆盖在合法数据上而引起系统异常的一种现象. 缓冲区溢出包括堆栈溢出和堆溢出. 0x02 进程内存的划分 要清楚缓冲区溢出的原理,就先要对计算机执行程序的内存结构加以分析. 根据不同的操作系统,一个进程可能被分配到不同的内存区域去执行,但不管什么样的操作系统.什么样的计算机架构,进程使