数据结构学习第三章栈和队列

栈和队列是线性表的特殊形式,分别具有“后进先出”和“先进先出”的特性。栈常用于括号匹配、表达式求值等问题,队列则应用于打印输出、多用户系统调度等场景。文章介绍了栈和队列的定义、操作(如入栈、出栈、入队、出队)以及在实际问题中的应用,并提供了相关算法的实现,包括顺序栈、链栈、循环队列和链队列的初始化、操作和管理。
摘要由CSDN通过智能技术生成

第三章栈和队列

  • 栈和队列是限定插入和删除只能在表的“端点”进行的线性表。是线性表的子集(插入和删除位置受限的线性表)

  • 栈具有“先进后出”的固有特性,例如碟盘子,每次只能在最上面放盘子,而且每次只能从最上面拿盘子。

需要用“栈”算法:数据转换、括号匹配检验、行编辑程序、迷宫求解、表达式求值、八皇后问题、函数调用、递归调用实现。

  • 队列具有“先进先出”的特性,例如排队,每次先排的人先走。

需要用“队列”的算法:

*脱机打印输出:按申请先后顺序依次输出

*多用户系统中,多个用户排成队,分时循环使用CPU和主存

*按用户的优先级排列成多个队,每个优先级一个队列

*实时控制系统中,信号按接收的先后顺序依次处理

*网络电文传输,按到达的时间先后顺序依次进行

1.栈:只能插入和删除最后一个

  • 栈(Stack)是一个特殊的线性表,是限定仅在一端(通常是表尾)进行插入和删除操作的线性表。栈后进先出(Last In First Out)的线性表,简称"LIFO"结构。

  • 表尾称栈顶Top;表头称栈底Base。

  • 插入元素到栈顶的操作,称为“入栈”,也叫“压栈”(PUSH)。

  • 从栈顶删除最后一个元素操作,称为“出栈”,也叫弹出(POP)。

  • 栈的示意图

在这里插入图片描述

  • 入栈操作示意图

在这里插入图片描述

  • 出栈操作示意图

在这里插入图片描述

【思考题】假设有3个元素a,b,c,入栈顺序是a,b,c,则它们的出栈顺序有几种可能。

答案:cba,abc,acb,bac,bca

2.队列:只能插入队尾,删除队头

  • 队列(queue)是一种先进先出(FIFO)的线性表。
  • 在表一端(表尾)插入,在另一端(表头)删除。

在这里插入图片描述

3案例引入

  • 栈:进制转换、括号匹配、表达式求值
  • 队列:舞伴问题

【案例3.1】进制转换

例:把十进制数159转换成八进制数

在这里插入图片描述

【案例3.2】括号匹配检验

左括号入栈,右括号跟栈顶括号配对。如果能配对出栈,不能配对则语法不对。

在这里插入图片描述

【案例3.3】表达式求值(运算符优先级优先算法)

  • 表达式组成:

*操作数:常数、变量。

*运算符:算术运算符、关系运算符、逻辑运算符

*界限符:左右括弧和表达式结束符

  • 任何一个算术表达式都由操作数(常数、变量)、算术运算符(+,-,*,/)、界限符组成(使用’#'作为起始结束符)。

例如:# 3 * (7 - 2) #

  • 为了实现表达式求值,需要设置两个栈:一个算符栈OPTR,寄存运算符;另一个操作数栈OPND,寄存运算数和运算结果。
  • 求值时,自左而右扫描表达式的每一个字符:

当扫描到运算数,压入栈OPND。

当扫描到运算符时,若运算符比OPTR栈顶优先级高,则入栈OPTR。若运算符比OPTR栈顶优先级低,则从OPND中弹出两个运算符,从栈OPTR中弹出栈顶运算符进行运算,将运算符结果压入栈OPND。

【案例3.4】舞伴问题

男士和女士各排成一队。舞会开始时,依次从男队和女队的队头各出一个人配成舞伴。如果两队初始人数不同,则较长的那队中未匹配者等待下一轮舞曲。

显然,先入队的男士或女士先出队配成舞伴。QM表示男士队列,QF表示女士队列。

在这里插入图片描述

4.栈的表示和操作的实现

4.1栈的抽象数据类型

ADT Stack{
    数据对象:D={ai | ai ∈ ElemSet, i=1,2……}
    数据关系:R1={<ai-1,ai>  | ai-1,ai∈D,i=2,……}
            约定an端为栈顶,a1段位栈底。
    基本操作:
       InitStck(&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返回其值
}ADT Stack

栈本身是线性表,所以栈也有顺序存储与链式存储两种实现方式(即顺序栈,链栈)。

4.2顺序栈的表示和实现

利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。

  • 设top指针,指示栈顶元素在顺序栈中的位置。

  • 设base指针,指示栈底元素在顺序栈中的位置。

  • 为方便操作,通常top指向栈顶元素之上的地址,即放到表最后一个元素后一位。

  • 用stacksize表示栈可使用最大容量。

例子1:一个顺序栈的示意图

在这里插入图片描述

使用数组作为顺序栈的存储方式特点:简单、方便、由于数组大小固定容易产生溢出(包括上溢、下溢)。

  • 上溢(overflow):栈满时,压入元素。是一种错误,使问题的处理无法进行。
  • 下溢(underflow):栈空时,弹出元素。认为是一种结束条件,问题处理结束。

顺序栈的表示

#define MAXIZE 100
typedef struct{
    SElemType *base; //栈底指针
    SElemType *top;  //栈顶指针
    int stacksize;  //栈可用最大容量
}SqStack;

例子:栈的例子示意图

在这里插入图片描述

【算法1】顺序栈的初始化

Status InitStack(SqStack &S){ //构造一个空栈
    S.base = new SElemType[MAXSIZE]; //S.base = (SElemType*)malloc(MAXSIZE*sizeof(SElemType));
    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 ClearStack(SqStack S){
    if(S.base) 
        S.top = S.base;
    return OK;
}

【算法5】销毁顺序栈

Status DestoryStack(SqStack &S){
    if(S.base){
        delete S.base;
        S.stacksize = 0;
        S.base = S.top = NULL;
    }
    return OK;
}

【算法6】顺序栈入栈

【算法步骤】

①判断是否栈满,若满则出错(上溢)

②元素e压入栈顶

③栈顶指针+1

Status Push(SqStack &S,SElemType e){
    if(S.top - S.base = S.stacksize) //栈满
        return ERROR;
    *S.top++ = e;// *S.top = e;S.top++;
    return OK;
}

【算法7】顺序栈出栈

【算法步骤】

①判断栈是否为空,若空则出错(下溢)

②栈顶指针-1,获取栈顶元素e

Status Pop(SqStack &S,SElemType e){
    if(S.top == S.base) //栈空
        return ERROR;
    e=*--S.top; //--S.top; e=*S.top;
    return OK;
}

4.3链栈的表示和实现

链栈:是运算受限的单链表,只能在链表头部进行操作。

链栈定义

typedef struct StackNode{
    SElemType data;
    struct StackNode *next;
}StackNode,*LinkStack;

LinkStack S;

链栈示意图:

在这里插入图片描述

【算法1】链栈初始化

void InitStack(LinkStack &S){
    //构造一个空栈,栈顶指针置空
    S = NULLreturn OK;
}

【算法2】链栈判空

Status StackEmpty(LinkStack S){
    if(S==NULL) 
        return TRUE;
    else 
        return FALSE;
}

【算法3】链栈的入栈

【算法步骤】

①分配入栈元素空间,用指针p指向

②将新结点数据域赋值

③将新结点压入栈顶。p->next = S;

④修改栈顶指针。S=p;

Status Push(LinkStack &S,SElemType e){
    //在栈顶插入元素e
    p=new StackNode; //生成新结点
    p->data = e;  //设置新结点数据域
    p->next = S;  //新结点压入栈顶
    S = p;  //修改栈顶指针
    return OK;
}

【算法4】链栈的出栈

【算法步骤】

①判断链栈是否为空,若为空返回ERROR;

②将栈顶元素赋值给e。

③临时保存栈顶空间,用指针p指向

④修改栈顶指针,指向新栈顶元素。

⑤释放p

Status Pop(LinkStack &S,SElemType &e){
    if(S=NULL)   //链栈判空
        return ERROR;
    e = S->data;  //栈顶元素赋值给e
    p = S;   //标记当前栈顶
    S = S->next;  //修改栈顶指针
    delete p;  //释放标记的栈顶
    return OK;
}

【算法5】取栈顶元素

SElemType GetTop(LinkStack S){
    if(S!=NULL)
        return S->data;
}

5栈与递归

  • 递归定义:若一个对象部分地包含自己,或者用它自己给自己定义,则称这个对象是递归的。

  • 若一个过程直接或间接调用自己,则称这个过程为递归的过程。

例如:递归求n阶乘

long Fact(long n){
    if(n == 0)  //基本项
        return 1;
    else   //归纳项
        return n*Fact(n-1);
}
  • 常用到递归的三种情况:

*递归定义数学函数

在这里插入图片描述

*具有递归特性的数据结构:二叉树、广义表

*可递归求解的问题:迷宫问题、

  • 递归问题——用分治法求解

分治法:对于一个比较复杂的问题,分解成几个简单的且解法相同或类似的子问题求解。

在这里插入图片描述

分治法求解递归问题算法一般形式:

void p(参数表){
    if(递归结束条件)
        可直接求解的步骤;  ——基本项
    else p(较小的参数);   ——归纳项
}
  • 递归过程解析:以求阶乘 n!为例子

在这里插入图片描述

6.队列的表示和操作的实现

6.1队列的抽象数据类型

ADT Queue{
    数据对象:D={ai | ai∈ElemSet,i=1,2……}
    数据关系:R={<ai-1,ai> | ai-1,ai∈D,i=1,2……} //约定其中a1端为队列头,an端为队列尾。
    基本操作:
        InitQueue(&Q)
          操作结果:构造一个空队列Q
        DestroyQueue(&Q)
          初始条件:队列Q已存在
          操作结果:队列Q被销毁
        ……
}ADT Queue

6.2顺序队的表示和实现

顺序队——用顺序存储方式存储队列。

用一维数组base来表示顺序队:

#define MAXQSIZE 100  //定义一个常量作为最大队列长度
typedef struct{
    QElemType *base;  //初始化动态分配存储空间
    int front;  //定义头指针
    int rear;   //定义尾指针
}SqQueue;

顺序表入队出队示意图:

在这里插入图片描述

  • 当rear=MAXQSIZE时,再入队发生溢出。

    *用顺序队时,如果还未有元素出队时,发生溢出时候为真溢出。

    *在已经有元素出队后,发生溢出的时候为假溢出。这个时候有空出来的位置。

在这里插入图片描述

  • 解决假溢出——引入循环队列

将base[0]接在base[MAXQSIZE -1]之后,若rear + 1 = MAXQSIZE,令rear = 0。

实现方法:利用模运算(mod,C语言中: %)。模运算即求整除的余数。

插入元素:Q.base[Q.rear] = x; Q.rear=(Q.rear+1) %MAXQSIZE;

删除元素:x=Q.base[s.front]; Q.front=(Q.front+1) %MAXQSIZE;

循环队列:循环使用为队列分配的存储空间。

在这里插入图片描述

循环队列解决队满时判断方法——少用一个元素空间

在这里插入图片描述

【算法1】队列初始化

Status InitQueue(SqQueue &Q){
    Q.base = new QElemType[MAXQSIZE]; //Q.base = (QElemType*) malloc (MAXQSIZE*sizeof(QElemType));
    if(!Q.base) //分配内存失败
        exit(OVERFLOW);
    Q.front=Q.rear=0;
    return OK;
}

【算法2】循环队列——求队列长度

int QueueLength(SqQueue Q){
    return ((Q.rear-Q.front+MAXQSIZE) % MAXQSIZE);
}

【算法3】循环队列——入队

Status EnQueue(SqQueue &Q,QElemType e){
    if((Q.rear+1)%MAXQSIEZE == Q.front)  //队满(少用一个元素空间)
        return ERROR;
    Q.base[Q.rear] = e; //新元素加入队尾
    Q.rear=(Q.rear + 1)%MAXQSIZE;  //队尾指针+1
    return OK;
}

【算法4】循环队列——出队

Status DeQueue(SqQueue &Q,QElemType &e){
    if(Q.front==Q.rear) //队空
        return ERROR;
    e=Q.base[Q.front];//保存队头元素
    Q.front = (Q.front +1)%MAXQSIZE;  //队头指针+1
    return OK;
}

【算法5】循环队列——取队头元素

SElemType GetHead(SqQueue Q){
    if(Q.front != Q.rear) //队列不为空
        return Q.base[Q.front];//返回队头指针元素,队头指针不变
}

6.3链队的表示和实现

若用户无法估计所用队列的长度,则适宜采用链队列。

在这里插入图片描述

链队列定义

#define MAXQSIZE 100 //最大队列长度
typedef struct Qnode{
    QElemType data;
    struct Qnode *next;
}QNode,*QuenePtr;

typedef struct{
    QuenePtr front;//队头指针
    QuenePtr rear; //队尾指针
}LinkQueue;

链队列运算指针变化状况

在这里插入图片描述

【算法1】链队初始化

Status InitQueue(LinkQueue &Q){
    Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode));
    if(!Q.front)
        exit(OVERFLOW);
    Q.front->next=NULL;
    return OK;
}

【算法2】链队列销毁

Status DestroyQueue(LinkQueue &Q){
    while(Q.front){
        p=Q.front->next;
        free(Q.front);
        Q.front=p;
    }
    return OK;
}

【算法3】链队入队

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;
}

【算法4】链队出队

Status DeQueue(LinkeQueue &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;
}

【算法5】链队列头元素

Status GetHead(LinkQueue Q,QElemType &e){
    if(Q.front == Q.rear)
        return ERROR;
    e=Q.front->next->data;
    return OK;
}

(QueuePtr)malloc(sizeof(QNode));
if(!p)
exit(OVERFLOW);
p->data=e;
p->next=NULL;
Q.rear->next=p;
Q.rear=p;
return OK;
}


【算法4】链队出队

```c
Status DeQueue(LinkeQueue &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;
}

【算法5】链队列头元素

Status GetHead(LinkQueue Q,QElemType &e){
    if(Q.front == Q.rear)
        return ERROR;
    e=Q.front->next->data;
    return OK;
}

学习视频:数据结构——王卓
参考文献:数据机构C语言版第2班——严蔚敏

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值