[Diversión con c++] introducción e implementación de simulación de priority_queue

Tema de este número: introducción e implementación de simulación de priority_queue

Página de inicio del blog: Xiaofeng

Comparta el conocimiento del editor y los problemas encontrados en Linux

La capacidad del editor es limitada, y si hay errores, espero que todos me brinden mucha ayuda.

  1. Introducción y uso de la cola_prioridad

1.1.priority_queue introducción

1. Una cola de prioridad es un adaptador de contenedor cuyo primer elemento es siempre el más grande entre los elementos que contiene de acuerdo con criterios estrictos de ordenamiento débil.
2. Similar a un montón, los elementos se pueden insertar en cualquier momento en el montón, y solo se puede recuperar el elemento más grande del montón (el elemento en la parte superior de la cola de prioridad) .
3. La cola de prioridad se implementa como un adaptador de contenedor, que encapsula una clase de contenedor específica como su clase de contenedor subyacente, y la cola proporciona un conjunto de funciones miembro específicas para acceder a sus elementos. Los elementos se extraen de la "cola" de un contenedor en particular, que se denomina la parte superior de la cola de prioridad.
4. El contenedor subyacente puede ser cualquier plantilla de clase de contenedor estándar o cualquier otra clase de contenedor especialmente diseñada. El contenedor debe ser accesible a través de un iterador de acceso aleatorio y admitir las siguientes operaciones: vacío (): verifica si el contenedor está vacío. tamaño (): devuelve la cantidad de elementos válidos en el contenedor. en el contenedor push_back( ): inserta elementos al final del contenedor
pop_back(): elimina elementos al final del contenedor
5. Las clases de contenedor estándar vector y deque cumplen con estos requisitos. De forma predeterminada, si no se especifica ninguna clase de contenedor para una instancia de clase de prioridad_cola específica, se usa el vector y generalmente no se usa deque, porque el acceso aleatorio de deque no es eficiente. La eficiencia de construir un montón es muy baja. 6. Necesidad de soporte iteración de acceso aleatorio
para mantener la estructura del montón interna en todo momento. El adaptador de contenedor hace esto automáticamente llamando automáticamente a las funciones algorítmicas make_heap, push_heap y pop_heap cuando es necesario.

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

Supongo que te gusta

Origin blog.csdn.net/zxf123567/article/details/129454910
Recomendado
Clasificación