【Play-with-Data-Structures-master】---part7:优先队列和堆

本文介绍了如何使用最大堆实现的优先队列解决LeetCode347题,即找出数组中出现频率前k高的元素。通过自定义ComTemp类,将数字及其出现次数作为优先级,确保队列始终保留出现频率最高的k个元素。这种方法的时间复杂度为O(nlogk),避免了O(nlogn)的排序操作,满足题目要求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考
MaxHeap.h

#include "Array.h"

template<class T>
class MaxHeap {
private:
    Array<T> *data;

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
    int leftChild(int index) {
        return index * 2 + 1;
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
    int rightChild(int index) {
        return index * 2 + 2;
    }
//使得添加的结点上升到满足二叉堆的性质
	void shiftUp(int k) {
        while (k > 0 && data->get(parent(k)) < data->get(k)) {
            data->swap(k, parent(k));
            k = parent(k);
        }
    }
    //将元素下移到合适位置的方法
    void shiftDown(int k) {
        while (leftChild(k) < data->getSize()) {
            int j = leftChild(k);
            //--------------------------------------------------首先找到 k 的左右孩子结点中最大的结点
            if (j + 1 < data->getSize() && data->get(j + 1) > data->get(j)) {
                j = rightChild(k);
            }
            if (data->get(k) > data->get(j)) {
                break;
            }
            data->swap(k, j);
            k = j;
        }
    }
    
public:
    class NoParent {
    };

	class Empty {
    };

    MaxHeap(int capacity) {
        data = new Array<T>(capacity);
    }

    MaxHeap() {
        data = new Array<T>();
    }

	MaxHeap(T arr[], int n){
        data = new Array<T>(arr, n);
        for (int i = parent(n - 1); i >= 0; --i) {
            shiftDown(i);
        }
    }

    int size() {
        return data->getSize();
    }

    bool isEmpty() {
        return data->isEmpty();
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的父亲节点的索引
    int parent(int index) {
        if (index == 0) {
            throw NoParent();
        }

        return (index - 1) / 2;
    }

    void add(T e) {
        data->addLast(e);
        shiftUp(data->getSize() - 1);
    }
    
    T findeMax() {
        if (data->isEmpty()) {
            throw Empty();
        }
        return data->get(0);
    }

    T extractMax() {
        T ret = findeMax();
        data->swap(0, data->getSize() - 1);
        data->removeLast();
        shiftDown(0);
        return ret;
    }
    // 取出堆中的最大元素,并且替换成元素e
    T replace(T e){
        T ret = findeMax();
        data->set(0, e);
        shiftDown(0);
        return ret;
    }
    
    void print() {
    	data->toPrint();
    }
};

PriorityQueue .h

#include "Queue.h"
#include "MaxHeap.h"

template<class T>
class PriorityQueue : public Queue<T> {
private:
    MaxHeap<T> *maxHeap;
public:
    PriorityQueue() {
        maxHeap = new MaxHeap<T>();
    }

    int getSize() {
        return maxHeap->size();
    }

    bool isEmpty() {
        return maxHeap->isEmpty();
    }

    T getFront() {
        return maxHeap->findeMax();
    }

    void enqueue(T e) {
        maxHeap->add(e);
    }

    T dequeue() {
        return maxHeap->extractMax();
    }

	void print() {
		std::cout << "Queue: size = " << maxHeap->size() << std::endl;
        std::cout << "front ";
        maxHeap->print();
        std::cout << " tail" << std::endl;
	}
};

LeetCode347 前k个高频元素

题目:给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
  提示:

1、你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
2、你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。(不能使用排序解决)
3、题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。(不会出现2个不同的数字出现次数一样,否则前k个高频元素不唯一)
4、你可以按任意顺序返回答案。

方法分析(这种思想很重要!)
  这个接法的技巧,就是通过一个 ComTemp 类,来定义什么是优先级。因为我们的优先队列是使用最大堆实现的,队首是优先级最大的元素,出队也是先出队优先级最大的元素。
  那么我们将 数字 以及 数字出现的次数 分钟到ComTemp类的属性中,然后在这个类中定义数字出现次数越少,优先级越大,这样我们将k个元素ComTemp对象入队后,队首肯定是优先级最高(数字出现次数最低)的ComTemp对象,队列中其他ComTemp的优先级肯定低于队首ComTemp(数字出现次数比他大)。新来一个对象,如果这个ComTemp数字出现次数比队首大(优先级比队首小),我们将队首优先级最大的ComTemp出队(出现次数最少的ComTemp出队),将新的ComTemp对象加入。
  这样,我们最后得到的长度为k的队列保存的ComTemp对象中,他们的数字就是原来数组中出现次数最大的前k个数字。
  这种方法的时间复杂度是:O(nlogk)。当然,我们将数字以及他们的出现次数保存到TreeMap后,我们也可以根据出现次数进行排序,使用归并排序或者快速排序,最后也可以得到出现次数也前k的数字,但是这样时间复杂度为 O(nlogn),不满足题目要求。

代码如下:

#include <iostream>
#include <vector>
#include <unordered_map>
#include "PriorityQueue.h"

using namespace std;

class Solution {
public:
    static vector<int> topKFrequent(vector<int> &nums, int k) {
        unordered_map<int, int> record;
        for (int i = 0; i < nums.size(); ++i) {
            record[nums[i]] += 1;
        }
        for (unordered_map<int,int>::iterator iterator2 = record.begin();iterator2!= record.end();++iterator2) {
            std::cout << iterator2->first << " " << iterator2->second << std::endl;
        }
        PriorityQueue<Freq> *priorityQueue = new PriorityQueue<Freq>();
//首先,先将k个Freq对象入队
            /**
             * 注意,pq队列的size初始值为0,当加入第一个元素的时候,size变为1,此时1<2,可以加入第二个元素,此时size变为2,2=2,无法加入第三个元素
             * 也就是说,这里如果我们向添加k个元素,那么必须设置 pq.getSize()<k,而不是pq.getSize()<=k,这样会添加k+1个元素!!
             */
        for (unordered_map<int, int>::iterator iterator1 = record.begin(); iterator1 != record.end(); ++iterator1) {
            if (priorityQueue->getSize() < k) {
                priorityQueue->enqueue(Freq(iterator1->first, iterator1->second));
            } else if (iterator1->second > priorityQueue->getFront().freq) {
//k个数字已经放满,如果后面遍历到的tm中的数字出现的频率高于队首的数字出现的频率,
                // 我们将队首的Freq对象出队,将新的数字构造成为Freq对象放入队列
                priorityQueue->dequeue();
                priorityQueue->enqueue(Freq(iterator1->first, iterator1->second));
            }
        }
   //结束循环后,队列中保存的就是出现频率前k的数字以及其出现频率所构造成为的ComTemp对象
        /**注意!!!
         * 这里不能使用for循环,因为pq在出队的过程中,size会发生变化,这样便获取不到正确的pq中的数,
         * 这里应该使用 while(!pq.isEmpty)
         */
        vector<int> res;
        while (!priorityQueue->isEmpty()) {
            res.push_back(priorityQueue->dequeue().e);
        }
        return res;
    }

private:
    class Freq {
    public:
        int e, freq;

        Freq(int e = 0, int freq = 0) {
            this->e = e;
            this->freq = freq;
        }

        bool operator<(const Freq &another) {
            if (this->freq < another.freq) {
                return false;
            } else {
                return true;
            }
        }

        bool operator>(const Freq &another) {
            if (this->freq > another.freq) {
                return false;
            } else {
                return true;
            }
        }
    };
};

如果我们想在N个元素中求出前M大的元素,同样可以使用最大堆实现的优先队列,只不过这里需要定义一个ComTemp,里面只有一个元素num,设置num越大,ComTemp越小。这样优先队列队首ComTemp的优先级最大,他得num也是整个队列最小。后面如果来一个新的ComTemp,且这个ComTemp的num大于队首的num,就将队首的num出队,将新的ComTemp添加进来即可!这样实现时间复杂度为O(nlogm)
  如果是求N个元素中前M小的元素,同样可以使用最大堆实现的优先队列,只不过这次我们可以使用num直接进行比较,较大的放在优先队列的队首,后面如果有比他小的num,将队首元素出队,将新的num添加到队列中即可!(不需要构造ComTemp)。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值