【数据结构】第三章——栈和队列(详解)

前言:
本系列是笔者暑假自学数据结构的笔记整理而来,共126页,3w+字。现在正式开学上课,补充老师所讲内容,并且仔细勘误,根据老师的教学进度分章节发布在CSDN上。

教材使用的是王红梅等所著的数据结构——从概念到C++实现(第三版)。
暑假自学时期主要参考的网课:
青岛大学-王卓
武汉大学-李春葆

定义与概念

1.栈和队列是两种常用的、重要的数据结构
2.栈和队列是限定插入和删除只能在表的“端点”进行的线性表
3.栈是一种只能在一段进行插入和删除的线性表
4.允许进行插入、删除操作的一段成为栈顶,表的另一段称为栈底
5.当栈中没有数据元素时,称为空栈
6.栈的插入操作通常称为进栈和入栈
7.栈的删除操作通常称为退栈和出栈
8.栈的主要特点是“先进后出”,即后进栈的元素先出栈,栈也称为后进先出表
在这里插入图片描述
Eg:
在这里插入图片描述

基本运算

/*
    栈的基本运算如下:
    InitStack(&s);初始化栈,构造一个空栈s
    DestoryStack(&s);销毁栈,释放栈s占用的空间
    StackEmpty(s);判断栈是否为空,若栈s为空,则返回真,否则返回假
    Push(&S, e);进栈,将元素e插入到栈s中作为栈顶元素
    Pop(&s, &e);出栈,从栈s中退出栈顶元素,并将其赋值给e
    GetTop(s, &e);取栈顶元素,返回当前的栈顶元素,并将其值赋值给e
    栈中元素逻辑关系与线性表相同,栈可以采用与线性表相同的存储结构
    因此栈分为两类,顺序栈与链栈
*/
#define ElemType int
#define MAXSIZE 1000
typedef struct 
{
    ElemType data[MAXSIZE];
    int top//约定:top总是指向栈顶元素,初始值即空栈时为-1
    //当top = MAXSIZE - 1时不能再进栈,因为栈满
    //进栈时top++,出栈时top--
}SqStack;
/*
    顺序栈四要素
    栈空条件:top = -1
    栈满条件:top = MAXSIZE - 1
    e进栈操作:top++;e放在top处
    e退栈操作:从top处取出e;top--

*/
//初始化栈
void InitStack(SqStack *&s)
{
    s = new SqStack;
    s->top = -1;//注意:s叫做栈指针,而top叫做栈顶指针
}
//销毁栈
void DestroyStack(SqStack *&s)
{
    delete s;
}
//判断栈是否为空栈
bool StackEmpty(SqStack *s)
{
    return s->top == -1;
}
//进栈
bool Push(SqStack *&s, ElemType e)
{
    if (s->top == MAXSIZE - 1)//栈满的情况,即栈上溢出
    {
        return false;
    }
    s->top++;
    s->data[s->top] = e;
    return true;
}
//出栈
bool Pop(SqStack *&s, ElemType &e)
{
    if (s->top == -1)//栈下溢出
    {
        return false;
    }
    e = s->data[s->top];
    s->top--;
    return true;
}
//取栈顶元素
bool GetTop(SqStack *s, ElemType &e)
{
    if (s->top == -1)//栈下溢出
    {
        return false;
    }
    e = s->data[s->top];
    return true;
}
//判断一段字符串是否是对称的
bool symmetry(ElemType str[])
{
    int i;
    ElemType e;
    SqStack *st;
    InitStack(st);//初始化栈
    for (int i = 0; str[i] != '\0'; i++)
    {
        Push(st, str[i]);
    }
    for (int i = 0; str[i] != '\0'; i++)
    {
        Pop(st, e);
        if (str[i] != e)//若e与当前串元素不同则不是对称串
        {
            DestroyStack(st);
            return false;
        }
    }
}

在这里插入图片描述

/*
    链栈四要素
    栈空条件:s->next = NULL;
    栈满条件:不考虑
    进栈操作:将包含e的节点插入到头结点之后
    退栈操作:取出头结点之后节点的元素并删除之
*/
typedef struct linknode
{
    ElemType data;
    struct linknode *next;
}LiStack;
//初始化栈InitStack(&s)
void InitStack(LiStack *&s)
{
    s = new LiStack;
    s->next = nullptr;
}
//销毁栈DestroyStack(&s)
void DestroyStack(LiStack *&s)
{
    LiStack *p = s, *q = s->next;
    while (q != nullptr)
    {
        delete p;
        p = q;
        q = p->next;
    }
    delete p;//此时p指向尾结点,释放其空间
}
//判断栈是否为空StackEmpty(s)
bool StackEmpty(LiStack *s)
{
    return s->next == nullptr;
}
//进栈Push(&s, e)
void Push(LiStack *&s, ElemType e)
{
    LiStack *p;
    p = new LiStack;
    p->data = e;//新建元素e对应的节点*p
    p->next = s->next;//插入*p节点作为开始节点
    s->next = p;
}
//出栈Pop(&s, &e)
bool Pop(LiStack *&s, ElemType &e)
{
    LiStack *p;
    if (s->next == nullptr)
    {
        return false;
    }
    p = s->next;//p指向开始节点
    e = p->data;
    s->next = p->next;//删除*p节点
    delete p;
    return true;
}
//取栈顶元素GetTop(s, e)
bool GetTop(LiStack *s, ElemType &e)
{
    if (s->next == nullptr)
    {
        return false;
    }
    e = s->next->data;
    return true;
}
//编写一个算法判断输入的表达式中括号是否配对
bool Match(char exp[], int n)
{
    int i = 0;
    char e;
    bool match = true;
    LiStack *st;
    InitStack(st);
    while (i < n && match)//扫描exp中所有字符
    {
        if (exp[i] == '(')//遇到任何(都进栈
        {
            Push(st, exp[i]);
        }
    }
    else if (exp[i] == ')')
    {
        if (GetTop(st, e) == true)//栈顶元素不为(时表示不匹配
        {
            if (e != '(')
            {
                match = false;
            }
            else
            {
                Pop(st, e);//将栈顶元素出栈
            }
        }
        else
        {
            match = false;//无法取栈顶元素时表示不匹配
        }   
    }
    i++;//继续处理其他字符
    if (!StackEmpty(st))//栈不空时表示不匹配
    {
        match = false;
    }
    DestroyStack(st);
    return match;
}//只有在表达式扫描完毕且栈空时返回true

队列

定义与概念

1.队列简称队,也是一种运算受限的线性表
2.队列只能选取一个端点进行插入操作,另一个端点进行删除操作
3.把进行插入的一端称作队尾rear
4.把进行删除的一端称作队首或队头front
5.向队列中插入新元素称为进队或入队,新元素进队后就称为新的队尾元素
6.队列的主要特点是先进先出,所以又把队列称为先进先出表
在这里插入图片描述

基本运算

/*
    队列的基本运算如下:
    InitQueue(&q):初始化队列,构建一个空队列q
    DestroyQueue(&q):销毁队列,释放队列q占用的存储空间
    QueueEmpty(q):判断队列是否为空,若队列q为空,则返回真,否则返回假
    enQueue(&q, e):进队列,将元素e进队作为队尾元素
    deQueue(&q, &e):出队列,从队列q中出队一个元素,并将其值赋值给e
    既然队列中元素逻辑关系与线性表的相同,队列可以采用与线性表相同的存储结构
    队列分为顺序队和链队
*/
#define ElemType int
#define MAXSIZE 100
typedef struct
{
    ElemType data[MAXSIZE];
    int front, rear;//队首,队尾指针
    //因为队列两端都在变化,所以需要两个指针来标示队列的状态
    //约定rear总是指向队尾元素
    //元素进队,rear++
    //约定front指向当前队中队首元素的前一位置
    //元素出队,front++
    //当rear == MAXSIZE - 1时不能再进队
}SqQueue;
/*
    顺序队的四要素,初始front = rear = -1
    队空条件:front = rear
    队满条件:rear = MAXSIZE - 1
    元素e进队:rear++;data[rear] = e
    元素e出队:front++;e = data[front]

*/
//初始化队列
void InitQueue(SqQueue *&q)
{
    q = new SqQueue;
    q->front = -1;
    q->rear = -1;
}
//销毁队列
void DestroyQueue(SqQueue *&q)
{
    delete q;
}
//判断队列是否为空
bool QueueEmpty(SqQueue *q)
{
    return q->front == q->rear;
}
//进队列
bool enQueue(SqQueue *&q, ElemType e)
{
    if (q->rear == MAXSIZE - 1)
    {
        return false;
    }
    q->rear++;
    q->data[q->rear] = e;
    return true;
}
//出队列
bool deQueue(SqQueue *&q, ElemType &e)
{
    if (q->front == q->rear)
    {
        return false;
    }
    q->front++;
    e = q->data[q->front];
    return true;
}

在这里插入图片描述

环形队列与循环队列

解决办法靠环形队列或循环队列
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

/*
    环形队列的四要素
    队空条件:front = rear
    队满条件:(rear + 1)%MAXSIZE = front
    进队e操作:rear = (rear + 1)%MAXSIZE;将e放在rear处
    出队e操作:front = (front + 1)%MAXSIZE;取出front处元素e
    在环形队列中,实现队列的基本运算算法与非环形队列类似
*/

在这里插入图片描述
根据以上推论和题意设计有

typedef struct 
{
    ElemType data[MAXSIZE];
    int front;//队头指针
    int count;//队列中元素个数
}QuType;

该环形队列的四要素:

/*
    该环形队列四要素:
    队空条件:count = 0
    队满条件:count = MAXSIZE
    进队e操作:rear = (rear + 1)%MAXSIZE;将e放在rear处
    出队e操作:front = (front + 1)%MAXSIZE;取出front处元素e
    注意:这样的环形队列存放最多MAXSIZE个元素
*/

对应算法如下:

//初始化队
void InitQueue(QuType *&qu)
{
    qu = new QuType;
    qu->front = 0;
    qu->count = 0;
}
//进队
bool EnQueue(QuType *&qu, ElemType x)
{
    int rear;//临时队尾指针
    if (qu->count == MAXSIZE)//队满上溢出
    {
        return false;
    }
    else
    {
        rear = (qu->front + qu->count)%MAXSIZE;//求队尾位置
        rear = (rear + 1)%MAXSIZE;//队尾循环增一
        qu->data[rear] = x;
        qu->count++;//元素个数增一
        return true;
    }
}
//出队
bool DeQueue(QuType *&qu, ElemType &x)
{
    if (qu->count == 0)
    {
        return false;
    }
    else
    {
        qu->front = (qu->front + 1)%MAXSIZE;
        x = qu->data[qu->front];
        qu->count--;
        return true;
    }
}
//判断空队列
bool QueueEmpty(QuType *qu)
{
    return qu->count == 0;
}

显然环形队列比非环形队列更有效利用内存空间,即环形队列会重复使用已经出队元素的空间,不会出现假溢出。但如果算法中需要使用所有进队的元素来进一步求解,此时可以使用非环形队列。

链队

在这里插入图片描述
在这里插入图片描述

//单链表中数据节点类型Qnode定义
typedef struct Qnode
{
    ElemType data;//数据元素
    struct Qnode *next;
}Qnode;
//链队中头结点类型LiQueue定义
typedef struct
{
    Qnode *front;//指向单链表队头结点
    Qnode *rear;//指向单链表队尾结点
}LiQueue;
/*
    链队的四要素
    队空条件:front = rear = null
    队满条件:不考虑
    e进队操作:将包含e的结点插入到单链表表尾
    出队操作:删除单链表首数据节点
*/
//初始化队列
void InitQueue(LiQueue *&q)
{
    q = new LiQueue;
    q->front = q->rear = nullptr;
}
//销毁一个队列
void DestroyQueue(LiQueue *&q)
{
    Qnode *p = q->front, *r;//p指向队头数据节点
    if (p != nullptr)//释放数据节点占用空间
    {
        r = p->next;
        while (r != nullptr)
        {
            delete p;
            p = r;
            r = p->next;
        }
    }
    delete p, q;//释放链队结点占用空间
}
//判断队列是否为空
void QueueEmpty(LiQueue *q)
{
    return q->rear == nullptr;
}
//进队
/*
    包含两种情况
    1.原队列为空
    2.原队列非空
*/
void enQueue(LiQueue *&q, ElemType e)
{
    Qnode *p;
    p = new Qnode;
    p->data = e;
    p->next = nullptr;
    if (q->rear == nullptr)//若链队为空,新结点是队首节点又是队尾结点
    {
        q->front = q->rear = p;
    }
    else
    {
        q->rear->next = p;//将*p结点链接到队尾,并将rear指向它
        q->rear = p;
    }
}
//出队
/*
    包含三种情况
    1.原队列为空
    2.原队列只有一个结点
    3.其他情况
*/
bool deQueue(LiQueue *&q, ElemType &e)
{
    Qnode *t;
    if (q->rear == nullptr)//队列为空
    {
        return false;
    }
    t = q->front;//t指向第一个数据节点
    if (q->front == q->rear)//队列中只有一个结点时
    {
        q->front = q->rear = nullptr;
    }
    else
    {
        q->front = q->front->next;//队列中有多个结点时
    }
    e = t->data;
    delete t;
    return true;
}

Eg:
采用一个不带头节点只有一个尾结点指针rear的循环单链表存储队列,设计队列的初始化,进队和出队等算法。

//初始化队列
void initQueue(LinkList *&rear)
{
    rear = nullptr;
}
//判空
bool queue(LinkList *rear)
{
    return rear == nullptr;
}
//进队
void enQueue(LinkList *&rear, ElemType x)
{
    LinkList *p;
    p = new LinkList;//创建新结点
    p.data = x;
    if (rear == nullptr)//原链队为空
    {
        p->next = p;//构成循环链表
        rear = p;
    }
    else
    {
        p->next = rear->next;//将*p结点插入到*rear节点之后
        rear->next = p;
        rear = p;//rear指向新插入的结点
    }
}
//出队
bool deQueue(LinkList *&rear, ElemType &x)
{
    LinkList *q;
    if (rear == nullptr)//队空
    {
        return false;
    }
    else if (rear->next == rear)//原队只有一个结点
    {
        x = rear->data;
        delete rear;
        rear = nullptr;
    }
    else//原队中有两个或以上的结点
    {
        q = rear->next;
        x = q->data;
        rear->next = q->next;
        delete q;
    }
}

栈和队列都是存放多个数据的容器,通常用于存放临时数据。
1.如果先放入的数据先处理,则使用队列
2.如果后放入的数据先处理,则使用栈

栈与队列的比较

在这里插入图片描述
一些习题:
1.设循环队列的存储空间为a[0…20],且当前队头指针(f指向队首元素的前一位置)和队尾指针(r指向队尾元素)的值分别为8和3,则该队列中元素个数为?
解:16,这里的MAXSIZE=21,其中的元素个数是(r-f+MAXSIZE)%MAXSIZE=
2.假设用一个不带头结点的单链表表示队列,队头在链表的(?)位置。
解:链头
3.若用一个大小为6的数组来实现循环队列,且当前rear和front的值分别为0和3,当从队列中删除一个元素,再加入两个元素后,rear和front的值分别为?
解:2和4,rear=0,进队2个元素后,rear循环递增2,rear=2;front=3,出队一个元素后,front循环递增1,front=4.
4.一个循环队列中用data[0…n-1]数组保存队中元素,另设置一个队尾指针rear和一个记录队中实际元素个数的变量count,则该队中最多可以存放的元素个数为?
解:n个,队满的条件为count==n
5.已知循环队列存储在一维数组A[0…n-1]中,且队列非空时front和rear分别指向队头元素和队尾元素。若初始时队列空,且要求第一个进入队列的元素存储在A[0]处,则初始时front和rear的值分别为?
解:0和n-1,在循环队列中,进队操作是队尾指针rear循环递增1,再在该处放置进队的元素,本题要求第一个进入队列的元素存储在A[0]处,则rear应为n-1,才能使得(rear+1)%n
=0。而队头指针指向队头元素,此时队头位置为0,所以front的初值为0。

以上 如果此篇博客对您有帮助欢迎点赞与转发 有疑问请留言或私信 2020/10/3

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值