C++中的优先级队列(priority_queue)、仿函数(函数对象)

目录

priority_queue的使用

仿函数(函数对象)

优先级队列常见经典OJ题(面试常考)


优先队列(piority queue)是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。优先级队列,是0个或多个元素的集合,每个元素都有一个优先权或值,对优先级队列执行的操作有 1) 查找一个元素;2) 插人一个新元素; 3) 删除一个元素。与这些操作分别对应的函数是top, push 和pop。在最小优先级队列( min piority queue)中,查找和删除的元素都是优先级最小的元素;在最大优先级队列( max priority queue)中,查找和删除的元索都是优先级最大的元素。优先级队列的元素可以有相同的优先级,对这样的元素,查找与删除可以按任意顺序处理。优先级队列类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。

priority_queue的使用

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。

注意: 默认情况下priority_queue是大堆。

函数声明 接口说明
priority_queue()/priority_queue(first,last)  构造一个空的优先级队列
empty() 检测优先级队列是否为空,是返回true,否则返回 false
top() 返回优先级队列中最大(最小元素),即堆顶元素
push(x) 在优先级队列中插入元素x
pop() 删除优先级队列中最大(最小)元素,即堆顶元素

1、默认情况下,priority_queue是大堆。 

 2、如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。

class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
        : _year(year)
        , _month(month)
        , _day(day)
    {}
    bool operator<(const Date& d)const
    {
        return (_year < d._year) ||
            (_year == d._year && _month < d._month) ||
            (_year == d._year && _month == d._month && _day < d._day);
    }
    bool operator>(const Date& d)const
    {
        return (_year > d._year) ||
            (_year == d._year && _month > d._month) ||
            (_year == d._year && _month == d._month && _day > d._day);
    }
    friend ostream& operator<<(ostream& _cout, const Date& d)
    {
        _cout << d._year << "-" << d._month << "-" << d._day;
        return _cout;
    }
private:
    int _year;
    int _month;
    int _day;
};
void TestPriorityQueue()
{
    // 大堆,需要用户在自定义类型中提供<的重载
    priority_queue<Date> q1;
    q1.push(Date(2023, 3, 29));
    q1.push(Date(2023, 3, 28));
    q1.push(Date(2023, 3, 30));
    cout << q1.top() << endl;
    // 如果要创建小堆,需要用户提供>的重载
    priority_queue<Date, vector<Date>, greater<Date>> q2;
    q2.push(Date(2023, 3, 29));
    q2.push(Date(2023, 3, 28));
    q2.push(Date(2023, 3, 30));
    cout << q2.top() << endl;

仿函数(函数对象)

仿函数的定义
仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载 operator() 运算符。因为调用仿函数,实际上就是通过类对象调用重载后的 operator() 运算符。

如果编程者要将某种“操作”当做算法的参数,一般有两种方法:
(1)一个办法就是先将该“操作”设计为一个函数,再将函数指针当做算法的一个参数。
(2)将该“操作”设计为一个仿函数(就语言层面而言是个 class),再以该仿函数产生一个对象,并以此对象作为算法的一个参数。

很明显第二种方法会更优秀,因为第一种方法扩展性较差,当函数参数有所变化,则无法兼容旧的代码。在我们写代码时有时会发现有些功能代码,会不断地被使用。为了复用这些代码,实现为一个公共的函数是一个解决方法。不过函数用到的一些变量,可能是公共的全局变量。引入全局变量,容易出现同名冲突,不方便维护。

使用仿函数了,写一个简单类,除了维护类的基本成员函数外,只需要重载 operator() 运算符 。这样既可以免去对一些公共变量的维护,也可以使重复使用的代码独立出来,以便下次复用。而且相对于函数更优秀的性质,仿函数还可以进行依赖、组合与继承等,这样有利于资源的管理。

  在优先级队列中,默认是大根堆,我们如果需要小根堆就可以使用仿函数。在C++中,仿函数一般不需要我们自己去写,库函数中有。需要包含头文件 #include <functional>。

int findKthLargest(vector<int>& nums, int k) {
        //优先级队列默认大堆,使用仿函数建立小堆
        priority_queue<int,vector<int>,greater<int>> pq(nums.begin(),nums.begin()+k);
        for(int i=k;i<nums.size();i++)
        {
            if(nums[i]>pq.top())
            {
                pq.pop();
                pq.push(nums[i]);
            }
        }
        return pq.top();
    }

 特殊的仿函数需要自己实现,比如地址的比较。

class PDateLess
{
public:
    bool operator()(const Date* p1, const Date* p2)
    {
        return *p1 < *p2;
    }
};

class PDateGreater
{
public:
    bool operator()(const Date* p1, const Date* p2)
    {
        return *p1 > *p2;
    }
};

int main()
{
    priority_queue<Date*, vector<Date*>, PDateGreater> q;
    q.push(new Date(2023, 10, 29));
    q.push(new Date(2023, 10, 30));
    q.push(new Date(2023, 10, 28));
    cout << *(q2.top()) << endl;
}

优先级队列常见经典OJ题(面试常考)

 剑指 Offer 41. 数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

  • void addNum(int num) - 从数据流中添加一个整数到数据结构中。
  • double findMedian() - 返回目前所有元素的中位数。

大顶堆存放的是不比中位数大的数 也就是说存放的是有序数组中左半部分
小顶堆存放的是比中位数大的数 也就是说存放的是有序数组中右半部分
大顶堆的栈顶表示的是左半部分最大的数
小顶堆的栈顶表示的是右半部分最小的数
同时这两个堆存放的元素数量相差不会超过1
当两个堆存放总数为奇数时,大顶堆(即左半部分堆)的栈顶就是中位数
当两个堆存放总数为偶数时,结合大顶堆栈顶和小顶堆栈顶可求出中位数
接下来考虑插入问题 即当新元素进来时,应该怎么插入数据? 
若两个堆的数据量为偶数时,那么就先插入到小顶堆找到最小值(堆顶元素),
随后把这个最小值放入到大顶堆(左半部分), 此时两个堆的数据量为奇数,中位数为大顶堆堆顶
若两个堆的数据量为奇数时,那么就先插入到大顶堆找最大值(堆顶元素),
随后把这个最大值放入到小顶堆(右半部分),此时两个堆的数据量为偶数,中位数通过两个堆堆顶求出。

class MedianFinder {
public:
    // 最大堆,存储左边一半的数据,堆顶为最大值
    priority_queue<int, vector<int>, less<int>> maxHeap;
    // 最小堆, 存储右边一半的数据,堆顶为最小值
    priority_queue<int, vector<int>, greater<int>> minHeap;
    /** initialize your data structure here. */
    MedianFinder() {
    }

    // 维持堆数据平衡,并保证左边堆的最大值小于或等于右边堆的最小值
    void addNum(int num) {
        /*
         * 当两堆的数据个数相等时候,左边堆添加元素。
         * 采用的方法不是直接将数据插入左边堆,而是将数据先插入右边堆,算法调整后
         * 将堆顶的数据插入到左边堆,这样保证左边堆插入的元素始终是右边堆的最小值。
         * 同理左边数据多,往右边堆添加数据的时候,先将数据放入左边堆,选出最大值放到右边堆中。
         */
        if (maxHeap.size() == minHeap.size()) {
            minHeap.push(num);
            int top = minHeap.top();
            minHeap.pop();
            maxHeap.push(top);
        } else {
            maxHeap.push(num);
            int top = maxHeap.top();
            maxHeap.pop();
            minHeap.push(top);
        }
    }
    
    double findMedian() {
        if (maxHeap.size() == minHeap.size()) {
            return (maxHeap.top()+minHeap.top())*1.0/2;
        } else {
            return maxHeap.top()*1.0;
        }
    }
};

 把数组排成最小的数

输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。

此题求拼接起来的最小数字,本质上是一个排序问题。设数组 nums 中任意两数字的字符串为 x 和 y ,则规定 排序判断规则为:

若拼接字符串 x+y>y+x,则 x “大于” y ;
反之,若 x+y<y+x,则 x“小于” y ;
x “小于” y 代表:排序完成后,数组中 x 应在 y 左边;“大于” 则反之。

class Solution {

public:

    //仿函数

    bool operator()(string s1,string s2)

    {

        return s1+s2 < s2+s1;

    }

    string minNumber(vector<int>& nums)

    {

        vector<string> a;

        for(int i=0;i<nums.size();i++)

        {

            a.push_back(to_string(nums[i]));

        }

        sort(a.begin(),a.end(),Solution());//仿函数的运用

        string ret;

        for(int i=0;i<a.size();i++)

        {

            ret += a[i];

        }

        return ret;

    }

};

猜你喜欢

转载自blog.csdn.net/m0_55752775/article/details/129860166