[STL] Explicação detalhada da fila de prioridade e do iterador reverso

Índice

1. Stack_required para perguntas de escovação

Dois, implementação de pilha

1. O que é um adaptador de contêiner

2. A estrutura subjacente de pilha e fila na biblioteca padrão STL 

Suplemento de compreensão: Container - deque 

1. O defeito de deque

2. Por que escolher deque como o contêiner padrão subjacente de pilha e fila

Três, implementação de fila

1. Fila normal 

2, fila prioritária (difícil)

<1>.Função

<2>. Implementação de simulação

1) Usando iterator_construct

2).Funtor

Compreensão do uso do funtor na função de classificação

4. Iterador reverso (tome a lista como exemplo)

2. Um pequeno detalhe sobre a documentação do iterador 

epílogo


1. Stack_required para perguntas de escovação

Interface comum: 

stack()     cria uma pilha vazia
vazio()    detecta se a pilha está vazia
size()        retorna o número de elementos na pilha
top()         retorna uma referência ao elemento superior da pilha
push()      empurra o elemento val para a pilha
pop()        exibe o elemento no final da pilha

 Aplicação simples, tire algumas dúvidas:

155. Pilha Mínima

Empilhar sequências push e pop Revelar papel

150. Avaliação reversa da expressão polonesa

Dois, implementação de pilha

Ideia: Durante o período da linguagem C, podemos realizar a pilha por meio da lista vinculada e do formato de array , e o formato de array é mais eficiente, de modo que a implementação da pilha pode reutilizar diretamente a interface vetorial e empacotá-la no primeiro - recurso in-last-out da pilha .

#pragma once
#include <iostream>
#include <vector>
#include <list>
using namespace std;
namespace my_s_qu
{
	template <class T >
	class stack
	{
	public:
		void push_back(const T& x)
		{
			Data.push_back(x);
		}

		void Pop()
		{
			Data.pop_back();
		}

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

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

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

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

	private:
		vector<T> Data;
	};
}

Simples, não é? Não termina aqui, descobrimos que nossa pilha só pode ser realizada através de vetor. De acordo com a biblioteca padrão C++, ainda falta uma implementação de adaptador, ou seja, a pilha que implementamos não suporta adaptadores de contêiner

1. O que é um adaptador de contêiner

Adaptador é um padrão de design (um padrão de design é um conjunto de uso repetido, conhecido pela maioria das pessoas, classificado e catalogado, e um resumo da experiência de design de código), que converte a interface de uma classe em outra que os clientes desejam.

2. A estrutura subjacente de pilha e fila na biblioteca padrão STL 

Embora os elementos também possam ser armazenados em pilha e fila, eles não são divididos nas classificações de contêineres em STL, mas são chamados de adaptadores de contêiner , porque pilhas e filas apenas envolvem as interfaces de outros contêineres, STL Na pilha e na fila, deque é usado por padrão .

Voltando à implementação de nossa pilha, oferecemos suporte apenas ao design do modo vetorial e o otimizamos em um adaptador de contêiner que suporta todos os contêineres e pode suportar o recurso primeiro a entrar e último a sair da pilha. (na minha opinião esta ideia é mais importante)

namespace my_s_qu
{
	template <class T, class container = deque<T> >  // 添加容器模板
	{
	public:
		void push_back(const T& x)
		{
			Data.push_back(x);
		}

......
private:
		container Data;  // 容器换成模板
	};
}

Entre eles, o que é deque?

Suplemento de compreensão: Container - deque 

Deque (fila dupla): É uma estrutura de dados de espaço "contínuo" de abertura dupla . O significado da abertura dupla é: as operações de inserção e exclusão podem ser executadas em ambas as extremidades da cabeça e da cauda, ​​e a complexidade do tempo é O (1). Comparado com o vetor, a eficiência do plug-in é alta e não há necessidade de mover elementos; em comparação com a lista, a taxa de utilização do espaço é relativamente alta.

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

A camada inferior da fila dupla é uma ilusão de espaço contínuo, que na verdade é segmentada e contínua. Para manter sua "continuidade geral" e a ilusão de acesso aleatório, ela recai sobre o iterador do deque, então o o design do iterador do deque é mais complicado , conforme mostrado abaixo:

 

Como o deque mantém sua hipotética estrutura contínua com a ajuda de seu iterador? 

 

1. O defeito de deque

Vantagem: 

Comparado com vector , a vantagem do deque é: a eficiência de inserção e exclusão de cabeçalho é alta.  Não há necessidade de mover elementos e a eficiência é particularmente elevada e, ao expandir, não há necessidade de mover um grande número de elementos , pelo que a sua eficiência deve ser elevada.
Comparado com a lista , vantagens : suporte ao acesso aleatório . A camada inferior é um espaço contínuo e a taxa de utilização do espaço é relativamente alta e não há necessidade de armazenar campos adicionais.

defeito:  

Não é adequado para travessia.
Porque ao percorrer, os iteradores deque precisam calcular frequentemente a posição de cada dado , resultando em baixa eficiência , e em cenários sequenciais, a travessia frequente pode ser necessária, portanto, na prática, quando uma estrutura linear é necessária, é preferível na maioria dos casos. vetor e lista, não há muitas aplicações de deque , e uma aplicação que pode ser vista até agora é que o STL o usa como estrutura de dados subjacente de pilha e fila .

2. Por que escolher deque como o contêiner padrão subjacente de pilha e fila

Stack é uma estrutura de dados linear especial, último a entrar, primeiro a sair, portanto, desde que tenha uma estrutura linear com operações push_back() e pop_back(), ela pode ser usada como o contêiner subjacente da pilha, como vetor e lista; fila é uma estrutura de dados linear especial, primeiro a entrar, primeiro a sair, desde que tenha uma estrutura linear com operações push_back e pop_front, pode ser usada como o contêiner subjacente da fila , como lista. No entanto, em STL, deque é selecionado como o contêiner subjacente por padrão para sack e queue, principalmente porque:
1. A pilha e a fila não precisam ser percorridas (portanto, a pilha e a fila não têm iteradores) e só precisam operar em uma ou ambas as extremidades do fixo.
2. Quando os elementos da pilha crescem, o deque é mais eficiente que o vetor (não há necessidade de mover muitos dados ao expandir), quando os elementos da fila crescem, o deque não é apenas eficiente, mas também tem alto uso de memória.
Combinando as vantagens do deque, evita perfeitamente seus defeitos.

Três, implementação de fila

1. Fila normal 

 Implementação da fila, desta vez usamos o adaptador de contêiner.

template <class T, class container = deque<T>>
	class queue
	{
	public:
		void push_back(const T& x)
		{
			Data.push_back(x);
		}


		void Pop()
		{
			Data.pop_front();
		}

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

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

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

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

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

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

	private:
		container Data;
	};

2, fila prioritária (difícil)

Introdução:

1. Uma fila de prioridade é um adaptador de contêiner cujo primeiro elemento é sempre o maior entre os elementos que contém, de acordo com critérios estritos de ordenação fraca.
2. A camada inferior da fila de prioridade é semelhante a um heap, os elementos podem ser inseridos no heap a qualquer momento e apenas o maior elemento do heap ( o elemento superior da fila de prioridade) pode ser recuperado.
3. A fila de prioridade é implementada como um adaptador de contêiner, que encapsula uma classe de contêiner específica como sua classe de contêiner subjacente, e a fila fornece um conjunto de funções de membro específicas para acessar seus elementos. Os elementos são retirados (Popped) da "cauda" de um contêiner específico, que é chamado de topo da fila de prioridade.
5. As classes de contêiner padrão vector e deque atendem a esses requisitos. Por padrão, se nenhuma classe de contêiner for especificada para uma instanciação de classe prioridade_queue específica, o vetor será usado
6. Necessidade de suporte a iteradores de acesso aleatório para que a estrutura de 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.

<1>.Função

Priority_queue()/priority_queue(fifirst, last) Construa uma fila de prioridade vazia
vazio() Verifica se a fila de prioridade está vazia, retorna verdadeiro se estiver, caso contrário retorna falso
top() retorna o maior (menor elemento) na fila de prioridade, ou seja, o elemento superior do heap
push(x) insere o elemento x na fila de prioridade
pop() exclui o maior (menor) elemento da fila de prioridade, ou seja, o elemento superior do heap
pop_back() remove elementos no final do contêiner

<2>. Implementação de simulação

Para completar a fila de prioridade, você precisa usar o conhecimento do heap.Se você não estiver familiarizado com o heap, é recomendável revisar o conteúdo da implementação do heap:

Explicação detalhada do conceito, estrutura e implementação de árvores e árvores binárias (Parte 1) - Huaguoshan~~Programmer's Blog-CSDN Blog

 Use o conhecimento do heap para completar a estrutura mais básica:

namespace my_priority_queue
{
	template <class T, class container = vector<T>> 
	class priority_queue
	{
	public:
		// 自定义类型,不需要给他初始化

		void ajust_up(size_t child)
		{
			int parent;
			while (child > 0)
			{
				parent = child / 2;
				if (_pri_queue[child] > _pri_queue[parent])  // 写大堆
				{
					std::swap(_pri_queue[child], _pri_queue[parent]);
					child = parent;
					parent = child / 2;
				}
				else
				{
					break;
				}
			}
		}

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

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

		void push(const T& x)
		{
			_pri_queue.push_back(x);
			// 向上调整
			ajust_up(_pri_queue.size() - 1);
		}

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

		void ajust_down()
		{
			size_t  parent = 0;
			size_t child = 2 * parent + 1;

			while (child < _pri_queue.size())
			{
				if (child + 1 < _pri_queue.size() && _pri_queue[child + 1] >  _pri_queue[child])
				{
					child++;
				}

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

		void pop()
		{
			std::swap(_pri_queue[0], _pri_queue[size() - 1]);
			// 向下调整
			_pri_queue.pop_back();
			ajust_down();
		}

	private:
		container _pri_queue;
	};
}

Aqui temos uma pergunta: o que devemos fazer se quisermos construir uma ordem crescente? O functor irá interpretar

1) Usando iterator_construct

// 自定义类型,不需要给他初始化
		priority_queue()
		{}

		template  <class newiterator>
		priority_queue(newiterator begin, newiterator end)
			: _pri_queue()
		{
			while (begin != end)
			{
				push(*begin);
				begin++;
			}
		}

2).Funtor

Neste cenário, nossa fila de prioridade já implementou a função de ordem decrescente, então como implementamos a ordem crescente? O que você diria para escrever outro parágrafo e mudar os símbolos? ?

Aqui tem que introduzir o functor, o functor, então não é uma função, o que é? (Funtores como: menos, maior)

 O functor é essencialmente uma classe . De acordo com o modelo de comparação na figura acima, o functor aqui é usado para alterar de forma flexível o tamanho do símbolo de heap .

Implementamos o resultado diretamente:

    template <class T>
	struct less
	{
		bool operator()(const T& left, const T& right)const
		{
			return left < right;
		}
	};

	template <class T>
	struct greater
	{
		bool operator()(const T& left, const T& right)const
		{
			return left > right;
		}
	};

 Essas duas classes estão sobrecarregadas com operador(), então após instanciar o objeto, use:

modelo <class T,class compare = less<T>>

compare t;

cout << t(1, 2) << endl;     

Visto de fora, parece uma chamada de função, mas na verdade é uma chamada para uma função membro de uma classe

t.operador(1,2) 

 Desta forma, podemos alterar diretamente a classe do functor para controlar o tamanho do heap. 

Compreensão do uso do funtor na função de classificação

No modelo de função

 A função sort é usada como parâmetro de linha :

  

sort(s.begin, s.end, maior<int>() ); // A função é para ser um parâmetro, adicionamos parênteses para se tornar um objeto anônimo

4. Iterador reverso (tome a lista como exemplo)

Se você tem amigos que ainda estão aprendendo iteradores comuns, consulte a implementação de iteradores comuns neste artigo.

[STL] lista de uso e implementação try-out_underlying_Huaguoshan ~~ Blog do programador-Blog CSDN

 Referindo-se ao código-fonte da lista, aqui está o resultado direto e descobriu que o código-fonte constrói um iterador reverso pegando emprestado um iterador comum .

 Vá diretamente para o código:

namespace my_list
{
	template <class T>
	struct list_node
	{
		list_node(const T& data = T())
			: _data(data)
			, _next(nullptr)
			, _prv(nullptr)
		{}

		T _data;
		list_node* _next;
		list_node* _prv;
	};

	template <class T, class Ref, class Ptr>
	struct list_iterator
	{
		typedef list_node<T> Node;
		typedef list_iterator< T, Ref, Ptr> iterator;

		Node* _node;

		list_iterator(Node* node)
			: _node(node)
		{}

		bool operator!= (const iterator& it)
		{
			return _node != it._node;
		}

		bool operator==(const iterator& it)
		{
			return _node == it._node;
		}

		iterator& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		iterator& operator--()
		{
			_node = _node->_prv;
			return *this;
		}

		iterator operator++(int)
		{
			iterator tmp(*this);
			_node = _node->_next;
			return *tmp;
		}

		Ptr operator*()
		{
			return _node->_data;

		}
		Ref operator->()
		{
			return &(operator*());
		}
	};

	template <class Iterator, class Ref, class Ptr>
	struct _reverse_iterator
	{
		typedef _reverse_iterator<Iterator, Ref, Ptr>  reverse_iterator;

		Iterator _cur;
		
		_reverse_iterator(const Iterator& cur)
			: _cur(cur)
		{}

		reverse_iterator& operator++()
		{
			--_cur;
			return *this;
		}

		reverse_iterator operator++(int)
		{
			reverse_iterator temp(*this);
			--_cur;
			return temp;
		}

		reverse_iterator& operator--()
		{
			++_cur;
			return _cur;
		}

		reverse_iterator operator--(int)
		{
			reverse_iterator temp(*this);
			++_cur;
			return temp;
		}

		// != 
		bool operator!=(const reverse_iterator& end)
		{
			return _cur != end._cur;
		}

		bool operator==(const reverse_iterator&  end)
		{
			return _cur == end._cur;
		}

		// *     
		Ptr operator*() 
		{
			auto tmp = _cur;
			--tmp;
			return *tmp;
		}

		// ->
		Ref operator->()
		{
			return &(operator*());
		}
	};

	template <class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		typedef list_iterator<T, T*, T&>  iterator;
		typedef list_iterator<T, const T*, const T&> const_iterator;

		typedef _reverse_iterator<iterator, T*, T&> reverse_iterator;
		typedef _reverse_iterator<const_iterator, const T*, const T&> const_reverse_iterator;

		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}

		const_reverse_iterator rbegin() const
		{
			return const_reverse_iterator(end());
		}

		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}

		const_reverse_iterator rend() const
		{
			return const_reverse_iterator(begin());
		}


		iterator begin()
		{
			return iterator(_head->_next);
		}

		iterator end()
		{
			return iterator(_head);
		}

		const_iterator begin() const
		{
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
			return const_iterator(_head);
		}
..... //list其他成员函数这里就不再赘述了

A ideia do design é relativamente simples. É essencialmente uma função que reutiliza iteradores comuns. A ideia de outras funções sobrecarregadas é semelhante à das funções comuns. Mas aqui está também um design mais artístico:

Então vamos discutir aqui, esse iterador reverso pode ser usado para vetores? ? a resposta é sim

Olha a foto:

Conclusão: Iteradores reversos: adaptadores para iteradores.

2. Um pequeno detalhe sobre a documentação do iterador 

Todos os recipientes são adequados? 

Não necessariamente, porque o iterador comum do contêiner deve pelo menos suportar a interface ++, -- (por exemplo: forward_list não suporta --, portanto não possui um iterador reverso)

Aqui estão alguns suplementos sobre o uso de documentos [STL], que são divididos em três categorias do ponto de vista das funções do iterador :

1. Suporte a forward_iterator (iterador unidirecional) ——> ++ Por exemplo: forward_list, etc.

2. bidirecional_iterador (iterador bidirecional) --> ++ -- como: lista, etc.

3. radom_access_iterator (iterador aleatório) --> ++ -- + - Por exemplo: vetor, deque, etc., o terceiro tipo de herança de iterador 1, 2

Qual é o significado disso? ?

Significado: É um lembrete de que, ao usar um iterador, a interface solicitará o tipo de iterador apropriado.

 

epílogo

   Esta seção está aqui, obrigado amigos por navegarem, se vocês tiverem alguma sugestão, sejam bem vindos a comentar na área de comentários, se vocês trouxerem algum ganho para seus amigos, por favor deixem seus likes, seus gostos e preocupações se tornarão blogueiros A força motriz da criação do mestre .

Acho que você gosta

Origin blog.csdn.net/qq_72112924/article/details/132067196
Recomendado
Clasificación