大话数据结构-第4章 栈与队列

第4章 栈与队列
4.2 栈的定义
栈(stack) 是限定仅在表层进行插入和删除操作的线性表
允许插入和删除的一端称为栈顶(top)
另一端称为栈底(bottom)
后进先出(Last In First Out) 线性表, 简称 LIFO

栈的插入操作: 进栈, 压栈, 入栈
栈的删除操作: 出栈, 弹栈

栈本身是一个线性表, 之前讨论的有关线性表的顺序存储和链式存储对于栈同样适用

4.4 栈的顺序存储结构及实现

4.5 两栈共享空间

4.6 栈的链式存储结构及实现

4.6.2 栈的链式结构-----进栈操作
栈是由栈元素构成的线性链表, 其中有一个栈元素为栈顶元素.
不存在栈溢出情况. 除非整个内存都用完了
空栈: 当 top = NULL 时候
栈元素:

        typedef struct StackNode
        {
            sElemType data;// 元素内容
            struct StackNode *next; //指向下一个元素地址
        } StackNode, *LinkStackPtr;

栈顶元素

        typedef struct LinkStack
        {
            LinkStackPtr top;// 指向栈顶元素位置
            int count;// 栈元素的数量
        }LinkStack;

进栈操作 (栈为链式存储结构时, 不用考虑栈溢出)

过程:

  • 创建新的栈元素 s , s 的数据data 装我们的新数据, s 的 next 指针 指向栈顶top元素

  • 把栈顶元素top指向这个新的栈元素 s, 让s 称为新的栈顶, 栈顶元素的cout属性加一

代码如下:

        // 先配置 新的栈元素
        // 再配置 栈顶元素
        Status push(LinkStack *S, sElemType e)
        {
            // 大写 S 是整个栈, e 是单纯的数据
            // 小写 s 是栈元素 
            LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
            // 栈元素的数据 为 e
            s->data = e;
            // 栈元素的 next 指向之前的栈顶元素.
            s->>next = S->top;
            // 栈顶元素现在是 s
            S->top = s;
            // 栈元素数量加1
            S->count++;
            return OK;
        }
        

4.6.3 栈的链式存储结构-----出栈结构

当栈顶 top = NULL 时 为空栈, 不能执行出栈操作(Pop)

过程:

  • 拿到栈顶数据

  • 取出栈顶元素

  • 栈顶元素更新为下一个栈元素

  • 释放取出的栈顶元素

代码如下:

        Status Pop(LinkStack *S, sElemType *e)
        {
            // 传入参数 S: 整个栈
            // 传入参数 *e : 用来存栈顶数据的存储位置
            //p : 栈顶元素 
            LinkStackPtr p;
            // 先判断是否是空栈
            if(StackEmpty( *s )){
            return ERROR;
            }
            // *e 获取栈顶的数据
            *e = S->top-data;
            // 给 p 赋值为 栈顶元素
            p = S->top;
            // 栈顶元素指向下一个元素(出栈)
            S->top = S->top->next;
            // 释放被出栈的栈顶元素p
            free(p);
            // 跟新栈元素数量
            S->count--;
            // 出栈操作结束
            return OK;
        }
        

总结: 如果栈的使用过程中元素变化不可预料, 有时很小, 有时非常大, 那么最好用链栈,

反之, 如果它的变化在可控范围内, 建议使用顺序栈会更好一些


4.8 栈的应用----递归

4.8.1 斐波那契数列实现

    F(n) = 0, 当n=0
    F(n) = 1, 当n=1
    F(n) = F(n-1) + F(n-2), 当n>1

打印前40位的斐波那契数列

简单的实现:

    int main()
    {
        // 从斐波那契数列这个名字上, 我们就可以看到, 可以用数组来实现.
        // 根据公式的规律把数列和数组的每个元素对应存起来.
        // 直接给 a[0] 设值 为 0
        // 直接给 a[1] 设值 为 1
        // a[i] = a[i-1] + a[i-2] 和公式 F(n) = F(n-1) + F(n-2) 对应起来.
        int i;
        int a[40];
        a[0] = 0;
        a[1] = 1;
        printf("%d", a[0]);
        printf("%d", a[1]);
        for(i = 2; i< 40; i++){
            a[i] = a[i-1] + a[i-2];
            printf("%d", a[i])
        }
        return 0;
    }

递归实现方法:


    /* 上一个实现方法的思路是 从头到尾 的推断方式
        先考虑 i=0 再考虑 i=1 一直到 i = 40
    */

    /* 这里的递归采用的是 从尾到头 的推断方式
       先是 i = 40, i = 39, i = 38, 一直到 i = 1, i = 0  
    */
    // 斐波那契的递归函数
    int Fbi( int i )
    {
        if (i<2)
        {
            // i=0 时 Fbi(0) = 0
            // i=1 时 Fbi(1) = 1
            return i==0 ? 0 : 1; 
        }
        // i >= 2时: Fib(i) = Fib(i-1) + Fib(i-2)
        return Fbi(i - 1) + Fbi(i - 2);// 这里Fbi 就是函数自己, 它在调用自己
    }

    int main ()
    {
        int i;
        for (int i = 0; i < 40; i++){
            // 输出 每一个斐波那契数列元素
            printf("%d", Fbi(i));
        }
        return 0;
    }

4.8.2 递归定义
我们把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数, 称作递归函数
每个递归定义, 必须至少有一个条件, 满足时递归不再进行, 即不再引用自身而是返回值退出

  • 递归和迭代:

    • 迭代: 使用循环结构, 不需要反复调用函数和占用额外的内存,
    • 递归: 使用选择结构, 使程序的结构更清晰, 更简洁, 更容易让人理解, 从而减少读懂代码的时间. 但是大量的递归调用会建立函数的副本, 会耗费大量的时间和内存.

    递归是用栈这种数据结构实现的. 简单的说, 在执行过程中, 对于每一层递归, 函数的局部变量, 参数以及返回地址都被压入栈中, 在退回阶段, 位于栈顶的局部变量, 参数值和返回地址被弹出, 用于返回调用层次中执行代码的其余部分, 也就是恢复了调用的状态.

4.9 栈的应用 -- 四则运算表达式求值

4.9.1 后缀 (逆波兰)表示法定义

4.9.2 后缀表达式计算结构

4.9.3 中缀表达式转后缀表达式

4.10 队列的定义
定义: 队列(queue) 是只允许在一端进行插入操作. 而在另一端进行删除操作的线性表.

队列是一种先进先出(First In First Out)线性表, 简称FIFO. 允许插入的一端称为队尾, 允许删除的一端称为队头. 假设队列是 q = (a1, a2, ......, an) , 那么 a1 就是队头元素, 而 an 是队尾元素, 这样我们就可以删除时, 总是从 a1 开始, 插入时, 列在最后.

           队头                 队尾
出队列 <--- a1, a2, a3, ...... an <---入队列

4.12 循环队列

  • 顺序队列的不足: 造成假溢出

  • 循环队列: 后面满了, 就再从头开始, 也就是头尾相接的循环. 我们把队列的这种头尾相接的顺序存储结构称为循环队列

c 语言中 区域运算符%

比如 5%4 = 1
4%5 = 4

循环队列的顺序存储结构代码

        //队列结构体
        typedef int QElemType; // QElemType 为 int 类型(类型按照实际情况来定, 这里假设为 int)
        typedef struct
        {
            QElemType data[MAXSIZE];// int 类型数组
            int front;              // 头指针
            int rear;               // 尾指针
        }sqQueue
        

循环队列的初始化代码

        Status InitQueue(sqQueue *Q)
        {
            Q->front=0;
            Q->rear=0;
            return OK;
        }

循环队列求队列长度代码如下:

当 rear > front 时, 队列长度为: QueueLength = rear - front
当 rear < front 时, 队列长度为: QueueLength = (QueueSize - front) + (0 + rear)
把上面两个条件结合在一起得到公式: QueueLength = (rear - front + QueueSize) % QueueSize

        int QueueLength(sqQueue Q)
        {
            return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
        }

循环队列的入队列操作代码如下:

        Status EnQueue(SqQueue *Q, QElemType d)
        {
            // 判断是否队列已经满了
            if((Q->rear + 1) % MAXSIZE == Q->front)
            {
                return ERROR;
            }
            // 队列中插入值
            Q->data[Q->rear] = e;
            // 后移队尾指针
            Q->rear = (Q->rear+1)%MAXSIZE;
            return OK;
        }

循环队列的出队列操作代码如下:

        Status DeQueue(sqQueue *Q, QElemType *e)
        {
            // 判断队列是否为空
            if(Q->front == Q->rear)
            {
                return ERROR;
            }
            // 取出队首数据
            *e = Q->data[Q->front];
            // 队首 指针后移
            Q->front = (Q->front+!)%MAXSIZE;
            return OK;
        }
        

4.13 队列的链式存储结构及实现
队列的链式存储结构, 其实就是线性表的单链表, 只不过它只能尾进头出而已, 我们把它简称为链队列
空队列时, front 和 rear 都指向头结点

链队列的结构:
- 头指针指向链队列的头结点.
- 队尾指针指向终点结点

    typedef int QElemType; // QElemType 是int 类型, (可以根据实际情况自己设定类型)

    typedef struct QNode  // 结点结构 
    {
        QElemType data;   // 结点数据
        struct QNode *next;//存放下一个结点的指针
    }

    typedef struct
    {
        QueuePtr front, rear; // 队头, 队尾 指针

    } LinkQueue;
    

队列的链式存储结构-----入队操作

    Status EnQueue(LinkQueue *Q, QElemType e)
    {
        // 给新的队列元素分配内存
        QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
        if(!s)
        {
            exit(OVERFLOW); // 退出程序, 并返回 OVERFLOW 的值给主程序.(当运算溢出时)
        }
        s->data = e;        // 新的队列元素赋值
        s->next = Null;     // 新的队列元素的下一个元素为 Null
        Q->rear->next = s;  // 队尾指针的下一个元素 为 s

        Q->rear = s;        // 队尾指针指向 s
        return OK;
    }

队列的链式存储结构---出队操作

    // 若队列不空, 删除Q的队头元素, 用e返回其值, 并返回OK, 否则返回ERROR
    Status DeQueue(LinkQueue *Q, QElemType *e)
    {
        QueuePtr p;
        // 判断队列是否为空
        if(Q->front == Q->rear)
        {
            return ERROR;
        }
        // p 为队首
        p = Q->front->next;
        // 取出队首的值
        *e = p->data;
        // 队头指针后移
        Q->front->next = p->next
        // 如果队列被清空
        if(Q->rear == p)
        {
            Q->rear = Q -> front;
        }
        // 释放p
        free(p);
        return OK
    }
    
  • 循环队列和链队列比较:
    • 时间上: 没有什么大的区别
    • 空间上: 循环队列必须有一个固定的长度, 就有了存储元素个数和空间浪费的问题.空间上,链队列会更加灵活.

    可以确定队列长度最大值时,使用循环队列
    不能预估队列长度时, 使用链队列

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容