【C++】pilha e fila

1. Adaptador de contêiner

Adaptador é um padrão de design (um padrão de design é um conjunto de experiências de design de código que são usadas repetidamente, conhecidas pela maioria das pessoas, classificadas e catalogadas).Esse padrão converte a interface de uma classe em outra que desejamos.

Embora os elementos também possam ser armazenados em pilhas e filas , eles não são classificados em contêineres no STL . Em vez disso, eles são chamados de adaptadores de contêiner . Isso ocorre porque pilhas e filas apenas envolvem as interfaces de outros contêineres. STL Em pilha e fila , deque é usado por padrão (descrito posteriormente), por exemplo:

Insira a descrição da imagem aqui

Insira a descrição da imagem aqui

Na verdade, os adaptadores de contêiner reutilizam outros contêineres e usam as funções de outros contêineres para adaptar um novo contêiner.

2. deque (entender)

deque (fila dupla): É uma estrutura de dados de espaço "contínuo" de abertura dupla. O significado da abertura dupla é que as operações de inserção e exclusão podem ser realizadas em ambas as extremidades, e a complexidade do tempo é O (1), que é o mesmo que Comparado com vector , a inserção de cabeçalho é mais eficiente e não requer elementos móveis; em comparação com list , a utilização do espaço é relativamente alta. Se você precisar de acesso aleatório eficiente e um grande número de inserções e exclusões, é recomendável usar o deque.

Deque não é um espaço verdadeiramente contínuo, mas é composto de pequenos espaços contínuos. O deque real é semelhante a uma matriz bidimensional dinâmica. Sua estrutura subjacente é mostrada na figura abaixo:

Insira a descrição da imagem aqui

Comparado com vector , a vantagem do deque é: ao inserir e excluir o cabeçalho, não há necessidade de mover elementos, o que é muito eficiente. Além disso, ao expandir, não há necessidade de mover um grande número de elementos, por isso sua eficiência é maior que vetor ;

Comparado com list , sua camada inferior é um espaço contínuo, a taxa de utilização do espaço é relativamente alta e não há necessidade de armazenar campos adicionais;

No entanto, deque tem uma falha fatal: não é adequado para travessia, porque durante a travessia, o iterador de deque precisa frequentemente detectar se ele se moveu para o limite de um determinado espaço pequeno, resultando em baixa eficiência. Em cenários sequenciais, ele pode ser necessário fazer Traversal com frequência, portanto, na prática, quando uma estrutura linear é necessária, a listaeo vetor Deque não possui muitos aplicativos, e um aplicativo que pode ser visto até agora é que o STL o usa como a estrutura de dados subjacente da da filaepilha

Então, por que escolher deque como o contêiner padrão subjacente para stack e queue ?

Stack é uma estrutura de dados linear especial com last-in-first-out . Portanto, qualquer estrutura linear com operações push_back() e pop_back() pode ser usada como o contêiner subjacente de stack , como vector e list ;

Queue é uma estrutura de dados linear especial que é o primeiro a entrar, primeiro a sair . Contanto que a estrutura linear tenha operações push_back e pop_front , ela pode ser usada como o contêiner subjacente de queue , como list . No entanto, deque é selecionado como contêiner subjacente por padrão para pilha e fila em STL , principalmente porque:

  1. A pilha e a fila não precisam ser percorridas (portanto, a pilha e a fila não possuem iteradores) , elas só precisam operar em uma ou ambas as extremidades fixas.
  2. Quando os elementos na pilha crescem, o deque é mais eficiente que o vetor (não há necessidade de mover uma grande quantidade de dados durante a expansão); quando os elementos na fila crescem, o deque não é apenas eficiente, mas também tem alto uso de memória . Combina as vantagens do deque e evita perfeitamente suas deficiências.

3. pilha

1. Introdução à pilha

Podemos primeiro dar uma olhada na documentação de stack : stack .

  1. Stack é um adaptador de contêiner especialmente utilizado em contextos com operações last-in-first-out. Sua exclusão só pode inserir e extrair elementos de uma extremidade do contêiner.

  2. Stack é implementado como um adaptador de contêiner. Um adaptador de contêiner encapsula uma classe específica como seu contêiner subjacente e fornece um conjunto de funções de membro específicas para acessar seus elementos. Ele usa uma classe específica como seu contêiner subjacente específico do elemento no final (Isso isto é, o topo da pilha) é empurrado e estourado.

  3. O contentor subjacente da pilha pode ser qualquer modelo de classe de contentor padrão ou alguma outra classe de contentor específica.Estas classes de contentores devem suportar as seguintes operações:

     		empty:判空操作
     		back:获取尾部元素操作
     		push_back:尾部插入元素操作
     		pop_back:尾部删除元素操作
    
  4. O vetor de contêineres padrão, deque e lista atendem a esses requisitos. Por padrão, se nenhum contêiner subjacente específico for especificado para a pilha , o deque será usado por padrão .

Vejamos primeiro brevemente o uso de stack :

		void test_stack()
		{
			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();
			}
			cout << endl;
		}

Os resultados da execução são os seguintes:

Insira a descrição da imagem aqui

2. Simular e implementar pilha

Usamos deque como a implementação de simulação do adaptador de stack :

		#pragma once
		#include <vector>
		#include <deque>
		
		namespace Young
		{
			template <class T, class Container = deque<T>>
			class Stack
			{
			public:
				// 入栈
				void push(const T& val)
				{
					_con.push_back(val);
				}
		
				// 出栈
				void pop()
				{
					_con.pop_back();
				}
		
				// 获取栈顶元素
				T& top()
				{
					return _con.back();
				}
		
				// const 对象获取栈顶元素
				const T& top() const
				{
					return _con.back();
				}
				
				// 获取栈的大小
				size_t size()
				{
					return _con.size();
				}
		
				// 判断栈是否为空
				bool empty()
				{
					return _con.empty();
				}
		
			private:
				Container _con;
			};
		}

Como acima, as interfaces comuns de pilha foram implementadas. Vamos testá-las com nossa própria pilha implementada:

Insira a descrição da imagem aqui

4.fila

1. Uso de fila

Vamos primeiro dar uma olhada na documentação da fila : queue .

  1. Uma fila é um adaptador de contêiner projetado para operar em um contexto FIFO (primeiro a entrar, primeiro a sair), onde os elementos são inseridos de uma extremidade do contêiner e extraídos da outra extremidade.

  2. A fila é implementada como um adaptador de contêiner, que encapsula uma classe de contêiner específica como sua classe de contêiner subjacente. A fila fornece um conjunto específico de funções de membro para acessar seus elementos. Os elementos são colocados na fila a partir do final da fila e retirados da fila do início.

  3. O contêiner subjacente pode ser um dos modelos de classe de contêiner padrão ou outras classes de contêiner especialmente projetadas. O contêiner subjacente deve suportar pelo menos as seguintes operações:

     		empty:检测队列是否为空
     		size: 返回队列中有效元素的个数
     		front:返回队头元素的引用
     		back: 返回队尾元素的引用
     		push_back:在队列尾部入队列
     		pop_front:在队列头部出队列
    
  4. As classes de contêiner padrão deque e list atendem a esses requisitos. Por padrão, se nenhuma classe de contêiner for especificada para instanciação de fila , o deque de contêiner padrão será usado .

Primeiro, vamos dar uma breve olhada no uso de queue :

		void test_queue()
		{
			queue<int> q;
			q.push(1);
			q.push(2);
			q.push(3);
			q.push(4);
			q.push(5);
			while (!q.empty())
			{
				cout << q.front() << ' ';
				q.pop();
			}
			cout << endl;
		}

Os resultados da execução são os seguintes:

Insira a descrição da imagem aqui

2. Simular implementação de fila

Também usamos deque para adaptar queue :

		#pragma once
		#include <deque>
		
		
		namespace Young
		{
			template<class T, class Container = deque<T>>
			class Queue
			{
			public:
				// 入队列
				void push(const T& val)
				{
					_con.push_back(val);
				}
		
				// 出队列
				void pop()
				{
					_con.pop_front();
				}
		
				// const 对象获取队头元素
				const T& front() const
				{
					return _con.front();
				}
		
				// 获取队头元素
				T& front()
				{
					return _con.front();
				}
		
				// const 对象获取队尾元素
				const T& back() const
				{
					return _con.back();
				}
		
				// 获取队尾元素
				T& back()
				{
					return _con.back();
				}
		
				// 获取队列长度
				size_t size()
				{
					return _con.size();
				}
		
				// 判断队列是否空
				bool empty()
				{
					return _con.empty();
				}
			private:
				Container _con;
			};
		}

Vamos usar a fila que implementamos para testá-la:

Insira a descrição da imagem aqui

3. prioridade_fila

(1) Introdução ao prioridade_queue

Priority_queue: A fila prioritária é um tipo de fila. Vamos primeiro dar uma olhada em sua documentação para apresentar Priority_queue .

  1. Uma fila de prioridade é um adaptador de contêiner cujo primeiro elemento é sempre o maior dos elementos que contém, de acordo com critérios estritos de ordenação fraca.

  2. Este contexto é semelhante a um heap, onde os elementos podem ser inseridos a qualquer momento e apenas o maior elemento do heap (o elemento no topo da fila de prioridade) pode ser recuperado.

  3. O contêiner subjacente pode ser qualquer modelo de classe de contêiner padrão ou outra classe de contêiner projetada especificamente. O contêiner deve ser acessível por meio de iteradores de acesso aleatório e suportar as seguintes operações:

     		empty():检测容器是否为空
     		size(): 返回容器中有效元素个数
     		front():返回容器中第一个元素的引用
     		push_back():在容器尾部插入元素
     		pop_back(): 删除容器尾部元素
    
  4. As classes de contêiner padrão vector e deque atendem a essas necessidades. Por padrão, vector é usado se nenhuma classe de contêiner for especificada para uma instanciação de classe prioridade_queue específica .

  5. Os iteradores de acesso aleatório precisam ser suportados para que a estrutura do heap seja sempre mantida internamente. O adaptador de contêiner faz isso automaticamente, chamando automaticamente as funções algorítmicas make_heap, push_heap e pop_heap quando necessário.

(2) Uso de prioridade_queue

Por padrão, a fila de prioridade usa o vetor como seu contêiner subjacente para armazenar dados. O algoritmo de heap é usado no vetor para construir os elementos do vetor em uma estrutura de heap . Portanto, prioridade_queue é um heap. Você pode considerar o uso de prioridade_queue sempre que um pilha é necessária. . Nota: Por padrão, priority_queue é um heap grande .

Insira a descrição da imagem aqui

Observe que quando olhamos para a documentação, prioridade_queue é uma pilha grande por padrão , mas a caixa vermelha na imagem acima é um functor (descrito mais adiante) que implementa comparação. Menos significa pequeno, mas é implementado em uma pilha grande. , preste atenção aqui. Em segundo lugar, quando usamos os parâmetros padrão, precisamos apenas passar o primeiro parâmetro, e os parâmetros subsequentes podem usar os parâmetros padrão. No entanto, quando precisamos usar um pequeno heap, precisamos passar todos os parâmetros; vamos pegar uma olhada primeiro. use:

		#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;
		}

Os resultados da execução são os seguintes:

Insira a descrição da imagem aqui

(3) Funtor

Functor, também conhecido como objeto de função, é uma classe que pode executar funções funcionais. Seu uso é igual às nossas chamadas de função habituais.

Primeiro temos que implementar uma classe. Esta classe precisa implementar a ()sobrecarga do operador. As funções implementadas nela precisam ser implementadas por nós mesmos. Suponha que precisamos implementar um grande heap em prioridade_queue , como segue:

		// 仿函数 --- 大堆,大的优先级大
		template <class T>
		class Less
		{
		public:
			bool operator()(const T& x, const T& y)
			{
				return x < y;
			}
		};

Então, como chamá-lo? Primeiro temos que criar um objeto e depois usar esse objeto para chamar a função:

			Less<int> less;
			cout << less(1, 8) << endl; // 与下等价
			cout << less.operator()(1, 8) << endl;

Os resultados da execução são os seguintes:

Insira a descrição da imagem aqui

Abaixo usamos o formulário functor para simular e implementar prioridade_queue .

(4) Simular implementação de prioridade_queue

	#pragma once
	#include <vector>
	
	namespace Young
	{
		// 模板参数
		template <class T, class Container = vector<T>, class Compare = Less<T>>
		class PriorityQueue
		{
		public:
			// 向上调整 --- 大堆
			void adjust_up(size_t child)
			{
				Compare com;
				size_t parent = (child - 1) / 2;
				while (child > 0)
				{
					//if (_con[parent] < _con[child]) // 与下等价
					if (com(_con[parent], _con[child]))
					{
						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() && _con[child + 1] > _con[child])
					{
						++child;
					}
	
					// if (_con[parent] < _con[child])  // 与下等价
					if (com(_con[parent], _con[child]))
					{
						swap(_con[child], _con[parent]);
						parent = child;
						child = parent * 2 + 1;
					}
					else
					{
						break;
					}
				}
			}
	
			// 入数据
			void push(const T& val)
			{
				_con.push_back(val);
				adjust_up(_con.size() - 1);
			}
	
			// 出数据
			void pop()
			{
				swap(_con[0], _con[_con.size() - 1]);
				_con.pop_back();
	
				adjust_down(0);
			}
	
			// 获取优先级最大的数据
			const T& top()
			{
				return _con.front();
			}
	
			// 判空
			bool empty()
			{
				return _con.empty();
			}
	
		private:
			Container _con;
		};
	}

Abaixo usamos a fila de prioridade que implementamos para teste:

		void test_priority_queue()
		{
			// Young::PriorityQueue<int, vector<int>, Greater<int>> pq; // 小堆
		
			// Young::PriorityQueue<int, vector<int>, Less<int>> pq; // 大堆,与下等价
			Young::PriorityQueue<int> pq;  // 缺省参数默认是大堆
			pq.push(2);
			pq.push(8);
			pq.push(1);
			pq.push(0);
			pq.push(10);
		
			while (!pq.empty())
			{
				cout << pq.top() << ' ';
				pq.pop();
			}
		
			cout << endl;
		}

Os resultados do teste são os seguintes:

Insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/YoungMLet/article/details/133272448
Recomendado
Clasificación