王道数据结构第三章(栈和队列)笔记

第三章栈和队列

    • 栈的基本概念
    • 栈的顺序存储
    • 栈的链式存储
  • 队列
    • 队列的基本概念
    • 队列的顺序存储
    • 循环队列
    • 队列的链式存储
  • 双端队列(选择题)
  • 栈和队列的应用
    • 常用应用
  • 矩阵的压缩存储

栈的基本概念

1.概念性质
栈:只允许在一端进行插入或删除操作的线性表
栈的特性:后进先出(LIFO)
栈的性质:n个不同元素进栈,出栈元素不同排列的个数为1/(n+1)Cn,2n(卡特兰数
2.基本操作
InitStack(&S)
StackEmpty(S)
Push(&S,x)
Pop(&S,&x)
GetTop(S,&x)
DestoryStack(&S)

栈的顺序存储

1.概念定义
采用顺序存储的栈成为顺序栈
栈顶指针:S.top,初始时设置S.top = -1
栈顶元素:S.data[S.top]
进栈操作:栈不满时,栈顶指针先+1,再送值到栈顶元素
出栈操作:栈非空时,先取栈顶元素值,再将栈顶指针-1
栈空条件:S.top == -1
栈满条件:S.top == MaxSize-1
栈长:S.top+1

结构体
#define MaxSize 50
typedef struct{
	ElemType data[MaxSize];
	int top;
}SqStack;

2.顺序栈的基本运算

初始化
void InitStack(SqStack &S){
	S.top == -1;
}
判栈空
bool StackEmpty(SqStack S){
	return S.top == -1;
}
进栈
bool Push(SqStack &S,ElemType x){
	if(S.top == MaxSize - 1)
		return false;
	S.data[++S.top] = x;
	return true;
}
出栈
bool Pop(SqStack &S,ElemType &x){
	if(S.top == -1)
		return false;
	x = S.data[S.top--];
	return true;
}
读栈顶元素
bool GetTop(SqStack S,ElemType &x){
	if(S.top == -1)
		return false;
	x = S.data[S.top];
	return true;
}

3.共享栈(了解)
共享栈为了更有效的利用存储空间

结构体
#define MaxSize 10
typedef struct {
	ElemType data[MaxSize];
	int bottom;
	int top;
}ShStack;
初始化栈
void InitStack(ShStack &S){
	S.bottom = -1;
	S.top = MaxSize;
}

栈满条件:bottom + 1 == top

栈的链式存储

1.概念定义
采用链式存储的栈成为链栈
链栈有点:便于多个栈共享存储空间和提高其效率,且不存在栈满溢出现象

结构体
typedef struct Linknode{
	ElemType data;
	struct Linknode *next;
}*LiStack;

2.基本操作

初始化
void InitStack(LiStack S){
	S = NULL;
}
判空
bool StackEmpty(LiStack S){
	return S == NULL;
}

队列

队列的基本概念

1.概念定义
队:只允许在表的一端进行插入,而在另一端进行删除
特性:先进先出(FIFO)
对头(front):允许删除的一端,又称队首
队尾(rear):允许插入的一端
2.基本操作
InitQueue(&Q):初始化
QueueEmpty(Q)
EnQueue(&Q,x)
DeQueue(&Q,&x)
GetHead(Q,&x)

队列的顺序存储

1.概念定义
初始状态(队空条件):Q.front == Q.rear == 0
进队操作:①队不满时②先送值到队尾元素③队尾指针+1
出队操作:①队不空时②先取队头元素值③队头指针+1
不能用Q.rear == MaxSize (×)作为队列满条件

循环队列

1.概念定义
循环队列:把存储队列元素的表从逻辑上视为一个环
初始时Q.front =0,rear = 0
队首指针+1:Q.front = (Q.front + 1) % MaxSize
队尾指针+1:Q.rear = (Q.rear + 1) % MaxSize
队列长度:(Q.rear - Q.front + MaxSize) % MaxSize

2.判空判满条件:三种处理方式

①:牺牲一个单元

判满条件:(Q.rear + 1) % MaxSize
判空条件:Q.rear == Q.front
元素个数:(Q.rear - Q.front + MaxSize) % MaxSize

②:增设表示元素个数的变量

判满条件:Q.size == MaxSize
判空条件:Q.size == 0
元素个数:Q.size

③增设tag变量,0:删除操作;1:插入操作

判满条件:Q.rear == Q.front && tag == 1
判空条件:Q.rear == Q.front && tag == 0
元素个数:(Q.rear - Q.front + MaxSize) % MaxSize
3.基本操作(牺牲一个单元)

初始化
void InitQueue(SqQueue &Q){
	Q.rear = Q.front = 0;
}
入队
bool EnQueue(SqQueue &Q,ElemType x){
	if((Q.rear + 1) % MaxSize == Q.front)
		return false;
	Q.data[Q.rear] = x;
	Q.rear = (Q.rear + 1) % MaxSize;
	return true;
} 
出队
bool DeQueue(SqQueue &Q,ElemType &X){
	if(Q.rear == Q.front)
		return false;
	x = Q.data[Q.front];
	Q.front = (Q.front + 1) % MaxSize;
	return true;
}

队列的链式存储

//结构定义
typedef struct LinkNode{
	ElemType data;
	struct LinkNode *next;
}LinkNode;
typedef struct{
	LinkNode *front,*rear;
}LinkQueue;

带头结点

//初始化
void InitQueue(LinkQueue &Q){
	Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
	Q.rear->next = NULL;
}
//判断队列是否为空
bool Empty(LinkQueue Q){
	return Q.rear == Q.front;
}
//新元素入队 
void EnQueue(LinkQueue &Q,ElemType x){
	LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));
	s->data = x;
	s->next = NULL;
	Q.rear->next = s;
	Q.rear = s;
//出队
bool DeQueue(LinkQueue &Q,ElemType &x){
	if(Q.front == Q.rear)
		return false;
	LinkNode *p = Q.front->next;
	x = p->data;
	Q.front->next = p->next;
	if(Q.rear == p)
		Q.rear = Q.front;
	free(p)
	return true;
}

不带头结点

//初始化
void InitQueue(LinkQueue &Q){
	Q.front = NULL;
	Q.rear = NULL;
}
//判断队列是否为空
bool Empty(LinkQueue Q){
	return Q.rear == NULL;
}
//新元素入队
void EnQueue(LinkQueue &Q,ElemType x){
	LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));
	s->data = x;
	s->next = NULL;
	if(Q.rear == NULL){
		Q.rear = Q.front = s;
	}else{
		Q.rear->next = s;
		Q.rear = s;
	}
}
//出队
bool DeQueue(LinkQueue &Q,ElemType &x){
	if(Q.rear == NULL)
		return false;
	LinkNode *q = Q.front;
	x = p->data;
	Q.front = p->next;
	if(q == Q.rear)
		Q.rear = Q.front;
	free(q);
	return true;
}

双端队列(选择题)

输入受限的双端队列
输出受限的双端队列

在栈中合法的输出序列在双端队列中必定合法

栈和队列的应用

1.括号匹配
遇到左括号就入栈
遇到右括号就消耗一个左括号
扫描到右括号且栈为空—右括号单身
处理完所有括号后,栈非空—左括号单身

//算法实现
//分析:1.循环扫描括号序列,判断是左括号还是右括号:若是左括号,进栈
//2.若是右括号,先判断栈是否为空,为空则匹配失败;不为空则判断是否与栈顶元素匹配:匹配则继续循环,不匹配则匹配失败
//3.最终遍历完,判断栈是否为空,为空则匹配成功,不为空则匹配失败
bool bracketCheck(char str[],int length){
	SqStack S;
	InitStack(S);
	for(int i = 0;i < length;i++){
		if(str[i] == '(' || str == '[' || str == '{')
			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.三种算数表达式
算术表达式有三部分组成:操作数、运算符、界限符:反映了计算的先后顺序

  • 中缀表达式:a+b-c*d
  • 后缀表达式(逆波兰表达式)★:ab+ cd* -
  • 前缀表达式(波兰表达式):- +ab *cd
    3.表达式之间转换
  • 中缀表达式转后缀表达式
    ①确定运算符生效顺序
    ②按左操作数,右操作数,运算符的方式重新组合
    左优先原则:只要左边的运算符能先算,就优先计算左边的

机算
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符
①遇到操作符:直接加入后缀表达式
②遇到界限符:若为‘ ( ’直接入栈,若为‘ ) ’则依次弹出栈内运算符并加入后缀表达式,直至弹出‘ ( ’为止(界限符不加入后缀表达式)
③遇到运算符:依次弹出栈内高于或等于当前运算符的所有运算符,并加入后缀表达式,直至遇到‘ ( ’或栈空则停止,再把当前运算符入栈
④处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式

  • 中缀表达式转前缀表达式
    ①确定运算符生效顺序
    ②按运算符,左操作数,右操作数的方式重新组合
    右优先原则:只要右边的运算符能先算,就优先计算右边的
    4.表达式求值
  • 后缀表达式求值(适用于栈)
    从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行运算,合为一个操作数
    特点:最后出现的操作数先被运算

用栈实现
①从左往右扫描元素
②若扫描到操作数,则压入栈中并回到①
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶
注:先出栈的是右操作数

  • 前缀表达式求值

用栈实现
①从右往左扫描元素
②若扫描到操作数,则压入栈中并回到①
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶
注:先出栈的是左操作数

  • 中缀表达式求值
    中缀转后缀 + 后缀表达式求值

用栈实现
初始化两个栈:操作数栈,运算符栈
若扫描到操作数,压入操作数栈
若扫描到运算符或界限符,则按照‘中缀转后缀’相同的逻辑压入运算符栈
(每当弹出一个运算符时,就需要在弹出两个操作数栈的栈顶元素,并执行相应运算,运算结果再压入操作数栈)

常用应用

1.栈在递归中的应用
可以把原始问题转换为属性相同,但规模较小的问题
太多层递归可能会导致栈溢出

  • 递归
  • 进制转换
  • 表达式求值
  • 迷宫求解
  • 括号匹配
  • 深度优先搜索

2.队列在操作系统中的应用
FCFS(先来先服务)争抢有限的系统资源

  • CPU资源的分配
  • 打印数据缓冲区
  • 缓冲区
  • 页面替换算法
  • 广度优先搜索

矩阵的压缩存储

数组的存储结构

  • 一维数组
    数组元素A[i]的存放地址为
    下标从0开始:LOC + i * sizeof(ElemType)
    下标从1开始:LOC + (i - 1) * sizeof(ElemType)

  • 二维数组
    行优先存储

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

列优先存储

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

特殊矩阵

  • 对称矩阵aij = aji
    i<j 上三角区,i > j 下三角区
    性质:aij = aji

只存储主对角线+下三角区
按行优先原则将各元素存入一维数组
1.数组大小:n(n + 1) / 2
映射关系:矩阵下标aij(i≥j) ➡一维数组下标(B[k])
2.aij是第几个元素:[1+2+…+(i - 1)] + j ➡第i(i-1)/2 + j个元素
3.数组下标k:k = i(i-1)/2 + j -1 (注:数组下标从0开始)
数组下标k:k = i(i-1)/2 + j (注:数组下标从1开始)

  • 三角矩阵

压缩存储策略:按行优先原则将下三角区元素存入一维数组,并在最后一个位置存储常量C
和对称矩阵类似,只是多了一个存储常量的位置

  • 三对角矩阵(带状矩阵)(当|i-j| > 1时,有aij = 0)

压缩存储策略:按行优先原则,只存储带状部分
映射关系:矩阵下标aij(|i - j| ≤ 1) ➡一维数组下标(B[k])
1.aij是第几个元素
分析过程:①前i-1行共3(i - 1) - 1个元素
②aij是第i行第j - i + 2 个元素
③aij是第2i + j - 2个元素
2.数组下标k:k = 2i + j - 3(数组下标从0开始)
3.已知数组下标k,如何得到ij
分析过程:①下标k即k+1个元素的i和j
②前i - 1行共3(i - 1) - 1;前i行共3i - 1个元素
③3(i - 1) - 1 < k + 1 ≤ 3i - 1
④i ≥ (k + 2) / 3
⑤i = 向上取整

  • 稀疏矩阵

压缩存储策略:
顺序存储 — 三元组<行,列,值>(只存储非零元素)
链式存储 — 十字链表法

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java程序员十六

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值