• 2022_08_04_106期__栈和队列


    目录

    顺序表和链表的优缺点

    顺序表的优点

    顺序表的缺点:

    链表的优点:

    链表的缺点:

    实现栈。

    栈的初始化

    栈的销毁

    栈的插入:

    栈的删除

    栈的访问:

    栈的数量

    打印栈

    有效的括号

    队列:

    队列的初始化:

    队列的销毁:

    接下来,我们来实现队列的插入:

    接下来,我们来讲删除队列元素

    取队头和队尾的数据:

    判断队列是否为空

    查找链表的元素:


    顺序表和链表的优缺点

     总结:

    顺序表的优点

    1:尾插尾删效率很高,扩容方便,一次就扩容两倍的容量。

    2:随机访问:顺序表可以用下标访问元素。

    顺序表的缺点:

    1:头部和中部的插入和删除效率低,原因是需要挪动数据。---O(N)

    2:扩容---性能消耗+空间浪费

    链表的优点:

    1:任意位置的插入和删除效率都很高 ---O(1)

    2:按需申请释放

    链表的缺点:

    1:不支持随机访问

    总结:顺序表能满足我们的基本要求,顺序表的效率更高。

    但是注意也是有例外,假如我们大量进行头部的插入和删除,那我们还是用链表最好。

     我们先分析后进先出:

    例如:

     我们的元素是从栈顶进去的,我们依次向栈中输入数据1,2,3,4,5.

     接下来,假如我们要从栈中去除数据,那么我们就要首先取出后进去的5.这就是后进先出的含义。

    我们把栈的插入操作叫做入栈,入数据在栈顶

    我们把栈的删除操作叫做出栈,出数据也在栈顶,

    注意:栈也是线性表,线性表一般就是用数组和链表实现的。

    我们写一个题目:

     我们进行画图分析,首先看A选项,1,4,3,2.

    这个是1先进,然后1出,然后2,3,4再进,然后2,3,4出,如图

    依次演变的顺序为

     

     然后1出栈

     然后2,3,4再入栈

    再出栈:

     我们再分析b选项:2,3,4,1

    我们先入栈1,2

     然后我们出栈2

     然后我们入栈3

     然后我们出栈3:

     再入栈4

     再出栈1,4

     我们再来分析c选项:3 1 4 2

    所以我们要入栈1 2 3

     然后我们出栈3

     但是我们发现:接下来是无法出栈1的,所以c选项错误。

    实现栈。

    我们可以用顺序表的方式实现栈

    1. #pragma once
    2. #include
    3. //#define N 100
    4. typedef int STDataType;
    5. struct Stack
    6. {
    7. STDataType*a;
    8. int top;
    9. int capacity;
    10. }ST;

     top表示我们的栈顶。

    接下来, 我们来实现栈函数

    1. void StackInit(ST*ps);
    2. void StackDestory(ST*ps);
    3. void StackPush(ST*ps, STDataType x);
    4. void StackPop(ST*ps);
    5. STDataType StackTop(ST*ps);
    6. bool StackEmpty(ST*ps);
    7. int StackSize(ST*ps);

    由上到下分别是栈的初始化,栈的销毁,栈的插入,栈的删除,栈顶位置,判定栈是否为空,栈的大小。

    栈的初始化

    1. void StackInit(ST*ps)
    2. {
    3. assert(ps);
    4. ps->a = NULL;
    5. ps->top = ps->capacity = 0;
    6. }

    接下来,我们写

    栈的销毁

    1. void StackDestory(ST*ps)
    2. {
    3. assert(ps);
    4. free(ps->a);
    5. ps->a = NULL;
    6. ps->capacity = ps->top = 0;
    7. }

    栈的插入:

    1. void StackPush(ST*ps, STDataType x)
    2. {
    3. assert(ps);
    4. if (ps->top == ps->capacity)
    5. {
    6. int newCapacity = ps->capacity = 0 ? 4 : ps->capacity * 2;
    7. STDataType*tmp = (STDataType*)realloc(ps->a, newCapacity*sizeof(STDataType));
    8. if (tmp == NULL)
    9. {
    10. perror("realloc fail");
    11. exit(-1);
    12. }
    13. ps->a = tmp;
    14. ps->capacity = newCapacity;
    15. }
    16. ps->a[ps->top] = x;
    17. ps->top++;
    18. }

     top是我们的栈顶的位置,因为我们的栈是后进先出,所以对应的图像为:

     假如我们插入元素时

     

     最后的图像为

     而capacity是我们的顺序表的容量

     当我们的容量和我们的栈顶相等时,我们就需要扩容。

    int newCapacity = ps->capacity = 0 ? 4 : ps->capacity * 2;

    当栈中没有元素时,容量和栈顶相等,所以我们要先开辟一部分空间,这里的意思是假如容量为0,返回4,也就是初始容量为4,假如容量不为0,就扩容二倍。

    STDataType*tmp = (STDataType*)realloc(ps->a, newCapacity*sizeof(STDataType));

    上一句话只是计算出所要扩容元素的个数,而这里表示扩容元素,ps->a就表示我们数组首元素的地址,所以我们要在数组首元素的地址处扩容newCapacity*sizeof(STDataType个字节。然后赋给tmp。因为realloc有失败的风险,失败的话,就会导致ps->a为空指针,造成内存泄漏,所以我们先创建一个临时变量tmp来接受,假如tmp不为空指针,我们再把他赋给ps->a

    1. if (tmp == NULL)
    2. {
    3. perror("realloc fail");
    4. exit(-1);
    5. }

     这里表示假如realloc扩容内存失败的时候,打印对应的错误信息并退出函数。

    1. ps->a = tmp;
    2. ps->capacity = newCapacity;

    每一次进入这个扩容函数都要使相应的ps->a和ps->capacity发生变化才是真正的扩容。

    接下来,我们实现

    栈的删除

    1. void StackPop(ST*ps)
    2. {
    3. assert(ps);
    4. assert(!StackEmpty(ps));
    5. --ps->top;
    6. }
    assert(!StackEmpty(ps));

    这句话的意思是假如栈不为空时,进入函数。

    栈的访问:

    1. STDataType StackTop(ST*ps)
    2. {
    3. assert(ps);
    4. assert(!StackEmpty(ps));
    5. return ps->a[ps->top - 1];
    6. }

    栈的数量

    1. int StackSize(ST*ps)
    2. {
    3. assert(ps);
    4. return ps->top;
    5. }

    打印栈

    1. int main()
    2. {
    3. ST st;
    4. while (!StackEmpty(&st));
    5. {
    6. printf("%d ", StackTop(&st));
    7. StackPop(&st);
    8. }
    9. printf("\n");
    10. return 0;
    11. }
    while (!StackEmpty(&st))

    判断栈是否为空,不为空时,打印栈位置对应的元素,然后出栈,继续循环。

    力扣

    有效的括号

    1. bool isValid(char* s)
    2. {
    3. ST st;
    4. StackInit(&st);
    5. while (*s)
    6. {
    7. if (*s == '{' || *s == '}' || *s == '(')
    8. {
    9. StackPush(&st, *s);
    10. }
    11. else
    12. {
    13. if (StackEmpty(&st))
    14. return false;
    15. char top = StackTop(&st);
    16. StackPop(&st);
    17. if ((*s == '}'&&top != '{')
    18. || (*s == ']'&&top != '[')
    19. || (*s == ')'&&top != '('))
    20. {
    21. return false;
    22. }
    23. }
    24. ++s;
    25. }
    26. StackDestory(&st);
    27. }

    队列:

     队列需要用链表来实现。

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. typedef int QDataType;
    7. typedef struct QueueNode
    8. {
    9. struct QueueNode*next;
    10. QDataType data;
    11. }QNode;

    我们把链表重命名为QNode

    因为我们的队列是先进先出,也就是说我们入列是在队尾,出列是在队头,所以我们需要两个指针,分别指向链表的头和链表的尾。

    1. typedef struct Queue
    2. {
    3. QNode*head;
    4. QNode*tail;
    5. }Queue;

    创建一个结构体,结构体有两个指针,分别指向链表的头和链表的尾

    1. void QueueInit(Queue*pq);
    2. void QueueDestory(Queue*pq);
    3. void QueuePush(Queue*pq,QDataType x);
    4. void QueuePop(Queue*pq);
    5. QDataType QueueFront(Queue*pq);
    6. QDataType QueueBack(Queue*pq);
    7. bool QueueEmpty(Queue*pq);
    8. int QueueSize(Queue*pq);

    这是我们对应的链表的接口,分别代表:队列初始化,队列销毁,队列插入,队列删除,找到队列的头,找到队列的尾,判断队列是否为空,返回队列的元素个数。

    队列的初始化:

    1. void QueueInit(Queue*pq)
    2. {
    3. assert(pq);
    4. pq->head = pq->tail = NULL;
    5. }

    队列的销毁:

    1. void QueueDestory(Queue*pq)
    2. {
    3. assert(pq);
    4. QNode*cur = pq->head;
    5. while (cur)
    6. {
    7. QNode*del = cur;
    8. cur = cur->next;
    9. free(del);
    10. }
    11. pq->head = pq->tail = NULL;
    12. }

    我们画图进行分析:

     我们把cur赋给队列的头,然后进行遍历,把cur->next赋给cur,然后释放掉cur,直到cur=NULL。

    接下来,我们来实现队列的插入:

    1. void QueuePush(Queue*pq, QDataType x)
    2. {
    3. assert(pq);
    4. QNode*newnode = (QNode*)malloc(sizeof(QNode));
    5. if (newnode == NULL)
    6. {
    7. perror("malloc fail");
    8. exit(-1);
    9. }
    10. else
    11. {
    12. newnode->data = x;
    13. newnode->next = NULL;
    14. }
    15. if (pq->tail == NULL)
    16. {
    17. pq->head = pq->tail = newnode;
    18. }
    19. else
    20. {
    21. pq->tail->next = newnode;
    22. pq->tail = newnode;
    23. }
    24. }

    我们首先来分析代码:

    1. QNode*newnode = (QNode*)malloc(sizeof(QNode));
    2. if (newnode == NULL)
    3. {
    4. perror("malloc fail");
    5. exit(-1);
    6. }

    用动态内存的知识创建一个新的节点,如果动态内存申请失败,打印对应的错误信息,退出程序。

    1. else
    2. {
    3. newnode->data = x;
    4. newnode->next = NULL;
    5. }

    如果创建成功的话,newnode对应的值赋为x,并把他的下一位置为空指针。

    1. if (pq->tail == NULL)
    2. {
    3. pq->head = pq->tail = newnode;
    4. }

    假如pq->tail为空指针,就表示队列中没有其他的元素,那么队列的头和队列的尾就相同。

    1. else
    2. {
    3. pq->tail->next = newnode;
    4. pq->tail = newnode;
    5. }

    假如队列中本身就有元素,那么就在队尾尾插一个newnode节点。

    我们画图象进行解释:

     

     然后把newnode置为tail

    接下来,我们来讲删除队列元素

    我们首先来绘制图像:

    因为我们的队列只能够进行头删元素。

    然后把我们的head'后置

     

    然后释放掉del

     

     我们编写代码进行尝试:

    1. void QueuePop(Queue*pq)
    2. {
    3. assert(pq);
    4. assert(!QueueEmpty(pq));
    5. if (pq->head->next == NULL)
    6. {
    7. free(pq->head);
    8. pq->head = pq->tail = NULL;
    9. }
    10. else
    11. {
    12. QNode*del = pq->head;
    13. pq->head = pq->head->next;
    14. free(del);
    15. del = NULL;
    16. }
    17. }
    assert(!QueueEmpty(pq));

    这里表示我们需要鉴别队列是否为空,当队列为空的时候,我们是无法删除元素的,所以我们要进行断言。

    1. if (pq->head->next == NULL)
    2. {
    3. free(pq->head);
    4. pq->head = pq->tail = NULL;
    5. }

    这里是特殊情况,假如我们删除的是最后一个元素:

     然后我们释放掉最后一个元素。

     我们的tail就形成了野指针,所以我们要分情况

    当队列中只有一个元素时,我们释放掉该元素,然后把链表全部置为空。

    当链表有多个元素的时候:

    1. else
    2. {
    3. QNode*del = pq->head;
    4. pq->head = pq->head->next;
    5. free(del);
    6. del = NULL;
    7. }

    取队头和队尾的数据:

    1. QDataType QueueFront(Queue*pq)
    2. {
    3. assert(pq);
    4. assert(!QueueEmpty(pq));
    5. return pq->head->data;
    6. }
    7. QDataType QueueBack(Queue*pq)
    8. {
    9. assert(pq);
    10. assert(!QueueEmpty(pq));
    11. return pq->tail->data;
    12. }

    判断队列是否为空

    1. bool QueueEmpty(Queue*pq)
    2. {
    3. assert(pq);
    4. return pq->head == NULL&&pq->tail == NULL;
    5. }

    查找链表的元素:

    1. int QueueSize(Queue*pq)
    2. {
    3. assert(pq);
    4. QNode*cur = pq->head;
    5. int n = 0;
    6. while (cur)
    7. {
    8. ++n;
    9. cur = cur->next;
    10. }
    11. return n;
    12. }

  • 相关阅读:
    JVM内存模型(JMM)
    网络知识——局域网和交换机
    foo 是什么意思
    day04-JavaScript01
    懵了?一夜之间,Rust 审核团队突然集体辞职
    Java面试——专业技能
    03--nginx架构实战
    DC-DC 保护调试经验
    Java线程安全问题
    RAW、RGB 、YUV三种图像格式理解
  • 原文地址:https://blog.csdn.net/qq_66581313/article/details/126259707