数据结构——栈


前言

数据结构有三要素——逻辑结构、数据的运算、存储结构(物理结构)
每种数据结构我们都将讨论它的定义(逻辑结构)和基本操作(数据的运算)
其中存储结构不同,运算的实现方式也有不同


一、定义

栈(Stack)是一种只允许在一端进行操作的线性表,不仅如此,栈还是一种后进先出(last in first off,LIFO)的数据结构。
栈可以顺序存储也可以链式存储,顺序栈和顺序表的定义几乎没有区别,只是在处理数据时加了一条后进先出的限制,链栈也是同样的道理。
在这里插入图片描述

二、顺序栈的基本操作

1.定义

#define MaxSize 10	//定义栈中元素的最大个数
typedef struct
{	
	ElemType data[MaxSize];	//静态数组存放栈中元素
	int top;				//栈顶指针,指向栈顶元素
}SqStack;

注意:顺序栈的缺点是栈的大小不可以改变,我们为了保证有足够大的空间去存储数据,通常会开辟一段比较大的连续空间去存储顺序栈,但这又导致了空间的浪费,这时候我们可以通过共享栈(两个栈共享一片空间)来最大化利用存储空间。要想使用共享栈,就需要两个栈顶指针,一个初始化在空间的最大位置,并且从上往下进行入栈,另一个初始化在空间的最小位置,从上往下进行入栈。

#define MaxSize 10	//定义栈中元素的最大个数
/*定义*/
typedef struct
{	
	ElemType data[MaxSize];	//静态数组存放栈中元素
	int top0;				//0号栈的栈顶指针
	int top1;				//1号栈的栈顶指针
}ShStack;
/*初始化*/
bool InitStack(ShStack& S)
{
	S.top0 = -1;
	S.top1 = MaxSize;
}
/*栈满的判断*/
bool IsFull(ShStack S)
{
	if (S.top0 + 1= S.top1)
		return true;
	else
		return false;
}

2.初始化

bool InitStack(SqStack& S)
{	
	S.top = -1;	//初始化栈顶指针即可
}

注意:把栈顶指针设为0也是一种常用的初始化方式,本文中涉及的操作栈顶指针都设为-1

3.进栈

bool Push(SqStack& S, ElemType x)
{	
	if (S.top == MaxSize - 1)	//栈满,报错
		return false;
	S.top++;	//栈顶指针加一
	S.data[S.top] = x;	//新元素入栈
	return true;
}

4.出栈

bool Pop(SqStack& S, ElemType& x)
{	
	if (S.top == -1)	//栈空,报错
		return false;
	x = S.data[S.top];	//元素出栈
	S.top--;	//栈顶指针减一
	return true;
}

注意:如果不用delete的话,栈顶元素还是存在物理内存中,只是在逻辑上被删除了

5.读出栈顶元素

bool GetTop(SqStack S, ElemType& x)
{	
	if (S.top == -1)	//栈空,报错
		return false;
	x = S.data[S.top];	//读出元素
	return true;
}

6.判空

bool StackEmpty(SqStack S)
{
	if (S.top == -1)	//栈空
		return true;
	else
		return false;
}

三、链式栈的基本操作

1.定义

typedef strunct LinkNode
{
	ElemType data;
	struct LinkNode* next;
}LinkNode, *LinkStack;

注意:因为在定义上,链栈和单链表没有什么区别,所以链栈的实现也有带头结点和不带头结点两种方式,但是在实现链栈时推荐不带头结点

2.初始化

bool InitStack(LinkStack S)
{//类似于不带头结点的单链表的初始化
	S == NULL;
	return true;
}

3.进栈

bool Push(LinkStack S, ElemType x)
{	
	LinkNode* p = new LinkNode;	//创建一个新的结点
	p->data = x;
	p->next = S;	//新结点的指针域指向原来的首元结点
	S = p;			//新结点成为了新的首元结点
	return true; 
}

注意:因为链栈的进栈操作必须后进先出,所以对应着单链表中头插法的操作

4.出栈

bool Pop(LinkStack S, ElemType &x)
{	
	if (S == NULL)	//此时栈中没有元素,无法出栈
		return false;
	LinkNode* p = S;	//临时指针
	x = p->data;	//元素出栈,由x带回
	S = p->next;	//此时的首元结点成为原结点的下一个
	delete p;		//释放出栈结点的内存
	p = NULL;
	return true; 
}

5.读出栈顶元素

bool GetTop(LinkStack S, ElemType& x)
{	
	if (S == NULL)	//栈空,报错
		return false;
	x = S->data;	//读出元素
	return true;
}

6.判空

bool Empty(LinkStack S)
{
	if(S == NULL)
		return true;
	else
		return false;
}

7.销毁

bool DestroyLinkStack(LinkStack S)
{
	LinkNode* p = S;	//临时指针
	while (p->next != NULL)
	{
		LinkNode* q = p;
		p = q->next;
		delete q;
	}
	S = NULL;
	return  true;
}

注意:顺序栈的销毁前面没有给出,因为顺序栈是通过在函数体内声明来分配空间的(如,SqStack S),这个空间是分配在栈区,函数体运行完毕后会由系统自动释放内存,理论上是不需要我们手动销毁的。而链栈的内存是我们手动开辟new出来的,这块内存开辟在堆区,函数体结束后系统不会自动释放,所以要由我们手动释放。

四、栈的应用

1.括号匹配

问题描述
比如有这样一组括号 {( ) [ ( { } ) ] }
按照我们之前所学习的逻辑,最后出现的左括号应该最先被匹配,这正与栈的特性有异曲同工之妙。

解决方法
遇到左括号就入栈,遇到右括号就拉上一个左括号一起“出栈”。这个过程就类似消消乐。但要注意,如果括号匹配后发现左右括号不是对应的,那么将直接报错不再继续检测下面的括号。
如果以代码的方式实现就是依次扫描所有字符,遇到左括号就入栈,遇到右括号就弹出栈顶元素并检查是否匹配。
匹配失败的情况:左括号单身,右括号单身,左右括号不匹配。
在这里插入图片描述

在这里插入图片描述图片源自知乎用户AProgrammer

实现代码

bool bracketCheck(char str[], int length)
{
	SqStack S;	//申明一个栈
	InitStack(S);		//初始化这个栈
	for (int i = 0; i < length; i++) {
		if (str[i] == '(' || str[i] == '[' || str[i] == '{') {	//扫描到左括号
			Push(S, str[i]);		//入栈
		}
		else {	//扫描到右括号
			if (StackEmpty(S))	//栈空,没有与之匹配的左括号
				return false;		//匹配失败
			char topElem;	//用来存放弹出的栈顶元素
			Pop(S, topElem);	//弹出栈顶元素
			/*匹配的括号类型不一致,报错*/
			if (str[i] == ')' && topElem != '(')
				return false;
			if (str[i] == ']' && topElem != '[')
				return false;
			if (str[i] == '}' && topElem != '{')
				return false;
		}
	}
	return StackEmpty(S);	//检索完全部括号之后,如果栈空则匹配成功
}

2.递归

在函数调用的时候,总是最后被调用的函数最先执行结束,这又与栈的特性有着异曲同工之妙,那么函数的调用应该可以使用栈来实现。实际也是如此,在系统开始一段函数时,就要开辟一个函数调用栈。
递归调用时,函数调用栈可称为“递归工作栈”,每进入一层递归,就将递归调用所需信息压入栈顶,每退出一层递归,就从栈顶弹出相应信息。但是,如果递归层数过多,可能会导致栈溢出。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值