【C++】stack的模拟实现

在这里插入图片描述


(一)基本介绍

在此之前我们我们用C语言实现过栈,栈的实现
对于栈的底层不熟悉的话,可以看看这篇文章
最主要的特点就是遵循 Last In First Out(LIFO)


1、基本概念

接下来,我们先从文档来认识,看文档中是如何描述的

  1. 从上我们看出stack是STL库中的一种容器,它用于存储数据,并遵循Last In First Out(LIFO)的规则;

  2. 堆栈是一种容器适配器,stack在C++ STL库中实现为一个模板类,提供一组特定的成员函数来访问其元素


2、容器适配器

  • 容器适配器(又叫配机器)是STL库中的一类容器,使用已有的容器类来实现适配器的功能
  • 简单来说,就是将通过封装某个序列式容器(如vector、deque和list等),并重新组合该容器中包含的成员函数,从而达到某种需求

容器适配器包括三种:stackqueuepriority_queue

容器适配器有以下特点

    1. 使用现有容器类作为底层实现
    1. 基于已有的功能来添加和删除元素(例如push()、pop()、top()等
    1. 通常有所限制,可优化

总体来说,容器适配器是为了方便使用STL当中已有容器类设计的,通过简洁的接口,简化某些数据结构的实现


(二)基本使用

void test()
{
	stack<int> st;
 
	// 在栈顶添加元素
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	st.push(5);
 
	//栈顶元素
	if (!st.empty())
		cout << st.top() << " " << st.size() << endl;
		
	// 从栈顶弹出元素
	st.pop();
 
	// 获取栈顶元素
	cout << st.top() << endl;
 
	// 检查栈是否为空
	if (st.empty())
		cout << "Stack is empty" << endl;
	else
		cout << "Stack is not empty" << endl;
		
	// 获取栈的大小
	cout << "Stack size is " << st.size() << endl;
}
  • stack是一个模板类,需要指定对应的数据类型

(三)stack模拟实现

stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类

这些容器类应该支持以下 操作:

  • empty:判空操作
  • back:获取尾部元素操作
  • push_back:尾部插入元素操作
  • pop_back:尾部删除元素操作

标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器, 默认情况=下使用deque

stack成员函数

在这里插入图片描述

stack模拟实现

对于stack来说,我们可以使用vector,list和deque来实现,我们默认使用deque

  • 1️⃣push()
void push(const T& x)
{
	_con.push_back(x);
}
  • 2️⃣pop()
void pop()
{
	_con.pop_back();
}
  • 3️⃣top()
const T& top()
{
	return _con.back();
}
  • 4️⃣empty()
bool empty()
{
	return _con.empty();
}
  • 5️⃣size()
size_t size()
{
	return _con.size();
}

完整代码如下

#pragma once
#include<iostream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
using namespace std;
namespace mystack
{
	template<class T, class Container = deque<T>>
	class stack
	{
	public:
		void push(const T& val)
		{
			_con.push_back(val);
		}

		void pop()
		{
			_con.pop_back();
		}
		const T& top()
		{
			return _con.back();
		}
		
		bool empty()
		{
			return _con.empty();
		}

		size_t size()
		{
			return _con.size();
		}
	private:
		Container _con;
	};
}

所以当我们实现顺序栈还是链式栈的时候,我们可以不用从头开始写了,可以使用适配器进行封装

当然,当我们想要换容器进行适配的时候

stack<int, vector<int>> st; // vector做适配器
stack<int, list<int>> st;  // list做适配器
stack<int> st;             // 默认deque做适配器

(四)逆波兰表达式求值

逆波兰表达式又称为后缀表达式,那什么是后缀表达式呢

  • 操作符位于操作数后面
  • 消除了括号

逆波兰表达式的的计算方法是从左到右遍历一遍,遇到操作数就入栈,遇到操作符就区栈顶的两个元素进行计算,然后将计算结果再次入栈,最后的计算结果就是栈顶元素的值

对于逆波兰表达式求值是比较简单的,那么问题就是如何将我们的中缀表达式转换为后缀表达式


思路:

  • 将操作符入栈,操作数直接放到结果当中
  • 当此刻的操作符的优先级低于栈顶元素的优先级,那么就说明可以先计算此时的结果,所以将弹出栈顶元素并且放到结果当中
  • 遇到左括号直接入栈,或者此时的栈顶元素优先级小于当前符号的优先级,同样的入栈
  • 遇到右括号,那么将括号之间的符号全部弹出,放到结果当中
  • 处理一下小细节,当-为 负号,而不是运算符,需要对其处理,出现时表达式开头,出现在括号内开头
#include<string>
#include<unordered_map>

#include"Stack.h"
int main()
{
	unordered_map<char, int>m;
	m.insert(make_pair('+', 1));
	m.insert(make_pair('-', 1));
	m.insert(make_pair('*', 2));
	m.insert(make_pair('/', 2));
	m.insert(make_pair('(', 3));
	m.insert(make_pair(')', 3));

	mystack::stack<char>sympol;
	queue<string>ans;

	string str;
	cin >> str;
	int n = str.size();
	int pre = 1;

	int i = 0, start = 0;
	while (i < n)
	{
		//处理前置符号
		if (pre == 1 && (str[i] == '+' || str[i] == '-'))
		{
			if (str[i] == '+')
				start++;
			i++;
		}

		//当前是数字
		while (i < n && str[i] >= '0' && str[i] <= '9')
			i++;
		if (i > start)
		{
			ans.push(str.substr(start, i - start));
			pre = 0;
		}

		//当前是符号
		if (m.find(str[i]) != m.end())
		{
			if (str[i] == '(')
				pre = 1;
			//当前没有符号
			if (sympol.empty())
				sympol.push(str[i]);

			//当前有符号
			else
			{

				if (str[i] == ')')
				{
					while (!sympol.empty() && sympol.top() != '(')
					{
						string tmp;
						tmp.push_back(sympol.top());
						sympol.pop();
						ans.push(tmp);
					}
					sympol.pop();

				}
				else if (str[i] == '(' || m[str[i]] > m[sympol.top()])
					sympol.push(str[i]);
				else
				{
					//while的原因是前面是* / ,而此时是+ -
					while (!sympol.empty() && sympol.top() != '(' && m[sympol.top()] >= m[str[i]])
					{
						string tmp;
						tmp.push_back(sympol.top());
						sympol.pop();
						ans.push(tmp);
					}
					sympol.push(str[i]);
				}
			}
		}
		i++;
		start = i;
	}

	while (!sympol.empty())
	{
		string tmp;
		tmp.push_back(sympol.top());
		sympol.pop();
		ans.push(tmp);
	}
	return 0;
}

那当此时的中缀表达式转为后缀表达式之后,那么就很好计算了,只需要从左往右遍历一遍就可以,就可以完成计算

	vector<string> popexp;
	while (!ans.empty())
	{
		string front = ans.front();
		popexp.push_back(front);
		cout << front << " ";
		ans.pop();
	}
	mystack::stack<int> num;
	for (int i = 0; i < popexp.size(); i++)
	{
		if (popexp[i] == "+" || popexp[i] == "-" || popexp[i] == "*" || popexp[i] == "/")
		{
			int b = num.top(); num.pop();
			int a = num.top(); num.pop();
			int ret = 0;
			if (popexp[i] == "+")
				ret = a + b;
			else if (popexp[i] == "-")
				ret = a - b;
			else if (popexp[i] == "*")
				ret = a * b;
			else
				ret = a / b;
			num.push(ret);
		}
		else
			num.push(stoi(popexp[i]));
	}

	cout <<endl<< num.top() << endl;

在这里插入图片描述


(总结)

  • 1、首先,我们通过文档的介绍了解了有关STL库关于stack的基本介绍,知道了在库中是一个模板类
  • 2 、其次给大吉介绍了有关容器适配器的基本知识
  • 3、接下来,我们简单的使用了一下stack,知道了用法
  • 4、紧接着,我们手动的去实现了一个stack,相比之前的stack的实现就显得十分简单了
  • 5、中缀表达式转为逆波兰表达式,然后在求值
    在这里插入图片描述
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值