Adaptador de contenedor diseñado por STL, además de análisis de temas clásicos

contenido

prefacio

adaptador de contenedor de pila + cola que comprende la implementación

Descripción de los adaptadores de contenedores

Piense si la pila y la cola tienen iteradores.

¿Qué contenedores se pueden apilar y poner en cola como contenedor subyacente? 

¿Cómo se pasa el contenedor subyacente?     

 Análisis de bloques de funciones clave

análisis de pila 

 análisis de colas

ejemplo clásico de pila

implementación de la descomposición de la cola de prioridad

Comprender la cola_prioridad de la estructura de datos

Ver la cola_prioridad desde la perspectiva de los componentes STL

Análisis clave de las funciones push y pop

ajustar hacia arriba

Ajustar hacia abajo

Código de implementación general

Priority_queue Análisis de problemas de TopK aplicable

Resumir


prefacio

Los ensayos de diseño STL de Xiaojie se seguirán actualizando. Hay ensayos de vectores y listas con Xindi antes. Adjunte el enlace aquí, puede leerlo si está interesado, y Xiaojie continuará produciendo buenos trabajos basados ​​en lo que ha aprendido. espero que todos puedan seguir, comunicarse, gracias, sus comentarios, lecturas y comentarios son el mayor reconocimiento y apoyo para Xiaojiedi

Análisis de bloques desde el prototipo de función hasta la implementación de bloques de C++ STL (vector) , ideas de diseño de iterador https://blog.csdn.net/weixin_53695360/article/details/123647344?spm=1001.2014.3001.5502

adaptador de contenedor de pila + cola que comprende la implementación

  • Descripción de los adaptadores de contenedores

Tomando un determinado contenedor existente como estructura subyacente, la función de interfaz se encapsula aún más sobre su base. Hágalo FIFO (función de pila) o FIFO (función de cola)   

Las características mencionadas anteriormente se basan en el contenedor subyacente, encapsulan una nueva interfaz y forman un contenedor con otra característica, que a menudo no se denomina contenedor, sino     adaptador .

Es por eso que stack + queue en STL a menudo no se clasifica como contenedor (container) sino como adaptador de contenedor adaptador de contenedor   ( adaptador de contenedor ) 

  • Piense si la pila y la cola tienen iteradores.

Ni la pila ni la cola tienen iteradores, porque tampoco tienen la función de atravesar y visitar, por lo que, naturalmente, no hay necesidad de diseñar iteradores.

  • ¿Qué contenedores se pueden apilar y poner en cola como contenedor subyacente? 

Está bien usar deque double-ended queue + list doublely circular linked list como contenedor subyacente.También está bien usar vector como contenedor subyacente para stack.

  • ¿Cómo se pasa el contenedor subyacente?     

Pásalo como un parámetro de plantilla de la siguiente manera

 

  •  Análisis de bloques de funciones clave

  • análisis de pila 

  • Debido a que esta pila es solo un adaptador, es equivalente a una capa de encapsulación basada en la interfaz del contenedor existente. Debido a que es una reencapsulación, el análisis de la función solo flota en el prototipo de la función. Siempre que la reencapsulamos de acuerdo con a las características de la pila, es       (Nota: las tomas de pila y cola son características, no implementaciones, pilas, primero en entrar, primero en salir, las viñetas se disparan primero)
  • Por lo tanto, solo necesitamos insertar datos y mostrar datos en una interfaz para cumplir con las características de la pila. Es posible seleccionar la cabeza o la cola. Aquí, el vector se usa como contenedor subyacente, así que uso la posición trasera. para empujar los elementos dentro y fuera  
namespace tyj {
	//template<class T, class Sequence = deque<T>>
	//template<class T, class Sequence = list<T>>
	template<class T, class Sequence = vector<T>>
	class stack {
	public:
		stack() {}//无参构造
		bool empty() const {
			return st.empty();
		}

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

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

		void push(const T& val) {
			st.push_back(val);
		}

		void pop() {
			st.pop_back();
		}

	private:
		Sequence st;
	};
}
  •  análisis de colas

  •  El principio de la cola y la pila es exactamente el mismo. Solo necesita cumplir con la característica de primero en entrar, primero en salir. Para cumplir con esta característica es la cola, es decir, los elementos se envían en un extremo del contenedor y los elementos se ingresan en el otro extremo Tal estructura es una cola.
  • Para la cola, debe operar en ambos extremos, un extremo ingresa al elemento y el otro extremo genera el elemento, por lo que debemos usar un contenedor de operación de doble extremo como contenedor subyacente, que puede ser un deque o una lista. lista enlazada de dos extremos. Aquí usamos la lista como la capa inferior. El contenedor encapsula aún más la interfaz
namespace tyj {
    template<class T, class Sequence = list<T>>
	class queue {
	public:
		queue() {} //无参构造
		bool empty() const {
			return q.empty();
		}

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

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

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

		void push(T& val) {
			q.push_back(val);
		}

		void pop() {
			q.pop_front();
		}
	private:
		Sequence q;
	};
}

ejemplo clásico de pila

La espada se refiere a la Oferta 31. La secuencia de empuje y pop de la pila

Entrada: empujado = [1,2,3,4,5], sacado = [4,5,3,2,1]
Salida: verdadero
Explicación: Podemos ejecutar en el siguiente orden:
empujar(1), empujar(2 ) , empujar(3), empujar(4), pop() -> 4,
empujar(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

El significado del título es dar una matriz empujada como la secuencia de apilamiento, y la matriz extraída es la secuencia de apilamiento. De acuerdo con la secuencia de apilamiento de la matriz empujada, ¿es la secuencia emergente de la matriz de apilamiento dada un resultado posible?

  • Descripción de la idea: para este posible problema de secuencia emergente de pila, debe manejarse mediante simulación de pila, porque es muy complicado y difícil resolver este problema directamente desde el pensamiento general. En este momento, podemos considerar que realmente se pone en el pila real de acuerdo con la secuencia de empujar y abrir, y simular este proceso para ver si tal secuencia de empujar puede producir tal secuencia de abrir.
  • Primero, pushI es el subíndice que apunta a la matriz empujada, y popJ es el subíndice que apunta a la matriz extraída.
  • Seguimos empujando el elemento señalado por pushI en la pila St. Si ocurre st.top == poped[popJ], significa que debemos sacar este elemento
  • Constantemente colocamos los elementos de la matriz empujada en la pila st y, al mismo tiempo, comparamos constantemente si el elemento st.top() es el elemento emergente actual. Una vez completada la secuencia, podemos juzgar si popJ ha alcanzado el final de la matriz emergente. Ir al final indica que es una secuencia emergente legal, de lo contrario no es
class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> st;
        int pushI = 0, popJ = 0;
        for (pushI = 0; pushI < pushed.size(); ++pushI) {
            st.push(pushed[pushI]);
            while (popJ < popped.size() && !st.empty() && popped[popJ] == st.top()) {
                st.pop();
                ++popJ;
            }
        }
        return popJ == popped.size();                  
    }
};

En el núcleo del problema anterior, en realidad colocamos los elementos de la secuencia en una pila real para simular todo el proceso. Durante el proceso de apilamiento, intentamos codiciosamente comparar los elementos superiores de la pila pop. Si todo el proceso ha terminado, popJ va al lugar poped El final es una secuencia pop válida, de lo contrario no lo sería. 

150. Evaluación de expresión en polaco inverso

输入:fichas = ["2","1","+","3","*"]
salida: 9
Explicación: Esta expresión se convierte en una expresión aritmética infija común: ((2 + 1) * 3) = 9

El significado del título: el título recibe una serie de expresiones de sufijo, y luego debemos convertirlo en nuestras expresiones de infijo visibles habituales para encontrar la respuesta de las expresiones de infijo.

  • Postfijo expresión lhs operando izquierdo rhs operando derecho operador op 
  • Necesitamos convertirlo a la forma lhs op rhs para manejar
  • Por lo tanto, en el proceso de atravesar el suelo, primero debemos empujar lhs a la pila y rhs a la pila. Cuando encontremos op, sacaremos los dos elementos superiores de la pila y empujaremos el resultado de la operación a la pila. al mismo tiempo.
  • El regreso final a st.top() es la respuesta final
class Solution {
public:
    int evalRPN(vector<string>& s) {
        stack<int> st;
        int lhs, rhs;
        for (int i = 0; i < s.size(); ++i) {
          if (s[i] == "+") {
              rhs = st.top(); st.pop();
              lhs = st.top(); st.pop();
              st.push(lhs + rhs);
          } else if (s[i] == "-") {
              rhs = st.top(); st.pop();
              lhs = st.top(); st.pop();
              st.push(lhs - rhs);
          } else if (s[i] == "*") {
              rhs = st.top(); st.pop();
              lhs = st.top(); st.pop();
              st.push(lhs * rhs);
          } else if (s[i] == "/") {
              rhs = st.top(); st.pop();
              lhs = st.top(); st.pop();
              st.push(lhs / rhs);
          } else {
              st.push(stoi(s[i]));
          }
        }
        return st.top();
    }
};

implementación de la descomposición de la cola de prioridad

Comprender la cola_prioridad de la estructura de datos

  • Primero entienda qué es una cola de prioridad. La cola de prioridad es una cola con pesos, que se almacena de acuerdo con el tamaño de los pesos. De hecho, es la estructura de datos del montón que tocamos en el lenguaje C. Cola de prioridad "== " montón
  • El peso del nodo principal debe ser mayor que el de los dos nodos secundarios.

Ver la cola_prioridad desde la perspectiva de los componentes STL

  •  Priority_queue es esencialmente un adaptador de contenedor como cola, y el contenedor subyacente es prácticamente un vector

  • que es comparar Es un funtor, un objeto invocable y una regla de comparación:   puede entenderse como un puntero de función en el lenguaje C.

Análisis clave de las funciones push y pop

  • La clave principal es entender por qué es necesario flotar y hundirse ( el ejemplo aquí es un pequeño montón superior )
  • Empuje los elementos hasta el final de la matriz y la parte inferior del montón, lo que puede afectar las características de toda la estructura del montón. Debe llamar al algoritmo de ajuste ascendente AdjustUp para ajustar los elementos insertados a la posición correcta.
 
  
  • El elemento emergente es el elemento superior del montón. El método es usar el elemento final del montón y el elemento superior para intercambiar, y luego ejecutar pop_back() para mostrar todo el elemento que debería estar en la parte superior del montón, y entonces el reemplazo del elemento superior afectará a todo el montón. Las características de la estructura hacen que no cumpla con los requisitos de características de la estructura del montón, por lo que es necesario llamar al algoritmo de ajuste descendente para operar el elemento inferior del montón al máximo. posición donde debería estar.

  • Hasta aquí la idea es muy clara, ¿cuál es el punto clave?, es el algoritmo de ajuste de AdjustUp y AdjustDown hacia arriba y hacia abajo.
  • ajustar hacia arriba

  • Ajustar hacia abajo

Código de implementación general

namespace tyj {	
    template<class T>
	struct less
	{
		bool operator()(const T& l, const T& r)
		{
			return l < r;
		}
	};

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

	//优先队列
	template<class T, class Sequence = vector<T>, class Compare = less<T> >
	class priority_queue {
	public:
		priority_queue() {
			h.push_back(T());//对于ind == 0 我们插入一个占位,我的习惯
		}
		bool empty() const {
			return h.size() == 1;
		}

		T& top() {
			return h[1];
		}

		void AdjustUp(size_t child) {
			Compare cmp;
			size_t fa = child >> 1;//fa ind
			while (fa) {//fa >= 1
				if (cmp(h[fa], h[child])) {
					//说明child 应该上浮操作
					swap(h[fa], h[child]);
					child = fa;
					fa = child >> 1;//继续向下
				}
				else {
					break;
				}
			}
		}
		void AdjustDown(size_t fa) {
			Compare cmp;
			size_t child = fa << 1, n = size();
			while (child <= n) {
				size_t l = child, r = l | 1;
				if (r <= n && cmp(h[l], h[r])) {
					++child;
				}
				if (cmp(h[fa], h[child])) {
					swap(h[fa], h[child]);
					fa = child;
					child = pa << 1;
				}
				else {
					break;//说明不能再下沉了
				}
			}
		}

		size_t size() {
			return h.size() - 1;
		}

		void pop() {
			//交换元素
			swap(h[1], h[size()]);//至此排除数组最后末尾元素
			h.pop_back();//弹掉堆顶
			AdjustDown(1);//从上往下做调整
		}
		void push(const T& val) {
			h.push_back(val);//插入val;
			//然后进行上浮 操作
			AdjustUp(size());
		}
	private:
		Sequence h;
	};
}

Priority_queue Análisis de problemas de TopK aplicable

La espada se refiere a la Oferta II 076. El k-ésimo número más grande de la matriz

El significado del título es muy simple, es decir, cómo obtener el k-ésimo número de tierra en la matriz

  • Idea: use el montón raíz, primero coloque todos los elementos en el montón, y luego la parte superior del montón es el primer elemento más grande, y luego seguimos haciendo estallar la parte superior del montón, y cuando salta al K-ésimo lugar, es el K-ésimo elemento más grande.
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> q;//默认大根堆
        for (int e : nums) {
            q.push(e);
        }
        int cnt = 1;//cnt代表第几大
        while (1) {
            if (cnt == k) return q.top();
            q.pop();
            cnt++;//弹出一个cnt ++ 
        }
    }
};

Pregunta de la entrevista 17.14 Número mínimo de K

El significado del título es muy simple, es decir, cómo obtener el número mínimo de K en la matriz

  • Para esta pregunta, necesitamos usar un montón raíz grande, lo que subvierte la cognición de muchas personas, ¿qué?, ¿por qué necesitamos usar un montón raíz grande para obtener el número mínimo de K?     
  • Idea 1: Al igual que en el caso anterior, mantener el pequeño montón raíz es lo más fácil. Primero colóquelos todos en el montón, y luego extraiga K, es el número mínimo de K. De esta manera, el montón de mantenimiento es relativamente grande y usted necesita ponerlos todos en el montón al principio y luego sacarlos.
  • Idea 2: mantenemos un montón con tamaño == k, pero mantenemos un montón raíz grande, y cada vez que se elimina el valor máximo entre los K valores mínimos actuales, que es la parte superior del montón, y el valor más pequeño es empujado en el

  •  Núcleo 1: mantener un montón superior grande con valores mínimos de K
  • Núcleo 2: ¿Por qué usar un montón superior grande? La parte superior del montón es la que tiene el terreno más grande entre los valores mínimos actuales de K. El valor más grande significa que debe eliminarse más (  eliminación codiciosa de valores de terreno grandes y empujar de valores de suelo más pequeños   )
  • Elimine el valor de tierra, mantenga los valores mínimos de K en el montón y, finalmente, obtenga los valores mínimos de K, y el tamaño del montón nunca excederá K, y la complejidad es menor que el primer método
class Solution {
public:
    vector<int> smallestK(vector<int>& arr, int k) {
        vector<int> ans;
        ans.clear();
        if (k == 0) return ans;
        priority_queue<int> q;//默认大顶堆
        for (int e : arr) {
            if (q.size() < k) {
                q.push(e);
                continue;
            }
            if (e < q.top()) {
                q.pop();
                q.push(e);
            }
        }
        while (!q.empty()) {
            ans.push_back(q.top());
            q.pop();
        }
        return ans;
    }
};

Resumir

  • En primer lugar, este artículo comienza principalmente con el adaptador de contenedor y analiza la cola + pila + cola de prioridad en STL
  • Los adaptadores de contenedores son esencialmente otros contenedores con ciertas características que se encapsulan aún más de los contenedores existentes.
  • La esencia de pila + cola radica en su naturaleza, pila FIFO, apertura única, cola FIFO, doble apertura, una apertura entra, la otra apertura
  • Priority_queue es una cola con pesos. De hecho, la capa inferior es el montón. El uso del montón puede resolver rápidamente el problema de Top K.
  • Para obtener el valor del K-ésimo más grande y el K-ésimo más pequeño, se utiliza directamente el montón superior grande o pequeño correspondiente.
  • Sin embargo, para obtener los valores de K más grandes o más pequeños, usamos el montón superior pequeño y el montón superior grande en su lugar. Podemos usar el montón superior pequeño para obtener rápidamente el más pequeño de los valores K actuales relativamente grandes, y el más pequeño es positivo .es el que se elimina con más facilidad, por lo que eliminaremos el más pequeño y empujaremos a los otros más grandes
  • El núcleo para obtener los K elementos más grandes y más pequeños es mantener un montón: este montón tiene solo K elementos, y los K elementos son los K más grandes o más pequeños. . .       Mantenimiento significa que los valores que no pertenecen a él deben eliminarse, por lo que mantenemos el montón opuesto para la eliminación.

Supongo que te gusta

Origin blog.csdn.net/weixin_53695360/article/details/123769997
Recomendado
Clasificación