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

定义

只允许在一端进行插入 or 删除操作的线性表。

栈顶(top) : 允许进行插入删除操作的一端。

栈底(bottom) : 固定不变, 不允许进行任何操作。

栈可以为空。

卡特兰(Catalan) 函数

n 个不同元素进栈, 出栈元素不同的排列顺序的个数为:1n+1C2nn\frac{1}{n+1}C_{2n}^{n}n+11C2nn

当n个元素以某种顺序入栈,并且可以在任意时刻出栈,可以得到的元素排列数目满足卡特兰函数

顺序栈

------采用顺序存储的栈, 利用一组连续地址的存储单元存放

数据结构:
c++ 复制代码
typedef struct Stack{  
Elemtype data[maxSize];  
int top;  
}SqStack;  
基本操作:

初始化:

c++ 复制代码
SqStack S;  
S.top=-1;  

入栈(先判满) :

c++ 复制代码
if(S.top != maxSize-1){  
	S.data[+ + S.top] = x;  
}else{  
	printf("栈满");  
}  

出栈(先判空) :

c++ 复制代码
if(S.top != -1){  
	x = S.data[S.top - -];  
}else{  
	printf("栈空");  
}

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

共享栈:

两个顺序栈共享同一个一维数组, 两个栈底分别位于共享数组的两端。

c++ 复制代码
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

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

链栈

------采用链式存储方式的栈。 栈顶在队头, 栈底在队尾, 采用头插法入栈。

数据结构:
c++ 复制代码
typedef struct LNode{  
	int data; //数据域  
	struct LNode *next; //指针
};
基本操作:

没有头节点的链栈入栈

c++ 复制代码
S->next = top;
top = S;

没有头节点的链栈出栈

c++ 复制代码
x = top;
top = top->next;
free(x);

一直出栈,需要判空

c++ 复制代码
LinkNode *p = top;
if (top == NULL){
    printf("栈已空");
}
top = top->next;
free(p);

有头节点的入栈

c++ 复制代码
S->next = top->next;
top->next = S;

队列

定义

只允许在一端插入, 另一端删除的线性表。

队头(front) : 允许删除的一端。

队尾(rear) : 允许插入的一端。

队列可以为空。

顺序队

------利用一块连续的存储单元进行存放。

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

初始化:

c++ 复制代码
SqQueue Q;
Q.rear = 0;  
Q.front = 0;  

入队(先判满):

c++ 复制代码
if(队不满){  
	Q.data[Q.rear++] = x;  
}else{  
	print("队满")  
} 

出队(先判空):

c++ 复制代码
if(队不空){  
	x = Q.data[Q.front++];  
}else{  
	print("队空")  
}

上溢

假溢出

循环队列:

由于普通队列存在"假溢出" , 故引入循环队列:

初始:

c++ 复制代码
Q.rear = Q.front = 0;

入队:

c++ 复制代码
Qu.data[Qu.rear] = x; 
Qu.rear = (Qu.rear + 1) % maxSize;  

出队:

c++ 复制代码
x = Qu.data[Qu.front]; 
Qu.front = (Qu.front + 1) % maxSize;  

队列长度

c++ 复制代码
(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;

链队

------利用链式结构进行存储, 实际上就是同时带有队头和队尾指针的单链表。

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

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

入队:

c++ 复制代码
Q.rear->next = s;  
Q.rear = s;  

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

c++ 复制代码
// 非空队列:
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;
}

有头节点的链式队列入队

c++ 复制代码
// 非空队列:
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;
}

出队:

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

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

c++ 复制代码
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);
}

有头节点的链式队列出队

c++ 复制代码
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. 适合多个队列的需求

双端队列

两端都可以进行入队和出队操作的队列。

输出受限双端队列: 两端都可入队, 但只能在一端进行出队。

输入受限双端队列: 两端都可出队, 但只能在一端进行入队。

应用

括号匹配

思路:

  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栈中就是计算结果。

相关推荐
syzyc4 小时前
[ABC267F] Exactly K Steps
数据结构·动态规划·题解
草莓熊Lotso5 小时前
【数据结构初阶】--顺序表(二)
c语言·数据结构·经验分享·其他
汤姆爱耗儿药5 小时前
数据结构——散列表
数据结构·散列表
秋说6 小时前
【PTA数据结构 | C语言版】出栈序列的合法性
c语言·数据结构·算法
hi0_66 小时前
03 数组 VS 链表
java·数据结构·c++·笔记·算法·链表
皮卡蛋炒饭.8 小时前
数据结构—排序
数据结构·算法·排序算法
??tobenewyorker9 小时前
力扣打卡第23天 二叉搜索树中的众数
数据结构·算法·leetcode
干净的坏蛋11 小时前
Microsoft Word 中 .doc 和 .docx 的区别
microsoft·word
艾莉丝努力练剑13 小时前
【C语言】学习过程教训与经验杂谈:思想准备、知识回顾(五)
c语言·开发语言·数据结构·学习·算法