• 【c++】模拟实现优先级队列(priority_queue)


    全部代码

    • 以容器适配器的玩法来实现,底层容器默认为vector

    • 使用了模板参数T表示存储在队列中的元素类型,Container表示底层容器类型,默认为vector,Compare表示比较器类型,默认为less。

    • adjustDown函数用于向下调整堆,保持堆的性质。它从指定的父节点开始,将其与子节点进行比较,如果子节点的值更大,则交换父节点和子节点的位置,并继续向下调整直到满足堆的性质。

    • adjustUp函数用于向上调整堆,保持堆的性质。它从指定的子节点开始,将其与父节点进行比较,如果子节点的值更大,则交换父节点和子节点的位置,并继续向上调整直到满足堆的性质。

    • 构造函数可以接受一个迭代器范围[first, last],用于初始化优先队列。在构造过程中,首先将迭代器范围内的元素存储到底层容器中,然后从最后一个非叶子节点开始,依次调用adjustDown函数,使得整个容器满足堆的性质。

    • empty函数用于判断优先队列是否为空,即底层容器是否为空。

    • size函数用于返回优先队列中元素的个数,即底层容器的大小。

    • top函数用于返回优先队列中的最大元素(根节点),但并不删除该元素。

    • push函数用于将一个元素插入到优先队列中。它将元素添加到底层容器的末尾,并调用adjustUp函数向上调整堆。

    • pop函数用于删除优先队列中的最大元素(根节点)。它首先将根节点与最后一个叶子节点交换位置,然后删除最后一个叶子节点,并调用adjustDown函数向下调整堆。

    1. #pragma once
    2. #include<vector>
    3. namespace hqj
    4. {
    5.     template <class T, class Container = vector<T>class Compare = less<T> >
    6.     class priority_queue
    7.     {
    8.     private:
    9.         void adjustDown(int parent)
    10.         {
    11.             int child = parent * 2 + 1;
    12.             while (child < _c.size())
    13.             {
    14.                 if (child + 1 < _c.size() && _comp(_c[child], _c[child+1]))
    15.                 {
    16.                     child++;
    17.                 }
    18.                 if (_comp(_c[parent], _c[child]))
    19.                 {
    20.                     swap(_c[parent], _c[child]);
    21.                     parent = child;
    22.                     child = parent * 2 + 1;
    23.                 }
    24.                 else
    25.                 {
    26.                     break;
    27.                 }
    28.              
    29.             }
    30.         }
    31.         void adjustUp(int child)
    32.         {
    33.             int parent = (child - 1/ 2;
    34.             while (child > 0)
    35.             {
    36.                 if (_comp(_c[parent],_c[child]))
    37.                 {
    38.                     swap(_c[parent], _c[child]);
    39.                     child = parent;
    40.                     parent = (child - 1/ 2;
    41.                 }
    42.                 else
    43.                 {
    44.                     break;
    45.                 }
    46.             }
    47.         }
    48.     public:
    49.         priority_queue()
    50.         {}
    51.         template <class InputIterator>
    52.         priority_queue(InputIterator first, InputIterator last)
    53.             :_c(first,last)
    54.         {
    55.             for (int i = _c.size() - 1 - 1; i >= 0; i++)
    56.             {
    57.                 adjustDown(i);
    58.             }
    59.         }
    60.         bool empty() const
    61.         {
    62.             return _c.empty();
    63.         }
    64.         size_t size() const
    65.         {
    66.             return _c.size();
    67.         }
    68.         T& top() const
    69.         {
    70.             return (T&)_c.front();
    71.         }
    72.         void push(const T& x)
    73.         {
    74.             _c.push_back(x);
    75.             adjustUp(_c.size() - 1);
    76.         }
    77.         void pop()
    78.         {
    79.             swap(_c.front(), _c.back());
    80.             _c.pop_back();
    81.             adjustDown(0);
    82.         }
    83.     private:
    84.         Container _c;
    85.         Compare _comp;
    86.     };
    87. };

    私有成员

    • _c容器对象:缺省的容器类型是vector

    • _comp比较器对象

    1. Container _c;
    2. Compare _comp;

    构造函数

    • 由于我们模拟实现的优先级队列是一个容器适配器,私有成员中不含内置类型。利用系统生成的默认构造函数特性,会自动调用私有成员的构造函数,所以我们可以不写该优先级队列的构造函数的内容

    1.  priority_queue()
    2.       {}

    析构函数

    • 同理根据系统默认生成的析构函数特性,当对象销毁时会自动调用自定义类型的析构函数,我们也可以不写

    向下调整函数

    • 向下调整函数的参数是父节点的下标

    • 首先我们通过父亲下标找到其左孩子

    • 随后我们通过比较左右孩子大小来确定父亲要与哪个孩子进行比较、交换,至于是选择较大孩子还是较小孩子要根据比较器_comp的返回值来确定,如果我们想建小堆则选取较小的孩子;若我们要建大堆,则选取较大的孩子

    • 交换父亲和孩子,并且更新父亲和孩子

    • 由于向下调整次数不一定唯一,我们需要用到while结构,循环终止条件为:child下标越界、父子间的大小关系不满足比较器的要求。

    1.  void adjustDown(int parent)
    2.       {
    3.           int child = parent * 2 + 1;
    4.           while (child < _c.size())
    5.           {
    6.               if (child + 1 < _c.size() && _comp(_c[child], _c[child+1]))
    7.               {
    8.                   child++;
    9.               }
    10.               if (_comp(_c[parent], _c[child]))
    11.               {
    12.                   swap(_c[parent], _c[child]);
    13.                   parent = child;
    14.                   child = parent * 2 + 1;
    15.               }
    16.               else
    17.               {
    18.                   break;
    19.               }
    20.            
    21.           }
    22.       }

    向上调整函数

    • 向上调整函数的参数是孩子的下标

    • 首先通过孩子的下标找到父亲的下标

    • 当父子关系满足比较器_comp的要求时进行调整,交换父亲和孩子,并更新父亲和孩子的下标

    • 同样,向上调整不止一次,需要用到while结构,循环终止条件为:孩子为根节点、父亲和孩子间的大小关系不满足比较器_comp要求

    1.   void adjustUp(int child)
    2.       {
    3.           int parent = (child - 1/ 2;
    4.           while (child > 0)
    5.           {
    6.               if (_comp(_c[parent],_c[child]))
    7.               {
    8.                   swap(_c[parent], _c[child]);
    9.                   child = parent;
    10.                   parent = (child - 1/ 2;
    11.               }
    12.               else
    13.               {
    14.                   break;
    15.               }
    16.           }
    17.       }

    迭代器构造函数

    • 由于模拟实现的优先级队列是容器适配器,直接使用底层容器的迭代器构造就行了,读入要建堆的数据

    • 都读入后,先找到最后一个叶子节点的父亲节点,随后传递该节点下标进向下调整函数,开始依次向下调整

    • 以arr数组为例:

    1.    priority_queue(InputIterator first, InputIterator last)
    2.           :_c(first,last)
    3.       {
    4.           for (int i = _c.size() - 1 - 1; i >= 0; i--)
    5.           {
    6.               adjustDown(i);
    7.           }
    8.       }

    empty函数

    • 还是底层容器接口的复用,调用底层容器的empty函数

    1.  bool empty() const
    2.       {
    3.           return _c.empty();
    4.       }

    size函数

    • 底层容器接口的复用,调用底层容器的size函数

    1.    size_t size() const
    2.       {
    3.           return _c.size();
    4.       }

    top函数

    • 函数作用是返回优先级队列队头元素(也就是堆顶元素),而该元素正好是底层容器的首元素,复用front接口就行,记得要强转

    1.      T& top() const
    2.      {
    3.          return (T&)_c.front();
    4.      }

    push函数的实现

    • 调用底层容器的push_back函数实现插入功能,然后再进行向上调整堆

    1.       void push(const T& x)
    2.       {
    3.           _c.push_back(x);
    4.           adjustUp(_c.size() - 1);
    5.       }

    pop函数的实现

    • 首先交换队头元素和队尾元素(堆顶元素和堆尾元素)

    • 将队尾元素删除

    • 从堆顶开始向下调整堆

    1.       void pop()
    2.       {
    3.           swap(_c.front(), _c.back());
    4.           _c.pop_back();
    5.           adjustDown(0);
    6.       }
  • 相关阅读:
    exe文件运行后无输出直接闪退如何找解决办法
    【无标题】
    每月固定日期提醒app用哪个?手机上可固定日期提醒的工具选择哪一个
    UEFI实战——键盘操作
    计算机毕设(附源码)JAVA-SSM基于的校园疫情防控管理
    Worthington丨Worthington胰蛋白酶化学性质及相关研究
    PSCA电源控制集成之分布式PPU
    裴蜀定理(详解)
    Vue--解决Scss报错:Syntax Error: TypeError: this.getOptions is not a function
    xml2txt
  • 原文地址:https://blog.csdn.net/ZHENGZJM/article/details/134008331