Priority queue (priority_queue), functor (function object) in C++

Table of contents

The use of priority_queue

functor (function object)

Common classic OJ questions in priority queues (commonly tested in interviews)


A priority queue is a container adapter whose first element is always the largest of its contained elements according to strict weak ordering criteria. The priority queue is a collection of 0 or more elements, each element has a priority or value, and the operations performed on the priority queue are 1) find an element; 2) insert a new element; 3) delete an element. The functions corresponding to these operations are top, push and pop, respectively. In the minimum priority queue (min priority queue), the elements to be searched and deleted are the elements with the lowest priority; in the maximum priority queue (max priority queue), the elements to be searched and deleted are the elements with the highest priority . The elements of the priority queue can have the same priority, and for such elements, the search and deletion can be processed in any order. A priority queue is similar to a heap, where elements can be inserted at any time, and only the largest heap element (the one at the top of the priority queue) can be retrieved . The priority queue is implemented as a container adapter, which encapsulates a specific container class as its underlying container class, and queue provides a set of specific member functions to access its elements. Elements are popped from the "tail" of a particular container, which is called the top of the priority queue.

The use of priority_queue

The priority queue uses vector as its underlying data storage container by default, and the heap algorithm is used on the vector to construct the elements in the vector into a heap structure, so priority_queue is a heap, and you can consider using priority_queue wherever you need to use the heap .

Note: priority_queue is big heap by default.

function declaration Interface Description
priority_queue()/priority_queue(first,last)  Construct an empty priority queue
empty() Check whether the priority queue is empty, return true, otherwise return false
top() Returns the largest (smallest element) in the priority queue, that is, the top element of the heap
push(x) Insert element x into the priority queue
pop() Delete the largest (smallest) element in the priority queue, that is, the top element of the heap

1. By default, priority_queue is a large pile. 

 2. If you put data of a custom type in priority_queue, the user needs to provide > or < overloads in the custom type.

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() {     // A large pile, requiring the user to provide an overload of <     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;     // If you want to create a small heap, you need the user Provides an overload of  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)

Definition of Functor
Functor (Functor), also known as Function Object (Function Object), is a class that can perform function functions. The syntax of the functor is almost the same as our ordinary function call, but as a class of the functor, the operator() operator must be overloaded. Because calling the functor is actually calling the overloaded operator() operator through the class object.

If the programmer wants to use a certain "operation" as a parameter of the algorithm, there are generally two methods:
(1) One way is to design the "operation" as a function first, and then use the function pointer as a parameter of the algorithm.
(2) Design the "operation" as a functor (a class at the language level), and then use the functor to generate an object, and use this object as a parameter of the algorithm.

Obviously, the second method will be better, because the first method is less scalable, and when the function parameters change, it cannot be compatible with the old code. When we write code, we sometimes find that some functional codes will be used continuously. In order to reuse the code, implementing it as a public function is a workaround. However, some variables used by the function may be public global variables. The introduction of global variables is prone to conflicts of the same name and is inconvenient to maintain.

Use the functor, write a simple class, in addition to maintaining the basic member functions of the class, you only need to overload the operator() operator. In this way, the maintenance of some public variables can be avoided, and the reused code can be separated for reuse next time. Moreover, compared to the more excellent properties of functions, functors can also perform dependencies, combinations, and inheritance, which is conducive to resource management.

  In the priority queue, the default is the large root heap. If we need a small root heap, we can use the functor. In C++, functors generally do not need to be written by ourselves, there are library functions. The header file #include <functional> needs to be included.

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();
    }

 Special functors need to be implemented by themselves, such as address comparison.

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;
}

Common classic OJ questions in priority queues (commonly tested in interviews)

 Pointing to Offer 41. Median in Data Streams

How to get the median in a data stream? If an odd number of values ​​are read from the data stream, the median is the value in the middle of all values ​​sorted. If an even number of values ​​are read from the data stream, the median is the average of the middle two numbers after all the values ​​are sorted.

For example,

[2,3,4] has a median of 3

The median of [2,3] is (2 + 3) / 2 = 2.5

Design a data structure that supports the following two operations:

  • void addNum(int num) - Adds an integer from the data stream to the data structure.
  • double findMedian() - Returns the median of all elements so far.

The big top heap stores numbers that are not larger than the median, that is to say, it stores the left half of the ordered array. The
small top heap stores numbers that are larger than the median, that is to say, it stores the right half of the ordered array. Half part
The top of the big top heap represents the largest number in the left half, and
the top of the small top heap represents the smallest number in the right half.
At the same time, the difference between the number of elements stored in the two heaps will not exceed 1.
When two heaps When the total number stored is an odd number, the top of the big top heap (that is, the left half of the heap) is the median.
When the total number stored in the two heaps is even, the median
connection can be obtained by combining the top of the big top stack and the top of the small top stack. Let's consider the insertion problem, that is, how to insert data when new elements come in? 
If the amount of data in the two heaps is even, then first insert into the small top heap to find the minimum value (heap top element), and
then put the minimum value into the large top heap (left half), at this time the two heaps The amount of data is odd, and the median is the top of the big top heap
. If the amount of data in the two heaps is odd, then first insert it into the big top heap to find the maximum value (heap top element), and
then put the maximum value into Go to the small top heap (the right half). At this time, the amount of data in the two heaps is even, and the median is calculated from the top of the two heaps.

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;
        }
    }
};

 Arrange the array into the smallest number

Input an array of non-negative integers, concatenate all the numbers in the array to form a number, and print the smallest one among all the numbers that can be concatenated.

This question seeks the smallest number that can be spliced ​​together, which is essentially a sorting problem. Let the strings of any two numbers in the array nums be x and y, then the sorting judgment rule is stipulated as:

If the concatenated string x+y>y+x, then x is "greater than" y;
otherwise, if x+y<y+x, then x is "less than" y; x
"less than" y means: After sorting is completed, in the array x should be to the left of y; "greater than" vice versa.

class Solution {

public:

    // functor

    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());//Use of functor

        string ret;

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

        {

            ret += a[i];

        }

        return ret;

    }

};

Guess you like

Origin blog.csdn.net/m0_55752775/article/details/129860166