目录
队列的顺序表示----用一维数组base[MAXQSIZE]
栈的定义和特点
- 栈和队列是线性表的子集(插入和删除位置受限的线性表)
- 栈是限制在表的一端进行插入和删除操作的线性表(运算规则)
后进先出(LIFO)
Last In First Out
- 栈顶(Top):允许插入、删除操作的一端,又称为表尾。用栈顶指针(top)来指示栈顶元素
- 栈底(Base):固定端,又被称为表头
- 插入元素到栈顶(即表尾)的操作,称为入栈
- 从栈顶(表尾)删除最后一个元素的操作,称为出栈
- 用顺序栈或链栈存储均可,但以顺序栈更常见
栈的表示
栈的抽象数据类型的类型定义
ADT Stack{
数据对象:D={Ai}
数据关系:R={<Ai-1,Ai>}
基本操作;初始化、进栈、出栈、取栈顶元素
}ADT Stack
栈的存储结构
- 由于栈本身就是线性表,于是栈也有顺序存储 (顺序栈) 和链式存储 (链栈)
顺序栈的存储方式:
- 同一般线性表的顺序存储结构完全相同
- 利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端
附设top指针,指示栈顶元素在顺序栈中的位置(通常top指示真正的栈顶元素之上的下标地址)
另设base指针,指示栈底元素在顺序栈中的位置 (一般指向的下标为0)
用stacksize表示栈可用的最大容量 (下标为 0 -- stacksize-1)
栈空的标志
base == top
栈满的标志
top - base == stacksize
栈满的处理方法:①报错,返回操作系统; ②分配更大的空间,作为栈的存储空间,将原栈的内容移入新栈
使用数组作为顺序栈存储方式的特点
简单、方便、但易产生溢出(数组大小固定)
- 上溢:栈已经满,又要压入元素
- 下溢:栈已经空,还要弹出元素(一种结束条件)
顺序栈的表示和实现
#define MAXSIZE 100
typedef struct{
SElemType *base; //栈底指针
SELemType *top; //栈顶指针
int stacksize; //栈可用最大容量
}SqStack;
顺序栈的初始化
Status InitStack(SqStack &S){ //构造一个空栈
S.base = new SElemType[MAXSIZE]; //分配SElemType类型的空间
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 DestoryStack(SqStack &S){
if(S.base){
delete S.base;
S.stacksize = 0;
S.base = S.top = NULL;
}
return OK;
}
顺序栈的入栈
Status Push(SqStack &S,SElemType e){
if(S.top - S.base == S.stacksize) //栈满
return ERROR;
*S.top++ = e; //先将e赋值给S的栈顶指针,然后栈顶指针++
return OK;
}
链栈的表示与实现
- 链栈是运算受限的单链表,只能在链表头部进行操作
链栈的定义
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStack;
LinkStack S;
- 栈顶存储的是an元素,next指针指向的是其前驱;a1存放在栈底位置
- 链表的头指针就是栈顶
- 不需要头结点
- 基本不存在栈满的情况(内存只要有空间)
- 空栈相当于头指针指向空
- 插入和删除仅在栈顶处执行
链栈的初始化
Status InitStack(LinkStack &S){
//构造一个空栈,栈顶指针置为空
S = NULL;
return OK;
}
判断链栈是否为空
Status StackEmpty(LinkStack S){
if(S == NULL) return TRUE;
else return FALSE;
}
链栈的入栈
Status Push(LinkStack &S,SElemType e){
p = new StackNode; //生成新结点p
p -> data = e; //将新结点数据域置为e
p -> next = S; //将新结点插入栈顶
S = p; //修改栈顶指针
return OK;
}
链栈的出栈
Status Pop(LinkStack &S,SElemType &e){
if(S == NULL) return ERROR;
e = S -> data;
p = S; //用指针p记录被删除的结点
S = S -> nxet;
delete p;
return OK;
}
取栈顶元素
SElemType GetTop(LinkStack S){
if(S != NULL)
return S -> data;
}
栈与递归
递归的定义
- 若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的
- 若一个过程直接地或间接地调用自己,则称这个过程是递归的过程
递归求n的阶乘
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;
}
分治法求解递归问题算法的一般形式
void p(参数表){
if (递归结束条件) 可直接求解步骤 //基本项
else p(较小的参数); //归纳项
}
函数调用过程
调用前,系统完成:
①将实参、返回地址等传递给被调用函数
②为被调用函数的局部变量分配存储区
③将控制转移到被调用函数的入口
调用后,系统完成:
①保存被调用函数的计算结果
②释放被调用函数的数据区
③依照被调用函数保存的返回地址将控制转移到调用函数
递归的优缺点
优点:结构清晰,程序易读
缺点:每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,恢复状态信息。时间开销大
①递归程序在执行时需要系统提供栈来实现
②仿照递归算法执行过程中递归工作站的状态变化可写出相应的非递归程序
③改写后的非递归算法与原来的递归算法相比,结构不够清晰,可读性较差,有的还需要经过一系列优化
队列的定义和特点
- 队列:运算受限的线性表。是一种先进先出的线性表。只允许在表的一端进行插入,而在另一端进行删除
- 队首:允许进行删除的一端称为队首
- 队尾:允许进行插入的一端称为队尾
- 用顺序队或者链队来存储,以循环顺序队列更常见
队列的表示和操作
队列的抽象数据类型定义
ADT Queue{
数据对象:D ={ ai|ai∈ElemSet, i=1, 2, …, n, n >= 0 }
数据关系:R = {<ai-1, ai> | ai-1, ai∈D, i=2,3,…,n }
基本操作
//约定a1端为队首,an端为队尾。
} ADT Queue
队列的顺序表示----用一维数组base[MAXQSIZE]
#define MAXQSIZE 100 //最大队列长度
typedef struct{
QElemType *base; //初始化的动态分配存储空间;指针指向的是动态的首元素
int front; //头指针
int rear; //尾指针
}SqQueue;
-
真溢出
front = 0;
rear = MAXQSIZE;
-
假溢出(此时还有存储空间)
front ≠ 0;
rear = MAXQSIZE;
解决假上溢的方法:
①将队中元素依次向队头方向移动;缺点:浪费时间。每移动一次,队中元素都要移动
②将队空间设想为一个循环的表,即分配给队列的m个存储单元可以循环使用,当rear为maxqsize时,若向量的开始端空着,又可从头使用空着的空间
引入循环队列
base[0]接在base[MAXQSIZE-1]之后,若rear+1 == M,则令rear = 0;(MAXQSIZE为6,则下标为0-5,故也就是5+1 = 0)
实现方法:利用模运算
插入元素
Q.rear[Q.rear] = x;
Q.rear = (Q.rear+1) % MAXQSIZE; //5+1 % 6 = 0
删除元素
x = Q.base[s.front];
Q.front = (Q.front+1) % MAXQSIZE;
循环队列:循环使用为队列分配的存储空间
判断队列是否为满 (少用一个元素空间)
循环队列的操作
队列的初始化
Status InitQueue(SqQueue &Q){
Q.base = new QElemType[MAXQSIZE] //分配数组空间
if(!Q.base) exit(OVERFLOW); //存储分配失败
Q.front = Q.rear = 0; //头指针尾指针置为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.rear == Q.front) return ERROR; //队空
e = Q.base[Q.rear]; //保存队头元素
Q.front = (Q.front+1) % MAXQSIZE; //队头指针+1
return OK;
}
循环队列取队头元素
SElemType GetHead(SqQueue Q){
if(Q.front != Q.rear) //队列不为空
return Q.base[Q.front]; //返回队头指针元素的值,队头指针不变
}
链队--队列的链式表示和实现(存在头结点)
链队列的类型定义
#define MAXQSIZE 100 //最大队列长度
typedef struct Qnode{
QElemType data;
strcut Qnode *next;
}Qnode,*QueuePtr;
typedef struct{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
链队列的初始化
Status InitQueue(LinkQueue &Q){
//Q.front = Q.rear = (QueuePtr) malloc (sizeof(Qnode));
Q.front = Q.rear = new QNode;
if(!Q.front) return (OVERFLOW);
Q.front -> next = NULL;
return OK;
}
链队列的销毁
Status DestoryQueue(LinkQueue &Q){
while(Q.front){
p = Q.front -> next; free(Q.front); Q.front = p;
}
return OK;
}
链队列的入队
Status EnQueue(LinkQueue &Q, QElemType e){
p = new QNode; //为入队元素分配结点空间,用指针p指向
p -> data = e;
p -> nxet = NULL; Q -> 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;
delete p;
return Ok;
}
求链队列的队头元素
Status GetHead(LinkQueue Q,QELemType &e){
if(Q.front == Q.rear) return ERROR;
e = Q.front -> next -> data;
return OK;
}
案例引入
进制转换(栈)
void conversion(int n){
//对于任意一个非负十进制数,打印与其等值的八进制数
InitStack(S); //初始化空栈
while(N){ //当N非零时,循环
Push(S,N % 8); //把N与8求余得到的八进制数压入栈S
N = N / 8; //N更新为N与8的商
}
while(!StackEmpty(s)){ //当栈S非空时,循环
Pop(S,e); //弹出栈顶元素e
cout<<e;
}
}
时间复杂度为O(log8n)
括号匹配的检验 O(n)
表达式求值
- 为了实现表达式求值,需要设置两个栈:一个是算符栈OPTR,用于寄存运算符
- 另一个称为操作数栈OPND,用于寄存运算数和运算结果
舞伴问题