C++ [pila y cola (3 tipos) e iterador inverso]

1. Adaptador de contenedor

Adapter 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 las personas, 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.
Las pilas, las colas y los iteradores inversos que se describen más adelante son todos adaptadores.
Aunque los elementos también se pueden almacenar en pilas y colas, no se dividen en rangos de contenedores en STL, sino que se denominan adaptadores de contenedores, porque las pilas y las colas solo envuelven las interfaces de otros contenedores, STL La implementación subyacente de pilas y colas en la capa intermedia usa deque por defecto. deque cola de dos extremos. Como se muestra abajo:
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

dos, la pila

(1) Definición de pila

stack es un adaptador de contenedor, que se usa especialmente en el entorno de contexto con la operación de último en entrar, primero en salir , y su eliminación solo puede insertar y extraer elementos de un extremo del contenedor. La pila se implementa como un adaptador de contenedor, que encapsula una clase específica como su contenedor subyacente y proporciona un conjunto de funciones de miembro específicas para acceder a sus elementos, utilizando una clase específica como su cola de contenedor específica de elemento subyacente (es decir, la parte superior de la pila) se empuja y se abre. El contenedor subyacente de la pila puede ser cualquier plantilla de clase de contenedor estándar o alguna otra clase de contenedor específica Estas clases de contenedor deben admitir las siguientes operaciones:
vacío: operación nula
atrás: obtener elemento de cola operación
push_back: inserción de elemento de cola operación
pop_back: eliminación de elemento de cola Operación
Los contenedores estándar vector, deque y list cumplen todos estos requisitos De forma predeterminada, si no se especifica ningún contenedor subyacente específico para la pila, se utiliza deque de forma predeterminada.

(2) interfaz de uso de pila

stack(): construye una pila vacía empty
(): comprueba si la pila está vacía
size(): devuelve el número de elementos de la pila
top(): devuelve la referencia al elemento superior de la pila
push(): empuja el elemento val en la pila
pop () : Pop el elemento al final de la pila

(3) Implementación de simulación de pila

Aquí, el contenedor subyacente de mi pila se implementa con vector.

(1) Análisis de la implementación de simulación de pila

#pragma once

namespace nza
{
    
    
	template<class T, class Container=vector<int>>
	class stack
	{
    
    
	public:
		void push(const T& x)
		{
    
    
			_co.push_back(x);
		}
		void pop()
		{
    
    
			_co.pop_back();
		}
		const T& top()
		{
    
    
			return _co.back();
		}
		size_t size()
		{
    
    
			return _co.size();
		}
		bool empty()
		{
    
    
			return _co.empty();
		}
	private:
		Container _co;
	};
}

Primero, reemplace el contenedor de la clase de plantilla deque en la plantilla con vector, e instancia el objeto en el dominio privado en la clase de pila. Debido a que la pila solo se puede operar en un extremo, debe insertar y eliminar el elemento superior de la pila. , que corresponde a la inserción de cola y Para la eliminación de cola, puede llamar directamente a las interfaces de inserción de cola de vector y eliminación de cola. Para obtener la parte superior de la pila, puede llamar directamente a la interfaz de cola de cola del vector. El tamaño y el juicio vacío también son los interfaces de vector.

(2) Código de implementación de simulación de pila


#pragma once

namespace nza
{
    
    
	template<class T, class Container=vector<int>>
	class stack
	{
    
    
	public:
		void push(const T& x)
		{
    
    
			_co.push_back(x);
		}
		void pop()
		{
    
    
			_co.pop_back();
		}
		const T& top()
		{
    
    
			return _co.back();
		}
		size_t size()
		{
    
    
			return _co.size();
		}
		bool empty()
		{
    
    
			return _co.empty();
		}
	private:
		Container _co;
	};
}

#include"simulate_stack.h"
#include<iostream>
#include<vector>
#include<list>
using namespace std;

void test1()
{
    
    
	nza::stack<int> s;
	s.push(6);
	s.push(1);
	s.push(8);
	s.push(3);
	s.push(7);
	while (!s.empty())
	{
    
    
		cout << s.top() << " ";
		s.pop();
	}
	cout << endl;
}
int main()
{
    
    
	test1();
}

(3) Resultados de simulación de pila

inserte la descripción de la imagen aquí

3. Cola

(1) Cola ordinaria

(1) Definición de cola ordinaria

Una cola es un adaptador de contenedor diseñado para operar en un contexto FIFO primero en entrar, primero en salir , donde los elementos se insertan desde un extremo del contenedor y se extraen desde el otro. Una cola 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 específico de funciones miembro para acceder a sus elementos. Los elementos ingresan a la cola desde la cola y salen de la cola desde la cabeza. El contenedor subyacente puede ser una de las plantillas de clase de contenedor estándar u otra clase de contenedor especialmente diseñada. El contenedor subyacente debería admitir al menos las siguientes operaciones:
vacío: comprobar si la cola está vacía
tamaño: devolver el número de elementos válidos en la cola
frente: devolver la referencia del elemento al principio de la cola
atrás: devolver la referencia de el elemento
al final de la cola push_back: ingresa a la cola al final de la cola
pop_front: Dequeue al principio de la cola
Las clases de contenedor estándar deque y list cumplen estos requisitos. De forma predeterminada, si no se especifica ninguna clase de contenedor para la creación de instancias de cola, se utiliza el deque de contenedor estándar.

(2) Interfaz de uso de cola

queue(): construye una cola vacía
empty(): detecta si la cola está vacía, devuelve verdadero si es así, de lo contrario devuelve falso
size(): devuelve el número de elementos válidos en la cola
front(): devuelve la referencia del elemento al principio de la cola
back() : Devuelve la referencia del elemento al final de la cola
push() : Pone el elemento val en la cola al final de la cola
pop() : Quita el elemento al principio de la cola la cola

(3) Implementación de simulación de cola ordinaria

(1) Análisis de la implementación de simulación de cola ordinaria


#pragma once

namespace nza
{
    
    
	template<class T, class Container = list<int>>
	class queue
	{
    
    
	public:
		void push(const T& x)
		{
    
    
			_co.push_back(x);
		}
		void pop()
		{
    
    
			_co.pop_front();
		}
		const T& front()
		{
    
    
			return _co.front();
		}
		const T& back()
		{
    
    
			return _co.back();
		}
		size_t size()
		{
    
    
			return _co.size();
		}
		bool empty()
		{
    
    
			return _co.empty();
		}
	private:
		Container _co;
	};
}

De manera similar, la cola aquí es para reemplazar primero el contenedor de la clase de plantilla deque en la plantilla con el vector, e instanciar el objeto en el campo privado en la clase de cola. Debido a que la cola de la pila es el primero en entrar, primero en salir, debe haber inserción y eliminación de Para la inserción de la cola y la eliminación de la cabeza, puede llamar directamente a las interfaces de inserción de la cola del vector y de eliminación de la cabeza. y el juicio vacío son también las interfaces del vector.

(2) Código de implementación de simulación de cola ordinaria


#pragma once

namespace nza
{
    
    
	template<class T, class Container = list<int>>
	class queue
	{
    
    
	public:
		void push(const T& x)
		{
    
    
			_co.push_back(x);
		}
		void pop()
		{
    
    
			_co.pop_front();
		}
		const T& front()
		{
    
    
			return _co.front();
		}
		const T& back()
		{
    
    
			return _co.back();
		}
		size_t size()
		{
    
    
			return _co.size();
		}
		bool empty()
		{
    
    
			return _co.empty();
		}
	private:
		Container _co;
	};
}

#include"simulate_queue.h"
#include<iostream>
#include<vector>
#include<list>

using namespace std;

void test1()
{
    
    
	nza::queue<int> s;
	s.push(9);
	s.push(7);
	s.push(5);
	s.push(3);
	while (!s.empty())
	{
    
    
		cout << s.front() << " ";
		s.pop();
	}
	cout << endl;

}
int main()
{
    
    
	test1();
}

(3) Resultados de simulación de cola común

inserte la descripción de la imagen aquí

(2) cola de prioridad

(1) Definición de cola de prioridad

Una cola de prioridad también es un adaptador de contenedor cuyo primer elemento es siempre el más grande entre los elementos que contiene de acuerdo con criterios estrictos de ordenamiento débil. Este contexto es similar a un montón, donde los elementos se pueden insertar en cualquier momento y solo se puede recuperar el elemento de montón más grande (el que está en la parte superior de la cola de prioridad). 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 la parte superior de la cola de prioridad. El contenedor subyacente puede ser cualquier plantilla de clase de contenedor estándar o cualquier otra clase de contenedor de un diseño específico. El contenedor debe ser accesible a través de un iterador de acceso aleatorio y admitir las siguientes operaciones:
vacío (): verifica si el contenedor está vacío.
tamaño ()
: devuelve la cantidad de elementos válidos en el contenedor. en el contenedor
push_back( ): inserta elementos al final del contenedor
Las clases de contenedor estándar vector y deque satisfacen estos requisitos. De forma predeterminada, se utiliza vector si no se especifica ninguna clase de contenedor para una instancia de clase de prioridad_cola en particular. Los iteradores de acceso aleatorio deben admitirse 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 es necesario.

(2) Interfaz de cola de prioridad

cola_de_prioridad()/cola_de_prioridad(primero,último): construye una cola de prioridad vacía
vacía( ): detecta si la cola de prioridad está vacía, devuelve verdadero si lo está, de lo contrario devuelve falso
arriba( ): devuelve el elemento más grande (más pequeño) en el cola de prioridad), es decir, el elemento superior
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

(3) Implementación de simulación de cola de prioridad

(1) Análisis de la implementación de simulación de cola de prioridad


#pragma once
namespace nza
{
    
    
	template<class T>
	struct less
	{
    
    
		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;
		}
	};

	template<class T ,class container= vector<T>,class compare=less<T>>
	class priority_queue
	{
    
    
	public:
		void AjustDown(size_t parent)
		{
    
    
			compare comp;
			size_t child = 2 * parent + 1;
			while (child<_con.size())
			{
    
    
				if (child + 1 < _con.size() && comp(_con[child] ,_con[child + 1]))
				{
    
    
					++child;
				}
				if (comp(_con[parent], _con[child]))
				{
    
    
					swap(_con[child], _con[parent]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
    
    
					break;
				}
			}
		}
		void AjustUp(int child)
		{
    
    
			compare comp;
			int parent = (child-1)/ 2;
			while (child>0)
			{
    
    
				if (comp(_con[parent], _con[child]))
				{
    
    
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
    
    
					break;
				}

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

		}
		void pop_back()
		{
    
    
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AjustDown(0);

		}
		const T& top()
		{
    
    
			return _con[0];
		}
		size_t size()
		{
    
    
			return _con.size();
		}
		bool empty()
		{
    
    
			return _con.empty();
		}
	private:
		container _con;
	};
}

Menos y mayor son funtores. Less crea un gran montón raíz y el resultado final de clasificación es de grande a pequeño. Greater crea un pequeño montón raíz y el resultado final de clasificación es de pequeño a grande.
1. Functor : define un objeto con una función miembro de operator(), que puede considerarse una función general, pero la función de esta función se implementa en el operador operator() en una clase, y es un objeto de función Utiliza la función como una forma de pasar parámetros.Aquí se aprovechan dos : la velocidad de ejecución del funtor es más rápida que la del puntero de función, y el puntero de función es llamado por la dirección, mientras que el funtor sobrecarga el operador operador Mejora la eficiencia de las llamadas y los funtores se pueden usar como parámetros de plantilla, porque cada funtor tiene su propio tipo.

Por ejemplo, Compare com aquí instancia un objeto, y Compare here controla el método de comparación, pasando mayor, _con es el objeto de mayor, y llama a operator(), que es el método de comparación de mayor que.

El Compare anterior es un tipo genérico, si pasa menos, no tiene nada que ver con mayor, com es un objeto menor. Puedo usar lo que se me pasa, soy un objeto less y llamo al menos oparator. La plantilla pasa el tipo y el funtor es una clase, por lo que se puede pasar como un parámetro de plantilla y se puede usar toda la clase.Si es un parámetro de función, se usa para pasar una determinada función a alguna parte. Puede pasar toda la clase y se puede usar todo.

2. Inserción: , inserte en la cola y luego ajuste hacia arriba, desde la última posición de _con.size() - 1. En este momento, necesitamos escribir un algoritmo de ajuste hacia arriba.
Algoritmo de ajuste ascendente :
lo que implemento aquí es un pequeño montón raíz. La idea del algoritmo de ajuste ascendente es comenzar desde el nodo secundario y comparar el tamaño del nodo secundario y el nodo padre de abajo hacia arriba. Si es más pequeño que el nodo padre, se intercambia el nodo padre-hijo. , y actualice el nodo hijo, es decir, deje que el nodo hijo vaya a la posición del padre, y descubra el tamaño del padre en este momento, y continúe comparando hasta el nodo hijo es 0, es decir, a la raíz, de lo contrario, si es más grande que el nodo padre Simplemente detenga el ciclo, no es necesario ajustar.

3. Eliminación : si no puede mover los datos directamente, la complejidad del tiempo aumentará. Primero debe intercambiar los primeros y últimos datos, luego eliminar los datos al final y finalmente ajustar hacia abajo. En este momento, debe escribir un algoritmo de ajuste hacia abajo.
Algoritmo de ajuste hacia abajo :
a partir del nodo raíz, que es el nodo principal, y la mayor comparación con sus dos nodos secundarios, ajuste de arriba hacia abajo. Primero asumo que el hijo izquierdo child es el más grande, y luego uso una función if para comparar.Si child + 1 < n, asegúrese de que haya un hijo derecho, y si child+1 es menor que child, significa que el el hijo izquierdo es pequeño, así que ++. Luego, si el hijo más pequeño es más pequeño que el nodo padre, ajuste y actualice el nodo padre, deje que el padre vaya a la posición del nodo hijo y encuentre el subíndice de su siguiente nodo hijo, y deténgase si es más grande.
4. Obtenga el elemento superior del montón : es decir, devuelva directamente los datos correspondientes al subíndice 0.
5. Juicio de tamaño y espacio : El juicio de tamaño y espacio también son interfaces para llamar directamente al vector.

(2) Interfaz de implementación de simulación de cola de prioridad


#pragma once
namespace nza
{
    
    
	template<class T>
	struct less
	{
    
    
		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;
		}
	};

	template<class T ,class container= vector<T>,class compare=less<T>>
	class priority_queue
	{
    
    
	public:
		void AjustDown(size_t parent)
		{
    
    
			compare comp;
			size_t child = 2 * parent + 1;
			while (child<_con.size())
			{
    
    
				if (child + 1 < _con.size() && comp(_con[child] ,_con[child + 1]))
				{
    
    
					++child;
				}
				if (comp(_con[parent], _con[child]))
				{
    
    
					swap(_con[child], _con[parent]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
    
    
					break;
				}
			}
		}
		void AjustUp(int child)
		{
    
    
			compare comp;
			int parent = (child-1)/ 2;
			while (child>0)
			{
    
    
				if (comp(_con[parent], _con[child]))
				{
    
    
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
    
    
					break;
				}

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

		}
		void pop_back()
		{
    
    
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AjustDown(0);

		}
		const T& top()
		{
    
    
			return _con[0];
		}
		size_t size()
		{
    
    
			return _con.size();
		}
		bool empty()
		{
    
    
			return _con.empty();
		}
	private:
		container _con;
	};
}
#include"Simulate_PriorityQueue.h"
#include<iostream>
#include<vector>
using namespace std;
void test()
{
    
    
	nza::priority_queue<int, vector<int>,nza::greater<int>> q;
	/*nza::priority_queue<int> q;*/
	q.push_back(8);
	q.push_back(4);
	q.push_back(2);
	q.push_back(9);
	q.push_back(6);
	q.push_back(1);
	while (!q.empty())
	{
    
    
		cout << q.top() << " ";
		q.pop_back();
	}
	cout << endl;
}
int main()
{
    
    
	test();
	return 0;

}

(3) Resultados de simulación de colas prioritarias

inserte la descripción de la imagen aquí

(3) Cola de dos extremos

Introducción:
deque es una cola de dos extremos, que es una estructura de datos de espacio "continuo" de doble apertura. El significado de doble apertura es: la inserción y la 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, tiene una alta eficiencia de inserción de encabezado y no necesita mover elementos; en comparación con la lista, tiene una mayor utilización del espacio. Deque no es un espacio continuo real, pero está empalmado por segmentos de pequeños espacios continuos. El deque real es similar a una matriz bidimensional dinámica.

Características estructurales:
la cola de dos extremos es en realidad un controlador central, que en realidad es una matriz de punteros, y la inserción se realiza desde el medio. Cuando el control central esté lleno, se expandirá, pero el costo de expansión es bajo. Su capa inferior es una ilusión de espacio continuo, que en realidad es segmentado y continuo. Para mantener su "continuidad general" y la ilusión de acceso aleatorio, recae en el iterador deque, por lo que el diseño del iterador deque es más complicado. . Cuatro punteros están encapsulados en el interior, el primero apunta a la posición de datos actual, el segundo apunta a la primera posición del búfer, el tercero es el último que apunta al final del búfer y el cuarto es el nodo que apunta al puntero de búfer de control central en el dispositivo.

Ventajas:
en comparación con el vector, el costo de la expansión de la capacidad es bajo, y la eficiencia de la eliminación del tapón principal y la eliminación del tapón trasero es alta, y también se admite el acceso aleatorio.

Desventajas:
en comparación con el vector, la ventaja de deque es que cuando se inserta y elimina la cabeza, no hay necesidad de mover elementos, y la eficiencia es particularmente alta, y cuando se expande, no hay necesidad de mover una gran cantidad de elementos , por lo que su eficiencia debe ser alta. En comparación con la lista, su capa inferior es un espacio continuo, la tasa de utilización del espacio es relativamente alta y no es necesario almacenar campos adicionales. Sin embargo, deque tiene una falla fatal: no es adecuado para atravesar, porque al atravesar, el iterador de deque necesita verificar con frecuencia si se ha movido al límite de un espacio pequeño, lo que resulta en una baja eficiencia. ser necesario atravesar con frecuencia, por lo que en la práctica, cuando se requiere una estructura lineal, el vector y la lista tienen prioridad en la mayoría de los casos. No hay muchas aplicaciones de deque, y una aplicación que se puede ver hasta ahora es que STL lo usa como la estructura de datos subyacente de la pila y la cola. La inserción y eliminación en el medio es complicada. No hay extremos de vector y lista.

Razones para elegir deque como el contenedor predeterminado subyacente de stack y queue:
stack 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(), puede ser utilizado como el contenedor subyacente de la pila, como Ambos, el vector y la lista son aceptables; la cola es una estructura de datos lineal especial de primero en entrar, primero en salir, siempre que tenga una estructura lineal con operaciones push_back y pop_front, puede usarse como el contenedor subyacente de la cola, como una lista. Sin embargo, en STL, deque se selecciona como el contenedor subyacente para la pila y la cola de forma predeterminada, principalmente porque:
1. No es necesario atravesar la pila y la cola (porque la pila y la cola no tienen iteradores) y solo es necesario operarlas. en uno o ambos extremos fijos.
2. Cuando los elementos en la pila crecen, deque es más eficiente que el vector (no es necesario mover una gran cantidad de datos al expandirse); cuando los elementos en la cola crecen, deque no solo tiene una alta eficiencia, sino que también tiene mucha memoria uso. Combinando las ventajas de deque, evita perfectamente sus defectos.

Cuarto, el iterador inverso

(1) Ideas y análisis de implementación del iterador inverso

Un iterador inverso es un iterador que atraviesa un contenedor en sentido inverso y es un adaptador para un iterador normal.Al redefinir las operaciones de autoincremento y autodecremento, se logra el propósito de atravesar elementos en orden inverso.

#pragma once
namespace nza
{
    
    
	template<class iterator, class re, class p>
	struct Reverseiterator
	{
    
    
		typedef Reverseiterator<iterator,re, p> self;
		iterator _cur;
		Reverseiterator(iterator it)
			:_cur(it)
		{
    
    }
		re operator*()
		{
    
    
			iterator tmp(_cur);
			--tmp;
			return *tmp;
		}
		self&  operator++()
		{
    
    
			--_cur;
			return *this;
		}
		self& operator--()
		{
    
    
			++_cur;
			return *this;

		}
		bool operator!=(const self& s)
		{
    
    
			return _cur!= s._cur;

		}
	};
}

Idea de implementación:
la idea normal es cambiar el iterador directo implementado por la lista original para invertirlo y luego hacer las modificaciones correspondientes a otras funciones de clase.
Pero esto solo puede lograr el iterador inverso del contenedor que estamos implementando actualmente.Si cambiamos el contenedor vectorial, no funcionará, porque su iterador directo es un tipo incorporado, y no podemos copiar directamente cv y cambiarlo directamente, a menos que no use punteros nativos Encapsule iteradores como listas. Al principio se decía que el iterador inverso es una especie de adaptador, ahora tenemos que hacer que este iterador inverso se adapte no solo a vector, sino también a lista, para lograr una reutilización real.

El código anterior se refiere a la idea de código escrito a mano en el código fuente stl, principalmente para eliminar el problema de la redundancia y la repetición, y volverse más genérico, es decir, adaptarse a varios contenedores más ampliamente.
La idea aquí es usar iteradores directos para encapsular iteradores inversos, por lo que no tenemos que preocuparnos si nuestros iteradores directos son nativos o encapsulados.De esta manera, se implementa un iterador inverso y salen los iteradores inversos de todos los contenedores. La premisa es un iterador bidireccional que admite la resta. Pasar la dirección de avance de la lista se adapta a la dirección inversa de la lista, pasar la dirección de avance del vector se adapta a la dirección inversa del vector, seguido de deque, etc., para resolver el problema de una vez por todas, cuando Todavía piense en realizar el reverso de la lista Cuando se trata de iteradores, el pensamiento del maestro es resolver el iterador inverso con contenedores.
Análisis de función:
1. El primer parámetro de la plantilla es un iterador de cualquier tipo, el segundo parámetro re es el tipo de valor de retorno en el iterador, el tercer parámetro es el tipo de valor de retorno del símbolo de flecha sobrecargado y luego usa el reenvío iterador Instancia un objeto _cur. El constructor de inicialización inicializa _cur con un iterador directo.
2. Su desreferenciación no toma la posición actual, es la posición anterior, porque el rend original está en la posición centinela, rbegin está al final, en este momento el rend está en el siguiente nodo malvado de la posición centinela, y rbegin está en el nodo de posición centinela Arriba, por supuesto, esta es una lista, y el vector es el mismo, excepto que es una matriz sin un nodo principal , por lo que al atravesar, debe restar y restar primero si desea comenzar desde la última posición. Aquí, para recorrer el siguiente bit de datos sin problemas, primero es necesario copiar una variable temporal y luego restarla, y debido a que la variable temporal es constante, debe haber un retorno de referencia (re es inútil aquí, porque tiene pasado en la implementación de la clase de contenedor) como se muestra en la Figura 2). Las características de diseño anteriores son en realidad para buscar una simetría, como se muestra en la Figura 1 a continuación:
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

3. De manera similar, el iterador inverso admite el movimiento. El ++ que implementa el iterador inverso es - -, y la implementación - - es ++, que también admite la comparación.

(2) Código de implementación del iterador inverso (tome el vector como ejemplo)

#pragma once
namespace nza
{
    
    
	template<class iterator, class re, class p>
	struct Reverseiterator
	{
    
    
		typedef Reverseiterator<iterator,re, p> self;
		iterator _cur;
		Reverseiterator(iterator it)
			:_cur(it)
		{
    
    }
		re operator*()
		{
    
    
			iterator tmp(_cur);
			--tmp;
			return *tmp;
		}
		self&  operator++()
		{
    
    
			--_cur;
			return *this;
		}
		self& operator--()
		{
    
    
			++_cur;
			return *this;

		}
		bool operator!=(const self& s)
		{
    
    
			return _cur!= s._cur;

		}
	};
}
# pragma once
#include<assert.h>
#include<algorithm>
//#include<vector>
#include<string>
#include"ReverseIterator.h"
namespace nza
{
    
    

     template<class T>
	 class vector
	 {
    
    
	 public:
		 typedef T* iterator;
		 typedef const T* const_iterator;

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

		 vector()//构造函数
		 {
    
    }
		 ~vector()
		 {
    
    
			 delete[] _start;
			 _start = _finish = _end_capacity = nullptr;
		 }
		 iterator begin()
		 {
    
    
			 return _start;
		 }
		 iterator end()
		 {
    
    
			 return _finish;

		 }
		 reverse_iterator rbegin()
		 {
    
    
			 return  reverse_iterator(end());
		 }
		 reverse_iterator rend()
		 {
    
    
			 return  reverse_iterator(begin());
		 }
		 const_iterator begin() const
		 {
    
    
			 return _start;

		 }
		 const_iterator end() const
		 {
    
    
			 return _finish;
		 }
		size_t size() const
		 {
    
    
			 return _finish - _start;
		 }
		size_t capacity() const
		{
    
    
			return _end_capacity - _start;

		}
		 
		
		 vector(const vector<T>& v)
		 {
    
    
			 /*_start = new T[v.capacity()];
			 for (size_t i = 0; i < v.size(); ++i)
			 {
				 _start[i] = v._start[i];
			 }
			 _finish = _start + v.size();
			 _end_capacity = _start + v.capacity();*/
			 vector<T> tmp(v.begin(), v.end());
			 swap(tmp);
		 }
		void swap(vector<T>& v)
		 {
    
    
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_capacity, v._end_capacity);
		 }
		 vector<T>& operator = (vector<T> v)
		 {
    
    
			/* if (this != &v)
			 {
				 T* tmp = new T[v.capacity()];
				 memcpy(tmp, v._start, sizeof(T)*v.size());
				 delete[] _start;
				 _start = tmp;
				 _finish = _start + v.size();
				 _end_capacity = _start + v.capacity();
			 }
			 return *this; */ //这是常规思路,可以复用swap函数
			 swap(v);
			 return *this;
		 } 

		 vector(size_t n, const T& val = T())
		 {
    
    
			 reserve(n);
			 for (size_t i = 0; i<n; ++i)
			 {
    
    
				 push_back(val);
			 }
		 }
		 vector(int n, const T& val = T())
		 {
    
    
			 reserve(n);
			 for (int i = 0; i<n; ++i)
			 {
    
    
				 push_back(val);
			 }
		 }
		 template<class InputIterator>
		 vector(InputIterator first, InputIterator last)
		 {
    
    
			 while (first != last)
			 {
    
    
				 push_back(*first);
				 ++first;
			 }
		 }


		 void resize(size_t n, T val = T())
		 {
    
    
			 if (n < size())
			 {
    
    
				 _finish = _start + n;
			 }
			 else
			 {
    
    
				 if (n>capacity())
				 {
    
    
					 reserve(n);
				 }
				 while (_finish != _start + n)
				 {
    
    
					 (*_finish) = val;
					 ++_finish;
				 }
			 }

		 }
		 void reserve(size_t n)
		 {
    
    
			 if (n > capacity())
			 {
    
    
				 T* tmp = new T[n];
				 size_t size1 = size();
				 if (_start)
				 {
    
    
					 for (size_t i = 0; i < size1; ++i)
					 {
    
    
						 tmp[i] = _start[i];
					 }
				     delete[] _start;
				 }
				 _start = tmp;
				 _finish = _start + size1;
				 _end_capacity = _start + n;
			 }
		 }
		 void push_back(const T& x)
		 {
    
    
			 if (_finish == _end_capacity)
			 {
    
    
				 reserve(capacity() == 0 ? 4 : capacity() * 2);
			 }
			 *_finish = x;
			 ++_finish;
		 }
		 void pop_back()
		 {
    
    
			 assert(!empty());
			 --_finish;
		 }
		 iterator insert(iterator pos, const T& val)
		 {
    
    
			 assert(pos <= _finish);
			 assert(pos >= _start);
			 if (_finish == _end_capacity)
			 {
    
    
				 int len = pos - _start;
				 reserve(capacity() == 0 ? 4 : capacity() * 2);
				 pos = _start + pos;
			 }
			 iterator end = _finish - 1;
			 while(end>=pos)
			 {
    
    
				 *(end+1) = *end;
				 --end;
			 }
			 *pos = val;
			 ++_finish;
			 return pos;
		 }
		iterator erase(iterator pos)
		 {
    
    
			 assert(pos <= _finish);
			 assert(pos >= _start);
			 itetator first = pos + 1;
			 while (first!=_finish )
			 {
    
    
				 *(first - 1) = *first;
				 ++first;
			 }
			 --_finish;
			 return pos;
		 }
		 bool empty()
		 {
    
    
			 return _start == _finish;

		 }
		 T& operator[](size_t pos)
		 {
    
    
			 assert(pos < size());
			 return _start[pos];

		 }
		 const T& operator[](size_t pos) const
		 {
    
    
			 assert(pos < size());
			 return _start[pos]

		 }
	 private:
		 iterator _start=nullptr;
		 iterator _finish=nullptr;
		 iterator _end_capacity=nullptr;
	 };



	 
#include<iostream>
using namespace std;
#include"simulate_vector.h"



class Solution {
    
    
public:
	nza::vector<nza::vector<int>> generate(int numRows) {
    
    
		nza::vector<nza::vector<int>> vv;
		vv.resize(numRows, nza::vector<int>());
		for (size_t i = 0; i < vv.size(); ++i)
		{
    
    
			vv[i].resize(i + 1, 0);
			vv[i][0] = vv[i][vv[i].size() - 1] = 1;
		}

		for (size_t i = 0; i < vv.size(); ++i)
		{
    
    
			for (size_t j = 0; j < vv[i].size(); ++j)
			{
    
    
				if (vv[i][j] == 0)
				{
    
    
					vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];
				}
			}
		}

		return vv;
	}
};

void test1()
{
    
    
	nza::vector<int> v1;
	v1.push_back(14);
	v1.push_back(15);
	v1.push_back(16);
	v1.push_back(17);
	v1.push_back(18);


	for (size_t i = 0; i < v1.size(); ++i)
	{
    
    
		cout << v1[i] << " ";
	}
	cout << endl;

	v1.pop_back();
	v1.pop_back();

	nza::vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
    
    
		cout << *it << " ";
		++it;
	}
	cout << endl;

	v1.pop_back();
	v1.pop_back();

	for (auto v : v1)
	{
    
    
		cout << v << " ";
	}
	cout << endl;

}
void test2()
{
    
    
	std::string s1("hello");
	nza::vector<int> v3(s1.begin(), s1.end());
	for (auto e : v3)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}
void test3()
{
    
    
	nza::vector<std::string> v3(3, "dddddddddd");
	for (auto e : v3)
	{
    
    
		cout<<e<<" ";
	}
	cout << endl;
	nza::vector<std::string> v4(v3);
	for (auto e : v4)
	{
    
    
		std::cout << e << " ";
	}
	cout << endl;

	v4.push_back("kkkkkkkkkkk");
	v4.push_back("kkkkkkkkkkk");
	v4.push_back("kkkkkkkkkkk");
	for (auto e : v4)
	{
    
    
		cout << e << " ";
	}
	cout << endl;
}
void test4()
{
    
    

	nza::vector<nza::vector<int>> ret =Solution().generate(5);
	for (size_t i = 0; i < ret.size(); ++i)
	{
    
    
		for (size_t j = 0; j < ret[i].size(); ++j)
		{
    
    
			cout << ret[i][j] << " ";
		}
		cout << endl;
	}
	cout << endl;

}
void test5()
{
    
    
	nza::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	nza::vector<int>::reverse_iterator  s= v.rbegin();
	while (s != v.rend())
	{
    
    
		cout << *s << " ";
		++s;
	}
	cout << endl;

}
int main()
{
    
    
	test1();
	test2();
	test3();
	test4();
	test5();
	return 0;
}

(3) El iterador inverso realiza el resultado

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/m0_59292239/article/details/129974627
Recomendado
Clasificación