当前位置:网站首页>【stack】【queue】【priority_queue】【deque】Detailed explanation

【stack】【queue】【priority_queue】【deque】Detailed explanation

2022-08-09 23:03:00 Blade Cc

Ⅰ. stack的介绍和使用

1.stack的概念

文档介绍:stack - C++ Reference

  1. stack 是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行 元素的插入与提取操作.

  2. stack 是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定 的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出.

  3. 标准容器 vector、deque、list 均符合这些需求,默认情况下,如果没有为 stack 指定特定的底层容器, 默认情况下使deque(双向队列).

  1. stack 的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:

    • empty: 判空操作
    • back: 获取尾部元素操作
    • push_back: 尾部插入元素操作
    • pop_back: 尾部删除元素操作

通过观察文档我们不难发现,接口相较于之前的 string、vectorlist 少了很多.它甚至连拷贝构造析构There is no your own,However, these benefit from容器适配器的使用.

不难发现, stack、queue 也没有迭代器,这也不难理解,Because stacks and columns are not originally used for traversal,Doing so will only undermine their principles.

2.stack的使用

函数说明接口说明
stack()构造空的栈
empty()检测 stack 是否为空
size()返回 stack 中元素的个数
top()返回栈顶元素的引用
push()将元素 val 压入 stack 中
pop()将 stack 中尾部的元素弹出

测试代码:

#include <iostream>
#include <stack>
using namespace std;
 
void test_stack() {
    
    /* 创建一个存储整型的栈 */
    stack<int> st;
 
    /* 入栈 */
    st.push(1);
    st.push(2);
    st.push(3);
    st.push(4);
 
    /* 打印栈 */
    while (!st.empty()) {
               // 如果栈不为空则进入循环
        cout << st.top() << " ";    // 打印栈顶元素
        st.pop();                   // 出栈
    }
    cout << endl;
}
 运行结果:4 3 2 1

Ⅱ.queue的介绍和使用

1.queue的概念

队列只允许在一端进行插入数据操作,在另一端进行删除数据操作 的特殊线性表.

入队列,进行插入操作的一端称为 队尾.出队列,进行删除操作的一端称为 队头.

队列中的元素遵循先进先出的原则,即 FIFO 原则(First In First Out).

文档介绍:queue - C++ Reference

  • 队列是一种容器适配器,专门用于在FIFO上下文**(先进先出)**中操作,其中从容器一端插入元素,另一端 提取元素.
  • 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue 提供一组特定的 成员函数来访问其元素.元素从队尾入队列,从队头出队列.
  • 标准容器类 dequelist 满足了这些要求.默认情况下,如果没有为 queue 实例化指定容器类,则使用标 准容器 deque(双向队列).
  • 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类.该底层容器应至少支持以下操作:
    • empty: 检测队列是否为空
    • size: 返回队列中有效元素的个数
    • front: 返回队头元素的引用
    • back: 返回队尾元素的引用
    • push_back: 在队列尾部入队列
    • pop_front: 在队列头部出队列

2.queue的使用

函数声明接口说明
queue()构造空的队列
empty()检测队列是否尾空,是返回 ture,否则返回 false
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在对尾将元素 val 入队列
pop()将队头元素出队列

代码演示:queue

#include <iostream>
#include <queue>
using namespace std;
 
void test_queue() {
    
    /* 创建一个存储整型的队列 */
    queue<int> Q;
 
    /* 入队 */
    Q.push(1);
    Q.push(2);
    Q.push(3);
    Q.push(4);
 
    /* 打印队列 */
    while (!Q.empty()) {
                 // 如果队列不为空则进入循环
        cout << Q.front() << " ";    // 打印队头元素
        Q.pop();                     // 出队
    }
    cout << endl;
}
 运行结果:4 3 2 1

Ⅲ.priority_queue(优先级队列)的介绍和使用

1.优先级队列的概念

文档介绍:priority_queue - C++ Reference

  1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的The first element is always the largest of the elements it contains.

  2. Similar to the context,在Elements can be inserted into the heap at any time,并且只能检索最大堆元素(优先队列中位于顶部的元素).

  3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素.元素从特定容器的“尾部”弹出,其称为优先队列的顶部.

  4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类.容器应该可以通过随机访问迭代器访问,并支持以下操作:

    • empty(): 检测容器是否为空
    • size(): 返回容器中有效元素个数
    • front(): 返回容器中第一个元素的引用
    • push_back(): 在容器尾部插入元素
    • pop_back(): remove elements at the end of the container
  5. 标准容器类 vectordeque 满足这些需求.默认情况下,如果没有为特定的 priority_queue 类实例化指定容器类,则使用vector .

  6. 需要支持随机访问迭代器,以便始终在内部保持堆结构.容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作.

2.优先级队列的使用

It can be seen from the above concepts,A priority queue is a heap,而且默认是个大堆

优先级队列默认使用 vector 作为其底层存储数据的容器,

vector 上又使用了堆算法将 vector 中元素构造成堆的结构,因为 priority_queue 就是堆.

所有需要用到的地方,都可以考虑使用 priority_queue.

优先级队列默认大的优先级高,传的是 less 仿函数,底层是一个大堆;

如果想控制小的优先级高,需手动传 greater 仿函数,其底层是一个小堆.

It may be because the large heap is descending from large to small by default,Therefore, the functor name is set to less 哈哈.

(The functor will be described in detail when we implement it below.)

函数声明接口说明
priority_queue() / priority_queue(first, last)构造一个空的优先级队列 / Constructs a priority queue of iterator range elements
empty()检测优先级队列是否为空,是返回 true,否则返回 false
top()返回优先级队列中最大(最小)元素,即堆顶元素
push(x)在优先级队列中插入元素 x
pop()删除优先级队列中最大(最小)元素,即堆顶元素

Definition of Priority Queue:

priority_queue<int> qe;
		(默认情况下)		
priority_queue<int, vector<int>, less<int>> qe;

//If you change the adapter container tolist
priority_queue<int, list<int>, less<int>> qe;

注: If you need to modify the functor,then the second parameter of the template cannot be skipped Container to set the functor directly,Because the template cannot be like this automatic identification is you want to modify the parameters of the second or third,So pass the second parameter too(下面是定义,仔细观察缺省值部分)

template <class T, class Container = vector<T>,
  class Compare = less<typename Container::value_type> > class priority_queue;

如下就是错误示范:

priority_queue<int, greater<int>>     //没有传第二个参数!
  1. 默认情况下,priority_queue是大堆.

代码演示:

#include <vector>
#include <queue>
#include <functional> // greater算法的头文件
void TestPriorityQueue()
{
    
     // 默认情况下,创建的是大堆,其底层按照小于号比较
     vector<int> v{
    3,2,7,6,0,4,1,9,8,5};
     priority_queue<int> q1;
     for (auto& e : v)
     	q1.push(e);
     cout << q1.top() << endl;
    
     // 如果要创建小堆,将第三个模板参数换成greater比较方式
     priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
     cout << q2.top() << endl;
}
 运行结果:9  0

解读: 默认是用 vector 存储的,注意这里没有明确指定 less 还是 greater,所以默认为 less.

  1. 如果在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; 
}
 运行结果:
2018-10-30
2018-10-28

Ⅳ.deque(双端队列)

1.deque(double-ended-queue) 的原理介绍

deque 是一个双端队列,是一种双开口的连续空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),vector 比较,头插效率高,不需要搬移元素;与 list 比较,空间利用率比较高.

​ 它提供了和 vector Similar interface but the underlying implementation and vector 却完全不同,最大的区别在于 vector All elements of are consecutive,但是 deque 不全是!因为 deque 也像 list 一样,按需申请空间,但是和 list 不一样的是,deque The requested space is not just the size of a space,而是多个,According to the structure,like a dynamic two-dimensional array!

dequeThe underlying implementation is to convert a continuous space Manage in segments,and use their first address with a 指针数组 进行管理,Such a special storage structure makes it increase the ratio of elements at the head and tailvector更加高效,But the underlying implementation is more complicated,A lot of extra information is stored.Add elements at the head and tail if thrown,Add an element anywhere in the middle,它的效率比 vector 更高,但是比 list 要低.Functionally it is vector 和 list 的结合!

问题: deque looks so naughty,Can it be used instead of ours? vectorlist 呢!

解答: 肯定不行!因为 deque More suitable for head and tail insertion and deletion,但是Not suitable for a large number of intermediate insertions and deletions and a large number of random accesses,Not as efficient as using it alone listvector 那么高.实际中 deque as a linear table structure,一般作为 stackqueue 的默认适配容器.所以 deque is difficult to replace 代 vectorlist 的!只是说 deque With functions of both!

2.deque 的常用接口

构造函数

deque();                                                  //Constructs an empty deque
deque(size_type n, const value_type& val = value_type()); //用n个值为valThe elements of the construct deque
deque(InputIterator first, InputIterator last);           //用[first, last)The interval constructs a deque
deque(const deque &x);                                    //Copy constructor for deque

迭代器相关

iterator begin();                       //返回dequestart position iterator
iterator end();                         //返回deque最后一个元素下一个位置的迭代器
reverse_iterator rbegin();              //返回dequereverse iterator of the starting position(即end())
reverse_iterator rend();                //返回deque最后一个元素下一个位置的反向迭代器(begin())

const_iterator cbegin() const;          //返回deque起始位置的const迭代器
const_iterator cend() const;            //返回deque最后一个元素下一个位置的const迭代器
const_reverse_iterator crbegin() const; //返回deque起始位置的const反向迭代器(即crend())
const_reverse_iterator crend() const;   //返回deque最后一个元素下一个位置的const反向迭代器(crbegin())

容量相关

size_type size() const;                           //返回deque中有效元素个数
bool empty() const;                               //检测deque是否为空,是返回true,否则返回false
void resize (size_type n, const T& val = T());    //将dequeThe elements in change tosz,多出的空间用val填充

增删查改

reference operator[](size_type n);                         //返回deque中na reference to the element at the position
reference front();                                         //返回deque中首元素的引用
reference back();                                          //返回deque中最后一个元素的引用

void push_back(const value_type &val);                     //deque尾部插入元素val
void pop_back();                                           //删除deque尾部元素
void push_front(const value_type &val);                    //deque头部插入元素val
void pop_front();                                          //删除deque头部元素

//在deque的position位置插入值为val的元素
iterator insert(iterator position, const value_type &val); 
//在deque的position位置插入n个值为val的元素
void insert(iterator position, size_type n,const value_type &val);
//在deque的position位置插入[first, last)区间中的元素
void insert(iterator position, InputIterator first, InputIterator last); 
//删除deque中position位置的元素,and returns the next position of that position
iterator erase(iterator position);                                       
//删除deque中[first, last)区间中的元素,并返回last位置
iterator erase(iterator first, iterator last);         
//交换两个deque中的内容
void swap(deque & x);          
//将deque中的元素清空
void clear();             

//在deque的positionposition constructor,Pass the required content of the element through the parameter class table
iterator emplace(const_iterator position, Args && ... args);             
void emplace_front(Args && ... args);          //在dequeThe head construction element of,The parameters of the element are passed in through the parameter list
void emplace_back(Args && ... args);           //在dequeThe tail of the structural elements,The parameters of the element are passed in through the parameter list

测试代码:

void testDeque()
{
    
    deque<int> dq;
    dq.push_back(1);
    dq.push_back(2);
    dq.push_back(3);
    dq.push_back(2);

    deque<int>::iterator it = dq.begin();
    while (it != dq.end())
    {
    
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    it = dq.erase(--it);
    it = dq.begin();
    while (it != dq.end())
    {
    
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}
 运行结果:
1 2 3 2
1 2 3

3.deque 的实现原理

  • deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,

  • 双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其 “整体连续” 以及随机访问的假象,落在了 deque的迭代器身上,因此deque的迭代器设计就比较复杂,

  • 那 deque 是如何借助其迭代器维护其假想连续的结构呢?

​ 由于dequeis not completely contiguous in memory and therefore wants to keep deque 的连续性,This task falls to the iterator.在底层实现上,dequeWill be a period of continuous memory is called a 缓冲区(buffer),and store the start and end addresses of these buffers in a map used for mapping,map The address of one of the storage buffers corresponds to a 结点(node) Information used to mark this key-value pair,This builds the infrastructure.

​ stored in the iterator4个信息,分别是当前结点(cur),the header of the current buffer(first),the end of the current buffer(last) 以及在 map used to mark the address of the current buffer in 结点(node) 信息.并且在dequeTwo iterators have been stored internallystartfinish用于标记deque的头和尾元素.In this way, the purpose of forming a continuous space in a logical structure can be accomplished.

​ When the traverse from the beginningdeque时,start迭代器中firstlast已经从 map Found the buffer head and tail information of the first node and saved it,于是cur就从firststart traversing this buffer,当遍历到lastcome again map Find the buffer end address of the write a node in and replace the originalfirstlast值,继续遍历,This completes the traversal until the last node is also traversed.

总结

双端队列dequeis a container that was not designed to be successful,If you want random access to a simple query, you can usevector而且更加方便,If frequent inserts are required thenlistEfficiency will be higher,因此deque并不常用,其The most commonly used place is as an adapterstackqueueThe underlying storage container for.

4.deque的缺陷

deque 有点像 vectorlist 的结合体 ,我们把 vectorlist 也拉出来进行优缺点的对比再合适不过了:

容器优点缺点
vector适合尾插尾删,随机访问① 不适合头部或者中部的插入删除,效率低下,需要挪动数据.
② 扩容有一定程度的性能消耗,还可能存在一定程度的空间浪费.
list① 适合任意位置的插入删除,效率高,O(1)时间复杂度
② 按需申请空间,空间利用率比较高
① 不支持随机访问
② CPU 高速缓存命中率低
deque① 头部和尾部插入数据效率不错
② 支持随机访问
③ 扩容代价小
④CPU高速缓存命中高
① 中部的插入删除效率不行
不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,
③ 不够极致.

那什么场景适合用 deque 呢?

虽然不够极致但是还是有用武之地的:大量头尾插入删除,偶尔随机访问的情况可以使用 deque.

5.为什么选择 deque 作为 stack 和 queue 的底层默认容器

stack是一种后进先出的特殊线性数据结构,因此只要具有 push_back()pop_back() 操作的线性结构,都可以作为 stack 的底层容器,比如 vectorlist 都可以;
queue 是先进先出的特殊线性数据结构,只要具有 push_back()pop_front() 操作的线性结构,都可以作为 queue 的底层容器,比如 list.但是 STL 中对 stackqueue 默认选择 deque 作为其底层容器,主要是因为:

  1. stackqueue不需要遍历(因此stack和queue没有迭代器) ,只需要在固定的一端或者两端进行操作.

  2. stack 中元素增长时,dequevector 的效率高**(扩容时不需要搬移大量数据)**;queue 中的元素增长时,deque 不仅效率高,而且内存使用率高.

结合了 deque 的优点,而完美的避开了其缺陷.

Ⅴ.容器适配器

在模拟实现 stack 和 queue 和 priority_queue,We have to revisit the container adapter,也可以参考 list Explanation in Notes.

  1. 什么是适配器

    适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口.

  2. STL标准库中stack和queue的底层结构

    虽然stackqueue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stackqueueIt's just the interface of other containers包装,STL中stack和queue默认使用deque,比如

也就是说,A container adapter is to convert an existing container(如vector)Take it and implement the adapter,达到复用的效果!

Ⅵ. stack的模拟实现

从栈的接口中可以看出,栈实际是一种特殊的 vector,因此使用 vector 完全可以模拟实现 stack.

但是为了和 STL The interface in the:STL 增加了一个模板参数 Container,利用 Container 来进行转换,而 STL still used deque As the default adapter implementation stack,So we use it uniformly here deque as default adapter!

思考: 可否用 string 作为适配器?

勉强可以,As long as you are a sequential container.但是用 string 是有风险的,Overflow and truncation may occur.

#pragma once
#include<iostream>
#include<deque>
using std::deque; //记得要引入std中的deque

namespace liren
{
    
    //Container adapters can be set arbitrarily,As long as the interface function requirements are met
    //template<class T, class Container = vector<int>>
    //template<class T, class Container = list<int>>
	template<class T, class Container = deque<T>>
	class stack
	{
    
	public:
		void push(const T& val) // 对于栈而言,Stacking is tail insertion
		{
    
			_con.push_back(val);
		}

		void pop() // 对于栈而言,Popping is tail deletion
		{
    
			_con.pop_back();
		}

		T& top() // Return the top data
		{
    
			return _con.back();
		}
		const T& top() const //const版本
		{
    
			return _con.back();
		}

		size_t size() const // 返回栈大小
		{
    
			return _con.size();
		}

		bool empty() const // 返回栈是否为空
		{
    
			return _con.empty();
		}

	private:
		Container _con;
	};
}

测试代码:

void Testmystack()
{
    
    liren::stack<int> st;
    st.push(1);
    st.push(4);
    st.push(3);
    st.push(2);
    while (!st.empty())
    {
    
        cout << st.top() << " ";
        st.pop();
    }
    cout << endl;

    //Different adapters can also be passed,照样能跑
    liren::stack<int, vector<int>> s;
    s.push(1);
    s.push(4);
    s.push(3);
    s.push(2);
    while (!s.empty())
    {
    
        cout << s.top() << " ";
        s.pop();
    }
    cout << endl;
}

总结: From a top-level perspective,It defaults to a deque,What do I use for the bottom,It's not important to implement this stack,as long as it meets the requirements.It saves the properties of the stack,Reuse the code of the container.

Ⅶ.queue的模拟实现

同样,queue 也用 deque to be implemented as the default container,与 stack The code is basically unchanged!

queue 是先进先出的,queue 的 push still tail insertion,而 pop Need to support deletion of headers

值得注意的是,queue 不能用 vector 去适配,因为 vector 压根就没有 pop_front() 等接口.

#pragma once
#include<iostream>
#include<deque>
using std::deque;  //also introducestd中的deque

namespace liren
{
    
	template<class T, class Container = deque<int>>
	class queue
	{
    
	public:
		void push(const T& val)
		{
    
			_con.push_back(val);
		}

		void pop()
		{
    
			_con.pop_front();
		}

		T& back()
		{
    
			return _con.back();
		}
		const T& back() const
		{
    
			return _con.back();
		}

		T& front()
		{
    
			return _con.front();
		}
		const T& front() const
		{
    
			return _con.front();
		}

		size_t size() const
		{
    
			return _con.size();
		}

		bool empty() const
		{
    
			return _con.empty();
		}
	private:
		Container _con;
	};
}

测试代码:

void Testmyqueue()
{
    
    liren::queue<int> q;
    q.push(1);
    q.push(3);
    q.push(4);
    q.push(2);
    while (!q.empty())
    {
    
        cout << q.front() << " ";
        q.pop();
    }
    cout << endl;

    //也可以传list容器,照样能跑
    liren::queue<int, list<int>> q1;
    q1.push(1);
    q1.push(3);
    q1.push(4);
    q1.push(2);
    while (!q1.empty())
    {
    
        cout << q1.front() << " ";
        q1.pop();
    }
    cout << endl;

    //It can't run,因为vector不支持pop_front()等函数
    liren::queue<int, vector<int>> q2;
    q2.push(1);
    q2.push(1);
    q2.push(1);
    q2.push(1);
    while (!q2.empty())
    {
    
        cout << q2.front() << " ";
        q2.pop();
    }
    cout << endl;
}

Ⅷ.priority_queue的模拟实现

1.仿函数(functor)(Use the library to import the header file<functional>)

Before the simulation implements the priority queue,We must first understand a concept,那就是仿函数

问题: 为什么实现 priority_queue To understand functors?

解答: 其实 priority_queue Essentially a heap,And the default is a lot of!也就是说 priority_queue The first element is the largest.

​ What if we need a small heap??那该怎么办?So at this time we have to passFunctors take templates as parameters,Let the template control what we want.

概念: 使一个类的使用看上去像一个函数.可以像函数一样使用的对象,称为函数对象.

Its implementation is overloading in the class operator(),Make the class behave like a function,就是一个仿函数类了.

C语言优先级,() 圆括号使用形式为 表达式 或 “As a function parameter list of parenthesis”

What we overload here is actually:函数名(形参表)

For example, the following code is a comparison 重载的()函数 的区别:

//仿函数 -- 函数对象(可以像函数一样去使用)
struct lessInt
{
    
    bool operator()(int left, int right)
    {
    
        return left < right;
    }
};

//normal form of function
bool lessFunc(int left, int right)
{
    
    return left < right;
}

void testFunctor()
{
    
    //仿函数的使用
    lessInt less;
    cout << less(1, 2) << endl;

    //正常函数的使用
    cout << lessFunc(1, 2) << endl;
}
 运行结果:
1
1

思考:我们在Cstage not learned函数指针么,Isn't it good to use function pointers??

Function pointers can indeed do these functions.但是Here function pointers are compared to functors,一点都不香! Pointers to functions are very complicated to use in some places.

比如下面的代码:

static void( * set_malloc_handler(void (*f)()))() 
{
    
    void (* old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = f;
    return(old);
}

What are the parameters of this function?函数的返回值是什么?

This is hell,This is the masterpiece of function pointers…… 所以 C++ functor,简化了好多.

Advantages of functors: 很多场景,replaced function pointers.

2.Implementation idea of ​​priority queue

priority_queue The underlying structure is,Therefore, it is only necessary to perform a general encapsulation of the heap..

The priority queue by default is a large heap,then if we like to make it a small heap,Should we pass the copy function separately,This only needs us to construct.

  • For the main frame

After have copy function understanding,We can control the size of the heap.!那我们先把 priority_queue the main structure of,因为 priority_queue 本质是堆,所以我们用 vector as its default container adapter!

#pragma once
#include<iostream>
#include<vector>
using std::vector;

namespace liren
{
    
    //这里的Compareis our functor!
	template<class T, class Container = std::vector<T>, class Compare = less<T>>
	class priority_queue
	{
    
    public:
        // 创造空的优先级队列
		priority_queue() : _con() 
		{
    }

	private:
		Container _con;
	};
}
  • For regular interface

stackqueue 一样,Several interfaces of the priority queue are also implemented in the same way,如 size(),empty()top(),So let's implement it first!

size_t size() const
{
    
	return _con.size();
}

// 堆顶元素不允许修改,因为:堆顶元素修改可以会破坏堆的特性
const T& top() const
{
    
	return _con.front();
}

bool empty() const
{
    
	return _con.empty();
}
  • 对于仿函数

As can be seen from the framework code above,对于大堆,STL 中 The default name is less,而对于小堆,we're going to be named greater

是不是觉得很奇怪,为什么大堆instead named less 呢?

解答: 以我的理解,The heap is in descending order,is successively smaller,所以用 less, 其实也是合理的,而 greater 则反之

代码如下:

//lessWho is used to compare small
template<class T>
    struct less
    {
    
        bool operator()(const T& left, const T& right)
        {
    
            return left < right;
        }
    };

//greaterto compare who is bigger
template<class T>
    struct greater
    {
    
        bool operator()(const T& left, const T& right)
        {
    
            return left > right;
        }
    };
  • Algorithms for up and down adjustments

因为 priority_queue 是个,So we'll make it happenWhen inserting and deleting, it is necessary to re-adjust the size order of the heap.,So we first write its adjustment algorithm!

注意一个点,It is this adjustment algorithm that is not allowed to be arbitrarily adjusted by other external interfaces.,So we'll set it toprivate.

还有一个点,就是In the upward adjustment algorithm,parent 和 child It is better not to set its type as size_t ,because there may be less than0的情况.

这个知识点并不难,As long as there is still a heap of knowledge,So write the code directly here:

private:
		//向下调整算法
		void AdjustDown(int parent)
		{
    
			Compare Com;

			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
    
				if (child + 1 < _con.size() && Com(_con[child], _con[child + 1]))
				{
    
					child += 1;
				}

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

		//向下调整算法
		void AdjustUp(int child)
		{
    
			Compare Com;

			int parent = (child - 1) >> 1;
			while (child > 0)
			{
    
				if (Com(_con[parent], _con[child]))
				{
    
					::swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) >> 1;
				}
				else
					break;
			}
		}
  • For tail plugs remove

Priority queue compared to normal queue,其区别主要是在 pushpop 上,

that needs to be inserted / 删除数据的同时,Add adjustment function,并且 STL The priority queue is maintained by the heap:

入队时After pushing the data in,Upscaling from the last element.

出队时Deletion method using heap,将根元素(即队头)swap with the last element,And call the tail to delete the head of the queue.

Since in the end exchange method,我们就不得不resize the queue,So down from the root position,To complete the team.

void push(const T& val)
{
    
    _con.push_back(val);
    AdjustUp(_con.size() - 1);
}

void pop()
{
    
    if (empty())
        return;

    ::swap(_con.front(), _con.back()); //Many call interface reuse of containers
    _con.pop_back();
    AdjustDown(0);
}
  • The realization of the iterator constructor

for priority queue,我们直接用 vector Constructor do iterator constructor!

最主要的问题是,用 vector The iterator range to construct vector 后,我们要对其进行建堆,Make it a veritable priority queue!

template<iterator>
priority_queue(iterator first, iterator last)
: _con(first, last)  //Here it is actually calledvectorThe constructor of the completes the construction
{
    
    // 将_con中的元素调整成堆的结构
    for (int parent = (_con.size() - 1 - 1) >> 1; parent >= 0; parent--)
    {
    
        AdjustDown(parent);
    }
}

3.测试代码:

void testMypriority_queue()
{
    
    liren::priority_queue<int> pq;
    pq.push(1);
    pq.push(2);
    pq.push(3);
    pq.push(4);
    pq.push(5);
    while (!pq.empty())
    {
    
        cout << pq.top() << " ";
        pq.pop();
    }
    cout << endl;

    //It can also be changed to small heap order
    liren::priority_queue<int, vector<int>, liren::greater<int>> pq2;
    pq2.push(1);
    pq2.push(2);
    pq2.push(3);
    pq2.push(4);
    pq2.push(5);
    while (!pq2.empty())
    {
    
        cout << pq2.top() << " ";
        pq2.pop();
    }
    cout << endl;

    //Iterators can also be used to construct ranges
    vector<int> v{
     5,1,4,2,3,6 };
    liren::priority_queue<int, vector<int>, liren::greater<int>> q2(v.begin(), v.end());
    cout << q2.top() << endl;

    q2.pop();
    q2.pop();
    cout << q2.top() << endl;
}

Ⅸ.完整代码

1. stack.h

#pragma once
#include<iostream>
#include<deque>
using std::deque; //记得要引入std中的deque

namespace liren
{
    
	template<class T, class Container = deque<T>>
	class stack
	{
    
	public:
		void push(const T& val) // 对于栈而言,Stacking is tail insertion
		{
    
			_con.push_back(val);
		}

		void pop() // 对于栈而言,Popping is tail deletion
		{
    
			_con.pop_back();
		}

		T& top() // Return the top data
		{
    
			return _con.back();
		}
		const T& top() const //const版本
		{
    
			return _con.back();
		}

		size_t size() const // 返回栈大小
		{
    
			return _con.size();
		}

		bool empty() const // 返回栈是否为空
		{
    
			return _con.empty();
		}

	private:
		Container _con;
	};
}

2. queue.h

#pragma once
#include<iostream>
#include<deque>
using std::deque;  //also introducestd中的deque

namespace liren
{
    
	template<class T, class Container = deque<int>>
	class queue
	{
    
	public:
		void push(const T& val)
		{
    
			_con.push_back(val);
		}

		void pop()
		{
    
			_con.pop_front();
		}

		T& back()
		{
    
			return _con.back();
		}
		const T& back() const
		{
    
			return _con.back();
		}

		T& front()
		{
    
			return _con.front();
		}
		const T& front() const
		{
    
			return _con.front();
		}

		size_t size() const
		{
    
			return _con.size();
		}

		bool empty() const
		{
    
			return _con.empty();
		}
	private:
		Container _con;
	};
}

3. priority_queue.h

#pragma once
#include<iostream>
#include<vector>
using std::vector;

namespace liren
{
    
	//lessWho is used to compare small
	template<class T>
	struct less
	{
    
		bool operator()(const T& left, const T& right) const
		{
    
			return left < right;
		}
	};

	//greaterto compare who is bigger
	template<class T>
	struct greater
	{
    
		bool operator()(const T& left, const T& right) const
		{
    
			return left > right;
		}
	};

	//这里的Compareis our functor!
	template<class T, class Container = std::vector<T>, class Compare = less<T>>
	class priority_queue
	{
    
	public:
		// 创造空的优先级队列
		priority_queue() : _con() 
		{
    }

		template<class Iterator>
		priority_queue(Iterator first, Iterator last)
			: _con(first, last)  //Here it is actually calledvectorThe constructor of the completes the construction
		{
    
			// 将_con中的元素调整成堆的结构
			for (int parent = (_con.size() - 1 - 1) >> 1; parent >= 0; parent--)
			{
    
				AdjustDown(parent);
			}
		}

		void push(const T& val)
		{
    
			_con.push_back(val);
			AdjustUp(_con.size() - 1);
		}

		void pop()
		{
    
			if (empty())
				return;

			::swap(_con.front(), _con.back()); //Many call interface reuse of containers
			_con.pop_back();
			AdjustDown(0);
		}

		size_t size() const
		{
    
			return _con.size();
		}

		// 堆顶元素不允许修改,因为:堆顶元素修改可以会破坏堆的特性
		const T& top() const
		{
    
			return _con.front();
		}

		bool empty() const
		{
    
			return _con.empty();
		}

	private:

		//向下调整算法
		void AdjustDown(int parent)
		{
    
			Compare Com;

			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
    
				if (child + 1 < _con.size() && Com(_con[child], _con[child + 1]))
				{
    
					child += 1;
				}

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

		//向下调整算法
		void AdjustUp(int child)
		{
    
			Compare Com;

			int parent = (child - 1) >> 1;
			while (child > 0)
			{
    
				if (Com(_con[parent], _con[child]))
				{
    
					::swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) >> 1;
				}
				else
					break;
			}
		}

	private:
		Container _con;
	};
}

4. test.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <vector>
#include <iostream>
#include <queue>
#include<list>
#include <functional> // greater算法的头文件
#include<deque>
#include "stack.h"
#include "queue.h"
#include "priority_queue.h"
using namespace std;

void test_queue() {
    
    /* 创建一个存储整型的队列 */
    queue<int> Q;

    /* 入队 */
    Q.push(1);
    Q.push(2);
    Q.push(3);
    Q.push(4);

    /* 打印队列 */
    while (!Q.empty()) {
                 // 如果队列不为空则进入循环
        cout << Q.front() << " ";    // 打印队头元素
        Q.pop();                     // 出队
    }
    cout << endl;
}
void TestPriorityQueue()
{
    
	// 默认情况下,创建的是大堆,其底层按照小于号比较
	vector<int> v{
     3,2,7,6,0,4,1,9,8,5 };
	priority_queue<int> q1;
	for (auto& e : v)
		q1.push(e);
	cout << q1.top() << endl;
	// 如果要创建小堆,将第三个模板参数换成greater比较方式
	priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
	cout << q2.top() << endl;
}

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 TestPriorityQueue2()
{
    
    // 大堆,需要用户在自定义类型中提供<的重载
    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;
}

void testDeque()
{
    
    deque<int> dq;
    dq.push_back(1);
    dq.push_back(2);
    dq.push_back(3);
    dq.push_back(2);

    deque<int>::iterator it = dq.begin();
    while (it != dq.end())
    {
    
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    it = dq.erase(--it);
    it = dq.begin();
    while (it != dq.end())
    {
    
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

void Testmystack()
{
    
    liren::stack<int> st;
    st.push(1);
    st.push(4);
    st.push(3);
    st.push(2);
    while (!st.empty())
    {
    
        cout << st.top() << " ";
        st.pop();
    }
    cout << endl;

    //Different adapters can also be passed,照样能跑
    liren::stack<int, vector<int>> s;
    s.push(1);
    s.push(4);
    s.push(3);
    s.push(2);
    while (!s.empty())
    {
    
        cout << s.top() << " ";
        s.pop();
    }
    cout << endl;
}

void Testmyqueue()
{
    
    liren::queue<int> q;
    q.push(1);
    q.push(3);
    q.push(4);
    q.push(2);
    while (!q.empty())
    {
    
        cout << q.front() << " ";
        q.pop();
    }
    cout << endl;

    //也可以传list容器,照样能跑
    liren::queue<int, list<int>> q1;
    q1.push(1);
    q1.push(3);
    q1.push(4);
    q1.push(2);
    while (!q1.empty())
    {
    
        cout << q1.front() << " ";
        q1.pop();
    }
    cout << endl;

    /*liren::queue<int, vector<int>> q2; q2.push(1); q2.push(1); q2.push(1); q2.push(1); while (!q2.empty()) { cout << q2.front() << " "; q2.pop(); } cout << endl;*/
}

//仿函数 -- 函数对象(可以像函数一样去使用)
struct lessInt
{
    
    bool operator()(int left, int right)
    {
    
        return left < right;
    }
};

//normal form of function
bool lessFunc(int left, int right)
{
    
    return left < right;
}

void testFunctor()
{
    
    //仿函数的使用
    lessInt less;
    cout << less(1, 2) << endl;

    //正常函数的使用
    cout << lessFunc(1, 2) << endl;
}

void testMypriority_queue()
{
    
    liren::priority_queue<int> pq;
    pq.push(1);
    pq.push(2);
    pq.push(3);
    pq.push(4);
    pq.push(5);
    while (!pq.empty())
    {
    
        cout << pq.top() << " ";
        pq.pop();
    }
    cout << endl;

    //It can also be changed to small heap order
    liren::priority_queue<int, vector<int>, liren::greater<int>> pq2;
    pq2.push(1);
    pq2.push(2);
    pq2.push(3);
    pq2.push(4);
    pq2.push(5);
    while (!pq2.empty())
    {
    
        cout << pq2.top() << " ";
        pq2.pop();
    }
    cout << endl;

    //Iterators can also be used to construct ranges
    vector<int> v{
     5,1,4,2,3,6 };
    liren::priority_queue<int, vector<int>, liren::greater<int>> q2(v.begin(), v.end());
    cout << q2.top() << endl;

    q2.pop();
    q2.pop();
    cout << q2.top() << endl;
}

int main()
{
    
    //test_queue();
    //TestPriorityQueue2();
    //testDeque();
    //Testmyqueue();
    //testFunctor();
    testMypriority_queue();
	return 0;
}
原网站

版权声明
本文为[Blade Cc]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/221/202208091933089374.html