栈
定义
只允许在一端进行插入 or 删除操作的线性表。
栈顶(top) : 允许进行插入删除操作的一端。
栈底(bottom) : 固定不变, 不允许进行任何操作。
栈可以为空。
卡特兰(Catalan) 函数
n 个不同元素进栈, 出栈元素不同的排列顺序的个数为:1n+1C2nn\frac{1}{n+1}C_{2n}^{n}n+11C2nn
当n个元素以某种顺序入栈,并且可以在任意时刻出栈,可以得到的元素排列数目满足卡特兰函数
顺序栈
——采用顺序存储的栈, 利用一组连续地址的存储单元存放
数据结构:
typedef struct Stack{
Elemtype data[maxSize];
int top;
}SqStack;
基本操作:
初始化:
SqStack S;
S.top=-1;
入栈(先判满) :
if(S.top != maxSize-1){
S.data[+ + S.top] = x;
}else{
printf(“栈满”);
}
出栈(先判空) :
if(S.top != -1){
x = S.data[S.top - -];
}else{
printf(“栈空”);
}
注意: 当 top 初始值为-1 时, 进栈时先移动指针, 出栈时后移动指针! 初始为 0 则反之~
共享栈:
两个顺序栈共享同一个一维数组, 两个栈底分别位于共享数组的两端。
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
优点: 更有效地利用储存空间。
链栈
——采用链式存储方式的栈。 栈顶在队头, 栈底在队尾, 采用头插法入栈。
数据结构:
typedef struct LNode{
int data; //数据域
struct LNode *next; //指针
};
基本操作:
没有头节点的链栈入栈
S->next = top;
top = S;
没有头节点的链栈出栈
x = top;
top = top->next;
free(x);
一直出栈,需要判空
LinkNode *p = top;
if (top == NULL){
printf("栈已空");
}
top = top->next;
free(p);
有头节点的入栈
S->next = top->next;
top->next = S;
队列
定义
只允许在一端插入, 另一端删除的线性表。
队头(front) : 允许删除的一端。
队尾(rear) : 允许插入的一端。
队列可以为空。
顺序队
——利用一块连续的存储单元进行存放。
数据结构:
typedef struct{
Elemtype data[MaxSize];
int front, rear; //队头指针, 队尾指针
}SqQueue;
基本操作:
初始化:
SqQueue Q;
Q.rear = 0;
Q.front = 0;
入队(先判满):
if(队不满){
Q.data[Q.rear++] = x;
}else{
print(“队满”)
}
出队(先判空):
if(队不空){
x = Q.data[Q.front++];
}else{
print(“队空”)
}
上溢
假溢出
循环队列:
由于普通队列存在“假溢出” , 故引入循环队列:
初始:
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;
区分队空/满
- 牺牲一个空间
队空:Q.rear = Q.front;
队满:(Q.rear + 1) % MaxSize == Q.front;
- 加一个成员变量size,记录当前队列的元素规模
入队:Q.size + 1;
出队:Q.size - 1;
- 增设结构成员tag
队空:Q.rear = Q.front, tag = 0;
队满:Q.rear = Q.front, tag = 1;
入队:Q.tag = 1;
出队:Q.tag = 0;
链队
——利用链式结构进行存储, 实际上就是同时带有队头和队尾指针的单链表。
数据结构:
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);
}
循环队列的提出是为了解决假溢出问题, 但是只能采取顺序存储的方式, 而链式队列不存在假溢出问题。 没有链式存储的循环队列。
使用链式队列的好处
- 内存动态分配
- 不存在假溢出的现象
- 适合多个队列的需求
双端队列
两端都可以进行入队和出队操作的队列。
输出受限双端队列: 两端都可入队, 但只能在一端进行出队。
输入受限双端队列: 两端都可出队, 但只能在一端进行入队。
应用
括号匹配
思路:
- 需要一个暂存括号的栈op
- 当遇到左括号时,入栈。
- 当遇到右括号时,将栈顶元素出栈,出栈元素应与当前右括号匹配,否则就是非法表达式;若当前栈顶为空,则也是非法表达式。
- 当遍历完成,若op栈不为空则表达式不合法,否则为合法表达式。
中缀表达式转换为后缀表达式
如何将a*(b+c)转换为逆波兰表达式?
思路:
- 需要两个栈,结果栈result 临时栈temp
- 遇到操作数时,直接将数字入栈result;
- 遇到操作符时
a)若是’(‘,则直接入temp栈
b)若是’)‘,说明temp中一定有’(‘与之对应,则将temp栈中的所有运算符依次出栈并入栈result,直到遇见’(',括号不会入result栈.
c)若是+-*/
运算符,则看当前的操作符与temp栈顶的运算符优先级,如果栈顶的运算符优先级更高,则将栈顶的运算符出栈并入栈result,否则当前操作符入栈temp。 - 当中缀表达式遍历完成后,将temp中的操作符依次出栈并入栈result, result栈中从栈底到栈顶的内容就是后缀表达式。
后缀表达式求值
【例题】计算后缀表达式23+1-2384/-的值
思路:
- 需要一个存储操作数的栈op
- 当遍历到操作数时,将操作数直接入op
- 当遍历到操作符时,将bp连续出栈两个操作数a,b,将运算 b op a的结果入栈op.
- 当遍历完成时,op栈底即为结果,
后缀表达式求值
【例题】利用栈直接计算表达式(2+3-1)2-3(8/4)的值思路:
将前面两个方法结合起来,中缀转后缀的过程中,每有一个op从temp栈中弹出时,都从result栈中弹出两个操作数a和b,计算b op a的结果再入栈result,最终遍历结束时result栈中就是计算结果。