栈
定义和特点
- 栈(stack)是一个特殊的线性表,是限定仅在一端(通常是表尾)进行插入和删除操作的线性表。
- 栈的操作具有后进先出的固有特性。
- 又称为后进先出(Last In First Out)的线性表,简称LIFO结构。
- 栈是仅在表尾进行插入、删除操作的线性表。
表尾(即a端)称为栈顶Top;表头(即a端)称为栈底Base
例如:栈s = (a1, a2,a3,..... ,an)
a1称为栈底元素
an称为栈顶元素
#define MAXSIZE 100
typedef struct{
SElemType *base;//栈底指针
SElemType *top;//栈顶指针
int stacksize;//栈可用最大容量
}SqStack;
栈与线性表的区别
- 栈
逻辑结构: 一对—
存储结构: 顺序栈、链栈
运算规则:随机存取
- 一般线性表
逻辑结构: 一对一
存储结构: 顺序表、链表
运算规则:后进先出
栈基本操作
- lnitStack(&S) 初始化操作
操作结果: 构造一个空栈S. - DestroyStack(&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已存在且非空。
操作结果: 删除S的栈顶元素an,并用e被回其值。
顺序栈
顺序栈的操作
存储方式: 同一般线性表的顺序存储结构完全相同,利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。
- 附设top指针,指示栈顶元素在顺序栈中的位置。
- 另设base指针,指示栈底元素在顺序栈中的位置。
为了方便操作,通常top指示真正的栈顶元素下一个单元的下标地址
- 用stacksize表示栈可使用的最大容量
栈空:
空栈: base == top是栈空标志
栈满
top-base==stacksize
栈中元素个数:
个数 =top-base
使用数组作为顺序栈存储方式的特点:
简单、方便、但易产生溢出(数组大小固定)
- 上溢(overflow):栈已经满,又要压入元素
- 下溢(underflow):栈已经空,还要弹出元素
注: 上溢是一种错误,使问题的处理无法进行;而下溢一般认为是—种结束条件,即问题处理结束。
1. 顺序栈的初始化
Status lnitStack(SqStack &S){//构造一个空栈s
S.base = new SElemType[MAXSIZE];
if (!S.base) exit (OVERFLOW);//存储分配失败
S.top= S.base;//栈顶指针等于栈底指针
S.stacksize = MAXSIZE;
return OK:
}
2.顺序栈的判空
Status StackEmpty(SqStack S){
//若栈为空,返回TRUE;否则返回FALSE
if (S.top == S.base)
return TRUE;
else
return FALSE;
}
3.求顺序栈长度
int StackLength( SqStack s ){
return S.top - S.base;
}
4.销毁栈
Status DestroyStack( SqStack &S ) {
if( S.base ) {
delete S.base ;S.stacksize = 0;
S.base = S.top = NULL;
}
return OK;
}
5.入栈(压栈)
算法思路:
(1)判断是否栈满,若满则出错(上溢)
(2)元素e压入栈顶
(3)栈顶指针加1
Status Push( SqStack &S, SElemType e){
if( S.top - S.base== S.stacksize )//栈满
return ERROR;
*S.top++=e;
return OK;
}
6.出栈
算法思路
(1)判断是否栈空,若空则出错(下溢)
(2)获取栈顶元素e
(3)栈顶指针减1
Status Pop(SqStack &S, SElemType &ey {
//若栈不空,则删除S的栈顶尤素,用e返向其值并返回OK;否则返回ERROR
if(S.top == S.base) //等价于if(StackEmpty(S))
return ERROR;
e = *--S.top;
retun OK;
}
链栈
概念
定义
链栈是运算受限的单链表,只能在链表头部(栈顶)进行操作。
特点
- 链表的头指针就是栈顶
- 不需要头结点
- 基本不存在栈满的情况
- 空栈相当于头指针指向空
- 插入和删除仅在栈顶处执行
类型定义
typedef struct StackNode{
SElemType data;
struct StackNode *next;
} StackNode,*LinkStack;LinkStack S;
链栈的操作
1.初始化
void lnitStack(LinkStack &s ) {//构造一个空栈,栈顶指针置为空
S=NULL;
return OK;
}
2.判空
Status StackEmpty(LinkStack S)
if (S==NULL) return TRUE;
else return FALSE;
}
3.入栈
Status Push(LinkStack &s,SElemType e){
p=new StackNode; //1.生成新结点
pp->data=e; //2.将新结点数据域置为e
p->next=S; //3.将新结点插入栈顶
S=p; //4.修改栈顶指针
return OK;
}
4.出栈
Status Pop (LinkStack &S,SElemType &e){
if (S==NULL)return ERROR;
e = s-> data;
p = S;
s = s-> next;
delete p;
return OK;
}
问:假设有3个元素a, b,c,入栈顺序是a,b,c,则它们的出栈顺序有几种可能?
递归
定义
- 若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的;
- 若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
- 程序必须有一个明确的递归出口,或称递归的边界。
//n!
long Fact ( long n ) {
if ( n == 0)
return 1;
else return n * Fact (n-1);
}
递归的一般形式
void p(参数表){
if(递归结束条件)可直接求解步骤;-----基本项
else p(较小的参数) ; ------归纳项
}
特点
优点: 结构清晰,程序易读。
缺点: 每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,恢复状态信息。时间开销大。
递归转非递归
**方法一:**尾递归、单向递归→循环结构。
1.尾递归
//尾递归
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;
}
2.单向递归:虽然有一处以上的递归调用语句,但各次递归调用语句的参数只和主调函数有关,相互之间参数无关,并且这些递归调用语句处于算法的最后。
long Fib ( long n ) {// Fibonacci数列
if(n==1 || n==2) return 1;
else return Fib (n-1)+Fib (n-2);
}
long Fib ( long n ) {
if(n==1 || n==2) return 1;
else{
t1=1; t2=1;
for(i=3; i<=n; i++){
t3=t1+t2;
t1=t2; t2=t3;}
return t3;}
}
**方法2:**自用栈模拟系统的运行时栈。
借助栈改写递归
--------递归程序在执行时需要系统提供栈来实现
--------仿照递归算法执行过程中递归工作栈的状态变化可写出相应的非递归程序
--------改写后的非递归算法与原来的递归算法相比,结构不够清晰,可读性较差,有的还需要经过一系列优化
队列
队列的定义和特点
- 队列(queue)是一种先进先出(Frist ln Frist Out ----FIFO)的线性表。在表一端插入(表尾),在另一端(表头)删除。
- 它是一种先进先出( FIFO )的线性表。
- 表尾即an端,称为队尾;表头即a端,称为队头。
逻辑结构:与同线性表相同,仍为一对—关系。
存储结构:顺序队或链队,以循环顺序队列更常见。
运算规则:只能在队首和队尾运算,且访问结点时依照先进先出(FIFO)的原则。
队列的基本操作
- lnitQueue(&Q)
操作结果:构造空队列Q - DestroyQueue(&Q)
条件:队列Q已存在;
操作结果:队列Q被销毁 - ClearQueue(&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;
顺序队的实现
顺序队的几种状态
0.正常状态
1.溢出
2.真溢出
3.假溢出
解决假溢出的办法
-
1、将队中元素依次向队头方向移动。
缺点:浪费时间。每移动一次,队中元素都要移动。 -
2、将队空间设想成一个循环的表,即分配给队列的m个存储单元可以循环使用,当rear为maxqsize时,若向量的开始端空着,又可从头使用空着的空间。当front为maxqsize时,也是一样。
- 实现方法: 利用模(mod,C语言中: %)运算。
- 插入元素:
Qbase[Q.rear]=x;
Q.rear=(Q.rear+1)% MAXQSIZE;
- 删除元素:
x=Qbase[s.front];
Qfront=(Qfront+1)% MAXQSIZE;
于是形成了循环队列:循环使用为队列分配的存储空间。
循环队列
循环队列的几种状态
队满
Q.front = Q.rear = 0;
队空
Q.front = Q.rear = 0;
解决队满时判断办法
少用一个元素空间:
队满:
(Q.rear+1)%MAXQSIXZE==Q.front;
循环队列的类型定义
#define MAXQSIZE 100//最大队列长度
typedef struct {
QElemType *base; //动态分配存储空间
int front; //头指针,若队列不空,指向队列头元素
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置
} SqQueue;
循环队列的操作
1.求队列长度
int QueueLength (SqQueue Q){
return ( Qrear-Qfront+MAXQSIZE)%MAXQSIZE ) ;
}
2.入队
Status EnQueue(SqQueue &Q,QElemType e){
if((Qrear+1)%MAXQSIZE==Qfront)
return ERROR; //队满
Qbase[Qrear]=e; //新元素加入队尾
Qrear=(Qrear+1)%MAXQSIZE;//队尾指针+1
return OK;
}
3.出队
Status DeQueue (SqQueue &Q,QElemType &e){
if(Q.front == Q.rear) return ERROR; //队空
e = Q.base[Q.front]; //保存队头元素
Q.front = (Q.front+1) % MAXQSIZE; //队头指针+1return OK;
}
链队列
链队列的类型定义
typedef struct QNode
{
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct{
QueuePtr front;
QueuePtr rear;
} LinkQueue;
链队列的实现
1.初始化
在这里插入代码片
2.入队
Status EnQueue (LinkQueue &Q, QElemTyP{ //插人元素e为Q的新的队尾元素
p=new QNode; //为入队元素分配结点空间,用指针p指
p->data=e; //将新结点数据域置为e
p->next=NULL; Q.rear->next=p; //将新结点插入到队尾
0.rear=p; //修改队尾指针
return OK;
}
出队
status DeQueue(LinkQueue &Q, QElemType &e)
{//删除Q的队头元素,用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;
}