《大话数据结构》从零开始 —— 第四章:栈与队列之栈(定义、代码实现、应用)

栈与队列

栈的定义

栈(stack)是限定仅在表尾进行插入和删除操作的线性表
  • 允许插入删除一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈

  • 栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构

  • 栈的插入操作,叫做进栈,也称压栈、入栈

  • 栈的删除操作,叫做出栈,也有的叫做弹栈

进栈出栈变化形式
  • 最先进栈的元素并不一定最后出栈

栈的抽象数据类型

ADT 栈(stack)
Data 
	同线性表元素具有相同数据类型,相邻元素具有前驱后继关系
Operation
	InitStack(*S):初始化,建立空栈
	DestoryStack(*S):若栈 S 存在,则销毁它
	ClearStack(*S):清空
	StackEmpty(S):空否,是则返回 true 否则返回 false
	GetTop(S, *e):若栈存在且非空,用 e 返回 S 的栈顶元素
	Push(*S, e):若栈存在,插入新元素 e 成为 S 的栈顶元素
	Pop(*S, *e):删除栈顶元素,返回其值 e
	StackLength(S):返回栈 S 元素个数
endADT

栈的顺序存储结构及实现

栈的顺序存储结构
  • 一个大小为5的栈的不同情况如图所示

一个大小为5的栈

  1. 栈底为下标为 0 的一端,因为首元素在栈底,变化最小
  2. 栈顶可以变化,但不能超出栈的存储长度 StackSize
  3. 空栈的判定条件为 top = -1
  • 栈的结构定义
#define MAXSIZE 5

typedef int SElemType;
typedef struct {
	SElemType data[MAXSIZE];
	int top; // 用于栈顶指针
}SqStack;

进栈(入栈、压栈)、出栈(弹栈)

bool Push(SqStack* S, SElemType e) {
	if (S->top == MAXSIZE - 1) {
		return false;
	}
	S->top++;
	S->data[S->top] = e;
	return true;
}

bool Pop(SqStack* S, SElemType* e) {
	if (S->top == -1) {
		return false;
	}
	*e = S->data[S->top];
	S->top--;
	return true;
}

两者均为涉及到循环语句,故时间复杂度为 O(1)


两栈共享空间

  • 两个栈的栈底分别是一个数组的两个端点,如果两个栈增加元素,则数据向中间延伸

  1. 当 top1 = 0 时,栈1 空栈;当 top2 = n 时,栈2 空栈
  2. 当 top1 + 1 = top2 时,栈满
#define MAXSIZE 100

// 结构
typedef int SElemType;
typedef struct {
	SElemType data[MAXSIZE];
	int top1; // 栈1 栈顶
	int top2; // 栈2 栈顶
}SqDoubleStack;

/* 入栈
* S 栈
* e 待输入的数据元素
* stackNum 为1时为 栈1,为2时为 栈2
*/
bool Push(SqDoubleStack* S, SElemType e, int stackNum) {
	if (S->top1 + 1 == S->top2) {
		return false;
	}
	if (stackNum == 1) {
		S->data[++S->top1] = e;
	}
	else
	{
		S->data[--S->top2] = e;
	}
	return true;
}

// 出栈
bool Pop(SqDoubleStack* S, SElemType* e, int stackNum) {
	if (stackNum == 1) {
		if (S->top1 == -1) {
			return false;
		}
		*e = S->data[S->top1--];
	}
	else
	{
		if (S->top2 == MAXSIZE) {
			return false;
		}
		*e = S->data[S->top2++];
	}
	return true;
}

这种结构适用于两个栈的空间结构需求有互补关系时,即当一个栈数据增长,另一个栈相应的缩减的情况。一般情况并不建议使用


栈的链式存储结构及实现

栈的连式存储结构
  • 栈的连式存储结构,简称链栈

  1. 对于链栈,基本不存在栈满的情况(栈满了电脑也崩了)
  2. 空栈:原定义为头指针为空,链栈空即为 top = NULL
  • 链栈结构
typedef int SElemType;
typedef struct StackNode 
{
	SElemType data;
	struct StackNode* next;
}StackNode, * LinkStackPtr;

typedef struct LinkStack 
{
	LinkStackPtr top;
	int countl
}LinkStack;
入栈、出栈

bool Push(LinkStack* S, SElemType e) 
{
	LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
	s->data = e; // 给新栈顶赋值
	s->next = S->top; // 新栈顶指向原栈顶
	S->top = s; // 栈顶指针更改为新栈顶
	S->count++; 
	return true;
}

bool Pop(LinkStack* S, SElemType* e)
{
	LinkStackPtr p;
	if (S->top == NULL) {
		return false;
	}

	p = S->top; // 临时指针指向当前栈顶
	*e = p->data;
	S->top = p->next; // 当前栈顶指针指向下一个栈元素
	free(p); // 释放原栈顶内存空间

	S->count--;
	return true;
}

栈的应用

递归
经典递归:斐波那契数列

// 递归实现斐波那契数列
int Fbi(int i)
{
	if (i < 2)
	{
		return i == 0 ? 0 : 1;
	}
	return Fbi(i - 1) + Fbi(i - 2); // 返回函数调用
}

int main() {

	for (int i = 0; i < 10; i++)
	{
		cout << Fbi(i) << " ";
	}
	cout << endl;

	return 0;
}
  • 以 i = 5 的执行过程为例

递归定义
  1. 我们把一个直接调用自己或通过一系列语句间接调用自己的函数,称为递归函数
  2. 每个递归定义必须有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出

迭代与递归的区别:

  1. 迭代使用循环结构,递归使用选择结构
  2. 递归能够使程序更清晰,更简洁,更易理解,但是大量的递归调用会建立函数副本,耗费大量时间和空间
  3. 迭代不需要调用自身和占用额外的内存

四则运算表达式求值
后缀(逆波兰)表示法定义
  • 一种不需要括号的后缀表达法,称为逆波兰(Reverse Polish Nation, RPN)表示

9 + (3 - 1) **** 3 + 10 / 2 的后缀表达式*为 9 3 1 - 3 *** + 10 2 / + ,称为后缀的原因是所有的符号都是在要运算的数字的后面出现

后缀表达式运算结果

后缀表达式:9 3 1 - 3 * + 10 2 / +

  • 运算规则:从左到右遍历表达式的每个数字和符号,遇到数字就进栈,遇到是符号,就将处于栈顶的两个数字出栈,进行运算,运算结果进栈,知道获得最终结果
中缀表达式 转 后缀表达式

中缀表达式 9 + (3 - 1) * 3 + 10 / 2 转为 上面的后缀表达式

  • 运算规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级低于栈顶符号则栈顶元素依次出栈并输出,并将当前符号进栈,直到输出最终的后缀表达式

总结
  • 要使计算机具有处理中缀表达式的能力,有重要的两步:
    1. 将中缀表达式转换为后缀表达式(栈用于进出运算的符号)
    2. 将后缀表达式转换为中缀表达式(栈用来进出运算的数字)
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值