数据结构笔记---栈与队列

三、栈与队列

1. 栈

1.1 基本概念与基本操作

1.1.1 基本概念与性质
  • 栈(stack):只允许在一端进行插入删除的操作的线性表
  • 栈顶(top):线性表允许进行插入删除的那一端。
  • 栈底(bottom):固定,不允许进行插入删除的另一端。
  • 空栈:不含任何元素的空表。

栈的操作特性:后进先出(last in first out,LIFO)

栈的数学性质:n个不同元素进栈,出栈元素不同排序的个数为 1 n + 1 C 2 n n {{1} \over {n+1}}C_{2n}^n n+11C2nn (卡特兰数Catalan)。

在这里插入图片描述

1.1.2 基本操作
  • InitStack(&S); 初始化表,构造一个空的线性表。
  • DestroyStack(&S);销毁操作,销毁线性表,释放线性表,释放L所占用的内存空间。
  • Push(&S,x);元素进栈,x称为新栈顶。
  • Pop(&S,&x);元素出栈,用x返回栈顶元素。
  • GetTop(S,&x);获得栈顶元素。
  • DestroyStack(&S); 销毁栈,释放S所占用的内存空间。

1.2 栈的顺序存储结构

1.2.1 顺序栈的实现

顺序栈:采用顺序存储的栈,利用地址连续的内存空间存放自栈底到栈顶的数据元素,附设指针(top),指向当前栈顶元素的位置。

结构体定义:

#define MaxSize 10

typedef struct {
	int data[MaxSize];
	int top;
}SqStack;

栈顶指针:S.top,初始设置为S.top=-1。
栈顶元素:S.data[S.top]。
入栈操作:栈不满时,栈顶指针先加1,再S.data[top]=data。
出栈操作:栈非空时,先取出栈顶元素,再将top减1,x=S.data[S.top–]。
空栈条件:S.top=-1.
栈满条件:S.top==MaxSize-1。
栈长:S.top+1。

1.2.2 顺序栈的基本运算实现
初始化
void InitStack(SqStack &S){
	S.top=-1;
}
入栈操作

基本操作:
1.判断栈是否已满,满则不能入栈。S.top==MaxSize-1;
2.栈顶指针后移。S.top=S.top+1;
3.插入栈顶元素。S.data[S.top]=data;

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

/*
	*Function:Push
	*Description:入栈操作
	*Input:
		arg1:SqStack &S 栈
		arg2:int data 入栈元素
*/
bool Push(SqStack &S,int data){
	if (S.top==MaxSize-1){
		return false ;
	}
	S.top=S.top+1;
	S.data[S.top]=data;
	return true;
}

出栈操作

1.判断栈是否为空,如果为空,则不能出栈。
2.先出栈,指针再减1。data=S.data[S.top–];
在这里插入图片描述在这里插入图片描述

/*
	*Function:Push
	*Description:出栈操作
	*Input:
		arg1:SqStack &S 栈
		arg2:int &data 返回的出栈元素
*/
bool Pop(SqStack &S,int &data){
	if (S.top==-1){
		return false ;
	}
	data=S.data[S.top--];
	return true;
}
取栈顶元素
/*
	*Function:GetTop
	*Description:读栈顶元素
	*Input:
		arg1:SqStack S
		arg2:int &data 返回的出栈元素
*/
bool GetTop(SqStack S,int &data){
	if (S.top==-1){
		return false ;
	}
	data=S.data[S.top];
	return true;
}

判空、打印栈操作
/*
	*Function:StackPrint
	*Description:打印栈操作
	*Input:
		arg1:SqStack S 需要打印的栈
*/
void StackPrint(SqStack S){
	for (int location=0;location<=S.top;location++){
		printf("栈的第%d个元素为:%d\n",location+1,S.data[location]);
	}
}

/*
	*Function:StackEmpty
	*Description:判空操作
	*Input:
		arg1:SqStack S 要判空的栈
	*Return:
		若空,返回true。若不空,返回false。
*/
bool StackEmpty(SqStack S){
	if (S.top == -1){
		return true;
	}else{
		return false;
	}
}
1.2.2 共享栈

共享栈:两个栈共享一片空间。
优点:两个栈共享空间,避免了空间的浪费,且没有增加存取的时间复杂度。
栈满的条件:top0+1==top1

在这里插入图片描述

#include <stdio.h>
#define MaxSize 10

typedef struct {
	int data[MaxSize];
	int top0;
	int top1;
}ShareStack;

void InitStack(ShareStack &S){
	S.top0=-1;
	S.top1=MaxSize;
}

1.3 栈的链式存储结构

链栈:栈的链式存储结构。
优点:便于多个栈共享存储空间和提高效率,而且不存在空间限制(栈满上溢)的情况。

在这里插入图片描述

结构体定义

#include <stdio.h>

typedef struct LinkNode{
	int data;
	struct  LinkNode *next;
} *LinkStack;

2. 队列

2.1 队列的基本概念与基本操作

队列的基本概念

在这里插入图片描述

  • 队列(Queue)的定义:操作受限的线性表,只允许一端插入另一端删除
  • 入队进队:插入元素。
  • 出队离队:删除元素。
  • 操作特点:First In First out, FIFO.
  • 队头、队首:允许删除一端。
  • 队尾:允许插入一端。
  • 空队列:不含任何元素的空表。
队列的基本操作

在这里插入图片描述

2.2 队列的顺序存储结构

2.2.1 队列的顺序存储
  • 顺序队列:分配连续的存储单元放置队列元素。
  • front:指向队头元素。
  • rear:指向队尾元素的下一个元素。
  • 队空条件:Q.front == Q.rear;
结构体定义及初始化

初始状态:Q.front == Q.rear == 0

typedef struct {
	int data[MaxSize];
	int front;
	int rear;
}SqQueue;

void InitQueue(SqQueue &Q){
	Q.rear=Q.front=0;
}
  • 入队操作:队不满时,先送值到队尾元素,再将队尾指针+1;
  • 出队操作:队不空时,先取队头元素值,再将队头指针+1;

注意:Q.rear=MaxSize不能作为队列满的条件(假溢出)

2.2.2 循环队列
基本概念
  • 循环队列:将顺序队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上视为环状。
  • 操作实现:取余运算。
  • 队空条件:Q.front==Q.rear。

在这里插入图片描述

注意:由上图(a)和图(d1)可知,队空的条件和队满的条件都为Q.front==Q.rear,为了区分有以下三种区分方式。

方法一:浪费一个存储单元来区分队空还是队满

约定:队头指针的下一位置作为队满的标志。如图(d2)

  • 队满条件:(Q.rear+1)%MaxSzie==Q.front;
  • 队空条件:Q.front == Q.rear;
  • 队列中元素的个数:(Q.rear-Q.front-MaxSize)%MaxSize;

方法二:

在结构体中附设size数据成员用于表示元素个数。

  • 队满条件:Q.size==MaxSize;
  • 队空条件:Q.size==0;
typedef struct {
	int data[MaxSize];
	int front;
	int rear;
	int size;
}CQueue;

方法三:

在结构体中附设tag数据成员用于最近一次的是删除操作还是插入操作(0位删除,1为插入)。

  • 队满条件:Q.front==Q.rear&&Q.tag == 1;
  • 队空条件:Q.front==Q.rear&&Q.tag == 0;
typedef struct {
	int data[MaxSize];
	int front;
	int rear;
	int tag;
}CQueue;
2.2.3 循环队列的操作
结构体定义以及初始化
#include <stdio.h>
#define MaxSize 50

typedef struct {
	int data[MaxSize];
	int front;
	int rear;
}CQueue;

void InitQueue(CQueue &CQ){
	CQ.rear=CQ.front=0;
}
入队

队尾指针后移:CQ.rear=(CQ.rear+1)%MaxSize;

bool EnQueue(CQueue &CQ,int data){
	
	if ((CQ.rear+1)%MaxSize == CQ.front)
		return false;

	CQ.data[CQ.rear]=data;
	CQ.rear=(CQ.rear+1)%MaxSize;
	return true;

}
出队

队头指针后移:CQ.front=(CQ.front+1)%MaxSize;

bool DeQueue(CQueue &CQ,int &data){
	
	if (CQ.rear==CQ.front)
		return false;

	data=CQ.data[CQ.front];
	CQ.front=(CQ.front+1)%MaxSize;
	return true;

}
打印、判空
bool QueueEmpty(CQueue &CQ){
	if(CQ.front == CQ.rear){
		return true;
	}else{
		return false;
	}
}

void QueuePrint(CQueue CQ){
	for (int i=0;i<(CQ.rear-CQ.front+MaxSize)%MaxSize;i++){
		printf("队列第%d个元素为:%d\n",i+1,CQ.data[(CQ.front + i) % MaxSize]);
	}
}

2.3 队列的链式存储结构

2.3.1 队列的链式存储
  • 链队列:队列的链式存储,本质上是同时带有队头指针与队尾指针点的单链表。
  • 队头指针:指向头结点。
  • 队尾指针:指向队尾结点(链表最后一个结点)。
  • 队列为空条件:Q.front==NULL&&Q.rear == NULL .

注意:为了插入、删除操作同一,通常采用带头结点的链式结构。

在这里插入图片描述

  • 空队列
    在这里插入图片描述
2.3.2 链式队列的基本操作
结构体定义及初始化

基本操作:分配头指针、尾指针的空间,因为带有头结点,所以让Q.front->next=NULL;

typedef struct LinkNode{
	int data;
	struct LinkNode *next;
}LinkNode;

typedef struct {
	LinkNode *front,*rear;
}LinkQueue;

void InitQueue(LinkQueue &Q){
	Q.front=Q.rear=(LinkNode *)malloc (sizeof(LinkNode));
	Q.front->next=NULL;
}
入队操作

基本操作:
1.分配插入结点s,s的data为插入值。s->data=data;
2.因为s是尾部进入,所以s指向NULL。s->next=NULL;
3.rear指针指向s。Q.rear->next = s;
4.rear指向队尾。Q.rear=s;
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

void EnQueue(LinkQueue &Q,int data){
	LinkNode *s=(LinkNode *)malloc (sizeof(LinkNode));
	s->data=data;
	s->next=NULL;
	Q.rear->next = s;
	Q.rear=s;
}
出队操作

基本操作:
1.分配p指向第一个结点(即出队结点)。LinkNode *p=Q.front->next;
2.获得出队结点值。data=p->data;
3.队列front越过出队结点指向后面。Q.front->next=p->next;
4.若为唯一结点出队,设置为空队。Q.rear=Q.front;
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

bool DeQueue(LinkQueue &Q,int &data){
	if (Q.front==Q.rear){
		return false;
	}
	LinkNode *p=Q.front->next;
	data=p->data;
	Q.front->next=p->next;
	if (Q.rear==p){
		Q.rear=Q.front;
	}
	free(p);
	return true;
}
判空、打印
bool IsEmpty(LinkQueue Q){
	if (Q.front==Q.rear)
		return true;
	else
		return false;
}

void PrintQueue(LinkQueue Q){
	LinkNode *q=Q.front;
	int i = 1;
	while (q->next!=NULL){
		q=q->next;
		printf("队列中第%d个元素的值为%d\n",i,q->data);
		i++;
	}
}

2.4 双端

  1. 双端队列:允许两端都可以进行入队和出队操作的队列。
    在这里插入图片描述
  2. 输入受限的双端队列:只允许一端插入、两端删除的线性表。在这里插入图片描述
  3. 输出受限的双端队列:只允许一端删除、两端插入的线性表。
    在这里插入图片描述

3. 栈和队列的应用

3.1 栈在括号匹配中的应用

算法的基本思想:

  1. 设置一个空栈,顺序读入括号。
  2. 若是右括号,则或者使置于栈顶的最急迫期待得以消解,若是括号不匹配、不合法,退出程序。
  3. 若是左括号,则压入栈中。算法结束时栈为空,则匹配,反之不匹配。

算法流程图如下:
在这里插入图片描述

bool bracketCheck(SqStack S){

	printf ("请输入需要检验是否匹配的括号序列,以@为结束标识:\n");
	ElementType c;
	ElementType str[100];
	int i = 0;
	while((c=getchar())!='@') 
	{
		str[i] = c;    
		i = i + 1;
	}
	for (int j=0;j<i;j++){
		if (str[j]=='('||str[j]=='['||str[j]=='{'){
			Push(S,str[j]);
		}if (str[j]==')'||str[j]==']'||str[j]=='}'){
			if (StackEmpty(S)){
				return false;
			}
			ElementType topElem;
			Pop(S,topElem);
			if (str[j]==')' && topElem!='('){
				return false;
			}
			if (str[j]=='}' && topElem!='{'){
				return false;
			}
			if (str[j]==']' && topElem!='['){
				return false;
			}
		}	
	}
	return StackEmpty(S);
}

3.2 栈在表达式求值中的应用

基本概念:

  • 中缀表达式:运算符在两个操作数中间。a+b-c*d;
  • 后缀表达式:运算符在两个操作数后面。ab+cd*-;
  • 前缀表达式:运算符在两个操作数前面。-+ab*cd;
3.2.1 后缀表达式

中缀表达式转后缀表达式(手算)

  1. 确定中缀表达式中各个运算符的运算顺序。
  2. 选择下一个运算符,按照 [左操作数 右操作数 运算符] 的方式组成一个新的操作数。
  3. 如果还有没有运算符没被处理,则继续2。

ps:
1.运算顺序不唯一,对应的后缀表达式也不唯一。
2.根据算法的确定性,机算结果只能是下图中的前者。(左优先原则)
在这里插入图片描述

后缀表达式的计算(手算)

  1. 从左到右,依次确定运算符顺序。
  2. 按照顺序,让操作符前面两个数执行响应的运算,合并为一个操作数。
  3. 运算向右移动。

在这里插入图片描述
中缀表达式转后缀表达式(机算)

从左到右依次处理各个元素,直到末尾。可能遇到三种情况:

  1. 如果遇到操作数,直接加入后缀表达式。
  2. 如果遇到界限符,遇到"(“直接入栈;遇到”)“则依次弹出栈內的运算符并加入后缀表达式,直到弹出”(“为止。”()"不加入后缀表达式。
  3. 遇到运算符,依次弹出栈空优先级高于或者等于当前运算符的所有运算符,并加入后缀表达式,若碰到"("或者栈空则停止。之后再把当前运算符入栈。

处理完所有字符后,将栈中剩余的运算符依次弹出,并加入后缀表达式。

后缀表达式的计算(机算、栈实现)

  1. 从左向右扫描下一个元素,直到处理完所有元素。
  2. 扫描到操作数则入栈,并到1,否则3。
  3. 扫描到运算符,弹出两个栈顶元素,执行相应的运算,先弹出的为右操作数,后弹出的为左操作数。运算结果压回栈顶。回到1。

计算示例如下:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

3.2.2 前缀表达式

中缀表达式转前缀表达式手算方法:

  1. 确定中缀表达式中各个运算符的运算顺序。
  2. 选择下一个运算符,按照 [运算符 左操作数 右操作数] 的方式组成一个新的操作数。
  3. 如果还有没有运算符没被处理,则继续2。

ps:
1.运算顺序不唯一,对应的后缀表达式也不唯一。
2.根据算法的确定性,机算结果只能是下图中的前者。(右优先原则)
在这里插入图片描述
前缀表达式的计算(机算、栈实现)

  1. 从右向左扫描下一个元素,直到处理完所有元素。
  2. 扫描到操作数则入栈,并到1,否则3。
  3. 扫描到运算符,弹出两个栈顶元素,执行相应的运算,先弹出的为左操作数,后弹出的为右操作数。运算结果压回栈顶。回到1。

在这里插入图片描述

3.3 栈在递归中的应用

递归定义/递归:若在一个函数、过程或者数据结构的定义中又应用了它自身,则这个函数、过程或者数据结构称为递归定义的,简称递归。

递归模型必须满足的两个条件

  • 递归表达式(递归体)
  • 边界条件(递归出口)

3.4 队列在层次遍历中的应用

3.5 队列在计算机系统中的应用

First Come First Service(FCFS):先来先服务

打印数据缓冲区

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值