[Fun with c++] introduction and simulation implementation of priority_queue

Topic of this issue: introduction and simulation implementation of priority_queue

Blog homepage: Xiaofeng

Share the editor's knowledge and problems encountered in Linux

The ability of the editor is limited, and if there are mistakes, I hope everyone will give me a lot of help.

  1. Introduction and use of priority_queue

1.1. priority_queue introduction

1. A priority queue is a container adapter whose first element is always the largest among the elements it contains according to strict weak ordering criteria.
2. Similar to a heap, elements can be inserted at any time in the heap, 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 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. The container should be accessible via a random access iterator and support the following operations: empty(): check if the container is empty size(): return the number of valid elements in the container front(): return a reference to the first element in the container push_back( ): Insert elements at the end of the container pop_back(): Delete elements at the end of the container 5. The standard container classes vector and deque meet these requirements. By default, if no container class is specified for a specific priority_queue class instantiation, vector is used, and deque is generally not used, because deque's random access is not efficient. The efficiency of building a heap is very low. 6. Need to support random access iteration to keep the heap structure internal at all times. The container adapter does this automatically by automatically calling the algorithmic functions make_heap, push_heap, and pop_heap when needed.




1.2.priority_queue使用

priority_queue的接口很简单,但是他的结构很厉害.
比如在使用堆排序,topK 问题的时候 ,是无可替代的.
  1. 模拟实现

直接上源码
namespace zxf
{

    //仿函数
    template<class T>
    struct lass
    {
        bool operator()(const T& x, const T& y)
        {
            return x < y;
        }
    };

    template<class T>
    struct greater
    {
        bool operator()(const T& x, const T& y)
        {
            return x > y;
        }
    };


    //优先级队列其实就是一个堆
    //底层也是一种适配器模式,可以使用 vector 或者 deque 来适配。
    //priority_queue的底层数据结构必须满足可以随机访问,
    //所以 只有 vector 和 deque,满足。
    template<class T,class container = vector<T>, class compare = lass<T>>//默认使用 lass 建立大堆
    class priority_queue
    {
        container _con;
    public:
        //向上调整
        void adjust_up(size_t child)
        {
            compare com;
            size_t parent;
            while (child > 0){//注意这里 child 不能等于 0 
                parent = (child - 1) / 2;
                if (com( _con[parent] ,_con[child])){
                    std::swap(_con[child], _con[parent]);
                    child = parent;
                }
                else{
                    break;
                }
            }
        }

        //向下调整
        void adjust_down(size_t parent)
        {
            //parent*2+1=left_child
            //parent*2+2=right_child
            compare com;
    
            size_t child = parent * 2 + 1;
            while (child < _con.size())
            {
                if (child + 1 < _con.size() && _con[child + 1] > _con[child])
                {
                    child++;
                }
                if (com(_con[parent], _con[child]))
                {
                    swap(_con[child], _con[parent]);
                    parent = child;
                    child = parent * 2 + 1;
                }
                else{
                    break;
                }
            }
        }

        priority_queue() {}

        template <class InputIterator>
        priority_queue(InputIterator first, InputIterator last)
            :_con(first, last)
        {
            //while (first != last)
            //{
            //    push(*first);
            //    ++first;
            //}
            

            //也可以
            //_con(first, last);
            //(child-1) / 2 = parent
            for (int i = (_con.size() - 1 - 1) / 2 ; i >= 0;i--)
            {
                adjust_down(i);
            }
        }

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

        bool empty()
        {
            return _con.empty();
        }
        size_t size()
        {
            return _con.size();
        }

        //堆顶的元素不允许修改,因为修改可能会导致结构破坏。
        const T& top()const
        {
            return _con[0];
        }
    };
}
  1. 常见问题

3.1.仿函数

仿函数,顾名思义就是仿照的函数.通常情况下会
//仿函数
//其实是一个类
//他的实例化出来的对象,可以像函数一样去使用。
namespace zxf
{
    template<class T>
    struct lass
    {
        bool operator()(const T& x,const T& y)
        {
            return x < y;
        }
    };

    template<class T>
    struct greater
    {
        bool operator()(const T& x, const T& y)
        {
            return x > y;
        }
    };
}
这个仿函数(类),实例化出来的对象可以像函数一样去使用
int main()
{
    //需要先给模板显式实例化一下。
    //greater是类名(类模板),greater<int> 是一个类型。
    zxf::greater<int> greaterfunc;
    //greaterfunc 是类型实例化出来的一个对象。其实和 vector<int> v; 相同。
    zxf::lass<int> lassfunc;

    //只不过这个自定义类型里面没有成员变量,是有一个函数调用访问符() 的重载。
    cout << greaterfunc(2, 1) << endl;//1
    cout << greaterfunc.operator()(2, 1) << endl;//1
    
    cout << lassfunc(2, 5) << endl;//1
    cout << lassfunc.operator()(2, 5) << endl;//1


    //这里不是
    return 0;
}
在priority_qeueue底层实现的时候就用到了仿函数 lass 和 greater 来控制建立大堆还是小堆.

3.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(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;
}
这里重载> 和 < 是为了供 函数greater或者lass 里面调用的.

注意 :
我们知道假如堆里面的每一个元素是自定义类型,我们只需要在类里面 重载 < 和> 即可.
但是如果是一个自定义的指针我们怎么办.
假如里面模板实例化的类型是Date* (自定义类型的指针) 的类型怎么办.
这个也简单,就是默认使用的是库里面的仿函数lass 和 greater ,底层就是通过 运算符< 和> 来比较的,但是我们自己写一个仿函数传给他,按照我们自己的意愿来比较就可以.

template <class T>
struct Date_lass
{
    bool operator()(const T& t1 , const T& t2)
    {
        return (*t1) < (*t2);//直接复用Date类里面的重载即可。

    }
};

template <class T>
struct Date_greater
{
    bool operator()(const T& t1, const T& t2)
    {
        return (*t1) > (*t2);//直接复用Date类里面的重载即可。

    }
};

void TestPriorityQueue()
{
    //我们可以看到这里每个堆元素是 Date* (日期类的指针)
    //如果我们还继续使用 库函数给我提供的 ;lass 和 greater 仿函数
    //直接就是地址相比。不是我们想要的效果。
    zxf:: priority_queue<Date*, vector<Date*>, Date_lass<Date*>> q1;//建立大堆
    q1.push(new Date(2018, 10, 29));
    q1.push(new Date(2018, 10, 28));
    q1.push(new Date(2018, 10, 30));
    cout << *(q1.top()) << endl;

    zxf::priority_queue<Date*, vector<Date*>, Date_greater<Date*>> q2;//建立小堆
    q2.push(new Date(2018, 10, 29));
    q2.push(new Date(2018, 10, 28));
    q2.push(new Date(2018, 10, 30));
    cout << *(q2.top()) << endl;
}


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

Guess you like

Origin blog.csdn.net/zxf123567/article/details/129454910