栈和队列其实都是线性表的应用和推广
文章目录
首先还是先看一下分类情况
|- 顺序栈
栈-----|- 链栈
|- 共享栈
|- 循环队列
队列-----|- 链式队列
|- 双端队列
|- 一维数组
数组-----|
|- 多维数组:压缩矩阵、稀疏矩阵
一、栈
1. 栈的定义
- 栈:一端进行插入和删除。
- 栈顶:允许插入和删除的一端;
- 栈底:固定的,不能插入和删除。
- 先进后出
- 栈的基本操作:
函数名称 | 实现操作 |
---|---|
InitStack(&S) | 初始化一个空栈 |
StackEmpty(S) | 判断栈是否为空 |
Push(&S, x) | 进栈 |
Pop(&S, &x) | 出栈 |
GetTop(S, &x) | 读栈顶元素 |
ClearStack(&S) | 销毁栈 |
2. 栈的顺序存储结构
对于n个不同元素进栈,出栈序列的个数为:
(1)栈的存储类型描述
一组地址连续的存储单元,top指针指向当前栈顶位置。
#define MaxSize 50
typedef struct{
ElemType data[MaxSize];
int top;
}SqStack;
- 栈顶指针:初始:S.top=-1
- 栈顶元素:S.data[S.top]
- 进栈:指针先加1,再送值到栈顶元素
- 出栈:先取栈顶元素,指针再减1
- 栈空:S.top == -1
- 栈满:S.top == MaxSize - 1
- 栈长:S.top+1
(2)初始化
void InitStack(&S){
s.top = -1;
}
(3)判栈空
bool StackEmpty(S){
if(s.top == -1){
return true;
}
else{
return false;
}
}
(4)进栈
bool Push(SqStack &S, ElemType x){
if(s.top == MaxSize-1){
return false;
}
S.data[++S.top] = x; //指针先加1,再入栈
return true;
}
(5)出栈
bool Pop(SqStack &S, ElemType &x){
if(S.top == -1){
return false;
}
x = S.data[S.top--]; //先出栈,指针再减1
return true;
}
(6)读栈顶元素
bool GetTop(SqStack S, ElemType &x){
if(S.top == -1){
return false;
}
x = S.data[S.top];
return true;
}
3. 共享栈
- 两个栈共享一个一维数据空间,为了更有效的利用空间。存取数据时间复杂度O(1)。
- 栈底分别设在共享空间栈的两端,栈顶向中间延伸。
- 栈顶指针:都指向栈顶元素。
- 栈空:0号栈为空:top0 = -1;1号栈为空:top1 = MaxSize。
- 栈满:top1 - top0 = 1.
- 进栈:0号栈:top0先加1再赋值;1号栈:top1先减1再赋值。
- 出栈:0号栈:top0先赋值再减1;1号栈:top1先赋值再加1。
4. 栈的链式存储结构
- 链栈优点:便于多个栈共享存储空间和提高效率,且不存在栈满上溢。
- 所有操作在表头进行。通常链栈没有头结点,Lhead指向栈顶元素。
- 带头结点和不带头结点的链栈,实现方面不同。
typedef struct LinkNode{
ElemType data;
struct LinkNode *next;
} *LiStack;
- 插入结点x
x->next = top;
top = x;
- Pop操作,并将出栈元素保存在x
x = top->data;
top = top->next;
二、队列
1. 队列的定义
- 队列:只允许在一端进行插入,在另一端进行删除。
- 队头:允许删除的一端;
- 队尾:允许插入的一端。
- 先进先出
- 常见的基本操作:
函数名称 | 实现操作 |
---|---|
InitQueue(&Q) | 初始化一个空队 |
QueueEmpty(Q) | 判断队列是否为空 |
EnQueue(&Q, x) | 入队 |
DeQueue(&Q, &x) | 出队 |
GetHead(Q, &x) | 读队头元素 |
2.队列的顺序存储结构
(1)队列的顺序存储类型
- 一块连续的存储单元,两个指针 head(队头) 和 rear(队尾) 。
- 队头指针:指向队头元素;
- 队尾指针:指向队尾元素的下一个位置。
(也可以让队尾指针指向队尾元素,队头指针指向队头元素的前一个元素。)
#define MaxSize 50
typedef struct{
ElemType data[MaxSize];
int front,rear;
}SqQueue;
- 初始状态(队空条件):Q.front = Q.rear = 0
- 进队:先送值到队尾元素,再将队尾指针加1。
- 出队:先取队头元素值,再将队头指针加1。
3. 循环队列
- 当队首指针 Q.front = MaxSize-1 后,再前进一个位置就自动到0。
- 初始:Q.front = Q.rear = 0
- 队首指针加1:Q.front = (Q.front +1)%MaxSize
- 队尾指针加1:Q.rear = (Q.rear+1)%MaxSize
- 队列长度:(Q.rear+MaxSize-Q.front)%MaxSize
- 注意:为了区分队空还是队满,有三种处理方式:
- 牺牲一个单元来区分队空和队满,入队时少用一个队列单元。(最普遍)
- 队满:(Q.rear+1) % MaxSize == Q.front
- 队空:Q.front == Q.rear
- 队列中元素个数:(Q.rear - Q.front + MaxSize) % MaxSize
- 增加一个表示元素个数的变量。
- 这两种情况都有Q.front == Q.rear
- 队满:Q.size == MaxSize
- 队空:Q.size == 0
- 增加 tag 数据成员
- 队满:tag=1, 因插入导致 Q.front == Q.rear
- 队空:tag=0, 因删除导致 Q.front == Q.rear
- 牺牲一个单元来区分队空和队满,入队时少用一个队列单元。(最普遍)
(1)初始化
void InitQueue(&Q){
Q.rear = Q.front = 0;
}
(2)判队空
bool isEmpty(Q){
if(Q.rear == Q.front){
return true;
}
else
return false;
}
(3)入队
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;
}
(4)出队
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;
}
4. 队列的链式存储结构
(1)队列的链式存储类型
- 链队列:同时带有队头指针和队尾指针的单链表。队头指针指向队头结点,队尾指针指向队尾结点。
typedef struct{ //链式队列结点
ElemType data;
struct LinkNode *next;
}LinkNode;
typedef struct{ //链式队列
LinkNode *front,*rear;
}LinkQueue;
- 当Q.front == NULL 且 Q.rear == NULL 时,链式队列空。
不设头结点的链式队列操作比较麻烦。
(2)初始化
void InitQueue(LinkQueue &Q){
Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode)); //建立头结点
Q.front->next = NULL; //初始为空
}
(3)判队空
bool IsEmpty(LinkQueue Q){
if(Q.front == Q.rear)
return true;
else
return false;
}
(4)入队
void EnQueue(LinkQueue &Q, ElemType x){
s = (LinkNode*)malloc(sizeof(LinkNode));
s->data = x; //创建新结点
s->next = NULL; //插入到链尾
Q.rear->next = s;
Q.rear = s;
}
(5)出队
bool DeQueue(LinkQueue &Q, ElemType &x){
if(Q.front == Q.rear) //空队
return false;
p = Q.front->next;
x = p->data;
Q.front->next = p->next;
if(Q.rear==p) //若原队列只有一个结点,删除后变空
Q.rear = Q.front;
free(p);
return true;
}
5. 双端队列
- 两端都可以进行入队和出队的操作,逻辑结构仍是线性结构。
- 进队:前端进的元素排在后端进的元素前面。
- 出队:无论前后端,先出的排在后出的元素前面。
(1)输出受限的双端队列
- 一端进行插入、删除;另一端只能插入
(2)输入受限的双端队列
- 一端进行插入、删除;另一端只能删除
三、栈和队列的应用
1. 栈在括号匹配中的应用
算法思想:
(1)初始设置空栈,顺序读入括号;
(2)若是右括号,①让其置于栈顶的最急迫期待得以消解;②不合法的情况则退出程序(括号序列不匹配);
(3)若是左括号:作为一个新的更急迫的期待压入栈中;
算法结束时,栈为空,否则括号序列不匹配
2. 栈在表达式求值中的应用
(1)通过后缀表达式计算表达式的值的过程:
(1)顺序扫描表达式的每一项;
(2)①如果该项是操作数,将其压入栈中;
②如果该项是操作符,则连续从栈中退出两个操作数Y和X,形成 “X<操作符>Y”,并将计算结果重新压入栈中;
(3)当表达式的所有项都扫描并处理完后,栈顶存放的是最终的计算结果。
(2)中缀转后缀的过程:
3. 栈在递归中的应用
通过栈可以实现递归向非递归的转换。(也可以不用栈,用迭代方式消除)
4. 队列在层次遍历中的应用
过程描述:
(1)根结点入队;
(2)若队空(所有结点处理完毕),则遍历结束;否则重复③
(3)队列中第一个结点出队,并访问。若其有左孩子,则将左孩子入队;若其有右孩子,则将右孩子入队,返回②。
5. 队列在计算机系统中的应用
(1)解决主机与外部设备之间速度不匹配的问题。(缓冲区)
(2)解决由多用户引起的资源竞争问题。
(3)页面替换算法。
6.栈其他应用
进制转换、迷宫求解
四、特殊矩阵的压缩矩阵
1. 数组的定义
数组是线性表的推广。
2. 数组的存储结构
(1)一维数组 A[0,1,…,n-1] ,其存储结构关系式为:
LOC(ai) = LOC(a0) + (i) * L
其中L是每个元素所占的存储单元。
(2)二维数组 A行下标和列下标分别为 [l1, h1]、[l2, h2]
①行优先:
LOC(a i,j) = LOC(a 0,0) + [(i - l1) * (h2 - l2 + 1) + (j - l2)] * L
②列优先:
LOC(a i,j) = LOC(a l1,l2) + [(j - l2) * (h1 - l1 + 1) + (i - l1)] * L
3. 矩阵的压缩存储
压缩矩阵:为多个相同的元素值分配一个存储空间,对零元素不分配存储空间,目的是为了节省存储空间。
特殊矩阵:具有许多相同矩阵元素或零元素,,并分布有规律。
(1)对称矩阵
关于主对角线对称,可以将矩阵 A[1…n][1…n] 存放在一维数组 B[n*(n+1)/2] 中。
aij 在 B中的下标k:
k = i * (i-1) / 2 + j -1; ( i>=j )
k = j * (j-1) / 2 + i -1; ( i<j )
(2)三角矩阵
①下三角矩阵
可以将矩阵 A[1…n][1…n] 存放在一维数组 B[n*(n+1)/2 + 1] 中。
k = i * (i-1) / 2 + j -1; ( i>=j )
k = n * (n-1) / 2 ; ( i<j )
②上三角矩阵
可以将矩阵 A[1…n][1…n] 存放在一维数组 B[n*(n+1)/2 + 1] 中。
k = (i - 1)* (2 * n - + 2) / 2 + (j - i); ( i<=j )
k = n * (n-1) / 2 ; ( i>j )
(3)三对角矩阵
也成为带状矩阵,对于任一 aij,当 | i - j | > 1 时,aij = 0.
用压缩存储时,可以按行优先存放。
aij 在数组B中存放的下标 k:
k = 2 * i + j - 3
反之,B中第k个元素,可得:
i = [ (k+1)/3 +1 ] ;(向下取整)
j = k - 2*i + 3:
4. 稀疏矩阵
- 矩阵元素个数 s 相对于矩阵中非零元素个数 t 非常多,即 s >> t。
- 存储方式:
- ①将非零元素及其相应的行和列构成一个三元组(行标,列标,值)。
- ②也可以用十字链表来存储。
- 稀疏矩阵压缩存储之后便失去了随机存取的特性。