c++数据结构之栈

一、栈的定义

栈(Stack)是由有限个数据类型相同元素组成的有序集合,对元素的操作只能在栈顶进行,遵循后进先出(Last In,First Out)的原则,其相关运算有创建空栈、判空、判满、入栈、出栈等。

二、栈的ADT

数据:
有限个数据类型相同元素所组成的有序集合,用top纪录栈顶元素的位置。
运算:
Create(): 创建一个空栈。
IsEmpty(): 若栈空,则返回1,否则返回0。
IsFull(): 若栈满,则返回1,否则返回0。
Push(): 让某元素入栈。
Pop(): 让栈顶元素出栈。
Display(): 输出栈元素

三、栈的数组实现

栈可以用数组来实现,定义数组data[MaxSize]用来存储栈元素,MaxSize是允许的最大容量,用变量top纪录栈顶元素的位置,top=-1表示空栈,这种栈称为顺序栈(Sequence Stack),用S或其它大写英文字母来表示。

#include<iostream>
using namespace std;
#define MaxSize 100//栈的最大容量
//顺序栈定义
struct SeqStack
{
	int data[MaxSize];//存放栈元素的数组
	int top;//栈顶指针
};
//创建空栈
void Create(SeqStack &S)
{
	S.top = -1;
}
//判空
int IsEmpty(SeqStack S)
{
	if (S.top == -1) return 1;
	else return 0;
}
//判满
int IsFull(SeqStack S)
{
	if (S.top >= MaxSize - 1) return 1;
	else return 0;
}
//入栈
void Push(SeqStack &S, int x)
{
	if (IsFull(S))//如果栈满
	{
		cout << "栈满,无法入栈" << endl;
		return;//什么都不做
	}
	S.data[++S.top] = x;//x入栈
}
//出栈
void Pop(SeqStack &S, int &x)
{
	if (IsEmpty(S))//如果栈空
	{
		cout << "栈空,无法出栈" << endl;
		return;//什么都不做
	}
	x = S.data[S.top--];//栈顶元素值赋给x后出栈
}
//输出顺序栈
void Display(SeqStack S)
{
	if (IsEmpty(S))
	{
		cout << "栈空,无元素输出" << endl;
		return;//什么都不做
	}
	int i = S.top;//定义循环控制变量i,让i取栈顶位置
	while (i >= 0)
		cout << S.data[i--] << " ";//输出栈中元素
	cout << endl;
}
//主函数
void main()
{
	SeqStack S;//定义栈
	Create(S);//创建空栈
	Push(S, 1);//入栈
	Push(S, 2);//入栈
	Display(S);
	int x;
	Pop(S, x);//出栈
	Display(S);
	Push(S, 3);//入栈
	Display(S);
}

四、栈的链表实现

顺序栈是用数组来存储栈中元素,由于数组的大小需事先声明,一般会将数组的存储空间预设的大一些,这有可能会造成内存空间的浪费。
利用链表来创建栈,可以克服顺序栈的这一缺点,一般将用链表创建的栈称之为链栈。链栈的优点是可以动态改变链表长度,不存在判别栈满的问题。由于链栈应遵循后进先出的原则,创建链栈时,应采用前插法来创建。

#include<iostream>
using namespace std;
//结点定义
struct Node
{
	int data;//数据域
	Node *next;//指针域
};
//链栈定义
struct LinkStack
{
	Node *top;
};
//创建空链栈
void Create(LinkStack &S)
{
	S.top = NULL;
}
//判空
int IsEmpty(LinkStack S)
{
	if (S.top == NULL) return 1;
	else return 0;
}
//入栈
void Push(LinkStack &S, int x)
{
	Node *NewNode;//新结点
	NewNode = new Node;//新结点申请内存
	NewNode->data = x;//新结点数据域赋值
	NewNode->next = NULL;//新结点指针域赋值
	if (IsEmpty(S))//如果链栈为空
		S.top = NewNode;//链栈顶指针指向新结点
	else//如果链栈非空
	{
		NewNode->next = S.top;//新结点前插入链栈
		S.top = NewNode;//更新栈顶指针
	}
}
//出栈
void Pop(LinkStack &S, int &x)
{
	if (IsEmpty(S))
	{
		cout << "栈空,无法出栈" << endl;
		return; //什么都不做
	}
	Node *p = S.top;//定义探测指针p并指向栈顶结点
	if (p->next == NULL)//如果链栈仅含一个结点
	{
		x = p->data;//获取栈顶结点值
		S.top = NULL;//置链栈为空
		delete p;//删除原栈顶结点
	}
	else//如果链栈中至少含有两个结点
	{
		x = p->data;//获取栈顶结点值
		S.top = S.top->next;//栈顶指针后移
		delete p;//删除原栈顶结点
	}
}
//输出链栈
void Display(LinkStack S)
{
	if (IsEmpty(S))//如果链栈为空
	{
		cout << "栈空,无结点输出" << endl;
		return;//什么都不做
	}
	Node *p = S.top;//定义探测指针p并指向栈顶结点
	while (p != NULL)
	{
		cout << p->data << "=>";//输出栈顶结点值
		p = p->next;//p后移
	}
	cout << "NULL" << endl;
}
//主程序
void main()
{
	LinkStack S;//定义栈
	Create(S);//创建空栈
	Push(S, 1);//入栈
	Push(S, 2);//入栈
	Display(S);
	int x;
	Pop(S, x);//出栈
	Display(S);
	Push(S, 3); //入栈
	Display(S);
}

五、栈的运用

5.1算术表达式

算术表达式是由运算符(+、-、x、/)和运算数(0、1、2、…)所组成。
在计算机中,有中序、前序和后序三种算术表达式。由于中序法存在着优先级和结合性问题,在编译处理上实在不方便,而前(后)序法更适合于编译处理。

中序表示法为:<运算数1><运算符><运算数2>
例如“2x3-4x5”就是用中序表示法描述的算术表达式。

前序表示法为:<运算符><运算数1><运算数2>
例如“2x3-4x5”对应的前序表达式为“-x23x45”。

后序表示法为:<运算数1><运算数2><运算符>
例如“2x3-4x5”对应的后序表达式为“23x45x-”。**

5.2算术表达式求值

以2x3-4x5为例,介绍中序表达式的求值方法。
第一步 创建两个空栈,分别用于存放运算符与运算数。
运算符栈
运算数栈
第二步 2入运算数栈,x入运算符栈,3入运算数栈。
运算符栈 x
运算数栈 2 3
第三步 -入运算符栈之前,先与运算符栈的栈顶元素x比较优先级,因-比x的优先级低,-暂不能入运算符栈,先让x出运算符栈,3与2出运算数栈,计算2x3=6,再将6入运算数栈,然后让-入运算符栈。
运算符栈 -
运算数栈 6
第四步 4入运算数栈,x入运算符栈之前,先与运算符栈的栈顶元素-比较优先级,因x比-的优先级高,让x入运算符栈,5入运算数栈。
运算符栈 - x
运算数栈 6 4 5
第五步 中序表达式处理完毕之后,让5与4出运算数栈,x出运算符栈,计算4x5=20,再将20入运算数栈。
运算符栈 -
运算数栈 6 20
第六步 20与6出运算数栈,-出运算符栈,计算6-20=-14,再将-14入运算数栈。
运算符栈
运算数栈 -14
第七步 运算符栈已空,让-14出运算数栈,使运算数栈也为空,-14就是中序表达式23-45的值。
运算符栈
运算数栈

以“-x23x45”为例,介绍前序表达式的求值方法。
第一步 创建两个栈,一个用来存放前序表达式字符,一个用来存放运算数。
前序表达式栈 - x 2 3 x 4 5
运算数栈
第二步 5与4出前序表达式栈,入运算数栈。
前序表达式栈 - x 2 3 x
运算数栈 5 4
第三步 x出前序表达式栈,4与5出运算数栈,计算4x5=20,再将20入运算数栈。
前序表达式栈 - x 2 3
运算数栈 20
第四步 3与2出前序表达式栈,入运算数栈。
前序表达式栈 - x
运算数栈 20 3 2
第五步 *出前序表达式栈,2与3出运算数栈,计算2x3=6,再将6入运算数栈。
前序表达式栈 -
运算数栈 20 6
第六步 -出前序表达式栈,6与20出运算数栈,计算6-20=-14,再将-14入运算数栈。
前序表达式栈
运算数栈 -14
第七步 前序表达式栈已空,让-14出运算数栈,使运算数栈也为空,-14就是前序表达式-2345的值。
前序表达式栈
运算数栈

以“23x45x-”为例,介绍后序表达式的求值方法。
第一步 创建两个栈,一个用来存放后序表达式字符(注意:逆序装入),一个用来存放运算数。
后序表达式栈 - x 5 4 x 3 2
运算数栈
第二步 2与3出后序表达式栈,入运算数栈。
后序表达式栈 - x 5 4 x
运算数栈 2 3
第三步 x出后序表达式栈,3与2出运算数栈,计算2x3=6,再将6入运算数栈。
后序表达式栈 - x 5 4
运算数栈 6
第四步 4与5出后序表达式栈,入运算数栈。
后序表达式栈 - x
运算数栈 6 4 5
第五步 x出后序表达式栈,5与4出运算数栈,计算4x5=20,再将20入运算数栈。
后序表达式栈 -
运算数栈 6 20
第六步 -出后序表达式栈,20与6出运算数栈,计算6-20=-14,再将-14入运算数栈。
后序表达式栈
运算数栈 -14
第七步 后序表达式栈已空,让-14出运算数栈,使运算数栈也为空,-14就是后序表达式2345-的值。
后序表达式栈
运算数栈

比较三种算术表达式的求值方法,不难发现:中序表达式符合我们的阅读习惯,但运算符的入栈需要考虑其优先级。因为在运算符栈中,只允许优先级高的运算符“压在”优先级低的运算符之上。前序(后序)表达式求值法,避免了运算符优先级的讨论,更适合于计算机去处理;由于后序表达式的入栈次序是逆序的,相比较而言,前序表达式求值方法更为方便。

5.3中序转前序或者后序

利用栈,可将中序表达式转换成前序表达式。在转换过程中,需要借助一个运算符栈和一个输出栈。还需要计算运算符的ISP值与ICP值。ISP(In Stack Priority)是指运算符的“栈内优先级”,而ICP(In Coming Priority)是指运算符的“输入优先级”。

中序转前序的算法如下

(1)规定运算符“)”在运算符栈内的优先级ISP最小,“(”的ISP最大;而运算符“)”在运算符栈外的优先级ICP最大,“(”的ICP最小。
总结:遇到’)’直接读入运算符栈,入栈后’)’变为最小的ISP,后续的运算符(除了’)’外)都可以直接读入。
总结:遇到’(’,先将’)’前面的运算符全部收入到输出栈,然后将’(’与‘)’消除。

(2)由右至左依次读入中序表达式中的每一个

(3)如果读入的是运算数,则直接进入输出栈。

(4)如果读入的是运算符,当读入运算符的ICP ≤ 运算符栈顶元素的ISP时,将运算符栈顶元素出栈并进入输出栈,直到运算符栈为空或者运算符栈顶元素的ISP小于ICP为止,然后再让读入的运算符入运算符栈。
(备注:可以简单理解为icp>isp为入栈条件)

(5)如果读入的是运算符“(”,让运算符栈顶元素出栈并进入输出栈,直到第一个“)”出栈为止。

(6)当中序表达式读完后,让运算符栈元素全部出栈并进入输出栈。

例题:将中序表达式“(A+B)D+E/(F+AD)+C”转换成前序表达式。
答:创建一个运算符栈和一个输出栈(假定两个栈的左端为栈顶),由右至左逐一读取中序表达(A+B)D+E/(F+AD)+C的每一个字符,转换过程如下图。

注意:我们一般默认左边为栈底,现在默认左边为栈顶,主要是在输出栈中可以直接看出表达式而不需要再进行栈的输出。

所求的前序表达式为+x+ABD+/E+FxADC。
注意上述用x代替了*。

c++代码实现中序转前序:

#include<iostream>
using namespace std;
#define MaxSize 100//存放中序表达式字符数组的最大容量
//优先级结构体定义
struct Priority
{
	char C;//运算符
	int P;//优先级
};
//优先级结构体数组赋初值(全局变量)
Priority ISP[8] = { { ')', 0 }, { ';', 1 }, { '=', 2 }, { '+', 3 }, { '-', 3 }, { '*', 4 }, { '/', 4 }, { '(', 5 } };
Priority ICP[8] = { { '(', 0 }, { ';', 1 }, { '=', 2 }, { '+', 3 }, { '-', 3 }, { '*', 4 }, { '/', 4 }, { ')', 5 } };
//结点定义
struct Node
{
	char data;//数据域
	Node* next;//指针域
};
//链栈定义
struct Stack
{
	Node* top;
};
//创建空链栈
void Create(Stack& S)
{
	S.top = NULL;
}
//判空
int IsEmpty(Stack S)
{
	if (S.top == NULL) return 1;
	else  return 0;
}
//入栈
void Push(Stack& S, char x)
{
	Node* NewNode;//新结点
	NewNode = new Node;//新结点申请内存
	NewNode->data = x;//新结点的数据域赋值
	NewNode->next = NULL;//新结点的指针域赋值
	if (IsEmpty(S))//如果链栈为空
		S.top = NewNode;//链栈顶指针指向新结点
	else//如果链栈非空
	{
		NewNode->next = S.top;//新结点前插入链栈
		S.top = NewNode;//链栈顶指针指向新结点
	}
}
//出栈
void Pop(Stack& S, char& x)
{
	if (IsEmpty(S))
	{
		cout << "栈空,无法出栈" << endl;
		return;
	}
	Node* p = S.top;//定义探测指针p并指向栈顶结点
	if (p->next == NULL)//如果链栈中仅含一个结点
	{
		x = p->data;//获取栈顶结点值
		S.top = NULL;//置链栈为空
		delete p;//删除原栈顶结点
	}
	else//如果链栈中至少含有两个结点
	{
		x = p->data;//获取栈顶结点值
		S.top = S.top->next;//栈顶指针后移
		delete p;//删除原栈顶结点
	}
}
//输出链栈
void Display(Stack S)
{
	if (IsEmpty(S))//如果链栈为空
	{
		cout << "栈空,无结点输出" << endl;
		return;//什么都不做
	}
	Node* p = S.top;//定义探测指针p并指向栈顶结点
	while (p != NULL)
	{
		cout << p->data;//输出栈顶结点值
		p = p->next;//p后移
	}
	cout << endl;
}
//输入中序表达式
void Input(char Infix[], int& n)
{
	cin >> Infix;//输入中序表达式
	n = 0;
	while (Infix[n] != '\0')//获取字符数组的长度
		n++;
}
//运算符优先级比较
int Compare(char InfixSymbol, char StackSymbol)
{
	int isp = 0, icp = 0;
	while (ISP[isp].C != StackSymbol)
		isp++;//查找运算符栈的栈顶运算符在ISP数组中位置isp
	while (ICP[icp].C != InfixSymbol)
		icp++;//查找读入运算符在ICP数组中的位置icp
	//若读入运算符的ICP大于栈顶运算符的ISP,返回1。否则,返回0
	return (ICP[icp].P > ISP[isp].P ? 1 : 0);
}
//中序转前序
void Infix2Prefix(char Infix[], int n, Stack& S, Stack& O)
{
	char x, y;
	do
	{
		y = Infix[n];//从右至左读取中序表达式字符
		switch (y)
		{	//y为(
		case '(':
			while (S.top->data != ')')//当栈顶元素字符不等于)时
			{
				Pop(S, x);//运算符栈的栈顶元素出栈
				Push(O, x);//再入输出栈
			}
			Pop(S, x);//让)出栈
			break;
			//y为);=+-*/
		case ')':
		case ';':
		case '=':
		case '+':
		case '-':
		case '*':
		case '/':
			if (IsEmpty(S))//如果运算符栈为空
				Push(S, y);//y直接入运算符栈
			else//如果运算符栈非空
			{	//当y的ICP小于或等于运算符栈顶元素的ISP时
				while (!Compare(y, S.top->data))
				{
					Pop(S, x);//运算符栈的栈顶元素出栈
					Push(O, x);//再入输出栈
					if (IsEmpty(S))//如果运算符栈为空
						break;
				}
				Push(S, y);//y入运算符栈
			}
			break;
			//y为运算数
		default:
			Push(O, y);//y入输出栈
			break;
		}
		n--;//下标减1
	} while (n != -1);
	//中序表达式字符读入完毕,让运算符栈元素出栈入输出栈
	while (!IsEmpty(S))//当运算符栈非空时
	{
		Pop(S, x);//运算符栈的栈顶元素出栈
		Push(O, x);//再入输出栈
	}
}
//主函数
void main()
{
	char Infix[MaxSize];//定义中序表达式字符数组
	Stack S, O;//定义运算符栈,输出栈
	Create(S);//创建空运算符栈
	Create(O);//创建空输出栈
	int n;//n表示中序表达式的字符长度
	cout << "输入中序表达式" << endl;
	Input(Infix, n);//调用中序表达式输入函数
	Infix2Prefix(Infix, n, S, O);//调用中序转前序函数
	cout << "输出前序表达式" << endl;
	Display(O);//显示输出栈
}

在这里插入图片描述

中序转后序的算法如下:
(1) 规定运算符“(”在运算符栈内的优先级ISP最小,“)”的ISP最大;而运算符“(”在运算符栈外的优先级ICP最大,“)”的ICP最小。(同中序转前序相反)
(2)由左至右依次读入中序表达式中的每一个字符。(同中序转前序相反)
(3)如果读入的是运算数,则直接输出。
(4)如果读入的是运算符,当读入运算符的ICP ≤ 运算符栈顶元素的ISP时,将运算符栈顶元素出栈并输出,直到运算符栈为空或者运算符栈顶元素的ISP小于ICP为止。然后再让读入的运算符入运算符栈。
(备注:可以简单理解为icp>isp为入栈条件,同中序转前序一样)
(5)如果读入的是运算符“)”,让运算符栈顶元素出栈并输出,直到第一个“(”出栈为止。(同中序转前序一样)
(6)当中序表达式读完后,让运算符栈元素全部出栈并输出。(同中序转前序一样)
显然,实现中序转后序算法,只需创建一个运算符栈。

c++代码实现中序转后序:

#include<iostream>
#include<stack>
#include<string>
using namespace std;
int priority(char ch)
{
	if (ch == '*' || ch == '/')
		return 1;
	if (ch == '+' || ch == '-')
		return 0;
	if (ch == '(')
		return -1;
}
int main()
{
	string input = "(A+B)*D+E/(F+A*D)+C";         //待处理中序表达式
	string output;
	stack<char> st;
	for (const auto& p : input)
	{
		if (p == '+' || p == '-' || p == '*' || p == '/' || p == '(')
		{
			if (p == '(')
				st.push(p);
			else
			{
				while ((!st.empty()) && (priority(p) <= priority(st.top())))
				{
					output += st.top();
					st.pop();
				}
				st.push(p);
			}
		}
		else if (p == ')')
		{
			while (st.top() != '(')
			{
				output += st.top();
				st.pop();
			}
			st.pop();
		}
		else                         //如果是操作数,直接输入到输出
			output += p;
	}
	while (!st.empty())
	{
		output += st.top();
		st.pop();
	}
	cout << output << endl;
	return 1;
}

在这里插入图片描述

5.4前序或后序转中序

对于前序表达式由右至左逐一读取表达式的每个字符,若为运算数则直接入栈,若为运算符则从栈中取出两个运算数,按<运算数1><运算符><运算数2>结合成一个中序表达式,然后再入栈。

将前序表达式“-x23x45”,转化成中序表达式的过程如下图:
在这里插入图片描述对于后序表达式由左至右逐一读取表达式的每个字符,若为运算数则直接入栈,若为运算符则从栈中取出两个运算数,按<运算数2><运算符><运算数1>结合成一个中序表达式,然后再入栈。

将后序表达式“23x45x-”,转化成中序表达式的过程如下图:
在这里插入图片描述

5.5进栈出栈规则

进栈出栈规则参考如下文档:

参考文档

5.6n个元素进栈,共有多少种出栈顺序?

n个元素进栈,共有多少种出栈顺序?参考如下文档:

参考文档

  • 13
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
近年来,计算器已成为人们日常工作和生活中必不可少的工具之一。在数据结构这门课程中,计算器的设计便成为了一个非常有趣的话题。那么如何设计一个实用的计算器呢? 首先,我们需要明确计算器的基本功能,包括加减乘除、括号、负数等常见的运算符号,以及数字键、小数点、退格键、清空键等常用按钮。通过合理的布局和设计,让这些按钮具有良好的可视性和易用性,使用户可以方便地进行计算操作。 其次,我们需要考虑计算器的内部实现。对于加减乘除等简单的运算,我们可以采用或队列等数据结构来实现。而对于更复杂的运算,如平方根、三角函数等,我们可以采用数值计算或逆波兰表达式等算法来实现。 另外,我们还需要考虑计算器的错误处理和结果输出。当用户输入非法字符或操作时,应给出相应的提示,避免程序出现异常或计算出错。同时,在结果输出上,我们应该保证数值精度和显示方式的合理性,使得用户可以清晰地看到计算结果。 最后,一个好的计算器还应该具备良好的扩展性和适应性,可以根据用户需求进行自定义设置,如更多的运算符号或科学计算功能,以及支持多种语言等。 综上所述,设计一个好的计算器需要考虑多方面因素,包括用户需求、内部实现、错误处理和结果输出等,只有在这些方面上做好了工作,才能设计出实用、高效、易用的计算器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@菜鸟一枚

你的鼓励是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值