queue in C++STL library


Article directory

  • Introduction to queue
  • Common interfaces for queue
  • Simulation implementation of queue
  • Introduction to priority_queue
  • Common interfaces of priority_queue
  • Simulation implementation of priority_queue
  • container adapter
  • Introduction to deque
  • Functor


1. Introduction to queue

  • 1. A queue is a container adapter designed to operate in a FIFO context (first in, first out), where elements are inserted from one end of the container and elements are extracted from the other end.
  • 2. The queue is implemented as a container adapter. The container adapter encapsulates a specific container class as its underlying container class. Queue provides a specific set of member functions to access its elements. Elements are put into the queue from the end of the queue and dequeued from the head.
  • 3. The underlying container can be one of the standard container class templates or other specially designed container classes. The underlying container should support at least the following operations:
    • empty: Check whether the queue is empty
    • size: Returns the number of valid elements in the queue
    • front: Returns a reference to the head element of the queue
    • back: Returns a reference to the last element of the queue
    • push_back: queue at the end of the queue
    • pop_front: Dequeue at the head of the queue
  • 4. The standard container classes deque and list meet these requirements. By default, if no container class is specified for queue instantiation, the standard container deque is used.

2. Common interfaces of queue

Function declaration interface description
queue()                                                                                  constructs an empty queue
empty()                                                  detects whether the queue is empty and returns true, otherwise it returns false
size()                                                                          returns the number of valid elements in the queue
front()                                                                              returns a reference to the head element of the queue
back()                                                                              returns a reference to the last element of the queue
push()                                                                           puts the element val into the queue at the end of the queue
pop()                                                                                   dequeues the head element of the queue
swap()                                                                                exchanges the contents of two containers 

 Demonstration of related interfaces:

#include <iostream>
#include <queue>

int main()
{
	std::queue<int> q;
	q.push(1);
	q.push(2);
	q.push(3);

	while (!q.empty()) {
		std::cout << q.front() << " ";
		q.pop();
	}
	return 0;
}

3. Simulation implementation of queue

#pragma once
namespace Queue
{
	//这里默认是采用deque这种适配器来模拟栈
	template<class T, class Contain = std::deque<T>>
	class queue
	{
	public:
		/*
		queue()//这里不需要显示写构造函数,因为是自定义类型,直接调用默认构造函数就行
		{}
		*/

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

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

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

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

		void push(const T& val)
		{
			_con.push_back(val);
		}

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

		void swap(queue<T, Contain>& q)
		{
			std::swap(_con, q._con);
		}

	private:
		Contain _con;

	};
}

4. Introduction to priority_queue

  • 1. A priority queue is a container adapter whose first element is always the largest of the elements it contains according to strict weak ordering criteria.
  • 2. This context is similar to a heap, where elements can be inserted at any time, and only the largest heap element (the top element in the priority queue) can be retrieved.
  • 3. The priority queue is implemented as a container adapter. The container adapter encapsulates a specific container class as its underlying container class. The queue provides a specific set of member functions to access its elements. Elements are popped from the "tail" of a specific container, which is called the top of the priority queue.
  • 4. The underlying container can be any standard container class template, or it can be other specifically designed container classes. The container should be accessible via random access iterators and support the following operations:
    • empty(): Check whether the container is empty
    • size(): Returns the number of valid elements in the container
    • front(): Returns a reference to the first element in the container
    • push_back(): Insert elements at the end of the container
    • pop_back(): delete the element at the end of the container
  • 5. The standard container classes vector and deque meet these needs. By default, vector is used if no container class is specified for a particular priority_queue class instantiation.
  • 6. Need to support random access iterators so that the heap structure is always maintained internally. The container adapter does this automatically by automatically calling the algorithmic functions make_heap, push_heap, and pop_heap when needed.

5. Common interfaces of priority_queue

By default, the priority queue uses vector as its underlying container for storing data. The heap algorithm is used on the vector to construct the elements in the vector into a heap structure. Therefore, priority_queue is a heap. You can consider using priority_queue wherever a heap is needed. . Note: By default priority_queue is a large heap .

Function declaration interface description
priority_queue()/priority_queue(first, last)                  constructs an empty priority queue
empty()             detects whether the priority queue is empty and returns true, otherwise it returns false                              
top()                                                          returns the largest (smallest element) in the priority queue, that is, the top element of the heap
push(x)                                                                          inserts element x into the priority queue
pop ()                                                  deletes the largest (smallest) element in the priority queue, that is, the top element of the heap

1. By default, priority_queue is a large pile

#include <iostream>
#include <deque>
#include <queue>
#include "queue.h"
#include <vector>
#include <functional>
#include <functional> // greater算法的头文件

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

2. If you put custom type data in priority_queue, the user needs to provide an overload of > or < in the custom type.

#include <iostream>
#include <deque>
#include <queue>
#include "queue.h"
#include <vector>
#include <functional>
#include <functional> // greater算法的头文件

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 std::ostream& operator<<(std::ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};
void TestPriorityQueue()
{
	// 大堆,需要用户在自定义类型中提供<的重载
	std::priority_queue<Date> q1;
	q1.push(Date(2018, 10, 29));
	q1.push(Date(2018, 10, 28));
	q1.push(Date(2018, 10, 30));
	std::cout << q1.top() << std::endl;
	// 如果要创建小堆,需要用户提供>的重载
	std::priority_queue<Date, std::vector<Date>, std::greater<Date>> q2;
	q2.push(Date(2018, 10, 29));
	q2.push(Date(2018, 10, 28));
	q2.push(Date(2018, 10, 30));
	std::cout << q2.top() << std::endl;
}

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

6. Simulation implementation of priority_queue

#pragma once
namespace Priority_queue
{
	template<class T>
	struct Less
	{
		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;
		}
	};
	template<class T,class Container=std::vector<T>,class Compare=Less<T>>
	class priority_queue
	{
	public:
		priority_queue()
		{}
		

		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
			while (first != last) {
				_con.push_back(*first);
				first++;

				int child = _con.size() - 1;
				int parent = (child - 1) / 2;
				for (int i = parent; i >= 0; i--) AdjustDown(i);
			}
		}

		void AdjustUp(size_t child)
		{
			Compare com;

			size_t parent = (child - 1) / 2;
			while (child) {
				if (com(_con[parent], _con[child])) {
					std::swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else break;
			}
		}

		void AdjustDown(size_t 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++;
				if (com(_con[parent], _con[child])) {
					std::swap(_con[parent], _con[child]);
					parent = (child - 1) / 2;
					child = parent * 2 + 1;
				}
				else break;
			}
		}

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

		void pop()
		{
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);
		}

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

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

		const T& top()
		{
			return _con[0];
		}

	private:
		Container _con;
	};

}

7. Container adapter

1. The concept of adapter

Adapter is a design pattern (a design pattern is a set of code design experiences that are repeatedly used, known to most people, classified and cataloged). This pattern converts the interface of a class into another one that the customer wants. interface.

 2. The underlying structure of stack and queue in the STL standard library

Although elements can also be stored in stacks and queues, they are not classified into containers in STL. Instead, they are called container adapters. This is because stacks and queues just wrap the interfaces of other containers. STL In stack and queue, deque is used by default, for example:

 

 

 8. Introduction to deque

1. Introduction to the principle of deque

deque (double-ended queue): It is a double-opening "continuous" space data structure. The meaning of double-opening is that insertion and deletion operations can be performed at both ends, and the time complexity is O(1), which is the same as Compared with vector, header insertion is more efficient and does not require moving elements; compared with list, space utilization is relatively high.

 

Deque is not a truly continuous space, but is made up of continuous small spaces. The actual deque is similar to a dynamic two-dimensional array. Its underlying structure is as shown in the figure below:

The bottom layer of the double-ended queue is an imaginary continuous space, which is actually segmented. In order to maintain its "overall continuity" and the illusion of random access, it falls on the iterator of deque, so the iterator design of deque is more complicated. As shown below :

 

 

 

2. Defects of deque

Compared with vector, the advantages of deque are :

  • When inserting and deleting the head, there is no need to move elements, which is very efficient. Moreover, when expanding, there is no need to move a large number of elements, so its efficiency must be high.
  • Compared with list, the bottom layer is continuous space, the space utilization is relatively high, and there is no need to store additional fields.

 However, deque has a fatal flaw: 

  • It is not suitable for traversal, because when traversing, the iterator of deque needs to frequently detect whether it moves to the boundary of a certain small space, resulting in low efficiency. In sequential scenarios, frequent traversal may be required, so in practice, it is necessary When it comes to linear structures, vector and list are given priority in most cases. There are not many applications of deque. One application that can be seen so far is that STL uses it as the underlying data structure of stack and queue.

3. Why choose deque as the underlying default container for stack and queue ?

Stack is a special linear data structure with last-in-first-out function. Therefore, any linear structure with push_back() and pop_back() operations can be used as the underlying container of stack, such as vector and list; queue is a special first-in-first-out function. Linear data structures, as long as they have push_back and pop_front operations, can be used as the underlying container of queue, such as list. However, deque is selected as the underlying container by default for stack and queue in STL, mainly because:
1. Stack and queue do not need to be traversed (so stack and queue do not have iterators), they only need to operate on one or both fixed ends.
2. When the elements in the stack grow, deque is more efficient than vector (there is no need to move a large amount of data when expanding); when the elements in the queue grow, deque is not only efficient, but also has high memory usage. It combines the advantages of deque and perfectly avoids its shortcomings.

9. Functor

1. The concept of functor

Functor, also called function object, is actually a struct or class that overloads the () operator. Since the () operator is overloaded, using it is like calling a function, so it is called a "fake" function.

2. Examples of how to write functors

    template<class T>
	struct Less
	{
		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;
		}
	};

3. Categories of functors

In the functional header file of C++, we have been provided with some functors that can be used directly.

1. Arithmetic Functor

1.plus calculates the sum of two numbers

    transform(begin(a), end(a), begin(b), begin(a), plus<int>());

2.minus subtracts two numbers

transform(begin(a), end(a), begin(b), begin(a), minus<int>());

3.multiplies two numbers multiplied by each other

transform(begin(a), end(a), begin(b), begin(a), multiplies<int>());

4.divides divides two numbers

transform(begin(a), end(a), begin(b), begin(a), divides<int>());

5.modules modulo operation

transform(begin(a), end(a), begin(b), begin(a), modulus<int>());

6.negate opposite number

transform(begin(a), end(a), begin(a), negate<int>());

2. Relation functor

  • 1.equal_to is equal
  • 2.not_equal_to is not equal
  • 3.greater greater than
  • 4.less less than
  • 5.greater_equal is greater than or equal to
  • 6.less_equal is less than or equal to

 3. Logic functor

  • 1.logical_and binary, find &
  • 2.logical_or binary, find |
  • 3.logical_not one yuan, please!

Guess you like

Origin blog.csdn.net/qq_67458830/article/details/132029337