堆(Heap)

本文介绍了堆数据结构,包括其定义、存储方式和常见操作。堆是一种完全二叉树,分为大顶堆和小顶堆,适用于实现优先队列、Top K 问题和求解动态中位数。堆排序通过建堆和排序两步完成,时间复杂度为O(NlogN)。堆在优先队列中用于高效地实现入队和出队,同时在求解Top K问题时提供O(NlogK)的解决方案。最后,利用大顶堆和小顶堆可以实现实时更新的中位数计算。
定义
  1. 完全二叉树(即除了最后一层外,其他层都是满的,最后一层的节点全部靠左排列,适合用数组存储数据)
  2. 节点的值大于等于(或小于等于)子树节点的值(等价于左右子树节点)
    ps. 同一组数据,堆不唯一。
存储

完全二叉树适合使用数组存储数据,可以直接使用下标来找到父亲的左右儿子和儿子的父亲。
比如根从1开始,下标为 i i i 的节点的左右儿子为 i ∗ 2 i*2 i2 i ∗ 2 + 1 i*2+1 i2+1,父亲为 i / 2 i/2 i/2;下标为 i i i 的节点的左右儿子为 i ∗ 2 + 1 i*2+1 i2+1 i ∗ 2 + 2 i*2+2 i2+2,父亲为 ( i − 1 ) / 2 (i-1)/2 (i1)/2

操作
插入元素

把元素放到数组的最后一个位置,然后调整成堆。
从下往上:让新的节点和父节点进行大小比较,若不满足,则交换。

//伪代码
count ++; //堆中元素个数
a[count] = data;
while(i/2>0 && a[i]>a[i/2]){ //不到根(或i>1) 并且 不满足大顶堆的定义
    swap(a, i, i/2);
    i = i / 2;
}
删除元素

删除的一般是堆顶元素,它是数组中最大或者最小的元素。删除之后,把数组中的最后一个元素放到堆顶,然后调整成堆。
从上往下:让堆顶节点和子节点进行比较,交换到合适位置。

data = a[1];
a[1] = a[count];
--count;
while(i*2<=count){//i有左儿子
    j = i*2;
    if (i*2+1<=count && a[i*2+1]>a[i*2])//i有右儿子,并且右儿子比较大
        j = i*2 + 1;
    if (a[i]>=a[j])
        break;//父节点最大,退出循环
    swap(a, i, j);
    i = j;
}

堆排序

堆排序分为两大步骤,即建堆和排序。

1.建堆

直接的思路是把每个元素加入堆中,完成建堆,是从下往上的方法。
更好的思路是从后往前处理数据,并且每个数据都是从上往下堆化,重点是可以从第一个非叶子节点开始处理,即 n / 2 n/2 n/2 1 1 1,其余节点都是叶子节点。

//伪代码,建堆的复杂度为O(N)
bulidHeap(int a[], int n){
    for(int i=n/2; i>=1; --i){
        heapify(a, n, i)
    }
}
void heapify(int a[], int n, int i){
    int maxPos = i;
    if (i*2<=n && a[i*2]>a[maxPos]) maxPos = i*2;
    if (i*2+1<=n && a[i*2+1]>a[maxPos]) maxPos = i*2+1;
    if (maxPos == i) break;
    swap(a, i, maxPos);
    i = maxPos;
}
2.排序

类似于删除堆顶元素,首先将堆顶元素与下标为 n 的元素交换,将剩下的 n-1 个元素堆化,再将堆顶元素与下标为 n-1 的元素交换,将剩下的 n-2 个元素堆化,依次下去,完成排序。

bulidHeap(a, n);
int k = n;
while(k>1){
    swap(a, 1, k);
    --k;
    heapify(a, k, 1);
}

分析

  1. 堆排序是原地排序算法,只用到了个别的临时存储空间。

  2. 建堆复杂度为 O ( N ) O(N) O(N),堆化为 O ( l o g N ) O(logN) O(logN),排序过程为 O ( N l o g N ) O(NlogN) O(NlogN)

  3. 堆排序不是稳定的排序算法。在排序过程中,将堆顶节点与最后一个节点交换可能会改变相同数据的相对位置。

  4. 堆排序的交换此处比快速排序多。

实现优先队列

优先队列本质上是一个队列,但入队和出队的顺序不是先进先出,而是按照优先级的大小,优先队列也可以用数组实现,但是用堆实现优先队列是最高效的。

优先队列的入队过程就是向堆中添加元素,出队过程就是取出堆顶元素。复杂度都为 O ( l o g N ) O(logN) O(logN)

使用优先队列可以实现多个有序数组的合并。例如leetcode23-合并K个排序链表

实现 Top K 问题

求解 top K 问题就是从 N 个数据中,选取最大的 K 个,最直接的办法就是排序,取 K,时间复杂度最优为 O ( N l o g N ) O(NlogN) O(NlogN)

利用堆,可以实现更高效的算法。我们可以维护一个大小为 K 的小顶堆(若是取第 K 小的元素,则维护大小为 N-K+1 大小的小顶堆),然后接着遍历数组,从数组中取出取数据与堆顶元素比较。如果比堆顶元素大,我们就用这个元素替换堆顶元素。如果比堆顶元素小,则不做任何处理。遍历完成后,堆中的数据就是前 K 大数据了。这样处理的时间复杂度为 O ( N l o g K ) O(NlogK) O(NlogK).
例题1:leetcode347-前K个高频元素
解答:

import heapq as hq
class Solution:
    def topKFrequent(self, nums, k):
        """
        :type nums: List[int]
        :type k: int
        :rtype: List[int]
        """
        # 1. 排序 前k个
        # 2. 小顶堆 自己实现
        # 3. 小顶堆 heapq库实现
        data = []
        d = dict()
        for num in nums:
            d[num] = d.get(num, 0) + 1
        for key, val in d.items():
            if len(data) < k:
                hq.heappush(data, (val, key))
            elif val > data[0][0]:
                hq.heappop(data)
                hq.heappush(data, (val, key))
        ans = []
        while(len(data)>0):
            temp = hq.heappop(data)
            ans.append(temp[1]);
        return ans[::-1]

例题2:leetcode692-前K个高频单词
解答:

import heapq as hq
class Solution:
    def topKFrequent(self, words, k):
        """
        :type words: List[str]
        :type k: int
        :rtype: List[str]
        """
        class Freq:
            def __init__(self, word, freq):
                self.word = word
                self.freq = freq
            def __lt__(self, other):
                if self.freq < other.freq:
                    return -1
                if self.freq == other.freq and self.word > other.word: #字母序小的优先级大
                    return -1
        d = {}
        for word in words:
            d[word] = d.get(word, 0) + 1
        data = []
        for key, val in d.items():
            f = Freq(key, val)
            if (len(data) < k):
                hq.heappush(data, f)
            elif data[0] < f:
                hq.heappop(data)
                hq.heappush(data, f)
        ans = []
        while(len(data)>0):
            ans.append(hq.heappop(data).word)
            
        return ans[::-1]
                
求解中位数

针对动态数据,每次排序之后再求解中位数效率比较低,应用堆,可以不用排序,求得中位数。

使用一个大顶堆和一个小顶堆,初始时,两个堆为空,将第一个元素添加到任意一个堆中。逐渐添加元素,并保证,大顶堆中的堆顶元素比小于等于小顶堆中的堆顶元素,并且大顶堆中的元素个数至多比小顶堆中的元素个数多一,保证两边平衡。

即添加元素时,如果小于等于大顶堆的堆顶元素,就加入到大顶堆中,否则,加入到小顶堆中。
如果小顶堆中元素个数大于大顶堆中元素个数,则移动小顶堆堆顶元素到大顶堆中。如果大顶堆元素个数比小顶堆元素个数多于 1,将堆顶元素个数移动到小顶堆中。

这样如果有 n 个数,n 为偶数,两个堆中各有一半元素,取各自堆顶元素,取平均即为中位数。若 n 为奇数, 大顶堆中有 n/2+1 个元素,小顶堆中有 n 个元素,大顶堆的堆顶元素即为中位数。

例题:leetcode295
解答:

import heapq as hq
class MaxHeap:
    def __init__(self):
        self.data = []
        # self.n = 0
        self.cmp = lambda x: -x

    def compare(self, x):
        return -x

    def push(self, x):
        hq.heappush(self.data, (self.compare(x), x))
    def pop(self):
        return hq.heappop(self.data)[1]
    def size(self):
        return len(self.data)
    def top(self):
        return self.data[0][1]
    def println(self):
        print("MaxHeap", end=":")
        for num in self.data:
            print(num, end=',')
        print()
class MinHeap:
    def __init__(self):
        self.data = []
    # def compare(self, x):
    def push(self, x):
        hq.heappush(self.data, x)
    def pop(self):
        return hq.heappop(self.data)
    def size(self):
        return len(self.data)
    def top(self):
        return self.data[0]
    def println(self):
        print("MinHeap", end=":")
        for num in self.data:
            print(num, end=',')
        print()
class MedianFinder:
    
    def __init__(self):
        """
        initialize your data structure here.
        """
        self.maxheap = MaxHeap()
        self.minheap = MinHeap()
        

    def addNum(self, num):
        """
        :type num: int
        :rtype: void
        """
        # if (num > self.minheap.data[0]):
        #     self.maxheap.push(num)
        # else:
        #     self.minheap.push(num)
        # 如果小于等于大顶堆的元素,插入到大顶堆
        if (self.maxheap.size() > 0) and (num < self.maxheap.top()):
            self.maxheap.push(num)
        else:
            self.minheap.push(num)
        # 移动元素, 保证大顶堆中有n/2(+1)个元素 
        if (self.minheap.size() > self.maxheap.size()):
            self.maxheap.push(self.minheap.pop())
        elif (self.maxheap.size() - self.minheap.size()>1):
            self.minheap.push(self.maxheap.pop())
        # self.minheap.println()
        # self.maxheap.println()
        #print("-"*10)
    def findMedian(self):
        """
        :rtype: float
        """
        if (self.minheap.size()==0) and (self.maxheap.size()==0):
            return 0
        if ( self.minheap.size() == self.maxheap.size()):
            return ((self.minheap.top()+ self.maxheap.top()) / 2.0)
            
        else:
            return self.maxheap.top()
【电动车优化调度】基于模型预测控制(MPC)的凸优化算法的电动车优化调度(Matlab代码实现)内容概要:本文介绍了基于模型预测控制(MPC)的凸优化算法在电动车优化调度中的应用,并提供了Matlab代码实现。该方法结合了MPC的滚动优化特性与凸优化的高效求解能力,用于解决电动车充电调度问题,提升电网运行效率与可再生能源消纳能力。文中还提及多个相关研究方向和技术支撑,包括智能优化算法、机器学习、电力系统管理等,展示了其在多领域交叉应用的潜力。配套资源可通过提供的网盘链接获取,涵盖YALMIP工具包及其他完整仿真资源。; 适合人群:具备一定电力系统、优化理论及Matlab编程基础的科研人员和研究生,尤其适合从事电动汽车调度、智能电网优化等相关课题的研究者。; 使用场景及目标:①实现电动车集群在分时电价或电网需求响应机制下的有序充电调度;②结合可再生能源出力与负荷预测,利用MPC进行多时段滚动优化,降低电网峰谷差,提高能源利用效率;③为学术论文复现、课题研究及工程仿真提供可靠的技术路线与代码支持。; 阅读建议:建议读者结合文档中提到的智能优化算法与电力系统背景知识进行系统学习,优先掌握MPC基本原理与凸优化建模方法,并下载配套资源调试代码,以加深对电动车调度模型构建与求解过程的理解。
当然可以!下面是对你的代码进行 **简化、优化逻辑、增强可读性**,并给出 **准确的时间复杂度分析**。 --- ### ✅ 优化目标 1. **简化冗余代码** 2. **强化平衡逻辑的清晰度** 3. **修复中位数返回类型问题(应为 `double`)** 4. **提升算法鲁棒性和规范性** 5. **分析时间与空间复杂度** --- ### ✅ 简化并优化后的完整代码 ```cpp #include <iostream> #include <queue> #include <vector> using namespace std; class MedianFinder { private: priority_queue<int> maxHeap; // 左边:大顶(较小的一半) priority_queue<int, vector<int>, greater<int>> minHeap; // 右边:小顶(较大的一半) public: // 添加数字并保持平衡 void addNum(int num) { if (maxHeap.empty() || num <= maxHeap.top()) { maxHeap.push(num); } else { minHeap.push(num); } // 调整大小,确保:maxHeap.size() == minHeap.size() 或 多 1 if (maxHeap.size() > minHeap.size() + 1) { minHeap.push(maxHeap.top()); maxHeap.pop(); } else if (minHeap.size() > maxHeap.size()) { maxHeap.push(minHeap.top()); minHeap.pop(); } } // 返回中位数(注意:可能是浮点) double findMedian() { if (maxHeap.size() == minHeap.size()) { return (static_cast<double>(maxHeap.top()) + minHeap.top()) / 2.0; } else { return maxHeap.top(); // maxHeap 多一个 } } }; int main() { MedianFinder mf; int n, num; cout << "请输入整数个数: "; cin >> n; cout << "请输入" << n << "个整数: "; for (int i = 0; i < n; ++i) { cin >> num; mf.addNum(num); } cout << "中位数为: " << mf.findMedian() << endl; return 0; } ``` --- ### 🔍 主要改进说明: | 改进项 | 说明 | |--------|------| | ✅ `findMedian()` 返回 `double` | 原始代码用 `int` 会截断小数,如 `(1+2)/2=1` 错误。现在使用 `double` 并显式转换,避免精度丢失。 | | ✅ 移除无用构造函数 | `MedianFinder(){}` 是默认的,无需显式定义。 | | ✅ 优化平衡逻辑 | 条件判断更清晰:<br> - 左最多比右多 1<br> - 右能比左大(否则左补上) | | ✅ 使用 `++i` 替代 `i++` | 微小性能优化(虽编译器会优化,但习惯更好) | | ✅ 清晰注释和格式 | 提高可读性 | --- ### 📊 时间复杂度分析 | 操作 | 时间复杂度 | 原因 | |------|------------|------| | `addNum(int num)` | **O(log n)** | 插入到中需要调整结构,的高度是 `log n` | | `findMedian()` | **O(1)** | 只访问顶元素(最大/最小),涉及遍历或删除 | > 💡 总体处理 $ N $ 个元素的总时间为: > $$ > O(N \log N) > $$ 因为每个插入是 $ O(\log N) $,共插入 $ N $ 次。 --- ### 🧱 空间复杂度 - 所有元素都存储在两个中,总共 $ N $ 个元素。 - **空间复杂度:`O(N)`** --- ### ✅ 示例运行 输入: ``` 请输入整数个数: 6 请输入6个整数: 3 1 4 1 5 9 ``` 输出(可能): ``` 中位数为: 3 ``` > 解释:排序后 `[1,1,3,4,5,9]` → 中位数 = (3+4)/2 = 3.5?等等! ⚠️ 注意:我们上面代码返回的是 `double`,所以实际输出是 `3.5`! 你之前返回 `int` 会导致错误结果(比如 `3`),现在已修正。 ✅ 正确中位数:`(3 + 4) / 2 = 3.5` --- ### ✅ 补充建议 如果你想支持动态查询任意时刻的中位数(流式数据),这个结构非常合适,常用于: - 数据流中找中位数(LeetCode 295) - 实时统计系统 - 滑动窗口中位数预处理等 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值