栈和队列
限定了插入和删除位置的线性表
栈
栈(stack),后进先出,LIFO
一般限定在表尾插入与删除
表尾称为栈顶
(Top)
表头称为栈底
(Base)
入栈
:插入元素到栈顶
出栈
:删除栈顶元素
问:三个元素a,b,c 入栈,顺序是 a,b,c,问出栈的可能顺序?
可能为
- a,b,c
- a,c,b
- b,a,c
- b,c,a
- c,b,a
不可能得到 c,a,b 的顺序
栈的应用
-
进制转换(10→8)
除 8 取余
-
括号匹配的检验
-
表达式求值
算符优先算法
-
舞伴问题
抽象数据类型
// 类型定义
ADT Stack{
数据对象:
D = { ai | ai∈ElemSet, i=1,2,...,n, n≧0 }
数据关系:
R1 = {<ai-1, ai> | ai-1, ai∈D, i=2,...,n}
约定an端为栈顶,a1端为栈底
基本操作:初始化、进栈、出栈、取栈顶元素等
}ADT Stack
基本操作
InitStack(&S)
// 栈的初始化
// 操作结果:构造一个空栈S
DestoryStack(&S)
// 栈的销毁
// 初始条件:栈S已存在
// 操作结果:栈S被销毁
StackEmpty(S)
// 判断栈S是否为空栈
// 初始条件:栈S已存在
// 操作结果:若栈S为空栈,返回TRUE,否则返回FALSE
StackLength(S)
// 求栈的长度
// 初始条件:栈S已存在
// 操作结果:返回S的元素个数,即栈的长度
GetTop(S,&e)
// 取栈顶元素
// 初始条件:栈S已存在且非空
// 操作结果:用e返回S的栈顶元素
ClearStack(&S)
// 将栈置空
// 初始条件:栈S已存在
// 操作结果:将S清空
Push(&S,e)
// 入栈
// 初始条件:栈S已存在
// 操作结果:插入元素e作为新的栈顶元素
Pop(&S,&e)
// 出栈
// 初始条件:栈S已存在
// 操作结果:删除栈顶元素an,并用e返回
顺序栈
**存储方式:**与一般线性表的顺序存储结构相同
设 top
指针,指示栈顶元素
设 base
指针,指示栈底元素
注:
为了操作方便,通常使 top 指针指示真正的栈顶元素之上的下标地址
另外,用 stacksize 表示栈可使用的最大容量
栈空标志: base == top
栈满标志: top - base == stacksize
使用数组作为顺序栈存储方式的特点:
简单、方便
但是容易溢出(数组大小固定)
· 上溢(overflow):栈满时压入元素
· 下溢(underflow):栈空时弹出元素
数据类型定义
// 顺序栈的表示
#define MAXSIZE 100
typedef struct{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
初始化
Status InitStack(SqStack &S){
S.base = (SElemType*)malloc(sizeof(SElemType)*MAXSIZE);
// C++: S.base = new SElemType[MAXSIZE];
if(!S.base)
exit (OVERFLOW); // 存储空间分配失败
S.top = S.base;
S.stacksize = MAXSIZE;
return OK;
}
顺序栈判空
Status StackEmpty(SqStack S){
// 栈空时返回 TRUE, 否则返回 FALSE
if(S.top == S.base)
return TRUE;
else
return FALSE;
}
求栈长
int StackLength(SqStack S){
return S.top-S.base;
}
清空顺序栈
Status ClearStack(SqStack S){
if(S.base)
S.top = S.base;
return OK;
}
销毁顺序栈
Status DestroyStack(SqStack &S){
if(S.base){
free(S.base); // 删除栈底指针
S.stacksize = 0; // 栈长度设置为0
S.base = S.top = NULL; // top 与 base 指针置空
// 疑问:base 指针不是已经在上面被删除了吗?
// 解答: free 是使数组回归内存,下面的语句是将结构类型的值设置为空
}
return OK;
}
入栈
- 判断栈是否满,若满则出错(上溢)
- 元素 e 压入栈顶
- 栈顶指针加 1
Status Push(SqStack &S, SElemType e){
if(S.top-S.base == S.stacksiZe) // 栈满
return ERROR;
*S.top++ = e;
return OK;
}
出栈
- 判断是否栈空,若空则出错(下溢)
- 栈顶指针减1
- 获取栈顶元素e
Status Pop(SqStack &S, SElemType &e){
if(S.top == S.base)
// 或 if(StackEmpty(S))
return ERROR;
e = *--S.top;
return OK;
}
链栈
运算受限的单链表,只能在链表头部进行操作
类型定义
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode, *LinkStack;
LinkStack S;
-
链栈中指针的方向与链表中指针的方向相反:an→an-1
-
链表的头指针就是栈顶
-
不需要头结点
初始化
void InitStack(LinkStack &S){
// 构造一个空栈,栈顶指针置空
S = NULL;
reutrn OK;
}
链栈判空
Status StackEmpty(LinkStack S){
if(S == NULL)
return TRUE;
else
return FALSE;
}
入栈
Status Push(LinkStack &S, SElemType e){
p = (LinkStack)malloc(sizeof(StackNode));
P->data = e;
p->next = S;
S = p;
return OK;
}
出栈
Status Pop(LinkStack &S, SElemType &e){
if(S == NULL)
return ERROR;
e = S->data;
p = S;
S = S->next;
free(p);
return OK;
}
取栈顶元素
Status GetTop(LinkStack S){
if(S!= NULL)
return S->data;
}
栈与递归
- 求 n 的阶乘
- Fibonaci 数列
- 二叉树
- 广义表
- 迷宫问题
- Hanoi 塔
**递归优点:**结构清晰,程序易读
**缺点:**每次调用都要生成工作记录,保存状态信息,入栈;返回时要出栈,回复状态信息,事件开销大
递归→非递归
-
尾递归、单向递归→循环结构
long Fact(long n){ if(n == 0) return 1; else return n*Fact(n-1); } // 改为 long Fact(long n){ t = 1; for(i=1; i<=n; i++) t = t*i; return t; }
-
自用栈模拟系统的运行时栈
队列
队列(queue),先进先出,FIFO
在表一端(表尾)插入,在另一端(表头)删除
队尾
:表尾 an 端
队头
:表头 a1 端
插入元素称为 入队
,删除元素称为 出队
队列的应用
- 脱机打印
- 多用户系统循环使用CPU
- 实时控制系统信号的处理
抽象数据类型定义
ADT Queue{
数据对象: D={ai | ai∈ElemSet, i=1,2,...,n, n≧0}
数据关系: R={<ai-1, ai> | ai-1, ai∈D, i=2,...,n}
// 约定 ai 端为队头,an端为队尾
基本操作:
InitQueue(&Q)
// 操作结果:构造空队列 Q
DestroyQueue(&Q)
// 条件:队列 Q 已存在;
// 操作结果:将 Q 清空
QueueLength(Q)
// 条件:队列 Q 已存在;
// 操作结果:返回 Q 的元素个数,即对长
GetHead(Q, &e)
// 条件:队列 Q 为非空队列;
// 操作结果:用 e 返回 Q 的队头元素
EnQueue(&Q, e)
// 条件:队列 Q 已存在;
// 操作结果:插入元素 e 为 Q 的队尾元素
DeQueue(&Q, &e)
// 条件:队列 Q 为非空队列;
// 操作结果:删除 Q 的队头元素,用 e 返回
// 还有将队列置空,遍历队列等……
}
顺序队列
数据类型定义
// 队列的顺序存储——用一维数组 base[MAXQSIZE] 表示
#define MAXQSIZE 100 // 最大队列长度
Typedef struct{
QElemType *base; // 初始化的动态分配存储空间
int front; // 头指针
int rear; // 尾指针
// 虽然它们叫指针,其实不是指针
}SqQueue;
队列初始情况,即 队空 时:front = rear = 0
入队: base[rear]=x; rear++;
出队:x=base[front]; front++;
,若出队到队空时,有 front == rear;
**注:**设数组大小为 MAXQSIZE,则 rear == MAXQSIZE;
时,发生溢出,此时存在两种情况
front == 0;
,真溢出front != 0;
,假溢出
-
解决 假溢出 的办法:循环队列
当
rear == MAXQSIZE
时,若向量的开始端空着,则rear
回到初始端,从头使用空着的空间;当front == MAXQSIZE
时也用同样的办法
采取“模运算”使尾指针回到 0
插入元素举例: Q.base[Q.rear]=x; Q.rear=(Q.rear+1)%MAXSIZE;
删除元素举例: x=Q.base[Q.front]; Q.front=(Q.front+1)%MAXQSIZE;
但是此时存在一个问题:
队空时:front == rear;
队满时:front == rear;
该如何区分队空和队满呢?
1. 设置一个标志来区分队空与队满;
2. 设置一个变量记录元素个数;
3. 少用一个元素空间(√)
少用一个元素空间
解决循环队列队满时的判断问题
队空时: front == rear;
队满时: (rear+1)%MAXQSIZE == front;
循环队初始化
Status InitQueue(SqQueue &Q){
Q.base = (QElemType*)malloc(MAXQSIZE*sizeof(QElemType));
// C++ 语法: Q.base = new QElemType[MAXQSIZE];
if(!Q.base)
// 存储分配失败
exit(OVERFLOW);
Q.front = Q.rear = 0; // 头指针尾指针置空,队列为空
return OK;
}
循环队列求队长
int QueueLength(SqQueue Q){
return ((Q.rear - Q.front + MAXQSIZE)%MAXQSIZE);
}
循环队列入队
Status EnQueue(SqQueue &Q, QElemType e){
if((Q.rear+1)%MAXQSIZE==Q.front)
return ERROR; // 队满
Q.base[Q.rear] = e;
Q.rear = (Q.rear + 1)%MAXQSIZE; // 队尾指针 +1
return OK;
}
循环队列出队
Status DeQueue(SqQueue &Q, QElemType &e){
if(Q.front == Q.rear)
return ERROR; // 队空
e = Q.base[front];
Q.front = (Q.front + 1)%MAXQSIZE;
return OK;
}
取队头元素
SElemType GetHead(SqQueu Q){
if(Q.front != Q.rear)
return Q.base[front];
}
链队
用户无法估计所用队列的长度时采用链队
类型定义
#define MAXSIZE 100 // 最大队列长度
typedef struct QNode{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct{
QueuePtr front; // 队头指针
QueuePtr rear; // 队尾指针
}LinkQueue;
链队初始化
Status InitQueue(LinkQueue &Q){
Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode));
if(!Q.front)
exit(OVERFLOW);
Q.front->next = NULL;
return OK;
}
销毁链队
Status DestroyQueue(LinkQueue &Q){
while(Q.front){
p = Q.front->next;
free(Q.front);
Q.front = p;
}
return OK;
}
元素e入队
Status EnQueue(LinkQueue &Q, QElemType e){
p = (QueuePtr)malloc(sizeof(QNode));
if(!p)
exit(OVERFLOW);
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}
出队
Status DeQueue(LinkQueue &Q, QElemType &e){
if(Q.front == Q.rear)
return ERROR;
p = Q.front->next;
e = P->data;
Q.front->next = p->next;
if(Q.rear == p)
Q.rear = Q.front;
free(p);
return OK;
}
求队头元素
Status GetHead(LinkQueue Q, QElemType &e){
if(Q.front == Q.rear)
return ERROR;
e = Q.front->next->data;
return OK;
}