数据结构——栈与队列

本文简要介绍了栈与队列的基本使用方法并列举了进制转换、括号匹配、逆波兰表达式等经典应用。

一、栈

栈也是一种存储多个数据的结构,其特点是只有一端开口,数据的访问与更改也只能从该段进行及后进先出!如下图所示,向栈中插入元素的操作成为push,反之成为pop。
栈示例

二、栈的接口函数

函数名 用途
Stack() 创造空栈
push(x) 顶端插入x
pop() 取出顶端元素
empty() 判断栈内是否有元素
size() 栈中元素个数
top() 返回顶端元素

三、栈应用

利用栈后进先出的特点,可以巧妙实现多种算法,这里简单列举部分经典算法。

1.进制转换——栈的逆序输出

栈结构最突出的特点便是逆序输出特性,该示例便是利用此特性方便地实现进制转换功能。

1)进制转化方法

众所周知,进制转换最直接的方法为短除后将余数逆序排列。先将10进制转2进制的示例展示如下,其余进制间转化与该方法类似。在这里插入图片描述
不难想象,在程序中将每次算出的余数push进栈结构中,计算结束后再逆序将其pop出来便可得到相应结果。

2)算法实现

void convert( Stack <char> &s, n, base)
{
	//考虑到16进制等常用较大进制从存在,将s声明为字符型数组并对digit做如下定义
	static char digit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
	while (n > 0)  //模仿短除操作,并将余数一次存入栈中。
	{
		s.push(digit[n % base]);
		n /= base;
	}
}

主函数中已经得到正确的栈s,只需用pop将其依次逆序输出即可。

main()
{
	Stack <char> s;
	int n;
	int base;
	//...
	convert(s, n, base);  //进行余数运算
	while(!s.empty())     //将结果逆序输出得到答案
		cout << s.pop;
}

2.括号匹配——栈的递归嵌套

当需解决的问题可递归描述且递归深度未知时,可巧妙利用栈的结构特点解决问题。

1)问题描述

我们编写程序时,需要保证左右括号的匹配问题,若出现括号不匹配的情况,则程序无法被编译。
如下图所示,上式中括号完全匹配而下式中的橙色括号缺失。
在这里插入图片描述
需要特别注意的是,并不是同种类左右括号数量相等及为匹配。反例如下:(该示例说明不能用简单计数器解决该问题)

在这里插入图片描述

2)解决思路

(1)该问题用栈结构可巧妙解决:
(2)首先通过简单扫描去除多余元素,使得待判断字符串只包含括号。
(3)之后逐个扫描字符串中元素,每当扫描到左括号便将其从顶端插入栈中。当扫描到右括号时,检测此括号是否与栈顶端括号相匹配,若匹配,则取出该元素并进行后续判断,否则说明字符串中括号匹配有误。
(4)当字符串中括号全部扫描完成后,查看栈中是否为空。若栈中不含有任何元素,则该字符串中各组括号匹配,否则匹配有误。

3)代码实现

bool paren(const char exp[], int lo, int hi)
{
	Stack<char> s;
	for(in i = lo; i < hi; i++)  //在exp[lo]~exp[hi]间查找
		if( exp[i] == '(' || exp[i] == '[' && exp[i] == '{')  //判断是否为左括号
			s.push(exp[i]);
		else if (!s.empty())
			if(exp[i] - s.top() <= 2) //判断是否为对应的右括号
				s.pop();
			else return false;
		else return false;
	return s.empty();
}

3.栈混洗

1)问题描述

现有A B S三个栈,需借助S栈的过渡按特定顺序将A栈中的数据转移至B栈中,可采用的操作方式有两种(如图)。

在这里插入图片描述
并非所有情况均可使用栈混洗解决,对于个数为你的序列,可进行栈混洗操作的个数为:在这里插入图片描述
同样,判断一个序列A是否可以进行栈混洗需如下要求:
对于任意i < j, 不含有[…,j + 1, …, i, …, j, …>
对于程序而言,进行如下判断即可:
在每次执行s.pop()之前,检测s是否已空,或需弹出元素在s中却非顶端元素。

4.中缀表达式求值——延迟缓冲

需要预读足够多数据才可进行处理

1)问题描述

当计算机进行表达式计算时,由于运算符优先级不同,会经常出现扫描在前,运算在后的情况,此时,利用栈结构的特点可轻松解决该问题。
在这里插入图片描述

2)解决方案

(1)总体方案

用两个栈分别存储扫描过的数字和符号,当扫描到符号时,判断该符号与栈顶端符号的优先级,若该符号优先级更高,则将其压入栈中,若栈顶符号优先级高,则进行计算后将结果存入数字栈中。
在这里插入图片描述

(2)算符优先级比较

各运算符优先级见下表,在程序中可存储于二维数组。
在这里插入图片描述

3)代码实现

(1)主算法

float evaluate(char * s, char * & RPN)
{
	Stack<float> opnd;  //数字栈
	Stack<char> optr;   //运算符栈
	optr.push('\0');
	while(!optr.empty())
		if(isdigit(*s))
			readNumber(s,opnd);  //读入数字
		else
			switch(orderBetween(optr.top(), *s)
			{
				/* 处理运算*/
			}
	return opnd.pop();  //将运算结果弹出
}

(2)运算符优先级判断算法

switch(orderBetween(optr.top(), *s))
{
	case '<':  //栈顶部运算符优先级更低
		optr.push(*s);  //新运算符进入
		s++;
		break;
	case '=':  //运算级相等及为括号或\0
		optr.pop();  //脱括号
		s++;
		break;
	case '>':  、、栈顶部运算优先级高,执行该运算
		char op = optr.pop();
		if('!' == op)  //一元运算
			opnd.push(calcu(op, opnd.pop()));
		else  //二元计算
		{
			float pOpnd2 = opnd.pop();
			float pOpnd1 = opnd.pop();
			opnd.push(calcu(pOpnd1, op, pOpnd2));
		}
		break;
}

5.逆波兰表达式

1)问题描述

逆波兰表达式又叫后缀表达式,是一种方便计算机计算的代数表达式。
在这里插入图片描述
该表达式见到运算符时只需依次进行计算即可。

2)代码

将中缀表达式转化为后缀表达式代码如下:

//该代码只需在上一问题代码中稍加修改即可
float evaluate(char* s, char* & RPN)
{
	while(!optr.empty())
	{
		if(isdigit(*s))
		{
			readNumber(s,opnd);
			append(RPN, opnd.top());
		}
		else
			switch(orderBetween(optr.top(), *s))
			{
				/*........*/
				case '>':
					char op = optr.pop();
					append(RPN, op);
			}
	}
}

四、队列

1.队列

队列的结构与栈正好相反,及先进先出后进后出。队列只存在两个口,一端为入口,一端为出口。(如图)
在这里插入图片描述

2.接口函数

函数名 作用
Queue() 创建队列
empty() 判断队列是否为空
enqueue(x) 输入端插入x
dequeue() 输出端取出数据
front() 获取出口处元素
size() 队列内部元素个数

//

发布了35 篇原创文章 · 获赞 18 · 访问量 6803

猜你喜欢

转载自blog.csdn.net/acslsr/article/details/104768459