当前位置:   article > 正文

数据结构_栈和队列学习

数据结构_栈和队列学习

第一部分:栈的理解与C语言实现

栈的基本概念
  • 定义:栈是一种线性数据结构,遵循“后进先出”(LIFO, Last In First Out)原则,类似于生活中的盘子堆叠。
  • 操作特性:主要操作包括压栈(push)、弹栈(pop)和查看栈顶元素,不支持随机访问。
数组实现栈

        数组:简单易实现,空间固定,可能造成空间浪费或溢出。

函数设计
  • 初始化栈:分配内存,设置栈顶指针。使用数组在实现栈
  • 入栈操作(pushstack):在栈顶添加元素,更新栈顶指针。
  • 出栈操作(popstack):移除栈顶元素,更新栈顶指针。
  • 查看栈顶元素(peek):返回栈顶元素,不改变栈状态。
  • 判断栈是否为空(is_empty):检查栈是否没有元素。
示例代码
  1. 1#include <stdio.h>
  2. 2#define MAX_SIZE 100
  3. 3
  4. 4typedef struct {
  5. 5 int data[MAX_SIZE];//静态数组
  6. 6 int top;
  7. 7} Stack;
  8. 8
  9. 9void initStack(Stack* s) {
  10. 10 s->top = -1; // 初始化栈顶指针为-1,表示空栈
  11. 11}
  12. 12
  13. 13void pushStack(Stack* s, int item) {
  14. 14 if (s->top == MAX_SIZE - 1) {
  15. 15 printf("Stack Overflow\n");
  16. 16 return;
  17. 17 }
  18. 18 s->data[++(s->top)] = item;
  19. 19}
  20. 20
  21. 21int popStack(Stack* s) {
  22. 22 if (s->top == -1) {
  23. 23 printf("Stack Underflow\n");
  24. 24 return -1;
  25. 25 }
  26. 26 return s->data[s->top--];
  27. 27}
  28. 28
  29. 29int peek(Stack* s) {
  30. 30 if (s->top != -1)
  31. 31 return s->data[s->top];
  32. 32 else
  33. 33 return -1; // 或其他错误码
  34. 34}
  35. 35
  36. 36int is_empty(Stack* s) {
  37. 37 return s->top == -1;
  38. 38}
链表实现栈

        

实现思路
  • 节点定义:每个节点包含数据和指向下一个节点的指针。
  • 栈结构:包含一个指向栈顶节点的指针。
函数设计
  • 初始化栈:栈顶指针设为NULL。
  • 入栈操作(push):创建新节点,将其连接到当前栈顶节点。
  • 出栈操作(pop):保存栈顶节点信息,更新栈顶指针为栈顶节点的下一个节点,然后释放原栈顶节点。
  • 查看栈顶(peek):直接返回栈顶节点的数据。
  • 判断空(is_empty):检查栈顶指针是否为NULL。

示例代码
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. typedef struct Node {
  4. int data;
  5. struct Node* next;
  6. } Node;
  7. typedef struct Stack {
  8. Node* top;
  9. } Stack;
  10. void initStack(Stack* s) {
  11. s->top = NULL;
  12. }
  13. void push(Stack* s, int item) {
  14. Node* newNode = (Node*)malloc(sizeof(Node));
  15. if (newNode == NULL) {
  16. printf("Memory allocation failed\n");
  17. return;
  18. }
  19. newNode->data = item;
  20. newNode->next = s->top;
  21. s->top = newNode;
  22. }
  23. int pop(Stack* s) {
  24. if (s->top == NULL) {
  25. printf("Stack Underflow\n");
  26. return -1;
  27. }
  28. Node* temp = s->top;
  29. int data = temp->data;
  30. s->top = temp->next;
  31. free(temp);
  32. return data;
  33. }
  34. int peek(Stack* s) {
  35. if (s->top != NULL)
  36. return s->top->data;
  37. else
  38. return -1;
  39. }
  40. int isEmpty(Stack* s) {
  41. return s->top == NULL;
  42. }
  43. // 注意:记得在程序结束时释放栈中所有节点的内存
使用数组或链表实现优劣评价
  • 优点
    • 动态大小,理论上不受限于预先分配的内存大小。
    • 插入和删除操作时间复杂度为O(1),高效。
  • 缺点
    • 需要额外的内存开销用于存储节点的指针。
    • 相比数组,链表的随机访问性能较差。

第二部分:队列的理解与C语言实践

队列的基础理论
  • 定义:遵循“先进先出”(FIFO, First In First Out)原则,与栈相反。
  • 常见类型
    • 循环队列:解决传统队列头尾指针移动问题,空间利用更高效。
    • 链式队列:通过链表实现,动态调整大小,但访问速度相对慢。
应用领域
  • 缓存系统、任务调度、广度优先搜索(BFS)等。
使用数组实现队列
  • 数据结构选择与比较:与栈类似,数组实现简单但可能有假满/假空问题,链式队列解决了这一问题。
函数设计
  • 初始化队列:清空队列,设置头尾指针。
  • 入队操作(pushqueue):在队尾添加元素,更新尾指针。
  • 出队操作(popqueue):从队首移除元素,更新头指针。
  • 查看队首元素(front):返回队首元素,不修改队列。
  • 判断队列是否为空(is_empty):检查队列是否为空。
循环队列的实现细节与优势
  • 细节:当队列满时,可以通过将尾指针回绕到数组起始位置来重新开始填充。
  • 优势:解决了普通队列的空间浪费问题,提高了空间利用率。
示例代码与分析
  1. 1#include <stdio.h>
  2. 2#define MAX_SIZE 100
  3. 3
  4. 4typedef struct {
  5. 5 int data[MAX_SIZE];
  6. 6 int front, rear;
  7. 7} Queue;
  8. 8
  9. 9void initQueue(Queue* q) {
  10. 10 q->front = q->rear = 0;
  11. 11}
  12. 12
  13. 13void enqueue(Queue* q, int item) {
  14. 14 if ((q->rear + 1) % MAX_SIZE == q->front) {
  15. 15 printf("Queue Overflow\n");
  16. 16 return;
  17. 17 }
  18. 18 q->data[q->rear] = item;
  19. 19 q->rear = (q->rear + 1) % MAX_SIZE;
  20. 20}
  21. 21
  22. 22int dequeue(Queue* q) {
  23. 23 if (q->front == q->rear) {
  24. 24 printf("Queue Underflow\n");
  25. 25 return -1;
  26. 26 }
  27. 27 int item = q->data[q->front];
  28. 28 q->front = (q->front + 1) % MAX_SIZE;
  29. 29 return item;
  30. 30}
  31. 31
  32. 32int front(Queue* q) {
  33. 33 if (!is_empty(q))
  34. 34 return q->data[q->front];
  35. 35 else
  36. 36 return -1; // 或其他错误码
  37. 37}
  38. 38
  39. 39int is_empty(Queue* q) {
  40. 40 return q->front == q->rear;
  41. 41}
使用链表实现队列
实现思路
  • 链式队列有两种常见方式
    • 单向队列:每个节点包含数据和指向下一个节点的指针。需要额外的队尾指针。
    • 双向队列:每个节点包含数据、前驱指针和后继指针。可以更高效地在两端进行插入和删除操作。
函数设计(以单向队列为示例)
  • 初始化队列:前后指针均设为NULL。
  • 入队操作(enqueue):在队尾添加新节点,更新队尾指针。
  • 出队操作(dequeue):保存队首节点信息,更新队首指针为其后继,然后释放原队首节点。
  • 查看队首(front):直接返回队首节点的数据。
  • 判断空(is_empty):检查队首指针是否为NULL。
示例代码
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. typedef struct Node {
  4. int data;
  5. struct Node* next;
  6. } Node;
  7. typedef struct Queue {
  8. Node* front;
  9. Node* rear;
  10. } Queue;
  11. void initQueue(Queue* q) {
  12. q->front = q->rear = NULL;
  13. }
  14. void enqueue(Queue* q, int item) {
  15. Node* newNode = (Node*)malloc(sizeof(Node));
  16. if (newNode == NULL) {
  17. printf("Memory allocation failed\n");
  18. return;
  19. }
  20. newNode->data = item;
  21. newNode->next = NULL;
  22. if (q->rear == NULL)
  23. q->front = q->rear = newNode;
  24. else {
  25. q->rear->next = newNode;
  26. q->rear = newNode;
  27. }
  28. }
  29. int dequeue(Queue* q) {
  30. if (q->front == NULL) {
  31. printf("Queue Underflow\n");
  32. return -1;
  33. }
  34. Node* temp = q->front;
  35. int data = temp->data;
  36. q->front = temp->next;
  37. if (q->front == NULL)
  38. q->rear = NULL;
  39. free(temp);
  40. return data;
  41. }
  42. int front(Queue* q) {
  43. if (q->front != NULL)
  44. return q->front->data;
  45. else
  46. return -1;
  47. }
  48. int isEmpty(Queue* q) {
  49. return q->front == NULL;
  50. }
  51. // 同样,确保在程序结束时释放队列中所有节点的内存

优劣评价

  • 优点
    • 动态大小,灵活应对数据量变化。
    • 插入和删除操作同样保持O(1)的时间复杂度。
  • 缺点
    • 与链式栈相似,链式队列也有额外的内存开销。
    • 相对于循环数组实现的队列,链式队列的访问效率较低,特别是随机访问。

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/596677
推荐阅读
相关标签
  

闽ICP备14008679号