利用最大堆和最小堆在线寻找中位数
题外话:
前段时间参加校园招聘,经常在一些公司的笔试或者面试中遇到一些不错的算法题,回到宿舍和同学进行交流,收获许多。这段时间,工作定下来后,整天闲着没事,就整理之前一些不错的算法题及其想法。下面这个算法题是一个同学去参加百度校园招聘面试时遇到的题目,当时他写了一篇日志。看到他那篇日志,我和舍友小平同学讨论了两三个小时。下面对当时的想法进行一些整理。
问题:
给定n个int型的数和一个空的集合,每次往集合中插入一个数,每次插入之后给出这个集合的中位数。(中位数的概念是:如果集合有奇数个数,给出排序后处在最中间的那个数;如果是偶数个数,给出排序后最中间两个数的均值。)
分析:
该同学在日记里写到,他看到题目时想到的是O(N*N)的算法,但是没有说具体的算法步骤和思想。我猜想,他可能是想在插入时是采用插入排序的思想,则在集合中插入一个数的复杂度为O(N),从排好序的集合中选出中位数则是一个复杂度为O(1)的过程,总共插入N次,所以总复杂度为O(N*N)。
小平和我在看到题目时,第一时间想到的都是“二分”。在每次将数插入集合时,采用“二分查找”寻找该数在集合中的位置,这个过程的复杂度是O(log(N)),得出中位数的过程则是O(1),插入了N次,所以复杂度为O(N*log(N))。
关于二分插入的思想,上面的复杂度分析好像毋庸置疑。但是,上面的分析过程是不对的。采用二分查找,则集合必须能随机访问,而这只能使用数组来实现。然而,在数组插入一个数,则需要将该位置后面的所有元素向后移动。上面的思想中,在二分查找到一个数的位置,并将该数插入集合中,则需要后移该位置后的所有元素。在最坏情况下,每次插入的位置都是第一个,则“移动元素”操作的复杂度为O(N),那么该思想的复杂度仍然是O(N*N)。
有个同学在该日记的回复给了我思路,该同学的回复是:
“感觉用堆也是可以的,所有比中位数大的组成一个最小堆,比中位数小的组成一个最大堆,每次插入只进行一个堆的插入,然后中位数一定是原中位数、最大堆的最大值和最小堆最小值中产生,再进行堆的调整就可以了,不过复杂度也是NlogN”
刚开始,小平和我对这个回复进行了一下讨论,但是没讨论出一个结果。但是,这个“最大堆和最小堆”却一直萦绕在我的潜意识中。躺在床上,准备睡觉的时候,恍然大悟:可以使用两个变量来控制最大堆和最小堆中元素的个数差。
具体的思路如下:
集合中元素,前一半存储在一个最大堆中,后一半存储在一个最小堆中。使用变量MaxHeapNum记录最大堆元素的个数,使用变量MinHeapNum记录最小堆元素的个数。控制MaxHeapNum与MinHeapNum的差不能超过1。每次将要插入的元素Num与最大堆顶部元素MaxHeapTop和最小堆的顶部元素MinHeapTop将进行比较,根据具体情况进行插入:
1.如果Num < MaxHeapTop,则
1.1 如果MaxHeapNum <= MinHeapNum,将Num插入最大堆;
1.2 如果MaxHeapNum == MinHeapNum + 1,将MaxHeapTop从最大堆中移到最小堆,并将Num插入最大堆。
2.如果MaxHeapTop <= Num <= MinHeapTop,则
2.1 如果MaxHeapNum <= MinHeapNum,将Num插入最大堆;
2.2 如果MaxHeapNum == MinHeapNum + 1,将Num插入最小堆;
3.如果Num > MinHeapTop,则
3.1 如果MinHeapNum <= MaxHeapNum,将Num插入最小堆;
3.2 如果MinHeapNum == MaxHeapNum + 1,将MinHeapTop移到最大堆中,将Num插入最小堆。
在每次插入后,都要根据情况对MaxHeapNum和MinHeapNum进行变更,并将有改动的堆进行堆调整。
上面的插入情况会保证最大堆和最小堆的元素个数差小于1,中位数就只在最大堆和最小堆的顶部元素中产生:如果最大堆和最小堆的元素个数相等,则中位数为最大堆和最小堆的顶部元素的平均值;否则,中位数为元素个数多的那个堆的堆顶元素。
复杂度分析:每次插入元素时的堆调整平均复杂度为O(log(N/2)),插入N次,所以总的复杂度为O(N*log(N/2))。
总结:
前面插入排序的思想和二分查找的思想之所以复杂度高,是因为其做了许多寻找中位数之外的操作,即排序。题目只是要求每次插入集合时,求出集合的中位数,而对集合中的元素是否排序没有要求。寻找中位数,我只需要知道中间的数就OK了,没有必要对所有元素排好序。这正是最后一种思想的精髓:尽量减少额外操作的消耗。
posted on 2011-12-06 13:54 lienhua34 阅读(5548) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端