zoukankan      html  css  js  c++  java
  • 关于优先队列priority_queue大小根堆、重载操作符的说明

    关于priority_queue的说明

    内部实现

    priority_queue 默认情况下,以vector 为底层容器,加上heap(默认max-heap) 处理规则;形成大根堆

    priority_queue被归为 container adapter,也就是对 container 进行封装一层。

    priority_queue 操作规则上是 queue,只允许在尾部加入元素,并从首部取出元素;只不过内部元素具有优先级,优先级高者先出。

    priority_queue 的所有元素进出具有一定规则,所以不提供遍历功能,也不提供迭代器。

    疑惑产生

    下面为priority_queue 的使用规则,第一个传入了类型,第二个为容器类型,第三个为比较函数。

    template<
        class T,
        class Container = std::vector<T>,
        class Compare = std::less<typename Container::value_type>   //comp默认为less
    > class priority_queue;

    疑惑关键就在于比较函数。

    priority_queue 默认形成大根堆,而传入的comp默认为less

    为何传入一个可将序列顺序调为有小到大的函数,建成的堆反而是大顶堆呢?

    不知你们有没有这种感觉?直觉上认为传入less,建成小顶堆,而传入greater,建成大顶堆。

    源码解析

    std::less()源码:若__x < __y,则返回true,顺序不变,否则,顺序发生变化。这个函数名含义与实现效果相一致。

    
    
    struct less : public binary_function<_Tp, _Tp, bool>
    {
        _GLIBCXX14_CONSTEXPR
        bool
        operator()(const _Tp& __x, const _Tp& __y) const
        { return __x < __y; }
    };

    make_heap中调用的__push_heap源码:

    __push_heap(_RandomAccessIterator __first, _Distance __holeIndex,
                _Distance __topIndex, _Tp __value, _Compare __comp)
        //__holeIndex  新添加节点的索引,即叫做孔洞
        //__topIndex  顶端索引
        //__value   新添加节点的值
        //__comp    比较函数,传入为less
    {
      _Distance __parent = (__holeIndex - 1) / 2;   //找到新节点父节点索引
      while (__holeIndex > __topIndex && __comp(*(__first + __parent), __value)) {
          //若孔洞没有到达最顶端  &&  父节点的值小于新添加节点的值,则要进行下列操作
          //less中,左边比右边小则返回true,与less愿意相同
        *(__first + __holeIndex) = *(__first + __parent);   //将父节点的值放入孔洞
        __holeIndex = __parent; //孔洞的索引编程了原来父节点的索引,往上移动了
        __parent = (__holeIndex - 1) / 2;   //那么孔洞的父节点又要继续往上找
      }
      *(__first + __holeIndex) = __value; //将新添加节点的值放在找到的位置(孔洞)
    }

    经过上面源码分析,传入的comp就是为了在比较孔洞节点与父节点的大小,若返回true,才会进行交换操作。

    所以传入less,返回true是因为父节点比孔洞节点小,所以要进行交换,则将大的值移动到前面,所以建成的堆为大顶堆。

    而传入greater,返回true是因为父节点比孔洞节点答,所以进行叫喊,则将大的值移动到了后面,所以建成的堆为小顶堆。

    所以,就明白了为什么传入less反而形成了大根堆,而传入greater则形成了小根堆。

    如何使用

    #include <queue>
    using namespace std;
    ​
    priority_queue<int> que;    //默认定义了最大堆,等同于将第三个参数使用less<int>
    priority_queue<int, vector<int>, less<int>> que;  //定义大根堆
    ​
    priority_queue<int, vector<int>, greater<int>> que;  //定义小根堆,VS下需要加入头文件#include<functional>
    //测试 priority_queue<int> que;
    que.push(3);
    que.push(5);
    que.push(4);
    cout << que.top() << endl;  //5
    //测试 priority_queue<int, vector<int>, less<int>> que;
    que.push(3);
    que.push(5);
    que.push(4);
    cout << que.top() << endl;  //5
    //测试 priority_queue<int, vector<int>, greater<int>> que;
    que.push(3);
    que.push(5);
    que.push(4);
    cout << que.top() << endl;  //3

    关于自定义优先级

    上面给出了在处理整型等基本数据类型时直接出入less或者greater既可以建立相应的大顶堆或者小顶堆。若是处理结构体呢?如何定义优先级呢?

    普通数据类型

    基本数据类型的比较函数可以直接使用less<int>或者greater<int>可以满足建立大根堆或者小根堆。

    结构体

    对于结构体而言,将结构体放入优先队列中,比较函数需要建立在针对结构体的具体成员。

    参考https://www.cnblogs.com/flipped/p/5691430.html 博客自定义优先级。

    自定义优先级的三种方法:

    1.<以成员函数重载:

    struct Node {   //我们将Node节点放入优先队列中希望以value进行比较
        Node(int _id, int _value) : id(_id), value(_value){}
        int id;
        int value;
    };
    ​
    //大根堆
    bool operator < (const Node& a, const Node& b)
    {
        return a.value < b.value; //将value的值由大到小排列,形成Node的大根堆
    }
    ​
    int main()
    {  
        struct Node node1(1, 5);
        struct Node node2(2, 3);
        struct Node node3(3, 4);
        
        priority_queue<Node> que;
        
        que.push(node1);
        que.push(node2);
        que.push(node3);
        
        cout << que.top().value << endl;    //5
    }
    
    //小根堆
    bool operator < (const Node& a, const Node& b)
    {
        return a.value > b.value; //将value的值由小到大排列,形成Node的小根堆
    }
    ​
    cout << que.top().value << endl;    //3

    我试了 bool operator > (),结果会报二进制“<”: 没有找到接受“const Node”类型的左操作数的运算符(或没有可接受的转换) 错误,我想原因应该是这样吧:priority_queue中默认的比较函数为less,less函数中只用到了 { return __x < __y; },所以重载中若只重载了>,函数找不到<,所以会出现错误。

    struct less : public binary_function<_Tp, _Tp, bool>
    {
        _GLIBCXX14_CONSTEXPR
        bool
        operator()(const _Tp& __x, const _Tp& __y) const
        { return __x < __y; }
    };
     

    2.自定义比较函数:

    
    
    struct cmp{
    ​
        bool operator ()(const Node& a, const Node& b)
        {
            return a.value < b.value;//将value的值由大到小排列,形成Node的大根堆
        }
    };
    priority_queue<Node, vector<Node>, cmp>q;
    cout << que.top().value << endl;    //5
    
    struct cmp{
    ​
        bool operator ()(const Node& a, const Node& b)
        {
            return a.value > b.value;//将value的值由小到大排列,形成Node的小根堆
        }
    };
    priority_queue<Node, vector<Node>, cmp>q;
    cout << que.top().value << endl;    //3

    上述在传入用户自定义的比较函数,那么在建堆过程中使用的comp函数即为我们自定义的cmp,这样分析同上。

    3.<以类成员函数重载

    struct Node {    //我们将Node节点放入优先队列中希望以value进行比较
        Node(int _id, int _value) : id(_id), value(_value){}
        int id;
        int value;
        //大根堆
        bool operator < (const Node& b) const    //注意,此处若没有const则会报错
        {
            return value < b.value; //将value的值由大到小排列,形成Node的大根堆
        }
    
    };
    
    cout << que.top().value << endl;  //5

    4.<以友元函数重载

    struct Node{
        int id;
        int value;
        friend bool operator<(const Node& a,const Node& b){
            return a.value<b.value;  //按value从大到小排列
        }
    };
    priority_queue<Node>q;    

    刚开始不太明白友元操作符重载函数,先了解下重载友元

    重载类型

    参考:https://www.cnblogs.com/Mayfly-nymph/p/9034936.html

    综合,在C++中,操作符重载实现通过类成员函数全局函数(非类成员函数)友元函数(不是类成员却能够访问类的所有成员 )。

    上面对<重载使用到类全局函数、友元函数、类成员函数

    使用友元函数重载有两个优点:和普通函数重载相比,它能够访问类的非公有成员将双目运算符重载为友元函数,这样就可以使用交换律

    交换律可以理解成复数加法中,以成员函数的形式重载 +,只能计算 c + 15.6,不能计算 28.23 + c,这是不对称的 。

    何为友元

    友元函数

    类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。

    尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

    友元可以是一个函数,该函数被称为友元函数

    友元也可以是一个,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

    如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend。如下设置友元函数:

    
    
    class Box
    {
       double width;
    public:
       double length;
       friend void printWidth( Box box );
       void setWidth( double wid );
    };

    声明类 Usb 的所有成员函数作为类 Phone 的友元,需要在类 Phone 的定义中放置如下声明:

    class Usb
    {
    private:
        int size;
        friend class Phone;  //声明 Phone为友元类
    };
    class Phone
    {
    public:
        Usb usb;
        void setPhone() 
    { usb.size
    += 1000; //因Phone是Usb的友元类,故此处可以访问其私有成员 } };

  • 相关阅读:
    JQ分页练习
    Dom1
    JQ轮播图
    Dom操作
    DYR
    jQ点击事件
    [z]vc boost安装
    [z] .net与java建立WebService再互相调用
    [z]
    git常用命令二
  • 原文地址:https://www.cnblogs.com/chenleideblog/p/12745271.html
Copyright © 2011-2022 走看看