【C++】——Introduction and simulation implementation of stack and queue (stack, queue) and priority queue (priority_queue)

1 Introduction

Today we will learn one of the six components of C++stl, container adapter, stack, queue and priority_queue are all container adapters. Let's go step by step, let's first understand what a container adapter is.

2. Container Adapter

2.1 Introduction of Container Adapter

Adapter is a design pattern (a design pattern is a set of repeated use, known to most people, classified and cataloged, and a summary of code design experience), which converts the interface of a class into another that customers want. interface.

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

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

insert image description here
insert image description here

insert image description here

2.3 A brief introduction to deque

Deque (double-ended queue): It is a double-opened "continuous" space data structure. The meaning of double-opening is: insertion and deletion can be performed at both ends of the head and tail, and the time complexity is O(1). Compared with vector, the plug-in efficiency is high, and elements do not need to be moved; compared with list, the space utilization rate is relatively high.

Deque is not a real continuous space, but is spliced ​​by a series of continuous small spaces. The actual deque is similar to a dynamic two-dimensional array. The bottom layer of the double-ended queue is an illusion of continuous space, which is actually segmented and continuous. , in order to maintain the illusion of "overall continuity" and random access, it falls on the deque iterator, so the deque iterator design is more complicated.

2.4 The defect of deque

Compared with vector:
The advantage of deque is that when the head is inserted and deleted, there is no need to move elements, and the efficiency is particularly high, and 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 a continuous space, the space utilization rate is relatively high, and no additional fields need to be stored.
However, deque has a fatal flaw:
it is not suitable for traversal, because when traversing, the iterator of deque needs to frequently check whether it has moved to the boundary of a small space, resulting in low efficiency. In sequential scenarios, it may be necessary to frequently Traversal, so in practice, when a linear structure is required, vector and list are given priority in most cases. There are not many applications of deque, and one application that can be seen so far is that STL uses it as the underlying data structure of stack and queue .

2.5 Why choose deque as the underlying default container of stack and queue

Stack is a special last-in-first-out linear data structure, so as long as it has a linear structure with push_back() and pop_back() operations, it can be used as the underlying container of the stack, such as vector and list; queue is a special first-in first-out A linear data structure, as long as it has a linear structure with push_back and pop_front operations, can be used as the underlying container of the queue, such as list. But in STL, deque is selected as the underlying container for stack and queue by default, mainly because:
1. Stack and queue do not need to be traversed (so stack and queue have no iterators), and only need to operate at one or both ends of the fixed.
2. When the elements in the stack grow, deque is more efficient than vector (it does not need to move a large amount of data when expanding); when the elements in the queue grow,
deque not only has high efficiency, but also has high memory usage.

3. stack

3.1 Introduction to stack

1. stack is a kind of container adapter, which is specially used in the context environment with last-in-first-out operation, and its deletion can only insert and extract elements from one end of the container.
2. stack is implemented as a container adapter, which encapsulates a specific class as its underlying container, and provides a set of specific member functions to access its elements, using a specific class as its underlying, element-specific container The tail (i.e. the top of the stack) is pushed and popped.
3. The underlying container of the stack can be any standard container class template or some other specific container class. These container classes should support the following operations: empty: null operation back: get
tail
element operation
push_back: tail insert element operation
pop_back: tail Delete element operation
4. The standard containers vector, deque, and list all meet these requirements. By default, if no specific underlying container is specified for the stack, deque is used by default.

3.2 Use of stacks

function name Function Description
stack() construct an empty stack
empty() Check if the stack is empty
size() Returns the number of elements in the stack
top() Returns a reference to the top element of the stack
push() Push the element val into the stack
pop() Pop the element at the end of the stack
void Test1()
{
    
    
	//fiora::stack<int,vector<int>> st;
	//fiora::stack<int, list<int>> st;
	//fiora::stack<int> st;
	//stack<int,vector<int>> st;
	//stack<int, list<int>> st;
	stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	st.push(5);

	while (!st.empty())
	{
    
    
		cout << st.top() << " ";
		st.pop();
	}
}

insert image description here

3.3 stack simulation implementation

namespace fiora
{
    
    
	template<class T, class Container = deque<T>>
	class stack
	{
    
    
	public:
		void push(const T& x)
		{
    
    
			_con.push_back(x);
		}

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

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

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

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

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


	private:
		Container _con;
	};
}

test:

void Test1()
{
    
    
	//fiora::stack<int,vector<int>> st;
	fiora::stack<int, list<int>> st1;
	fiora::stack<int> st;
	//stack<int,vector<int>> st;
	//stack<int, list<int>> st;
	//stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	st.push(5);

	while (!st.empty())
	{
    
    
		cout << st.top() << " ";
		st.pop();
	}

}

insert image description here

4. queue

4.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 extracted from the other end.
2. The queue is implemented as a container adapter, which encapsulates a specific container class as its underlying container class, and queue provides a set of specific member functions to access its elements. Elements enter the queue from the tail and dequeue 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 at least support the following operations:
empty: check whether the queue is empty
size: return the number of valid elements in the queue
front: return the reference of the element at the head of the queue
back: return the reference of the element
at the end of the queue push_back: enter the 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.

4.2 Use of queues

function name Function Description
queue() construct an empty queue
empty() Check if the queue is empty
size() Returns the number of valid elements in the queue
front() Returns a reference to the element at the head of the queue
back() Returns a reference to the element at the end of the queue
push() Enqueue the element val at the end of the queue
pop() Dequeue the head element
void Test2()
{
    
    
	//fiora::queue<int> q;
	//fiora::queue<int, list<int>> q;
	//fiora::queue<int, vector<int>> q;错误,vector不支持头删
	queue<int> q;
	//queue<int, list<int>> q;
	//queue<int, vector<int>> q;错误,vector不支持头删
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);
	q.push(5);

	while (!q.empty())
	{
    
    
		cout << q.front() << " ";
		q.pop();
	}
}

insert image description here

4.3 queue simulation implementation

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

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

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

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

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

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


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

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

	private:
		Container _con;
	};
}

test:

void Test2()
{
    
    
	fiora::queue<int> q;
	fiora::queue<int, list<int>> q1;
	//fiora::queue<int, vector<int>> q;错误,vector不支持头删
	//queue<int> q;
	//queue<int, list<int>> q;
	//queue<int, vector<int>> q;错误,vector不支持头删
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);
	q.push(5);

	while (!q.empty())
	{
    
    
		cout << q.front() << " ";
		q.pop();
	}
}

insert image description here

5. priority_queue (priority queue)

5.1 Introduction to priority queues

The priority queue uses vector as its underlying data storage container by default, and the heap algorithm is used on the vector to construct the elements in the vector into a heap structure, so priority_queue is a heap, and you can consider using priority_queue wherever you need to use the heap . Note: priority_queue is big heap by default.

1. A priority queue is a container adapter whose first element is always the largest among 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, which encapsulates a specific container class as its underlying container class, and queue provides a set of specific member functions to access its elements. Elements are popped from the "tail" of a particular container, which is called the top of the priority queue.
4. The underlying container can be any standard container class template, or any other specially designed container class. The container should be accessible via a random access iterator and support the following operations:
empty(): check if the container is empty
size(): return the number of valid elements in the container
front(): return a reference to the first element in the container
push_back( ): Insert an element at the end of the container
pop_back(): Delete an element at the end of the container
5. The standard container classes vector and deque meet these requirements. 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.2 Use of priority queues

function name Function Description
priority_queue()/priority_queue(first,last) Construct an empty priority queue
empty() Check if the priority queue is empty
top() Returns the largest (smallest element) in the priority queue, that is, the top element of the heap
push() Insert element val in the priority queue
pop() Delete the largest (smallest) element in the priority queue, that is, the top element of the heap
void Test3()
{
    
    
	priority_queue<int> pq;
	//fiora::priority_queue<int> pq;

	pq.push(3);
	pq.push(1);
	pq.push(2);
	pq.push(5);
	pq.push(1);
	pq.push(4);
	pq.push(0);

	while (!pq.empty())
	{
    
    
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
}

insert image description here

void Test4()
{
    
    
int a[] = {
    
     5,4,6,9,1,2,3,0,8,7 };
	//fiora::priority_queue<int> heap(a, a + sizeof(a) / sizeof(int));
	//fiora::priority_queue<int> heap(a, a + sizeof(a) / sizeof(int));
	//fiora::priority_queue<int, vector<int>, greater<int>> heap(a, a + sizeof(a) / sizeof(int));
	priority_queue<int, vector<int>, greater<int>> heap(a, a + sizeof(a) / sizeof(int));

	while (!heap.empty())
	{
    
    
		cout << heap.top() << " ";
		heap.pop();
	}	
	cout << endl;
}

insert image description here

5.3 Implementation of Priority Queue Simulation

namespace fiora
{
    
    
	//大堆
	template<class T, class Container = 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++;
			}

			//建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
    
    
				adjust_down(i);
			}

		}


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

		void adjust_down(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[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
    
    
					break;
				}
			}
		}

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

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

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

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

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


	private:
		Container _con;
	};
}

test:

void Test3()
{
    
    
	//priority_queue<int> pq;
	fiora::priority_queue<int> pq;
	
	pq.push(3);
	pq.push(1);
	pq.push(2);
	pq.push(5);
	pq.push(1);
	pq.push(4);
	pq.push(0);

	while (!pq.empty())
	{
    
    
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;


	int a[] = {
    
     5,4,6,9,1,2,3,0,8,7 };
	//fiora::priority_queue<int> heap(a, a + sizeof(a) / sizeof(int));
	fiora::priority_queue<int> heap1(a, a + sizeof(a) / sizeof(int));
	fiora::priority_queue<int, vector<int>, greater<int>> heap(a, a + sizeof(a) / sizeof(int));
	//priority_queue<int, vector<int>, greater<int>> heap(a, a + sizeof(a) / sizeof(int));

	while (!heap.empty())
	{
    
    
		cout << heap.top() << " ";
		heap.pop();
	}
	
	cout << endl;
}

insert image description here

6. Ending

We have come to an end about the learning of stacks and queues. Stacks and queues are the same as the containers we learned before. They are very simple to use. The most important thing is to be able to understand their underlying principles so that they can be used flexibly.
Finally, I would like to thank you for your patient reading and support. Friends who think this article is well written can follow and support it three times. If you have any questions or there are mistakes in this article, you can private message me or leave a message in the comment area Discussion, thanks again everyone.

Guess you like

Origin blog.csdn.net/qq_43188955/article/details/131155232