第三章 栈.队列和数组

3.1栈

3.1.1 栈的基本概念

:是只允许在一端进行插入或删除操作的线性表。
栈顶: 线性表允许进行插入删除的那一端。
栈底: 固定的,不允许进行插入和删除的另一端。
空栈: 不含任何元素的空表。
某个栈S=(a1,a2,a3,a4,a5),a1为栈底元素,a5为栈顶元素,进栈顺序为a1,a2,a3,a4,a5,而出栈顺序为a5.a4,a3,a2,a1。所以栈的操作特性为:先进后出,后进先出(LIFO)
栈的数学性质: n个不同元素进栈,出栈元素不同排列个数为
在这里插入图片描述
栈的基本操作:
InitStack(&S): 初始化一个空栈S
StackEmpty(S): 判断一个栈是否为空
Push(&S, x): 进栈,若栈S未满,则将x加入使之称为新栈顶
Pop(&S, &x): 出栈,若栈S非空,则弹出栈顶元素,并用x返回
GetTop(S, &x): 读栈顶元素,若栈S非空,则用x返回栈顶元素
DestoryStack(&S): 销毁栈,并释放栈S占用的存储空间

3.1.2 栈的顺序存储结构

栈是一种操作受限的线性表,类似于线性表,也有对应的两种存储方式
1.顺序栈的实现:
采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置

#define MaxSize 50   //定义栈中元素的最大个数
typedef struct{
	int data[MaxSize];     //存放栈中元素
	int top;  //栈顶指针
}SqStack;

栈顶指针: S.top,初始值设置为S.top = -1;
栈顶元素: S.data[S.top]
进栈操作: 栈不满时,栈顶指针先加1,在送值到栈顶元素
出栈操作: 栈非空时,先取栈顶元素的值,再将栈顶指针减1
栈空条件: S.top == -1
栈满条件: S.top == MaxSize-1
栈长: S.top+1

由于顺序栈的入栈操作受数组上界的约束,当对栈的最大使用空间估计不足时,有可能发生栈上溢,此时应及时向用户报告消息,及时处理。
初始化操作:

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

进栈操作:

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

	//S.data[S.top++] = x;
	return true;
}

出栈操作:

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

	//x= S.data[S.top--];
	return true;
}

读取栈顶元素:

bool GetStack(SqStack S, int &x){
	if(S.top == -1)  //栈空
		return false;
	x = S.data[S.top];  //x记录栈顶元素
	return true;
}

**共享栈:**两个栈共享同一片空间

#define MaxSize 10   //定义栈中元素的最大个数
typedef struct{
	int data[MaxSize];   //静态数组存放栈中元素
	int top1;   //1号栈栈顶指针
	int top2;   //2号栈栈顶指针
}ShStack;


//初始化栈
void InitStack(ShStack &S){
	S.top1 = -1;   //初始化两个栈顶指针
	S.top2 = MaxSize;
}

在这里插入图片描述

链栈: 采取链式存储的栈,便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况,通常采用单链表实现,推荐使用不带头结点,所有操作都在表头进行

typedef struct Linknode{
	int data;   //数据域
	struct Linknode *next;   //指针域
}*LiStack;   //栈类型定义

在这里插入图片描述

3.2队列

3.2.1队列的基本概念

队列: 操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。插入元素:入队和进队;删除元素:出队和离队;操作特性为先进先出(FIFO)
队头: 允许删除的一端
队尾: 允许插入的一端
空队列
**InitQueue(&Q): ** 初始化队列,构造一个空队列Q
QueueEmpty(Q): 判队列是否为空
EnQueue(&Q, x): 入队,若队列Q未满,将x加入,使之成为新的队尾
DeQueue(&Q, &x): 出队,若队列Q非空,删除队头元素,并用x返回
GetHead(Q, &x): 读队头元素

因为栈和队列是操作受限的线性表,因此有些操作不合法,比如,不可以随便读取栈或队列中间的某个数据。
在这里插入图片描述

队列的顺序实现:

#define MaxSize 10   //定义队列中元素的最大个数
typedef strcut{
	int data[MaxSize];  //用静态数组存放队列元素
	int front,rear;  //队头指针和队尾指针,rear指向队尾元素的后一个位置
}SqQueue;

初始化队列:

void InitQueue(SqQueue &Q){
	Q.rear = Q.front = 0;   //初始时,队头,队尾指针指向0
}

判断队列是否为空:

bool EmptyQueue(SqQueue Q){
	if(Q.front==Q.rear)  //队空条件
		return true;
	else
		return false;
}

**入队: **

bool EnQueue(SqQueue &Q, int x){
	if((Q.rear+1)%MaxSize==Q.front)  //队满则报错
		return fasle;
	Q.data[Q.rear]=x;   //新元素插入队尾
	Q.rear = (Q.rear+1)%MaxSize;   //队尾指针加1取模
	return true;
}

%MaxSize:取模运算,将存储空间在逻辑上变成了”环状“,即将无限的证书域映射到有限的整数集合{0,1,2…,MaxSize-1},所以此时队列可称为循环队列

循环队列-出队:

bool DeQueue(SqQueue &Q, int &x){
	if(Q.front==Q.rear)  //判断队空
		return false;
	x = Q.data[Q.front];   //传出队头元素
	Q.front = (Q.front+1)%MaxSize;  //队头指针后移
	return true;
}

获得队头元素:

bool GetQueue(SqQueue Q, int &x){
	if(Q.front==Q.rear)  //判断队空
		return false;
	x = Q.data[Q.front];   //传出队头元素
	return true;
}

判断队列已满/已空的条件:
1.普通方法
队列元素个数:(rear+MaxSize-front)%MaxSize
队列已满条件:(Q.rear+1)%MaxSize = Q.front 队尾指针的下一个位置是队头
队列为空条件: Q.rear == Q.front

2.给队列加一表示长度的参数

#define MaxSize 10   
typedef strcut{
	int data[MaxSize]; 
	int front,rear;  
	int size;   //队列当前长度
}SqQueue;

初始化时:size=0
插入成功:size++
删除成功:size–
队满条件:sizeMaxSize
队列为空:size
0

3.队列增加一个表示插入删除操作的参数,二进制的01表示

#define MaxSize 10   
typedef strcut{
	int data[MaxSize]; 
	int front,rear;  
	int tag;   //最近进行的操作时删除/插入
}SqQueue;

初始化时: tag=0

每次删除操作成功时,令tag=0
每次插入操作成功时,令tag=1

因为只有删除操作,才可以导致队空,因此如果队列为空,上一步操作一定为删除,即tag=0
因为只有插入操作,才可以导致队满,因此如果队列已满,上一步操作一定为插入,即tag=1
队空条件 : front == rear && tag ==0
队满条件 : front == rear && tag ==1

普通的队尾指针指向队尾元素的后一个位置。而也可以进行变形,即队尾指针指向队尾元素,此时在入队操作就需要一些变化,之前是先增加数据,在后移队尾指针,但现在队尾指针指向队尾元素,所以入队时先要将队尾指针后移,然后再增加数据。出队操作不变:

Q.rear = (Q.rear+1)%MaxSize;
Q.data[Q.rear] = x;

此时判空: (Q.rear+1)%MaxSize == Q.front
判满: 普通方法不行,需要牺牲一个存储单元或者增加辅助变量

在这里插入图片描述

队列的链式实现:

typedef struct LinkNode{  //链式队列结点
	int data;
	struct LinkNode *next;
}LinkNode;

typedef struct{  //链式队列
	LinkNode *front,*rear;   //队列的队头和队尾指针
}LinkQueue;

链队列: 链式存储实现的队列(带头结点,不带头结点)

初始化(带头结点):

void InitQueue(LinkQueue &Q){
	Q.front = Q.rear = new LinkNode;   //初始化时front,rear都指向头结点
	q.front->next = NULL;  
}

判断队列是否为空:(带头结点)

bool IsEmpty(LinkQueue Q){
	if(Q.front == Q.rear)
		return true;
	else
		return false;
}

初始化队列(不带头结点):

void InitQueue(LinkQueue &Q){
	Q.front = NULL;  //初始化时,front,rear都指向空
	Q.rear = NULL;
}

判断队列是否为空:(不带头结点)

bool IsEmpty(LinkQueue Q){
	if(Q.front == NULL)
		return true;
	else
		return false;
}

入队(带头结点):

void EnQueue(LinkQueue &Q, int x){
	LinkNode *s = new LinkNode;  //新分配一个结点
	s->data = x;  //存入数据
	s->next = NULL;  //新结点后为空
	Q.rear->next = s;  //新结点插入到rear之后 
	Q.rear = s;  //修改队尾指针,保证队尾指针始终指向队尾结点
}

入队(不带头结点):

void EnQueue(LinkQueue &Q, int x){
	LinkNode *s = new LinkNode;  //分配新结点
	s->data = x;  //存储数据
	s->next = NULL;  //新结点后指向空
	if(Q.front == NULL){   //如果在空队列中插入第一个元素
		Q.front = s;  //队头队尾指针都指向新结点,该种情况特殊处理
		Q.rear = s;
	}
	else{
		Q.rear->next = s;  //新结点插入到rear之后
		Q.rear = s;  //更新队尾结点
	}
}

出队(带头结点):

bool DeQueue(LinkQueue &Q, int &x){
	if(Q.front == Q.rear)  //空队列
		return false;
	LinkNode *p=Q.front->next;   //用p指针指向要出队的结点
	x = p->data;  //变量x返回队头结点元素
	Q.front->next = p->next;  //修改头结点的next指针
	if(Q.rear == p)  //这是最后一个结点出队
		Q.rear = Q.front;  //修改队尾指针,变成空队列
	delete p;  //释放被删除的结点空间
	return true;
}

出队(不带头结点):

bool DeQueue(LinkQueue &Q, int &x){
	if(Q.front == NULL)  //空队列
		return false;
	LinkNode *s = Q.front;  //p指向要出队的结点
	x = p->data;   //变量x返回队头元素
	if(Q.rear == p){   //这是最后一个结点出队
		Q.front = NULL;  //变成不带头结点的空队列
		Q.rear = NULL;
		}
	delete p;  //释放结点空间
	return true;
}

顺序存储–预分配的空间耗尽时队满
链式存储–一般不会队满,除非内存不足

在这里插入图片描述

栈:只允许一端插入删除的线性表
队列:只允许一端插入,另一端删除的线性表
双端队列:只允许从两端插入,两端删除的线性表
输出受限的双端队列:只允许从一端输入,两端删除的线性表
输出受限的双端队列:只允许从两端输入,一端删除的线性表

考点:判断输入序列合法性

在这里插入图片描述
括号匹配问题:
最后出现的左括号最先被匹配(LIFO,后进先出)
每出现一个右括号,就“消耗”一个左括号(出栈)
算法过程:
遇到左括号就入栈,遇到右括号,就“消耗”一个左括号
(当前扫描到的右括号与栈顶左括号不匹配;处理完所有括号,栈非空)

在这里插入图片描述

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);   //检测所有括号后,栈为空说明匹配成功
}
  • 为了防止栈存满,可以使用链栈
  • 可以直接使用基本操作,使用注解说明接口即可

在这里插入图片描述

表达式求值问题:
由三部分组成:操作数,运算符,界限符
不用界限符也能表达运算顺序:
逆波兰表达式 = 后缀表达式
波兰表达式 = 前缀表达式
中缀表达式: 运算符在两个操作数中间 (a+b)
后缀表达式: 运算符在两个操作数后面 (ab+)
前缀表达式: 运算符在两个操作数前面 (+ab)

中缀转后缀的手算方法
1.确定中缀表达式中各个运算符的运算顺序
2.选择下一个运算符,按照【左操作数 右操作数 运算符】的方式组成一个新的操作数
3.如果还有运算符没被处理,就重复2

运算顺序不唯一,所以对应后缀表达式也不唯一,可以规定满足“左优先”原则,保证手算与机算结果相同

后缀表达式的手算方法:
从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应运算,合体为一个新操作数。(注意操作数的左右顺序)

后缀表达式的机算(用栈实现):
1.从左往右扫描下一个元素,直到处理完所有元素
2.若扫描到操作数则压入栈,并回到1,否则执行3
3.若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到1
先出栈的是”右操作数“;若表达式合法,最后栈中只会留下一个元素,就是最终结果

中缀转前缀的手算方法:
1.确定中缀表达式中各个运算符的运算顺序
2.选择下一个运算符,按照【运算符 左操作数 右操作数】的方式组合成一个新的操作数
3.如果还有运算符没被处理,就继续2
”右优先“原则:优先算右边的

用栈实现前缀表达式的机算:
1.从右往左扫描下一个元素,直到处理完所有元素
2.若扫描到操作数则压入栈,并回到1;否则执行3
3.若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到1

在这里插入图片描述

中缀表达式转后缀表达式(机算):
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符。
从左到右处理各个元素,直到末尾,可能遇到三种情况:
1.遇到操作数,直接加入到后缀表达式。
2.遇到界限符。遇到”(“直接入栈;遇到”)“则依次弹出栈内运算符并加入后缀表达式,直到弹出”(“为止。注意:”(“不加入后缀表达式
3.遇到运算符。依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式,若碰到”(“或栈空则停止,之后再把当前运算符入栈

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

中缀表达式的机算(用栈实现):
初始化两个栈,操作数栈和运算符栈
若扫描到操作数,压入操作数栈
若扫描到运算符或者界限符,则按照”中缀转后缀“相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)

在这里插入图片描述

函数调用背后的过程:
函数调用特点:最后被调用的函数最先执行结束(LIFO)
函数调用时,需要用一个栈存储:
1.调用返回地址
2.实参
3.局部变量

在这里插入图片描述

栈在递归中的应用:
适合用递归算法解决:可以把原始问题转化为属性相同,但规模较小的问题

递归算法求阶乘:

int factorial(int n){
	if(n==0 || n==1)
		return 1;
	else
		return n*factorial(n-1);
}

递归调用时,函数调用栈可称为”递归工作栈“
每进入一层递归,就将递归调用所需信息压入栈顶
每退出一层递归,就从栈顶弹出相应信息
(缺点:太多层次递归可能会导致栈溢出;及递归空间复杂度较高;其中会包含很多重复计算)

在这里插入图片描述

队列应用-图的广度优先遍历

队列在操作系统中的应用:
多个进程争抢着使用有限的系统资源时,(FCFS,先来先服务)是一种常用策略,可用队列实现

一维数组的存储结构:
起始地址:LOC
各数组元素大小相同,且物理上连续存放
数组元素a[i]的存放地址 = LOC + i*sizeof(ElemType)
(数组下标默认从0开始,除非题目特别说明)

二维数组的存储结构:
行优先存储 : b[M][N],
b[i][j]的存储地址 = LOC +(i*N+j)sizeof(ElemType)
列优先存储 : b[M][N],
b[i][j]的存储地址 = LOC +(j
N+i)*sizeof(ElemType)

普通矩阵存储:
用二维数组存储,注意矩阵行列一般从1开始,数组下标通常从0开始,注意审题!

对称矩阵的压缩存储:
普通存储:n*n二维数组
压缩存储策略: 只存储主对角线+下三角区(或上三角区,以下三角区为例)
按行优先原则将各元素存入一维数组中

1.数组大小? -----(1+n)*n/2
2.对称矩阵压缩存储后怎样方便使用? -----实现一个”映射“函数:数组下标->一维数组下标( aij->B[k] )
在这里插入图片描述

在这里插入图片描述
出题角度:

  1. 存储上三角?下三角?
  2. 行优先?列优先?
  3. 矩阵元素的下标从0?1?开始
  4. 数组下标从0?1?开始

三角矩阵的压缩存储:
在这里插入图片描述
在这里插入图片描述

三对角矩阵(带状矩阵)的压缩存储:
在这里插入图片描述
在这里插入图片描述

稀疏矩阵的压缩存储:
稀疏矩阵:非零元素远远少于矩阵元素的个数
压缩存储策略:顺序存储–三元组<行,列,值>
压缩存储策略二:链式存储–十字链表法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值