[STL] Explicación detallada de la cola de prioridad y el iterador inverso

Tabla de contenido

1. Stack_required para preguntas sobre cepillado

Dos, implementación de pila

1. ¿Qué es un adaptador de contenedor?

2. La estructura subyacente de pila y cola en la biblioteca estándar STL 

Suplemento de comprensión: Contenedor - deque 

1. El defecto del deque

2. ¿Por qué elegir deque como contenedor predeterminado subyacente de pila y cola?

Tres, implementación de cola

1. Cola ordinaria 

2, cola prioritaria (difícil)

<1>.Función

<2> Implementación de simulación

1) Usando iterator_construct

2) Functor

Comprensión del uso de funtores en la función de clasificación

4. Iterador inverso (tome la lista como ejemplo)

2. Un pequeño detalle sobre la documentación del iterador. 

epílogo


1. Stack_required para preguntas sobre cepillado

Interfaz común: 

stack()     crea una pila vacía
vacío()    detecta si la pila está vacía
size()        devuelve el número de elementos en la pila
top()         devuelve una referencia al elemento superior de la pila
push()      empuja el elemento val a la pila
pop()        muestra el elemento al final de la pila

 Aplicación sencilla, cepille algunas preguntas:

155. Pila mínima

Apilar secuencias de empujar y hacer estallar Desarrollar papel

150. Evaluación de expresión polaca inversa

Dos, implementación de pila

Idea: durante el período del lenguaje C, podemos implementar la pila a través de una lista vinculada y una forma de matriz , y la forma de matriz es más eficiente, por lo que la implementación de la pila puede reutilizar directamente la interfaz vectorial y empaquetarla en la primera. Característica de entrada y salida de la pila .

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

Sencillo ¿no? Esto no termina aquí, descubrimos que nuestra pila solo se puede implementar a través de vectores. Según la biblioteca estándar de C ++, todavía nos falta una implementación de adaptador, es decir, la pila que implementamos no admite adaptadores de contenedor

1. ¿Qué es un adaptador de contenedor?

Un adaptador es un patrón de diseño (un patrón de diseño es un conjunto de uso repetido, conocido por la mayoría de la gente, clasificado y catalogado, y un resumen de la experiencia de diseño de código), que convierte la interfaz de una clase en otra interfaz que los clientes desean.

2. La estructura subyacente de pila y cola en la biblioteca estándar STL 

Aunque los elementos también se pueden almacenar en pila y cola, no se dividen en rangos de contenedores en STL, sino que se denominan adaptadores de contenedor , porque las pilas y colas solo envuelven las interfaces de otros contenedores, STL En pila y cola, se usa deque por defecto .

Volviendo a la implementación de nuestra pila, solo admitimos el diseño en modo vectorial y lo optimizamos en un adaptador de contenedor que admite todos los contenedores y puede admitir la característica de primero en entrar y último en salir de la pila. (en mi opinión esta idea es más 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 ellos, ¿qué es deque?

Suplemento de comprensión: Contenedor - deque 

Deque (cola de doble extremo): es una estructura de datos espaciales "continua" de doble apertura . El significado de doble apertura es: las operaciones de inserción y eliminación se pueden realizar en ambos extremos de la cabeza y la cola, y la complejidad del tiempo es O (1). En comparación con el vector, la eficiencia del complemento es alta y no es necesario mover los elementos; en comparación con la lista, la tasa de utilización del espacio es relativamente alta.

Deque no es un espacio continuo real , sino que está empalmado por espacios pequeños continuos . El deque real es similar a una matriz dinámica bidimensional . Su estructura subyacente se muestra en la siguiente figura:

La capa inferior de la cola de dos extremos es una ilusión de espacio continuo, que en realidad está segmentada y continua. Para mantener su "continuidad general" y la ilusión de acceso aleatorio, cae sobre el iterador de la deque, por lo que El diseño del iterador del deque es más complicado , como se muestra a continuación:

 

¿Cómo mantiene deque su hipotética estructura continua con la ayuda de su iterador? 

 

1. El defecto del deque

Ventaja: 

Comparado con vector , la ventaja de deque es: la eficiencia de la inserción y eliminación de cabezales es alta.  No es necesario mover elementos y la eficiencia es particularmente alta . Al expandirse, no es necesario mover una gran cantidad de elementos , por lo que su eficiencia debe ser alta.
En comparación con la lista , ventajas : admite acceso aleatorio . La capa inferior es un espacio continuo, la tasa de utilización del espacio es relativamente alta y no es necesario almacenar campos adicionales.

defecto:  

No apto para travesías.
Debido a que al atravesar, los iteradores deque necesitan calcular con frecuencia la posición de cada dato , lo que resulta en baja eficiencia . En escenarios secuenciales, es posible que se requiera un recorrido frecuente, por lo que en la práctica, cuando se requiere una estructura lineal, se prefiere en la mayoría de los casos. vector y lista, no hay muchas aplicaciones de deque , y una aplicación que se puede ver hasta ahora es que STL lo usa como estructura de datos subyacente de pila y cola .

2. ¿Por qué elegir deque como contenedor predeterminado subyacente de pila y cola?

La pila es una estructura de datos lineal especial de último en entrar, primero en salir, por lo que siempre que tenga una estructura lineal con operaciones push_back() y pop_back(), se puede utilizar como contenedor subyacente de la pila, como vector y lista; la cola es una estructura de datos lineal especial primero en entrar, primero en salir, siempre que tenga una estructura lineal con operaciones push_back y pop_front, se puede utilizar como contenedor subyacente de la cola , como la lista. Sin embargo, en STL, deque se selecciona como contenedor subyacente de forma predeterminada para saco y cola, principalmente porque:
1. No es necesario atravesar la pila y la cola (por lo que la pila y la cola no tienen iteradores) y solo necesitan operar en uno o ambos extremos del fijo.
2. Cuando los elementos en la pila crecen, deque es más eficiente que el vector (no es necesario mover muchos datos al expandirse); cuando los elementos en la cola crecen, deque no solo es eficiente, sino que también tiene un alto uso de memoria.
Combinando las ventajas del deque, evita perfectamente sus defectos.

Tres, implementación de cola

1. Cola ordinaria 

 Implementación de cola, esta vez usamos el adaptador de contenedor.

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, cola prioritaria (difícil)

Introducción:

1. Una cola de prioridad es un adaptador de contenedor cuyo primer elemento es siempre el más grande entre los elementos que contiene según estrictos criterios de ordenamiento débiles.
2. La capa inferior de la cola de prioridad es similar a un montón: los elementos se pueden insertar en el montón en cualquier momento y solo se puede recuperar el elemento del montón más grande ( el elemento superior de la cola de prioridad).
3. La cola de prioridad se implementa como un adaptador de contenedor, que encapsula una clase de contenedor específica como su clase de contenedor subyacente, y la cola proporciona un conjunto de funciones miembro específicas para acceder a sus elementos. Los elementos se extraen de la "cola" de un contenedor en particular, que se denomina parte superior de la cola de prioridad.
5. Las clases de contenedores estándar vector y deque cumplen estos requisitos. De forma predeterminada, si no se especifica ninguna clase de contenedor para una instancia de clase de cola prioritaria particular, se utiliza vector
6. Necesidad de admitir iteradores de acceso aleatorio para que la estructura del montón siempre se mantenga internamente. El adaptador de contenedor hace esto automáticamente llamando automáticamente a las funciones algorítmicas make_heap, push_heap y pop_heap cuando sea necesario.

<1>.Función

Priority_queue()/priority_queue(fifirst, last) Construye una cola de prioridad vacía
vacío () Comprueba si la cola de prioridad está vacía, devuelve verdadero si lo está; de lo contrario, devuelve falso
top() devuelve el elemento más grande (el más pequeño) en la cola de prioridad, es decir, el elemento superior del montón
push(x) inserta el elemento x en la cola de prioridad
pop() elimina el elemento más grande (más pequeño) en la cola de prioridad, es decir, el elemento superior del montón
pop_back() elimina elementos al final del contenedor

<2> Implementación de simulación

Para completar la cola de prioridad, debe utilizar el conocimiento del montón. Si no está familiarizado con el montón, se recomienda revisar el contenido de implementación del montón:

Explicación detallada del concepto, estructura e implementación de árboles y árboles binarios (Parte 1) - Huaguoshan ~~ Blog del programador-Blog CSDN

 Utilice el conocimiento del montón para completar el marco más básico:

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

Aquí tenemos una pregunta, ¿qué debemos hacer si queremos construir un orden ascendente? El funtor 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) Functor

En este escenario, nuestra cola de prioridad ya ha implementado la función de orden descendente, entonces, ¿cómo implementamos el orden ascendente? ¿Qué dices de escribir otro párrafo y cambiar los símbolos? ?

Aquí hay que introducir el funtor, el funtor, entonces no es una función, ¿qué es? (Funtores como: menor, mayor)

 El funtor es esencialmente una clase . Según la plantilla de comparación en la figura anterior, el funtor aquí se usa para cambiar de manera flexible el tamaño del símbolo del montón .

Implementamos el resultado directamente:

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

 Estas dos clases están sobrecargadas con operator(), por lo que después de crear una instancia del objeto, use:

plantilla <clase T, comparación de clases = menos<T>>

comparar t;

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

Desde fuera parece una llamada a una función, pero en realidad es una llamada a una función miembro de una clase.

t.operador(1,2) 

 De esta forma, podemos cambiar directamente la clase del functor para controlar el tamaño del montón. 

Comprensión del uso de funtores en la función de clasificación

En la plantilla de función : 

 La función de clasificación se utiliza como parámetro de línea :

  

sort(s.begin, s.end, major<int> ()); // La función debe ser un parámetro, agregamos paréntesis para convertirla en un objeto anónimo

4. Iterador inverso (tome la lista como ejemplo)

Si tiene amigos que todavía están aprendiendo iteradores ordinarios, consulte la implementación de iteradores ordinarios en este artículo.

[STL] lista de uso y prueba_implementación subyacente_Huaguoshan ~~ Blog del programador-Blog CSDN

 Refiriéndose al código fuente de la lista, aquí está el resultado directamente y descubrí que el código fuente construye un iterador inverso tomando prestado un iterador común .

 Vaya directamente al 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其他成员函数这里就不再赘述了

La idea de diseño es relativamente simple: es esencialmente una función que reutiliza iteradores ordinarios, y la idea de otras funciones sobrecargadas es similar a la de las funciones ordinarias. Pero aquí también hay un diseño más artístico:

Entonces analicemos aquí: ¿se puede usar este iterador inverso para vectores? ? la respuesta es sí

Mira la foto:

Conclusión: Iteradores inversos: adaptadores para iteradores.

2. Un pequeño detalle sobre la documentación del iterador. 

¿Todos los contenedores son adecuados? 

No necesariamente, porque el iterador ordinario del contenedor debe al menos admitir la interfaz ++, -- (por ejemplo: forward_list no admite --, por lo que no tiene un iterador inverso)

Aquí hay algunos suplementos sobre el uso de documentos [STL], que se dividen en tres categorías desde la perspectiva de las funciones de iterador :

1. Compatibilidad con forward_iterator (iterador unidireccional) ——> ++ Por ejemplo: forward_list, etc.

2. bidireccional_iterator (iterador bidireccional) --> ++ - como: lista, etc.

3. radom_access_iterator (iterador aleatorio) --> ++ -- + - Por ejemplo: vector, deque, etc., el tercer tipo de herencia de iterador 1, 2

¿Cuál es el significado de eso? ?

Importancia: es un recordatorio de que cuando utilice un iterador, la interfaz le indicará el tipo de iterador apropiado.

 

epílogo

   Esta sección terminó aquí, gracias amigos por navegar, si tienen alguna sugerencia, bienvenidos a comentar en el área de comentarios, si aportan algunas ganancias a sus amigos, dejen sus me gusta, sus me gusta y sus inquietudes se convertirán en bloggers. La fuerza impulsora de la creación del maestro .

Supongo que te gusta

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