• 数据结构 栈与队列详解!!


    一.栈

     关于内存中的栈和数据结构中的栈是不同的,本章着重讲的是数据结构的栈。

     这是一张关于栈的表达图。从图中可以看出栈很像是一副卡牌,发牌时只能从上取出,即出栈。

    而入栈则是像你出牌后,要把你出的牌压在上一张出的牌上面。这是入栈。

    栈可以用链表或者顺序表实现,这里采用的是顺序表的结构。

    1.栈的头文件 

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. typedef int StackDatatype;
    7. typedef struct Stack
    8. {
    9. StackDatatype* data;
    10. int capacity;
    11. int Top;
    12. }ST;
    13. void STPush(ST* pst, StackDatatype x);
    14. void StackInit(ST* pst);
    15. void StackDestroy(ST* pst);
    16. void Push(ST* pst, StackDatatype x);
    17. void Pop(ST* pst);
    18. StackDatatype StackTop(ST* pst);
    19. int StackSize(ST* pst);
    20. bool StackEmpty(ST* pst);

     2.栈的初始化

    1. void StackInit(ST* pst)
    2. {
    3. assert(pst);
    4. pst->capacity = 0;
    5. pst->data = NULL;
    6. pst->Top = -1;
    7. }

    这里的pst->Top可以用-1,或者0.用-1的话后续你的pst->Top 所代表的下标就是栈顶元素。

    如果用0 那pst-Top 之后的下标就是栈顶元素的下一个位置。这里可以自己考虑,代码的多样性。 (本章采用的是-1 的写法)

    3.栈的插入

    1. void Push(ST* pst, StackDatatype x)
    2. {
    3. assert(pst);
    4. pst->Top++;
    5. JudgeCapacity(pst);
    6. pst->data[pst->Top] = x;
    7. }

    栈的插入就是入栈,top++ 是pst->top 指向当前即将插入元素的位置(栈顶的位置)。

    后面在判断pst中的容量,不够需要扩容。把x 插入栈顶位置。 

    4.栈的取出

    1. void Pop(ST* pst)
    2. {
    3. assert(pst);
    4. assert(pst->Top > -1);
    5. pst->Top--;
    6. }

    和顺序表一样只要 top--即可,不用删除,因为top -- 以后,你下次在使用该位置的时候,其实就是把原来这个位置的元素更改就可以了,不需要删除。

    5.栈的栈顶元素

    1. StackDatatype StackTop(ST* pst)
    2. {
    3. assert(pst);
    4. return pst->data[pst->Top];
    5. }

     这个函数的存在意义就是取当前的栈顶元素的值。

    6.栈的元素个数

    1. int StackSize(ST* pst)
    2. {
    3. assert(pst);
    4. return pst->Top+1;
    5. }

    因为我们的Top 用的是-1开头,所以,当它指向第一个元素的时候,这时候Top == 0, 所以加一。

    7.栈的判空

    1. bool StackEmpty(ST* pst)
    2. {
    3. assert(pst);
    4. return pst->Top == -1;
    5. }

    判断栈此时是否为空,用pst- top == -1 的判断表达式返回即可。 

    8.栈的销毁

    1. void StackDestroy(ST* pst)
    2. {
    3. assert(pst);
    4. free(pst->data);
    5. pst->data = NULL;
    6. pst->capacity = 0;
    7. pst->Top = -1;
    8. }

    因为栈是创建出来的一个空间。所以最后要将这段空间free,并将所有数据都置空 

    栈的容量判断

    1. void JudgeCapacity(ST* pst)
    2. {
    3. assert(pst);
    4. if (pst->capacity == pst->Top)
    5. {
    6. int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;
    7. StackDatatype* tmp = (StackDatatype*)realloc(pst->data, sizeof(StackDatatype) * newcapacity);
    8. if (tmp == NULL)
    9. {
    10. perror("malloc failed!");
    11. return;
    12. }
    13. pst->data = tmp;
    14. pst->capacity = newcapacity;
    15. }
    16. }

     和顺序表的容量判断一样,有兴趣的可以直接去看我的顺序表详解,这里给大家简单的说一下,用三目表达式判断并赋值newcapacity,然后扩容pst->data这一段空间。

    最后把扩容好的空间地址给到tmp,newcapacity给到原来的capacity。

    二.队列

     上图是队列的表达图,队列如字意就像是排队一样,先进入的人,就先获得服务。

    所以 队列和 栈的不同点就是出栈和出队,队列出的是头元素,而栈出的是尾元素(栈顶)

    对比入队和 入栈两者相似都是尾插。

    ,还有队列用的是链表,栈用的是顺序表。

    1.队列的头文件

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. typedef int QeDataType;
    7. typedef struct QueueNode
    8. {
    9. struct QueueNode* next;
    10. QeDataType data;
    11. }Qnode;
    12. typedef struct Queue
    13. {
    14. Qnode* head;
    15. Qnode* back;
    16. }Qe;
    17. void QueueInit(Qe* q);
    18. void QueueDestroy(Qe* q);
    19. void Queuepush(Qe* q, QeDataType x);
    20. void QueuePop(Qe* q);
    21. QeDataType QueueFront(Qe* q);
    22. QeDataType QueueBack(Qe* q);
    23. int QueueSize(Qe* q);
    24. bool QueueEmpty(Qe* q);

     相比栈 队列多用了一个typedef 原因是,队列要记录头元素和尾元素。因为入队入的在尾部,出队出的是头部

    2.队列的初始化

    1. void QueueInit(Qe* q)
    2. {
    3. assert(q);
    4. Qnode* newnode = CreateNode(-1);
    5. q->head = newnode;
    6. q->back = newnode;
    7. }

     这里采用的是有头结点的队列,当然没有头结点(哨兵位)也是可以的,根据个人喜好选择。

    初始化创立一个头结点(哨兵位)后,头指针和尾指针都指向头结点(哨兵位)

    3.队列的插入

    1. void Queuepush(Qe* q, QeDataType x)
    2. {
    3. assert(q);
    4. Qnode* newnode = CreateNode(x);
    5. q->back->next = newnode;
    6. q->back = newnode;
    7. }

     关于队列的插入,就是链表的尾插,如果链表还没明白的朋友,可以去看我之前关于单链表的博客。

    4.队列的取出

    1. void QueuePop(Qe* q)
    2. {
    3. assert(q);
    4. assert(q->head->next);
    5. Qnode* next = q->head->next;
    6. q->head->next = next->next;
    7. free(next);
    8. next = NULL;
    9. if (q->head->next == NULL)
    10. {
    11. q->back = q->head;
    12. q->back->next = NULL;
    13. }
    14. }

    关于队列的取出,实质上就是链表的头删。 

    5.队列的头元素

    1. QeDataType QueueFront(Qe* q)
    2. {
    3. assert(q);
    4. assert(q->head->next);
    5. return q->head->next->data;
    6. }

     队列的头元素返回就是返回哨兵位后的第一个节点的数据。因为要返回数值,所以这个第一个节点不能为空,用assert断言。

    6.队列的尾元素

    1. QeDataType QueueBack(Qe* q)
    2. {
    3. assert(q);
    4. assert(q->head->next);
    5. return q->back->data;
    6. }

     既然要返回尾元素的数据,那就是用到尾指针,当然链表第一个节点不能为空。

    7.队列的元素个数

    1. int QueueSize(Qe* q)
    2. {
    3. Qnode* size = q->head->next;
    4. int num = 0;
    5. while (size)
    6. {
    7. size = size->next;
    8. num++;
    9. }
    10. return num;
    11. }

    队列的元素个数,就把链表遍历一遍,用num记录遍历次数,就是元素个数。 

    8.队列的判空

    1. bool QueueEmpty(Qe* q)
    2. {
    3. return q->head->next == NULL;
    4. }

     队列的判空就是判断第一个节点是否为空,return 一个 表达式即可,也可以用if else 语句。因人而异。

    创造一个节点

    1. Qnode* CreateNode(QeDataType x)
    2. {
    3. Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));
    4. newnode->data = x;
    5. newnode->next = NULL;
    6. return newnode;
    7. }

     创造一个节点在链表的初始化和插入都会用到,在之前链表的那篇博客有讲到,偏简单。

  • 相关阅读:
    如何下载国外硕博论文?
    STM32入门100步
    【五:Spring MVC】
    bryntum gantt 5.0.6
    C/C++新手看过来----新手问题汇总分析
    web前端 -- 笔记
    抖音播映量破500的原因找到了,不是内容不好,而是这5个功能
    yolov5优化策略
    对于聚合物聚乙二醇PEG大家了解多少了?以及在生活中的应用
    9个python自动化脚本,PPT批量生成缩略图、添加图片、重命名
  • 原文地址:https://blog.csdn.net/a1275174052/article/details/134477821