数据结构算法刷题笔记——四、栈与队列

4.1 栈与队列

:限定仅在表尾进行插入和删除操作的线性表
队列:只允许在一端进行插入操作,在另一端进行删除操作的线性表

4.2 栈的定义

4.2.1栈的定义

(stack):是限定仅在表尾进行插入和删除操作的线性表

  • 栈顶:允许插入和删除的一端
  • 栈底:栈顶的另一端
  • 空栈:不含任何数据元素的栈
  • LIFO:别名,后进先出(Last InFirst Out)的线性表
    • 栈是线性表
    • 栈元素具有线性关系(前驱后继关系)
    • 表尾——栈顶
    • 表头——栈底
  • 插入操作:进栈、压栈、入栈
  • 删除操作:出栈、弹栈

4.2.2 进栈出栈变换形式

保证栈顶元素先出栈就可以
在这里插入图片描述

4.3 栈的抽象数据类型

  • 理论上:线性表的操作特性它都具备
  • 针对它的特殊性:一些操作有一些变化
  • 插入和删除操作:改为 进栈push 和 出栈pop
    在这里插入图片描述在这里插入图片描述

4.4 栈的顺序存储结构及实现

4.4.1 栈的顺序存储结构

栈的顺序存储:顺序栈:线性表顺序存储的简化

  • 栈底:下标为0
  • 栈顶:top变量,指示栈顶元素在数组中的位置
    • top 小于等于存储站长度StackSize
  • 空栈:top == -1

栈的结构定义

typedef int SElemType;	/* SElemType类型根据实际情况而定,这里加黑色为int */
/* 顺序栈结构 */
typedef struct
{
	SElemType data[MAXSIZE];
	int top;			/* 用于栈顶指针 */
}SqStack;

在这里插入图片描述

4.4.2 栈的顺序存储结构——进栈操作 push

进栈操作 push

  • 时间复杂度O(1)
/* 插入元素e为新的栈顶元素 */
Status Push(SqStack *S, SElemType e)
{
	if(S->top == Maxize-1) {	/* 判断栈满 */
		return ERROR;
	}
	S->top++;								/* 栈顶指针增加一 */
	S->data[s->top] = e;			/* 将新插入元素赋值给栈顶空间 */
	return OK;
}

在这里插入图片描述

4.4.3 栈的顺序存储结构——出栈操作 pop

出栈操作 pop

  • 时间复杂度O(1)
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR */
Status Pop(SqStack *S, SElemType *e)
{
	if(S->top == -1)			/* 栈为空,无可删除元素 */
	*e = S->data[S->top];	/* 将要删除的栈顶元素赋值给e */
	S->top--;						/* 栈顶指针减一 */
	return OK;
}

4.5 两栈共享空间

出发点:必须事先确定数组存储空间大小,万一不够用了,就需要用编程手段来扩展数组的容量
解决方案:两个相同类型的栈,用一个数组来存储这两个栈

  • 数组两个端点,作为两个栈的栈底
    • 下表为0,栈一的栈底
    • 下表为n-1,栈二的栈底
  • 两个栈的栈顶在数组中间,由两端向中间靠拢
    • top1 = -1,栈一为空栈
    • top2 = n,栈二为空栈
    • top1 + 1 == top2 :栈满
  • 使用范围:当两个栈的空间需求有相反关系,一个栈增长时另一个栈缩短的情况

两栈共享空间结构

/* 两栈共享空间结构 */
typedef struct
{
	SElemType data[MAXSIZE];
	int top1;		/* 栈1栈顶指针 */
	int top2;		/* 栈2栈顶指针 */
}SqDoubleStack;

两栈共享空间——插入元素 push

  • 函数参数需要一个判断是栈一或栈二的栈号参数stackNumber
  • 注意:++S->top1, --S->top2(插入元素,先自增长度,再赋值)
/* 插入元素e为新的栈顶元素 */
Status Push(SqDoubleStack *S, SElemType e, int stackNumber)
{
	if(S->top1 + 1 == S->top2)				/* 栈已满,不能再push新元素了 */
		return ERROR;
	if(stackNumber == 1)							/* 栈1 有元素进栈 */
		S->data[++S->top1] = e;					/* 若是栈1则先top1+1后给数组元素赋值 */
	else if(stackNumber == 2)
		S->data[--S->top2] == e;
	return OK;
}

两栈共享空间——删除元素 pop

  • 注意:S->top1–, S->top2++(删除元素,先赋值,再自减长度)
Status Pop(SqDoubleStack *S, SElemType *e, int stackNumber)
{
	if(stackNumber == 1){
		if(S->top1 == -1)					/* 判断栈1已经是空栈,则删除操作会溢出 */
			return ERROR;
		*e = S->data[S->top1--];		/* 将栈1的栈顶元素出栈 */
	}	
	else if(stackNumber == 2){
		if(S->top2 == MAXSIZE)
			return ERROR;
		*e = S->data[S->top2++];
	}
}

4.6 栈的链式存储结构及实现

4.6.1 栈的链式存储结构

栈的链式存储结构:简称链栈

  • 只有栈顶进行插入和删除操作
  • 栈顶:放在单链表的头部
    • 有了栈顶在头部,单链表中的头结点失去了意义,链栈基本没有头结点
  • 栈满:链栈基本不会栈满,除非计算机内存没空间
  • 空栈:top == NULL
  • 其他操作和单链表类似,在插入和删除上有一些特殊
/* 链栈结构 */
typedef struct StackNode
{
	SElemType data;
	struct StackNode *next;
}StackNode, * LinkStackPtr;

typedef struct
{
	LinkStackPtr top;			/* 栈顶指针 */
	int count;
}LinkStack;

在这里插入图片描述

4.6.2 栈的链式存储结构——进栈操作 push

进栈 push 操作:元素值为e的新节点s,top为栈的栈顶指针

  • 时间复杂度O(1)
/* 插入元素e为新的栈顶元素 */
Status Push(LinkStack *S, SElemType e)
{
	LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
	s->data = e;
	s->next = S->top;
	S->top = s;
	S->count++;
	return OK;
}

在这里插入图片描述

4.6.3 栈的链式存储结构——出栈操作 pop

链栈的出栈 pop 操作:变量p用来存储要删除的栈顶结点,将栈顶指针下移一位,最后释放p即可

  • 时间复杂度O(1)
/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK,否则返回ERROR */
Status Pop(LinkStack *S, SElemType *e)
{
	LinkStackPtr p;
	if(StackEmpty(*S))
		return ERROR;
	*e = S->top->data;
	p = S->top;							/* 将栈顶结点赋值给p */
	S-top = S->top->next;			/* 使得栈顶指针下移一位,指向后一结点 */
	free(p);									/* 释放指针p */
	S->count--;
	return OK;
}

链栈和顺序栈选择

  1. 链栈:栈的使用过程中元素变化不可预料,有时很小,有时很大,最好用链栈
  2. 顺序栈:栈的变化在可控范围内,使用顺序栈更好

4.7 栈的作用

栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题核心

4.8 栈的应用——递归

4.8.1 斐波那契数列的实现

斐波那契数列:前面相邻两项之和,构成了后一项
在这里插入图片描述在这里插入图片描述
打印前40位斐波那契数列
迭代实现方法:

int main()
{
	int i;
	int a[40];
	a[0] = 0;
	a[1] = 1;
	printf("%d", a[0]);
	printf("%d", a[1]);
	for(i = 2; i < 40; i++){
		a[i] = a[i-1] + a[i-2];
		printf("%d", a[i]);
	}
	return 0;
}

递归实现方式:

/* 斐波那契的递归函数 */
int Fbi(int i)
{
	if(i < 2)
		return i = 0 ? 0 : 1;
	return Fbi(i-1)+Fbi(i-2);			/* 这里是Fbi就是函数自己,等于在调用自己 */
}

int main()
{
	int i;
	printf("递归显示斐波那契数列:\n");
	for(int i = 0; i < 40; i++){
		printf("%d",Fbii());
	}
	return 0;
}

在这里插入图片描述

4.8.2 递归的定义

递归函数:直接调用自己或通过一系列的调用语句间接地调用自己的函数

  • 每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值推出

迭代和递归的区别

  • 迭代:使用循环结构
    • 优点:不需要反复调用函数和占用内存
  • 递归:使用选择结构
    • 优点:是程序结构更简洁,更容易理解
    • 缺点:递归调用,会建立函数的副本,耗费大量的时间和内存

递归过程就是操作系统通过栈的形式实现的

  • 前行阶段:对每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中
  • 退回阶段:位于栈顶的局部变量、参数值和返回值被弹出,用于返回调用层次中执行代码的其余部分,恢复了调用的状态

4.9 栈的应用——四则运算表达式求值

4.9.1 后缀(逆波兰)表示法的定义

栈的应用:比较常见的——数学表达式的求值
后缀表达法(逆波兰):一种不需要括号的后缀表达法

  • 所有符号都要在运算数字的后面出现
  • 规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果。

4.9.2 后缀表达式的计算结果

在这里插入图片描述

4.9.3 中缀表达式转后缀表达式

标准四则运算表达式中缀表达式 :9+(3-1)*3+10/2
中缀表达式——转换为——后缀表达式

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

4.10 队列的定义

队列:只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

  • FIFO:一种先进先出的线性表,简称FIFO
  • 队尾:允许插入的一端
  • 队头:允许删除的一端
  • 排在第一个的优先出列,最后的在队伍最后
    在这里插入图片描述

4.11 队列的抽象数据类型

队列有类似于线性表的各种操作

  • 不同点:
    • 插入数据只能在队尾
    • 删除数据只能在队头
      在这里插入图片描述在这里插入图片描述

4.12 循环队列

队列也存在顺序存储和链式存储两种结构

4.12.1 队列顺序存储的不足

队列的顺序存储结构:按照最基础的数组存储形式

  • 插入操作:在队尾插入一个元素,不需要移动任何元素

    • 时间复杂度O(1)
      在这里插入图片描述
  • 删除操作:在队头出列,即下标为0的位置

    • 时间复杂度O(n)
      在这里插入图片描述

存在不足:每次出队(删除)都在队头下标为0的位置,性能大大增加
改进方式:队头不需要一定在下标为0的位置,循环队列的使用

  • 引入两个指针:
    • front指针:指向队头元素
    • rear指针:指向队尾元素的下一个位置

4.12.2 循环队列的定义

出发点:解决假溢出,后面满了,再从头开始,头尾相接的循环。
循环队列:队列的这种头尾相接的顺序存储结构成为循环队列

  • front指针:指向队头元素

  • rear指针:指向队尾元素的下一个位置

  • 队列空的条件:front == rear

  • 队列满时,保留一个元素空间

  • 队列满的条件:(rear+1)%QueueSize == front

    • 在这里插入图片描述
  • 队列长度:(rear-front+QueueSize)%QueueSize

  1. 循环队列的顺序存储结构
typedef int QElemType;		/* QElemType类型根据实际情况而定,这里假设为int */
/* 循环队列的顺序存储结构 */
typedef struct
{
	QElemType data[MAXSIZE];
	int front;							/* 头指针 */
	int rear;							/* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SqQueue;

  1. 循环队列的初始化
/* 初始化一个空队列Q */
Status InitQueue(SqQueue *Q)
{
	Q->front = 0;
	Q->rear = 0;
	return OK;
}
  1. 循环队列求队列长度
/* 返回Q的元素个数,也就是队列的当前长度 */
int QueueLength(SqQueue Q)
{
	return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
  1. 循环队列的入队操作
/* 若队列未满,则插入元素e为Q新的队尾元素 */
Status EnQueue(SqQueue *Q, QElemType e)
{
	if((Q->rear+1)%MAXSIZE == Q->front)			/* 队列满的判断 */
		return ERROR;
	Q->data[Q->rear] = e;										/* 将元素e插入到队尾 */
	Q->rear = (Q->rear+1)%MAXSIZE;					/* rear指针向后移一位置 */
																				/* 若到最后则转到数组头部 */
	return OK;
}
  1. 循环队列的出队操作
/* 若队列不空,则删除Q中队头元素,用e返回其值 */
Status DeQieie(SqQueue *Q, QElemType *e)
{
	if(Q->front == Q->rear)									/* 队列空的判断 */
		return ERROR;
	*e = Q->data[Q->front];									/* 队头元素赋值给e*/
	Q->front = (Q->front+1)%MAXSIZE;					/* front指针向后移一位置 */
																				/* 若到最后则转到数组头部 */
	return OK;
}

4.13 队列的链式存储结构及实现

循环队列的算法时间性能并不高,但是循环队列面临着数组可能会溢出的问题
需要研究一下不需要担心队列长度的链式存储结构
链队列:队列的链式存储结构,就是线性表的单链表,只不过它只能尾进头出

  • 队头指针:指向链队列的头结点
  • 队尾指针:指向终端结点
  • 空队列:front和rear都指向头结点
    在这里插入图片描述在这里插入图片描述

链队列的结构

typedef int QElemType;		/* QElemType类型根据实际情况而定,这里假设为int */
typedef struct QNode			/* 结点结构 */
{
	QElemType data;
	struct QNode *next;
}QNode, *QueuePtr;

typedef struct						/* 队列的链表结构 */
{
	QueuePtr front,rear;		/* 队头、队尾指针 */
}LinkQueue;

4.13.1 队列的链式存储接哦古——入队操作

入队操作

  • 在链表尾部插入结点
/* 插入元素e为Q的新的队尾元素 */
Status EnQueue(LinkQueue *Q, QElemType e)
{
	QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
	if(!s)									/* 存储分配失败 */
		exit(OVERFLOW);
	s->data = e;
	s->next = NULL;
	Q-rear->next = s;			/* 把拥有元素e的新节点s赋值给原队尾结点的后继 */
	Q->rear = s;						/* 把当前的额s设置为队尾结点,rear指向s */
	return OK;
}

在这里插入图片描述

4.13.2 队列的链式存储结构——出队操作

出队操作

  • 头结点的后继结点出队
  • 将头结点的额后继改为它后面的结点
  • 若链表除头结点外只剩下一个元素,则需将rear指向头结点

出队操作

/* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */
Status DeQueue(LinkQueue *Q, QElemType *e)
{
	QueuePtr p;
	if(Q->front == Q->rear)
		return ERROR;
	p = Q->front->next;						/* 将欲删除的队头结点暂存给p */
	*e = p->data;								/* 将欲删除的队头结点的值赋值给e */
	Q->front->next = p->next;			/* 将原队头结点的后继p->next赋值给头结点后继 */
	if(Q->rear = p)							/* 若队头就是队尾,则删除后将rear指向头结点 */
		Q->rear = Q->front;
	free(p);
	return OK;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值