“要像星星一样发光。”
目录
3.1 栈和队列
定义:栈和队列是插入和删除只能在表的“端点”进行的线性表。
- 栈——后进先出
- 队列——先进先出
3.2 案例引入
1 进制转换
【例】把十进制数159转换成八进制数。
转换法则:除以d倒取余
n=(n div d)*d+n mod d
(div为整除运算,mod为求余运算)
2 括号匹配的检验
【例】检验 ( ( )] )是否匹配
3 表达式求值
3.3 栈的表示和操作的实现
3.3.1 栈的类型定义
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
1 基本操作:
- InitStack(&S) 初始化操作
- DestroyStack(&S) 销毁栈操作
- StackEmpty(S) 判断S是否为空栈
- StackLength(S) 求栈的长度
- GetTop(S,&e) 取栈顶元素
- ClearStack(&S) 栈置空操作
- Push(&S,e) 入栈操作
- Pop(&S,&e) 出栈操作
3.3.2 顺序栈的表示与实现
- top指针,指示栈顶元素在顺序栈中的位置。
- base指针,指示栈底元素在顺序栈中的位置。
- stacksize,表示栈可使用的最大容量。
(top指针指示栈顶元素之上的下标位置)
- 空栈:base == top
- 栈满:top-base == stacksize
- stacksize = ( )
(栈满的处理方法:
- 报错,返回操作系统。
- 分配更大的空间。)
顺序存储方式特点:简单、方便、容易溢出。
- 上溢:栈满,又要压入元素。
- 下溢:栈空,还要弹出元素。
(上溢是一种错误,无法运行;而下溢是一种结束条件)
【代码部分】
1 定义
#define MAXSIZE 100
typedef struct{
SElemType *base; //栈底指针
SElemType *top; //栈顶指针
int stacksize; //栈可用最大容量
}SqStack;
2 初始化
Status InitStack(SqStack &S){ //构造一个空栈
S.base = new SElemType[MAXSIZE];
if (!S.base)exit (OVERFLOW); //存储分配失败
S.top = S.base; //栈顶指针等于栈底指针
S.stacksize = MAXSIZE;
return OK;
}
3 入栈
【算法步骤】
- 判断是否栈满,若满则出错(上溢)
- 将元素e压入栈顶
- 栈顶指针加1
Status Push (SqStack &S,SElemType e){
if(S.top - S.base == S.stacksize) //栈满
return ERROR;
*S.top++=e;
return OK;
}
S.top++=e; *S.top=e; S.top++;
4 出栈
【算法步骤】
- 判断是否栈空,若空则出错(下溢)
- 获取栈顶元素e
- 栈顶指针减一
Status Pop(SqStack &S,SElemType &e){
// 若栈不空,则删除S的栈顶元素,用e返回其职,并返回OK;否则返回ERROR
if (S.top == S.base) //等价于if(StackEmpty(S))
return ERROR;
e= *--S.top;
return OK;
}
e= *--S.top; = - - S.top;e = *--S.top;
【算法补充】
1 顺序栈判断是否为空
Status StackEmpty(SqStack S){ //栈空返回TRUE
if (S.top == S.base)
return TRUE;
else
return FALSE;
}
2 求顺序栈长度
int StackLength( SqStack S )
{
return S.top - S.base;
}
3 清空顺序栈
Status ClearStack( SqStack S ){
if( S.base )S.top = S.base;
return OK;
}
4 销毁顺序栈
Status DestroyStack( SqStack &S ){
if(S.base){
delete S.base;
S.stacksize = 0;
S.base = S.top = NULL;
}
return OK;
}
3.3.2 链栈的表示与实现
- 链栈是运算受限的单链表。
- 链表的头指针就是栈顶
- 不需要头结点
- 基本不存在栈满的情况
- 空栈相当于头指针指向空
- 插入和删除仅在栈顶处执行
1 定义
typedf struct StackNode
{
ElemType data;
struct StackNode *next;
}StackNode,*LinkStack;
2 初始化
void InitStack(LinkStack &S){ //构造一个空栈,栈顶指针为空
S=NULL;
return OK;
}
3 入栈
【算法步骤】
- 为入栈元素e分配空间,用指针P指向。
- 将新结点数据域置为e。
- 将新结点插入栈顶。
- 修改栈顶指针为p。
Status Push(LinkStack &S,SElemType e){
p=new StackNode; //生成新结点P
p->data = e; //将新结点数据域置为e
p->next = S; //将新结点插入栈顶
S=p; //修改栈顶指针
return OK;
}
4 出栈
【算法步骤】
- 判断栈是否为空,若空则返回ERROR。
- 将栈顶元素赋值给e。
- 临时保存栈顶元素的空间,以备释放。
- 修改栈顶指针,指向新的栈顶元素。
- 释放原栈顶元素的空间。
Status Pop (LinkStack &S,SElemType &e){
if (S ==NULL) return ERROR; //栈空
e = S-> data; //将栈顶元素赋值给e
p = S; //用p临时保存栈顶元素空间,以备释放
S = S-> next; //修改栈顶指针
delete p; //释放预原栈顶元素的空间
return OK;
}
5 取栈顶元素
【算法补充】
1 判断链栈是否为空
Status StackEmpty(LinkStack S){
if (S == NULL)return TRUE;
eles return FALSE;
}
3.4 栈与递归
递归:若一个函数、过程直接或间接地调用自己,则称这个过程是递归的过程。
- 例:阶乘函数、2阶Fibonaci数列、二叉树、广义表、迷宫问题、Hanoi塔问题
递归问题——分治法(将一个问题分解称几个相对简单的子问题来求解)
void p (参数表){ if (递归结束条件)可直接求解 ; //基本项 else p(较小的参数); //归纳项 } |
例如:
long Fact (long n){
if (n == 0) return 1; //基本项
else return n*Fact (n-1); //归纳项
}
递归的优缺点
- 优点:结构清晰,程序易读。
- 缺点:每次调用都要生成工作记录,保存状态信息,入栈;返回时要出栈,恢复状态信息。时间开销大。
3.5 队列的表示和操作的实现
3.5.1 队列(Queue)
1 定义 只能在表尾(Rear)进行插入运算,在表头(Front)进行删除运算的线性表(头删尾插)。
2 逻辑结构 一对一关系
3 存储结构 顺序队,链队
4 运算规则 先进先出(FIFO)
【代码部分】
定义:
ADT Queue{
数据对象
数据关系
基本操作:
InitQueue
DestroyQueue
ClearQueue
QueueLength
GetHead
EnQueue
DeQueue
}ADT Queue
1 顺序队列
#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 |
- rear = MAXQSIZE,溢出
- front =0,rear = MAXQSIZE 真溢出
- front ≠0,rear = MAXQSIZE 假溢出
解决假上溢——循环队列
- 实现方法:利用模(mod, %)取余运算。
- 插入元素:Q.base[Q.rear]=x ; Q.base[Q.rear+1)%MAXQSIZE;
- 删除元素:x = Q.base[s.front] ; Q.front = (Q.front+1)% MAXQSIZE
解决队列队满时判断方法——少用一个元素空间
- 队空:front == rear
- 队满:(rear+1)%MAXQSIZE == front
1)循环队列——定义
2)循环队列——初始化
2)循环队列——求队列长度
3)循环队列——入队
4)循环队列——出队
5)循环队列——取队头元素
2 链队
若用户无法估计所用队列的长度,则宜采用链队列。
1)类型定义
#define MAXQSIZE 100 //最大队列长度
typedef struct Qnode {
QElemType data;
stuct Qnode *next;
}QNode, *QueuePtr;
typedaf struct{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
2)初始化
Status InitQueue(LinkQueue &Q)
{//构造一个空队列Q
Q.front = Q.rear = new QNode; //生成新结点作为头结点,队头和队尾指针指向头结点
Q.front -> next = NULL ; //头结点的指针域置空
return OK;
}
3)入队
Status EnQueue(LinkQueue &Q , QElemType e )
{//插入元素e为Q的新队尾元素
p=new QNode; //为入队元素分配结点空间,用指针p指向
p->data=e; //将新结点数据域置为e
p->next=NULL ; Q.rear->next=p; //将新结点插入到队尾
Q.rear=p; //修改队尾指针
return OK;
}
4)出队
p=Q.front->next;
e=p->data;
Q.front->next = p->next;
Status DeQueue(LinkQueue &Q ,QElemType &e)
{//
if(Q.front == Q.rear)return ERROR; //若队空,返回ERROR
p=Q.front->next; // p指向队头元素
e=p->data; //e保存对头元素的值
Q.front->next=p->next; //修改头结点的指针域
if(Q.rear ==p) Q.rear = Q.front ; //最后一个元素被删,队尾指针指向头结点
delete p; // 释放原队头元素的空间
return OK;
}
5)取队头元素
e=Q.front -> next ->data;
SElemType GetHead(LinkQueue Q)
{//返回Q的对头元素,不修改头指针
if(Q.front == Q.rear) return ERROR; //队列非空
e=Q.front -> next ->data;
return OK;
}
6)销毁队列(补充)
Status DestroyQueue (LinkQueue &Q){
while(Q.front):
p=Q.front->next ; free(Q.front); Q.front = p;
}
return OK;
}