栈和队列的定义和实现
栈和队列的定义和特点
- 栈和队列是限定插入和删除只能在表的“端点”进行的线性表
- 是线性表的特殊情况
栈
- 栈的示意图
- 栈的应用
数值转换、表达式求值
括号匹配、八皇后问题
行编译程序、函数调用
迷宫问题、递归调用的实现
-
栈的定义
栈是限定仅在表尾进行插入和删除操作的线性表
-
栈的相关概念
1.定义:限定只能在表的一端进行插入和删除运算的线性表(只能在栈顶操作)
2.逻辑结构:与线性表相同,仍为一对一关系。
3.存储结构:用顺序栈戟链栈存储均可,但以顺序栈更常见
4.运算规则:只能在栈顶运算,且访问结点时依照进先出(LIFO)的原则
5.实现方式:关键是编写入栈和出栈函数,具体实现依顺序栈或链栈的不同而不同
6.插入和删除都在一端进行,进行插入和删除操作的一端叫栈顶,另一端叫栈底
-
栈的特点
后进先出
-
栈与一般线性表用什么不同
队列
- 队列的示意图
-
队列的应用
- 脱机打印输出:按申请的先后顺序依次输出。
- 多用户系统中,多个用户排成队,分时地循环使用CPU和主存
- 按用户的优先级排成多个队,每个优先级一个队列
- 实时控制系统中,信号按接收的先后顺序依次处理
- 网络电文传输,按到达的时间先后顺序依次进行
-
队列的定义
插入在线性表的一端(表尾),删除在表的另一端(表头)
-
队列的相关概念
1.定义:只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表(头删尾插)
2.逻辑结构:与线性表相同,仍为一对一关系
3.存储结构:顺序队或链队,以循环顺序队列更常见
4.运算规则:只能在队首和队尾运算,且访问结点时依照先进先出(FIFO)的原则
5.实现方式:关键是掌握入队和出队操作,具体实现依顺序队或链队的不同而不同 -
栈的特点
先进先出
栈和队列的实现
栈
顺序栈表示和实现
- 存储方式:同一般线性表的顺序存储结构完全相同
- 栈满栈空标志
- 栈满时的处理方法:
1、报错返回操作系统。
2、分配更大的空间,作为栈的存储空间将
原栈的内容移入新栈。
- 使用数组作为顺序栈存储方式的特点:
简单、方便、但易产生溢出(数组大小固定)
上溢( overflow)):栈已经满,又要压入元素
下溢( underflow)):栈已经空,还要弹出元素
注:上溢是一种错误,使问题的处理无法进行;而溢般认为是
一种结束条件,即问题处理结束。
- 顺序栈的表示
#define MAXSIZE 100
typedef struct{
SElemType *base;// 栈底指针
SElemType *top;// 栈顶指针
int stacksize;// 栈的最大个数
}SqStack;
- 顺序栈的初始化
Status InitStack(SqStack &S){
S.base=new SElemType[MAXSIZE];// 初始化空间
if(!S.base)exit(OVERFLOW);
S.top=S.base;// 建立空栈
S.stacksize=MAXSIZE;
return OK;
}
- 判断顺序栈是否为空
Status StackEmpty(SqStack S){
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) {
delete S.base;
S.stacksize = 0;
S.base=S.top=NULL;
}
return OK;
}
- 顺序栈的入栈
(1)判断是否栈满,若满则出错
(2)元素压入栈顶
(3)栈顶指针加1
Status Push(SqStack &S,SElemType e){
if(S.top-S.base==S.MAXSIZE)return ERROR;// 判断是否栈满
*S.top++=e;//加入新元素
return OK;
}
- 顺序栈的出栈
(1)判断是否栈空,若空则出错
(2)获取栈顶元素e
(3)栈顶指针减1
Status Pop(SqStack &S,SElemType &e){
if(S.top==S.base)return ERROR;//判断栈是否为空
e=*--S.top;//取栈顶元素
return OK;
}
链栈的表示
- 链栈是运算受限的单链表,只能在链表头部进行操作
typedef struct StackNode{
SElemType data;
struct StackNode *next;
}StackNode,*LinkStack;
LinkStack S;
- 链栈的存储
-
注:
1.链表的头指针就是栈顶
2.不需要头结点
3.基本不存在栈满的情况
4.空栈相当于头指针指向空
5.插入和删除仅在栈顶处执
行 -
链栈的初始化
void 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->data=S;
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;//更新栈顶
delete p;
return OK;
}
- 取栈顶元素
SElemType GetTop(LinkStack S){
if(S!=NULL) return S->data;
}
栈与递归
-
递归的定义
- 若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的;
- 若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
例如:递归求n的阶乘
-
以下三种情况常常用到递归方法
- 递归定义的数学函数(阶乘函数、斐波那契数列)
- 具有递归特性的数据结构(二叉树、广义表)
- 可递归求解的问题(迷宫问题、Hanoi塔问题)
-
递归问题-用分治法求解
- 分治法:对于一个较为复杂的问题,能够分解成几个相对简单的且解法
相同或类似的子问题来求解 - 必备的三个条件
- 1、能将一个问题转变成一个新问题,而新题与原问题的解法相同
或类同,不同的仅是处理的对象,且这些处理对象是变化有规律的 - 2、可以通过上述转化而使问题简化
- 3、必须有一个明确的递归出口,或称递归的边界
- 1、能将一个问题转变成一个新问题,而新题与原问题的解法相同
- 分治法:对于一个较为复杂的问题,能够分解成几个相对简单的且解法
-
分治法求解递归问题算法的一般形式:
void p(参数表){
if(递归结束条件) 可直接求解; ---基本项
else p(较小的参数); ---归纳项
}
eg:
long Fact(long n){
if(n==0)return 1;//基本项
else return n*Fact(n-1);//归纳项
}
- 函数调用过程
- 调用前,系统完成:
- (1)将实参返回地址等传递给被调用函数
- (2)为被调用函数的局部变量分配存储区
- (3)将控制转移到被调用函数的入口
- 调用后系统完成
- (1)保存被调用函数的计算结果
- (2)释放被调用函数的数据区
- (3)依照被调用函数保存的返回地址将控制转移到调用函数
- 调用前,系统完成:
- 多个函数嵌套调用:
- 递归的优缺点
- 优点:结构清晰,程序易读
- 缺点:每次调用要生成工作记录,保存状态信,入栈;返回时要出栈恢复状态信息。时间开销大。
-
递归->非递归实现
方法1:尾递归、单向递归→循环结构
方法2:自用栈模拟系统的运行时栈 -
尾递归->循环结构
- 单线递归->循环结构
- 借助栈改写递归(了解)
- 借助栈改写递归方法(了解)
队列
- 队列的相关术语
队列的顺序表示
- 队列的类型定义
#define MAXQSIZE 100
typedef struct {
QElemType *base;//初始化动态存储空间
int front,rear;//对头和对尾
}SqQueue;
-
队列的操作
初始化:front=rear=0;
入队:base[rear]=x,rear++;
出队:x=base[front],front++;
队空标志:front==rear; -
解决假溢出的方法–引入循环队列
取余实现 -
循环队列队满对空判断
1.另设一个标志以区别对空队满
2.另设一个变量,记录元素的个数
3.少用一个元素*** -
少用一个元素空间判断队空队满
队满:(rear+1)%maxsize == front
队空:rear == front -
队列初始化
Status InitQueue(SqQueue &Q){
Q.base=new QElemType[MAXQSIZE];//分配数组空间
if(!Q.base)exit(OVERFLOW);//分配失败
Q.front = Q.rear = 0;
return 0;
}
- 求队列的长度
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;
return OK;
}
- 循环队列出队
Status DeQueue(SqQueue &Q,QElemType &e){
if(Q.front==Q.rear)return ERROR;
e=Q.base[Q.front];
Q.front=(Q.front+1)%MAXQSIZE;
return OK;
}
- 取队头元素
SElemType GetHead(SqQueue Q){
if(Q.front!=Q.rear)
return Q.base[Q.front];
}
队列的链式存储
- 链队列的类型定义
#define MAXQSIZE 100
typedef struct QNode{
QElemType data;
struct QNode *next;
}QNode,*QueuePtr;
typedef struct{
QueuePtr front;
QueuePtr rear;
}LinkQueue;
-
链队列的指针变化
尾插头删 -
链队列的初始化
Status InitQueue(LineQueue &Q){
Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));
if(!Q.front)exit(OVERFLOW);
Q.front->next=NULL;
return OK;
}
- 链队列的销毁
Status DestroyQueue(LinkQueue &Q){
LinkQueue p;
while(Q.front){
p=Q.front->next;free(Q.front);Q.front=p;
}
return OK;
}
- 链队列的入队
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->next;
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;
}