NO.3数据结构栈和队列|顺序栈|共享栈|链栈|顺序队|循环队列|链队|双端队列|括号匹配|中缀表达式转后缀|后缀表达式求值

![[Pasted image 20250712091028.png]]

定义

![[Pasted image 20250712091322.png]]

只允许在一端进行插入 or 删除操作的线性表。
栈顶(top) : 允许进行插入删除操作的一端。
栈底(bottom) : 固定不变, 不允许进行任何操作。
栈可以为空。
![[Pasted image 20250712091347.png]]

卡特兰(Catalan) 函数

n 个不同元素进栈, 出栈元素不同的排列顺序的个数为:1n+1C2nn\frac{1}{n+1}C_{2n}^{n}n+11C2nn
当n个元素以某种顺序入栈,并且可以在任意时刻出栈,可以得到的元素排列数目满足卡特兰函数

顺序栈

——采用顺序存储的栈, 利用一组连续地址的存储单元存放
![[Pasted image 20250712091359.png]]

数据结构:
typedef struct Stack{  
Elemtype data[maxSize];  
int top;  
}SqStack;  
基本操作:

初始化:
![[Pasted image 20250712091629.png]]

SqStack S;  
S.top=-1;  

入栈(先判满) :
![[Pasted image 20250712091709.png]]

if(S.top != maxSize-1){  
	S.data[+ + S.top] = x;  
}else{  
	printf(“栈满”);  
}  

出栈(先判空) :
![[Pasted image 20250712091733.png]]

if(S.top != -1){  
	x = S.data[S.top - -];  
}else{  
	printf(“栈空”);  
}

注意: 当 top 初始值为-1 时, 进栈时先移动指针, 出栈时后移动指针! 初始为 0 则反之~

共享栈:

两个顺序栈共享同一个一维数组, 两个栈底分别位于共享数组的两端。
![[Pasted image 20250712092134.png]]

S0.top = -1 //initial
S0.data[++S0.top] = x; //push
x = S0.data[S0.top++]; //pop

S1.top = -1 //initial
S1.data[--S0.top] = x; //push
x = S1.data[S0.top++]; //pop

S0.top+1 == S1.top //OOM
S1.top-1 == S0.top //OOM

优点: 更有效地利用储存空间。

链栈

——采用链式存储方式的栈。 栈顶在队头, 栈底在队尾, 采用头插法入栈。
![[Pasted image 20250712092308.png]]

数据结构:
typedef struct LNode{  
	int data; //数据域  
	struct LNode *next; //指针
};
基本操作:

没有头节点的链栈入栈
![[Pasted image 20250712093450.png]]

S->next = top;
top = S;

没有头节点的链栈出栈
![[Pasted image 20250712100708.png]]

x = top;
top = top->next;
free(x);

一直出栈,需要判空

LinkNode *p = top;
if (top == NULL){
    printf("栈已空");
}
top = top->next;
free(p);

有头节点的入栈
![[Pasted image 20250712101109.png]]

S->next = top->next;
top->next = S;

![[Pasted image 20250712101405.png]]

队列

定义

只允许在一端插入, 另一端删除的线性表。
队头(front) : 允许删除的一端。
队尾(rear) : 允许插入的一端。
队列可以为空。

顺序队

——利用一块连续的存储单元进行存放。
![[Pasted image 20250712102214.png]]

数据结构:
typedef struct{  
	Elemtype data[MaxSize];  
	int front, rear; //队头指针, 队尾指针  
}SqQueue;
基本操作:

初始化:

SqQueue Q;
Q.rear = 0;  
Q.front = 0;  

入队(先判满):
![[Pasted image 20250712102406.png]]

if(队不满){  
	Q.data[Q.rear++] = x;  
}else{  
	print(“队满”)  
} 

出队(先判空):
![[Pasted image 20250712102416.png]]

if(队不空){  
	x = Q.data[Q.front++];  
}else{  
	print(“队空”)  
}

上溢
![[Pasted image 20250712102435.png]]

假溢出
![[Pasted image 20250712102449.png]]

循环队列:

由于普通队列存在“假溢出” , 故引入循环队列:
![[Pasted image 20250712102520.png]]

初始:

Q.rear = Q.front = 0;

入队:

Qu.data[Qu.rear] = x; 
Qu.rear = (Qu.rear + 1) % maxSize;  

出队:

x = Qu.data[Qu.front]; 
Qu.front = (Qu.front + 1) % maxSize;  

队列长度

(Q.rear + Q.MaxSize - Q.front) % MaxSize;
区分队空/满
  1. 牺牲一个空间
    队空:Q.rear = Q.front;
    队满:(Q.rear + 1) % MaxSize == Q.front;
  2. 加一个成员变量size,记录当前队列的元素规模
    入队:Q.size + 1;
    出队:Q.size - 1;
  3. 增设结构成员tag
    队空:Q.rear = Q.front, tag = 0;
    队满:Q.rear = Q.front, tag = 1;
    入队:Q.tag = 1;
    出队:Q.tag = 0;

链队

——利用链式结构进行存储, 实际上就是同时带有队头和队尾指针的单链表。
![[Pasted image 20250712103015.png]]

数据结构:
typedef struct{  
	Elemtype data;  
	struct LinkNode *next;  
}LinkNode;//链队结点  
typedef struct{  
	LinkNode *front, *rear;  
}LinkQueue;//链队  
基本操作

队空:Q.front == null, Q.rear == null;
入队:

Q.rear->next = s;  
Q.rear = s;  

没有头节点的链式队列入队

// 非空队列:
LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));
s->data = x;
s->next = NULL;
Q.rear->next = s;
Q.rear = s;

// 空队列:
if (Q,rear == NULL){
    Q.rear = s;
    Q.front = s;
}

有头节点的链式队列入队

// 非空队列:
LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode));
s->data = x;
s->next = NULL;
Q.rear->next = s;
Q.rear = s;

// 空队列:
if (Q,rear == Q.front){
    Q.rear->next = s;
    Q.front = s;
}

出队:

x = Q.front->data;  
if(Q.front->next == null){ //被删除结点是不是队列最后一个元素  
	Q.front = null;  
	Q.rear = null;  
}else{  
	Q.front = Q.front->next;  
}

没有头节点的链式队列出队

if (Q.front != NULL){ //队不为空
    LinkNode *p = Q.front;
    if (Q.front->next == NULL){ //被删除节点是队列最后一个元素
        Q.front = NULL;
        Q.rear = NULL;
    }else{
        Q.front = Q.front->next;
    }
    free(p);
}

有头节点的链式队列出队

if (Q.front->next != NULL){ //队不为空
    LinkNode *p = Q.front->next;
    Q.front->next = p->next;
    if (Q.rear == p){ //被删除节点是队列最后一个元素
        Q.rear = Q.front;
    }
    free(p);
}

循环队列的提出是为了解决假溢出问题, 但是只能采取顺序存储的方式, 而链式队列不存在假溢出问题。 没有链式存储的循环队列。

使用链式队列的好处

  1. 内存动态分配
  2. 不存在假溢出的现象
  3. 适合多个队列的需求

双端队列

两端都可以进行入队和出队操作的队列。
![[Pasted image 20250712103401.png]]

输出受限双端队列: 两端都可入队, 但只能在一端进行出队。
![[Pasted image 20250712103412.png]]

输入受限双端队列: 两端都可出队, 但只能在一端进行入队。
![[Pasted image 20250712103424.png]]

应用

括号匹配

思路:

  1. 需要一个暂存括号的栈op
  2. 当遇到左括号时,入栈。
  3. 当遇到右括号时,将栈顶元素出栈,出栈元素应与当前右括号匹配,否则就是非法表达式;若当前栈顶为空,则也是非法表达式。
  4. 当遍历完成,若op栈不为空则表达式不合法,否则为合法表达式。
中缀表达式转换为后缀表达式

如何将a*(b+c)转换为逆波兰表达式?
思路:

  1. 需要两个栈,结果栈result 临时栈temp
  2. 遇到操作数时,直接将数字入栈result;
  3. 遇到操作符时
    a)若是’(‘,则直接入temp栈
    b)若是’)‘,说明temp中一定有’(‘与之对应,则将temp栈中的所有运算符依次出栈并入栈result,直到遇见’(',括号不会入result栈.
    c)若是+-*/运算符,则看当前的操作符与temp栈顶的运算符优先级,如果栈顶的运算符优先级更高,则将栈顶的运算符出栈并入栈result,否则当前操作符入栈temp。
  4. 当中缀表达式遍历完成后,将temp中的操作符依次出栈并入栈result, result栈中从栈底到栈顶的内容就是后缀表达式。
后缀表达式求值

【例题】计算后缀表达式23+1-2384/-的值
思路:

  1. 需要一个存储操作数的栈op
  2. 当遍历到操作数时,将操作数直接入op
  3. 当遍历到操作符时,将bp连续出栈两个操作数a,b,将运算 b op a的结果入栈op.
  4. 当遍历完成时,op栈底即为结果,
后缀表达式求值

【例题】利用栈直接计算表达式(2+3-1)2-3(8/4)的值思路:
将前面两个方法结合起来,中缀转后缀的过程中,每有一个op从temp栈中弹出时,都从result栈中弹出两个操作数a和b,计算b op a的结果再入栈result,最终遍历结束时result栈中就是计算结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值