[C++] The use of STL_priority_queue (priority queue) and the underlying simulation implementation, container adapter, and introduction to the principle of deque

Insert image description here

1、priority_queue

1.1 Introduction and use of priority_queue

priority_queue document introduction
Insert image description here

Translation:
1. A priority queue is a container adapter based on strict weak ordering criteria ,Its first element is always the largest of the elements it contains.
2. This context is similar to heap, can be inserted at any time in the heap elements, and only the largest heap element (the element at the top of the priority queue) can be retrieved.
3. The priority queue is implemented as a container adapter. The container adapter encapsulates a specific container class as its underlying container class. The queue provides a specific set of member functions to access its elements. Elements are popped from the "tail" of a specific container, which is called the top of the priority queue.
4. The underlying container can be any standard container class template, or it can be other specifically designed container classes. The container should be accessible via a random access iterator and support the following operations:
empty(): detect whether the container is empty
size(): Returns the number of valid elements in the container
front(): Returns the reference to the first element in the container

push_back(): At the end of the container Insert element
pop_back(): Delete the element at the end of the container

5. The standard container classes vector and deque meet these requirements a> 6. Need to support random access iterators so that the heap structure is always maintained internally. The container adapter does this automatically by automatically calling the algorithmic functions make_heap, push_heap, and pop_heap when needed. vector is used. By default, if no container class is specified for a particular priority_queue class instantiation, .

1.2 Use of priority_queue

The priority queue uses vector as its underlying data storage container by default, and uses the heap algorithm on the vector to construct the elements in the vector into a heap structure, so priority_queue is a heap Bold style, You can consider using priority_queue wherever the heap is needed. Note: By default priority_queue is a large pile.

function declaration Interface Description
priority_queue()/priority_queue(first,last) Construct an empty priority queue
empty( ) Checks whether the priority queue is empty, returns true if it is, otherwise returns false
top( ) Returns the largest (smallest element) in the priority queue, that is, the top element of the heap
push() 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

Note:
1. By default, priority_queue is a large pile.

#include <vector>
#include <queue>
#include <functional> // greater算法的头文件

int main()
{
    
    
    // 默认情况下,创建的是大堆,其底层按照小于号比较
    vector<int> v{
    
     3,2,7,6,0,4,1,9,8,5 };
    priority_queue<int> q1;
    for (auto& e : v) q1.push(e);
    while (!q1.empty())
    {
    
    
        cout << q1.top() << " ";
        q1.pop();
    }
    cout << endl;

    // 如果要创建小堆,将第三个模板参数换成greater比较方式
    priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
    while (!q2.empty())
    {
    
    
        cout << q2.top() << " ";
        q2.pop();
    }
    cout << endl;

    return 0;
}

Insert image description here

2. If you put custom type data in priority_queue, the user needs to provide the overload of > or < 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()
{
    
    
	// 大堆,需要用户在自定义类型中提供<的重载
	priority_queue<Date> q1;
	q1.push(Date(2018, 10, 29));
	q1.push(Date(2018, 10, 28));
	q1.push(Date(2018, 10, 30));
	cout << q1.top() << endl;

    // 如果要创建小堆,需要用户提供>的重载
	priority_queue<Date, vector<Date>, greater<Date>> q2;
	q2.push(Date(2018, 10, 29));
	q2.push(Date(2018, 10, 28));
	q2.push(Date(2018, 10, 30));
	cout << q2.top() << endl;
}

int main()
{
    
    
    TestPriorityQueue();
    
    return 0;
}

Simulation implementation:

We have just learned that priority_queue is a heap, and the default is a large heap. The underlying container encapsulates vector, so it is relatively simple for us to simulate and implement priority_queue.

#include<vector>
#include<functional>
using namespace std;

namespace lcx
{
    
    
    // 仿函数
    template <class T>
    class Less
    {
    
    
    public:
        bool operator()(const T& x, const T& y)
        {
    
    
            return x < y;
        }
    };

    template <class T>
    class Greater
    {
    
    
    public:
        bool operator()(const T& x, const T& y)
        {
    
    
            return x > y;
        }
    };

    template <class T, class Container = vector<T>, class Compare = Greater<T>>
    class priority_queue
    {
    
    
    public:
        priority_queue()
        {
    
    }

        template <class InputIterator>
        priority_queue(InputIterator first, InputIterator last)
            :_con(first, last)
        {
    
    
            for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
            {
    
    
                adjust_down(i);
            }
        }

        bool empty() const
        {
    
    
            return _con.empty();
        }

        size_t size() const
        {
    
    
            return _con.size();
        }

        const T& top() const
        {
    
    
            return _con[0];
        }

        void push(const T& x)
        {
    
    
            _con.push_back(x);
            adjust_up(_con.size() - 1);
        }

        void pop()
        {
    
    
            swap(_con[0], _con[_con.size() - 1]);
            _con.pop_back();
            adjust_down(0);
        }
    private:
        void adjust_up(int child)
        {
    
    
            int parent = (child - 1) / 2;
            while (child > 0)
            {
    
    
                //if (_con[child] > _con[parent])
                if(comp(_con[child], _con[parent]))
                {
    
    
                    swap(_con[child], _con[parent]);
                    child = parent;
                    parent = (child - 1) / 2;
                }
                else break;
            }
        }

        void adjust_down(size_t parent)
        {
    
    
            size_t child = parent * 2 + 1;
            while (child < _con.size())
            {
    
    
                //if (child + 1 < _con.size()
                //      && _con[child + 1] > _con[child])
                if(child + 1 < _con.size()
                    && comp(_con[child + 1], _con[child]))
                {
    
    
                    child++;
                }

                //if (_con[child] > _con[parent])
                if (comp(_con[child], _con[parent]))
                {
    
    
                    swap(_con[child], _con[parent]);
                    parent = child;
                    child = parent * 2 + 1;
                }
                else break;
            }
        }

    private:
        Container _con;
        Compare comp;
    };
};

If there are students here who are not familiar with heaps, we can read my other article, which specifically explains heaps:Detailed version of heap implementation in C language

2. Container adapter

2.1 What is an adapter

Adapter is a design pattern(A design pattern is a set of code design experiences that are repeatedly used, known to most people, classified and cataloged), a>This mode is to convert the interface of a class into another interface that the customer wants.

2.2 The underlying structure of stack and queue in the STL standard library

Although elements can also be stored in stacks and queues, they are not classified into containers in STL. Instead, they are called container adapters, This is because stack and queue just wrap the interfaces of other containers. In STL, stack and queue use deque by default, such as:
Insert image description here
Insert image description here
Insert image description here

3

3.1 Introduction to the principle of deque

deque (double-ended queue): It is a double-opening "continuous" space data structure. The meaning of double-opening is: it can be in < a i=2>Insert and delete at both ends, and the time complexity is O(1). Compared with vector, the head insertion efficiency is high and there is no need to move elements; compared with list, The space utilization rate is relatively high.

Insert image description here
deque is not a truly continuous space, but is made up of continuous small spaces. The actual deque is similar to a dynamic two-dimensional array, and its underlying structure As shown in the figure below:
Insert image description here
The bottom layer of the double-ended queue is an imaginary continuous space, which is actually segmented and continuous. In order to maintain its "overall continuity" and the illusion of random access, it falls on the deque on the iterator, so the iterator design of deque is more complicated, as shown in the figure below:
Insert image description here
How does deque maintain its imaginary continuity with the help of its iterator? What about structure?
Insert image description here

3.2 Defects of deque

Compared with vector, the advantage of deque is: when inserting and deleting the head, there is no need to move elements, which is very efficient, and when expanding, there is no need to move. A large number of elements, so its efficiency must be high.
Compared with list, its bottom layer is continuous space, the space utilization rate is relatively high, No need to store additional fields.
deque has a fatal flaw: is not suitable for traversal, because during traversal, the iterator of deque needs to frequently detect whether it has moved to a small space. boundaries, leading to inefficiency.
In sequential scenarios, frequent traversal may be required, so
In practice, when a linear structure is required, vector and list are given priority in most cases a>
STL uses it as stack and queue the underlying data structure. one of the applications that can be seen currently is that , there are not many applications of deque, and

4. Why choose deque as the underlying default container for stack and queue?

stack is a special last-in-first-out linear data structure. Therefore, any linear structure with push_back() and pop_back() operations can be used as the underlying container of the stack. For example, both vector and list can be used;
queue is a special linear data structure that is first in, first out. Any linear structure with push_back and pop_front operations can be used as a queue. The underlying container, such as list. However, in STL, deque is selected as the underlying container for stack and queue by default, mainly because:
1. stack and queue do not need to be traversed< a i=6> (so stack and queue have no iterators), you only need to operate on one or both fixed ends. 2. When the elements in the stack grow, deque is more efficient than vector (there is no need to move a large amount of data when expanding); when the elements in the queue grow, deque is not only more efficient, but also has better memory usage. high. combines the advantages of deque and perfectly avoids its shortcomings.

Guess you like

Origin blog.csdn.net/Ljy_cx_21_4_3/article/details/134922511