Have you really grasped the essence of "priority queue"?

Preamble

If we assign a number to each element to mark its priority, let's set a lower number to have a higher priority, so that we can access the highest priority element in a collection and find and delete it Operated. In this way, we introduced the data structure of priority queue .

One, the introduction of priority_queue

1. A priority queue is a container adapter whose first element is the largest among all elements according to strict weak sorting criteria.
2. This structure is similar to a heap. Elements can be inserted into the heap at any time, and only the largest heap element (the element at the top in the priority queue) can be retrieved.
3. The priority queue is implemented as a container adapter , which encapsulates a specific container class as its underlying container class , and priority_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 .
4. The underlying container can be any standard container class template, or any other specially designed container class. Containers should be accessible via random access iterators and support the following operations:
empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素
pop_back():删除容器尾部元素
5. The standard container classes vector and deque meet these requirements. By default, vector is used if no container class is specified for a particular priority_queue class instantiation.
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.

Second, the simulation implementation of priority_queue

The functions to be implemented by priority_queue are as follows:

void adjust_up(int child)//push的时候要用
void adjust_down(int parent)//pop的时候要用
void push(const T& x)//在优先级队列中插入元素x
void pop()//删除优先级队列中最大(最小)元素,即堆顶元素
const T& top()//返回优先级队列中最大(最小元素),即堆顶元素
size_t size()//返回队列中的有效数据个数
bool empty()//判空
Since the underlying structure is the sequential structure of the heap, the simulation actually uses the underlying container to implement a heap .

2.1 Member variables

template<class T,class Container=vector<T>,class Compare> 
    class priority_queue
    {
    private:
        Container _con;
    };
The implementation of the underlying container here uses vector by default . One thing to note here is that we do not need to write a constructor here. The default constructor will automatically call the constructor of the underlying container .

2.2 adjust_up()/adjust_down()

We mentioned in the introduction that the structure of the heap used by the priority queue is a large heap by default, and if the heap is used, upward adjustment and downward adjustment are indispensable in the process of push and pop, so we first use this The two functions are completed, and the rest is very simple.

2.2.1 adjust_up()

adjust_up adjusts upwards, usually used for push. After pushing a value at the end, adjust it to the position it should go by adjusting upwards. Here we adopt the pattern of graphic explanation. The following is the above picture
void adjust_up(int child)
        {
            int parent = (child - 1) / 2;
            while (child>0)
            {
                if (_con[parent] < _con[child])
                {
                    swap(_con[parent], _con[child]);
                    child = parent;
                    parent = (child - 1) / 2;
                }
                else
                {
                    break;
                }
            }
        }

2.2.2 adjust_down()

ajust_down adjusts down, used for pop, it will be explained when pop
adjust_down needs to consider the cross-border problem of the left and right children, here you need to pay attention
//向下调整
        void adjust_down(int parent)
        {
            int child = parent * 2 + 1;
            while (child < _con.size())
            {
                //看右孩子是否越界,然后取左右孩子大的值
                if (child + 1 < _con.size() && _con[child + 1] > _con[child])
                {
                    child = child + 1;
                }

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

2.3 push()/pop()

2.3.1 push()

push:在优先级队列中插入元素x
实现逻辑:尾插插入元素x,然后通过adjust_up调整位置,堆的结构不被破坏。
        void push(const T& x)
        {
            _con.push_back(x);
            adjust_up(_con.size()-1);
        }

2.3.2 pop()

pop:删除优先级队列中最大(最小)元素,即堆顶元素
实现逻辑:队列中的 top最后一个数据进行 交换,然后进行 尾删,在对换到top位置的数据进行adjust_down,保证堆的 结构不被破坏
        void pop()
        {
            swap(_con[0], _con[_con.size() - 1]);
            _con.pop_back();
            adjust_down(0);
        }

2.4 top()/size()/empty()

这三个接口的实现比较简单,直接用底层容器的接口实现
        const T& top()
        {
            return _con[0];
        }
        size_t size()
        {
            return _con.size();
        }
        bool empty()
        {
            return _con.empty();
        }

三,priority_queue的精髓

priority_queue的大体框架大致如上,但这并不是它的精髓,那么他的精髓是什么呢?
不知道有没有老铁发现priority_queue的模板参数有三个,而上面的内容只用了前两个,最后一个还没有用,而最后一个就是精髓。
那么它有什么作用呢,众所周知堆分大堆和小堆,大堆top是最大值,小堆top是最小值,而priority_queue也是支持这种转变的,如何实现的呢,就是靠第三个模板参数。
如上图所示就是库中priority_queue第三个参数转为小堆的使用方法,如上可知greater也是一个类,因为他需要传类模板参数,那么greater是什么呢?
这里还需要涉及一个知识点,那就是仿函数。

3.1 仿函数

仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个 operator (),这个类就有了类似函数的行为,就是一个仿函数类了。
需要注意的是,仿函数也需要实例化.

3.2 构建greater()/less()

greater()/less()的构建和上面类似,不过多了一个模板参数
    template <class T>
    class less//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;
        }
    };
而运用less、greater需要注意,less在库中表示的是大堆,而我们想要使用就需要调整位置,调整成<。
然后就可以套用类函数。
    void adjust_up(int child)
        {
            int parent = (child - 1) / 2;
            while (child>0)
            {
                if (_com(_con[parent],_con[child]))//类函数
                {
                    swap(_con[parent], _con[child]);
                    child = parent;
                    parent = (child - 1) / 2;
                }
                else
                {
                    break;
                }
            }
        }

        //向下调整
        void adjust_down(int parent)
        {
            int child = parent * 2 + 1;
            while (child < _con.size())
            {
                //看右孩子是否越界,然后取左右孩子大的值
                if (child + 1 < _con.size() && _con[child + 1]>_con[child])
                {
                    child = child + 1;
                }

                if (_com(_con[parent], _con[child]))//类函数
                {
                    swap(_con[parent], _con[child]);
                    parent = child;
                    child = parent * 2 + 1;
                }
                else
                {
                    break;
                }
            }
        }
成功运行

Guess you like

Origin blog.csdn.net/zcxmjw/article/details/129755383