C++: uso de pila y cola e implementación subyacente

1.Modo adaptador

El adaptador es un patrón de diseño (experiencia de diseño de código) que convierte la interfaz de una clase en otra interfaz que el usuario desee . La capa inferior del contenedor presentada en este artículo en realidad se implementa utilizando otros contenedores, y la interfaz proporcionada a los usuarios es solo una simple encapsulación de las interfaces de otros contenedores .


2.Introducción y uso de la pila.

2.1Introducción a apilar

  1. pila es una pila.

  2. Stack es un adaptador de contenedor, que se caracteriza por último en entrar, primero en salir . Su eliminación solo puede insertar y extraer elementos de un extremo del contenedor .

  3. La pila es una plantilla de clase (plantilla <clase T, clase Contenedor = deque<T> > pila de clase). El contenedor subyacente de la pila puede ser cualquier plantilla de clase de contenedor estándar o algunas otras clases de contenedor específicas. Estas clases de contenedor deben admitir lo siguiente operaciones:
    (1) vacío: operación vacía
    (2) atrás: operación de obtener elemento de cola
    (3) push_back: operación de inserción de elemento de cola
    (4) pop_back: operación de eliminación de elemento de cola

  4. Los contenedores estándar vector, deque y list cumplen con estos requisitos. De forma predeterminada, si no se especifica ningún contenedor subyacente específico para la pila, se utiliza deque (cola de dos extremos, que se analiza más adelante).

  5. Utilice <stack> para incluir archivos de encabezado.
    Insertar descripción de la imagen aquí

2.2Uso de pila

Descripción de la interfaz común:

función ilustrar
pila() Constructor, construye una pila vacía.
vacío() Determine si la pila está vacía, devuelva verdadero si está vacía; de lo contrario, devuelva falso
tamaño() Devuelve el número de elementos de la pila.
arriba() Obtenga una referencia a los datos en la parte superior de la pila.
empujar(val) Empuje el elemento val a la pila
estallido() Pop el elemento superior de la pila.

Ejercicio:
pila mínima

//这个题目的要点是记录最小值,但出栈后可能最小值可能变化,只用一个变量记录不够
//我们可以设计两个栈:
//(1)s栈,正常出入数据
//(2)min栈,在s栈入栈时进行判断,如果自身为空或者新入栈的元素小于等于min栈顶就入栈
//在s栈出栈时也进行判断,如果出栈的元素等于min栈顶,min也要出栈

//不过这个题目还有优化的空间,那就是[1,1,1,1,1]这样多个相同数的情况
//为避免空间浪费,可以设计一个count计数记录每个数,多次出现的数入、出栈只需要减计数
//计数归0才真正的出栈
class MinStack {
    
    
public:
    MinStack() 
    {
    
    }
    
    void push(int val) 
    {
    
    
        if(min.empty() || min.top() >= val)
        {
    
    
            min.push(val);
        }
        s.push(val);
    }
    
    void pop() 
    {
    
    
        if(s.top() == min.top())
        {
    
    
            min.pop();
        }
        s.pop();
    }
    
    int top() 
    {
    
    
        return s.top();
    }
    
    int getMin() 
    {
    
    
        return min.top();
    }
private:
    stack<int> s;
    stack<int> min;
};

Secuencia de pop y push de pila

//这个题目最好的办法就是模拟栈的压入弹出过程
//比如pushv[1,2,3,4,5]这个序列得到popv[4,5,3,2,1]
//先入栈s,1,1 != popV.top(),继续入栈
//入栈s,2, 2 != popV.top(),继续入栈
//入栈s,3,3 != popV.top(),继续入栈
//入栈s,4, 4 == popV.top(),同时出栈,popV是一个数组,下标加1视为出栈
//出栈结束栈s.top() = 3 != popV.top() = 5,继续入栈
//入栈, 5, 5 == popV.top(),同时出栈
//出栈结束s.top() = 3 == popV.top() = 3,同时出栈
//………………………………………………………………………………
//最后pushV的所有元素都入栈,并且s栈刚好出空,说明pushV可以得到popV
//如果pushV所有元素入栈,s栈没法出空,说明pushV无法得到popV
class Solution {
    
    
public:
    bool IsPopOrder(vector<int>& pushV, vector<int>& popV) 
    {
    
    
        size_t pushi = 0;
        //popi下标加1视为popV出栈
        size_t popi = 0;
        stack<int> s;

        while (pushi < pushV.size()) 
        {
    
    
            s.push(pushV[pushi++]);
            while(!s.empty() && s.top() == popV[popi])
            {
    
    
                s.pop();
                popi++;
            }
        }

        return s.empty();
    }
};

Evaluación de expresión polaca inversa

//这个题目思路并不难,借助一个栈s即可
//(1)遇到数字:直接入s栈
//(2)遇到运算符,把栈中的两个左右数出栈,计算完把结果入s栈即可
//重复上面的步骤,一直到遍历完tokens即可,最后s栈顶即为结果
class Solution {
    
    
public:
    int evalRPN(vector<string>& tokens) 
    {
    
    
        stack<int> s;
        for(auto str : tokens)
        {
    
    
            if(!(str == "+" || str == "-" || str == "*" || str == "/"))
            {
    
    
               s.push(atoi(str.c_str()));
            }
            else
            {
    
    
                int right = s.top();  s.pop();
                int left = s.top();  s.pop();
                switch(str[0])
                {
    
    
                    case '+':
                        s.push(left + right);
                        break;
                    case '-':
                        s.push(left - right);
                        break;
                    case '*':
                        s.push(left * right);
                        break;
                    case '/':
                        s.push(left / right);
                        break;
                }
            }
        }
        return s.top();
    }
};

3.Introducción y uso de cola.

3.1 Introducción a la cola

  1. la cola es una cola.

  2. Queue es un adaptador de contenedor, caracterizado por primero en entrar, primero en salir , en el que los elementos se insertan desde un extremo del contenedor y los elementos se extraen del otro extremo.

  3. La cola es una plantilla de clase (plantilla <clase T, clase Contenedor = deque<T> > cola de clase;) El contenedor subyacente puede ser una de las plantillas de clase de contenedor estándar u otras clases de contenedor especialmente diseñadas. El contenedor subyacente debe admitir al menos las siguientes operaciones:
    (1) vacío: detectar si la cola está vacía
    (2) tamaño: devolver el número de elementos válidos en la cola
    (3) frente: devolver una referencia al elemento principal de la cola cola
    (4) atrás: regresar a la cola Referencia al elemento de cola
    (5) push_back: colocar en la cola al final de la cola
    (6) pop_front: salir de la cola al principio de la cola

  4. Las clases de contenedores estándar deque y list cumplen con estos requisitos. De forma predeterminada, si no se especifica ninguna clase de contenedor para la creación de instancias de cola, se utiliza la deque de contenedor estándar.

  5. Utilice <queue> para incluir el archivo de encabezado.

Insertar descripción de la imagen aquí

3.2Uso de cola

Descripción de la interfaz común:

función ilustrar
cola() Constructor, construye una cola vacía.
vacío() Determine si la cola está vacía, devuelva verdadero si está vacía; de lo contrario, devuelva falso
tamaño() Devuelve el número de elementos en la cola.
frente() Devuelve una referencia al elemento principal de la cola.
atrás() Devuelve una referencia al último elemento de la cola.
empujar(val) Ingrese el elemento val al final de la cola
estallido() El elemento principal del equipo está fuera de la cola.

Ejercicio:
implementar una pila usando una cola

//这个题目的思路是有两个队列实现栈(其实也可以单队列实现,这里主要不是讲题目)
//需要始终有一个队列为空,比如[1,2,3]这个顺序队列
//队列q1为1 -> 2 - > 3,q2为空,top()只需要返回不为空的队列的队尾即可
//难点在出栈,出栈的话把1 -> 2转移到另一个队列q2,剩余一个元素即为栈顶,保存了再出栈即可
class MyStack {
    
    
public:
    MyStack() {
    
    }
    
    void push(int x) {
    
    
        if(q2.empty())
            q1.push(x);
        else
            q2.push(x);
    }
    
    int pop() {
    
    
        //默认q1为空栈
        queue<int>* empty_q = &q1;
        queue<int>* non_empty_q = &q2;
        if(q2.empty())
        {
    
    
            empty_q = &q2;
            non_empty_q = &q1;
        }
        while(non_empty_q->size() > 1)
        {
    
    
            empty_q->push(non_empty_q->front());
            non_empty_q->pop();
        }
        int ret = non_empty_q->front();
        non_empty_q->pop();
        return ret;
    }
    
    int top() {
    
    
        if(q1.empty())
            return q2.back();
        else
            return q1.back();
    }
    
    bool empty() {
    
    
        return q1.empty() && q2.empty();
    }
    queue<int> q1;
    queue<int> q2;
};

4. Introducción a los functores

Si necesita usarlo más adelante, primero permítame presentarlo brevemente y luego explicarle los diversos usos en detalle.

//仿函数,又称函数对象,其实就是一个类,重载了operator()
// 使得类对象可以像函数一样使用
// 相比传函数指针,传仿函数要更加灵活一些
//开一个命名空间,和库里面的区分开
namespace my_compare
{
    
    
	template<class T>
	class less
	{
    
    
	public:
		bool operator()(const T& x, const T& y)const
		{
    
    
			return x < y;
		}
	};

	template<class T>
	class  greater
	{
    
    
	public:
		bool operator()(const T& x, const T& y) 
		{
    
    
			return x > y;
		}
	};
}

5.Introducción y uso de prioridad_queue

5.1Introducción a prioridad_queue

  1. Priority_queue (cola prioritaria) es un montón.

  2. Una cola de prioridad es un adaptador de contenedor cuyo primer elemento es siempre el mayor de los elementos que contiene, según estrictos criterios de ordenamiento débiles.

  3. Los elementos se pueden insertar en cualquier momento en la cola de prioridad y solo se puede recuperar el elemento más grande (pequeño) (el elemento en la parte superior de la cola de prioridad) .

  4. El contenedor subyacente puede ser cualquier plantilla de clase de contenedor estándar u otra clase de contenedor diseñada específicamente. El contenedor debe admitir acceso aleatorio (subíndice más []) y admitir las siguientes operaciones:
    (1) vacío(): detecta si el contenedor está vacío
    (2) tamaño(): devuelve el número de elementos válidos en el contenedor
    (3 ) front(): Devuelve una referencia al primer elemento del contenedor
    (4) push_back(): Inserta un elemento al final del contenedor
    (5) pop_back(): Elimina el elemento al final del contenedor

  5. Las clases de contenedores estándar vector y deque satisfacen estas necesidades. De forma predeterminada, se utiliza vector si no se especifica ninguna clase de contenedor para una instancia de clase de cola prioritaria particular.

  6. Es necesario admitir el acceso aleatorio para que la estructura del montón siempre se mantenga internamente.

  7. prioridad_queue是一个模板类, plantilla <clase T, clase Contenedor = vector<T>, clase Comparar = menos< nombre de tipo Contenedor::valor_tipo>> clase prioridad_queue;

  8. Incluya el archivo de encabezado <queue> antes de usarlo

5.2Uso de prioridad_queue

Descripción de la interfaz común:

función ilustrar
cola_prioridad() Construir una cola de prioridad vacía
cola_prioridad(comienzo(),fin()) Pasar la inicialización del rango del iterador
vacío() Determine si la cola de prioridad está vacía; devuelva verdadero si está vacía; de lo contrario, devuelva falso
arriba() Devuelve el elemento más grande (pequeño) en la cola de prioridad, es decir, el elemento superior del montón.
empujar(val) Insertar elemento val en la cola de prioridad
estallido() Elimine el elemento más grande (pequeño) en la cola de prioridad, es decir, el elemento superior del montón

Aviso:

  1. Por defecto, Priority_queue es un montón grande.
#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;
}
  1. Si coloca datos de tipo personalizado en Priority_queue, el usuario debe proporcionar una sobrecarga de > o < en el tipo personalizado.

práctica:

El Késimo elemento más grande de la matriz.

//对于取数组中前K个最大的元素(TOPK),很容易就能想到利用堆
//先把原数组构造一个堆,要求第K大的元素
//只需要pop() K - 1次即可,最后堆顶一定是第k个大的元素
//其中建堆的时间复杂度为O(N),K - 1次的删除后调整为 (K-1) * logN
//在 K比较小的情况下,时间复杂度接近于O(N)
class Solution {
    
    
public:
    int findKthLargest(vector<int>& nums, int k)
    {
    
    
        //其实这个题目也可以利用快速排序,不过必须选择性的排序区间
        priority_queue<int> p(nums.begin(), nums.end());
        for(int i = 0; i < k - 1; i++)
        {
    
    
            p.pop();
        }
        return p.top();
    }
};

6.Introducción al deque

deque (cola de doble extremo): es una estructura de datos espaciales " continua " de doble apertura. El significado de doble apertura es que se pueden realizar operaciones de inserción y eliminación en ambos extremos , y la complejidad del tiempo es O (1), que es lo mismo que en comparación con el vector, la inserción de encabezados es más eficiente y no requiere elementos móviles; en comparación con la lista, la utilización del espacio es relativamente alta.

6.1 Principio de implementación deque

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

Insertar descripción de la imagen aquí

6.2Defectos deque

  1. No es adecuado para recorrido , porque al atravesar, el iterador de deque necesita detectar con frecuencia si se mueve hasta el límite de un determinado espacio pequeño, lo que resulta en una baja eficiencia. En escenarios secuenciales, es posible que se requiera un recorrido frecuente, por lo que en la práctica, Es necesario cuando se trata de estructuras lineales, en la mayoría de los casos se prefieren vectores y listas . 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 .
    (Incluye el archivo de encabezado <deque> si realmente quieres usarlo)
  2. La inserción de cabeza a cola es muy eficiente, pero la inserción intermedia aún requiere mover datos (la longitud del subconjunto es constante) y el acceso frecuente al subíndice más [] consume muchos cálculos. En este momento, la eficiencia es mucho menor que el de vector y lista.

6.3 Razones para elegir deque como contenedor predeterminado subyacente para pila y cola

  1. No es necesario atravesar la pila y la cola (por lo que la pila y la cola no tienen iteradores), solo necesitan operar 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 datos durante la expansión ); cuando los elementos en la cola crecen, deque no solo es eficiente, sino que también tiene un alto uso de memoria .

7. Implementación de simulación

7.1Implementación de simulación de pila

namespace my_std
{
    
    
	template<class T, class container = deque<T>>
	class stack
	{
    
    
	public:
		void push(const T& x)
		{
    
    
			_con.push_back(x);
		}

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

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

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

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

	private:
		container _con;
	};
}

7.2Implementación de simulación de cola

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

		void pop()
		{
    
    
			_con.pop_front();
		}

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

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

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

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

	private:
		container _con;
	};
}

7.3 Implementación de simulación de prioridad_queue

Este artículo no habla principalmente sobre algoritmos relacionados con el montón. Si tiene preguntas sobre la construcción y el ajuste del montón, puede leer las explicaciones anteriores del montón:
Implementación del montón
, Aplicación práctica del montón y análisis de complejidad del tiempo.

namespace my_std
{
    
    
	//优先级队列
	template<class T, class container = vector<T>, class comparation = less<T>>
	class priority_queue
	{
    
    
	public:
		//向上调整
		void adjust_up(size_t child)
		{
    
    
			comparation com; //用于比较的仿函数
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
    
    
				// _con[parent]  < _con[child]
				//默认大堆,如果孩子大,就交换和父亲的值
				//然后更新孩子和父亲的下标
				if (com(_con[parent], _con[child]))
				{
    
    
					std::swap(_con[parent], _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
    
    
					break;
				}
			}
		}

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

		//向下调整
		void adjust_down(size_t parent)
		{
    
    
			size_t child = parent * 2 + 1;
			comparation com;
			while (child < _con.size())
			{
    
    
				//_con[child] < _con[child + 1]
				//默认左孩子大,如果右孩子大就让孩子下标加1,注意右孩子不存在的情况
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
    
    
					child++;
				}
				//_con[parent] < _con[child]
				//默认大堆。如果孩子大就和父亲交换,然后更新孩子下标和父亲下标
				if (com(_con[parent], _con[child]))
				{
    
    
					std::swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;	 
				}
				else
				{
    
    
					break;
				}
			}
		}

		void pop()
		{
    
    
			std::swap(_con.front(), _con.back());
			_con.pop_back();
			adjust_down(0);
		}


		//无参构造,这个必须写,不然就没有默认构造用了
		priority_queue()
		{
    
    }

		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{
    
    
			while (first != last)
			{
    
    
				_con.push_back(*first);
				++first;
			}

			// 建堆,自下而上建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
    
    
				adjust_down(i);
			}
		}

		
		/// ///
		
		T& top()
		{
    
    
			return _con.front();
		}

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

		bool empty()
		{
    
    
			return _con.empty();
		}
	private:
		container _con;
	};
}

Supongo que te gusta

Origin blog.csdn.net/2301_76269963/article/details/132788066
Recomendado
Clasificación