The underlying principle and simulation implementation of stack and queue of c++ stl

1. The use of stack

Second, the simulation implementation of stack

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<vector>
#include<list>


//adapter pattern
namespace sk
{
	template <class T, class Container = deque<T>>
	class stack
	{
	public:
		bool empty()const
		{
			return _con.empty();
		}
		size_t size()const
		{
			return _con.size();
		}
		void push(const T& val)
		{
			_con.push_back(val);
		}
		void pop()
		{
			_con.pop_back();
		}
		const T& top()const
		{
			return _con.back();
		}
	private:
		Container _con;
	};
	void stack_test()
	{
		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;
	}
}

Three, the use of queue

Fourth, the simulation implementation of queue

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<vector>
#include<list>
namespace sk
{
	template <class T, class Container = deque<T>>
	class queue
	{
	public:
		bool empty()const
		{
			return _con.empty();
		}
		size_t size()const
		{
			return _con.size();
		}
		void push(const T& val)
		{
			_con.push_back(val);
		}
		void pop()
		{
			_con.pop_front();
		}
		const T& front()const
		{
			return _con.front();
		}
		const T& back()const
		{
			return _con.back();
		}
	private:
		Container _con;
	};
	void queue_test()
	{
		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;
	}
}

Five, the use of priority_queue

function declarationInterface Description
priority_queue()/priority_queue(first,last)Construct an empty priority queue
empty( )Check whether the priority queue is empty, return true, otherwise return false
top( )Returns the largest (smallest element) in the priority queue, that is, the top element of the heap
push(x)Insert element x into the priority queue
pop()Delete the largest (smallest) element in the priority queue, that is, the top element of the heap

[Notice]

  1. By default, priority_queue is a large heap.
    Sample code:
#include <vector>
#include <queue>
#include <functional> // header file for greater algorithm
void TestPriorityQueue()
{
// By default, a large heap is created, and its bottom layer is compared according to the less than sign
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;
// If you want to create a small heap, replace the third template parameter with a greater comparison method
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
}
  1. If you put custom type data in priority_queue, users need to provide > or < overload in the custom type.
    Sample code:
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()
{
// A large pile, requiring the user to provide an overload of < in the custom type
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;
// If you want to create a small heap, you need to provide an overload of >
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;
}

Six, the simulation implementation of priority_queue

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
namespace sk
{
	//functor
	template <class T>
	struct lesser
	{
		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;
		}
	};

	//specialization
	template <>
	struct lesser<char>
	{
		bool operator()(const char& x, const char& y)
		{
			return x > y;
		}
	};
	template<class T, class Container = vector<T>, class Compare = lesser<T>>
	class priority_queue
	{
		Compare com;
		void adjust_up(size_t child)
		{
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				if (com(_con[parent] , _con[child]))
				{
					swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2 ;
				}
				else
				{
					break;
				}
			}
		}
		void adjust_down(size_t parent)
		{
			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]))
				{
					swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
	public:
		priority_queue()
		{}

		template<class Inputiterator>
		priority_queue(Inputiterator first, Inputiterator last)
			:_con(first, last)
		{
			//build pile
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
				adjust_down(i);
			}
		}
		void push(const T& val)
		{
			_con.push_back(val);
			size_t child = _con.size() - 1;
			adjust_up(child);
		}
		void pop()
		{
			assert(!empty());
			swap(_con.front(), _con.back());
			_con.pop_back();
			adjust_down(0);
		}
		const T& top()const
		{
			return _con.front();
		}
		bool empty()const
		{
			return _con.empty();
		}
		size_t size()const
		{
			return _con.size();
		}
	private:
		Container _con;
	};
	void priority_queue_test1()
	{
		//priority_queue<int, vector<int>, greater<int>> pq;//Small heap
		std::vector<int> v = { 8,1,5,9,7,4,1,3,5,48,65,6,4,5 };
		priority_queue<int> pq(v.begin(), v.end());//default large heap
		pq.push(1);
		pq.push(3);
		pq.push(4);
		pq.push(5);
		pq.push(2);
		while (!pq.empty())
		{
			cout << pq.top() << " ";
			pq.pop();
		}
		cout << endl;
	}
	void priority_queue_test2()
	{
		//priority_queue<int, vector<int>, greater<int>> pq;//Small heap
		std::vector<char> v = { 'a','f','d','v' };
		priority_queue<char> pq(v.begin(), v.end());//default large heap
		pq.push('q');
		pq.push('w');
		pq.push('e');
		pq.push('r');
		pq.push('t');
		while (!pq.empty())
		{
			cout << pq.top() << " ";
			pq.pop();
		}
		cout << endl;
	}
}

7. Container Adapter

7.1 What is an 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.

7.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:

7.3 A brief introduction to deque

7.3.1 Introduction to the principle of 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 there is no need to move elements; compared with list, the space utilization rate is relatively high.
Deque is not a real continuous space, but is spliced ​​by segments 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 its "overall continuity" and the illusion of random access, it falls on the iterator of the deque. Therefore, the iterator design of deque is more complicated.

7.3.2 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, its 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 .

7.4 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 for stack and
queue chooses deque as its underlying container 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 (no need to move a lot of data when expanding); when the elements in the queue grow, deque is not only efficient, but also has high memory usage.

Combining the advantages of deque, it perfectly avoids its defects.

Tags: C++ data structure Back-end programming language

Posted by scialom on Wed, 11 Jan 2023 05:02:44 +0300