程序中的栈和队列也就这些事

1、什么是栈和队列

栈的定义

栈(Stack)是一种遵循后进先出(Last In First Out,简称LIFO)原则的数据结构。栈可以看作是一个容器,元素被加入和删除的地方只有一端,这一端被称为栈顶(top),另一端被称为栈底(bottom)。在栈中,新元素被添加到栈顶,任何时候只能访问栈顶的元素。删除元素时也是从栈顶开始,即最后添加的元素最先被删除。

栈的示意图

在这里插入图片描述

队列的定义

队列(Queue)是一种遵循先进先出(First In First Out,简称FIFO)原则的线性数据结构。队列可以看作是一个容器,元素被加入和删除的地方分别称为队尾(rear)和队头(front),新元素被添加到队尾,而删除元素时则从队头开始。

队列的示意图

在这里插入图片描述

2、栈和队列的实现方式

数组实现

通过数组实现栈

栈需要支持两个操作:压入(push)和弹出(pop)。我们可以使用一个数组来实现它。

首先,我们需要定义一个数组和一个栈顶指针。栈顶指针指向当前栈顶元素的位置。

#define MAX_SIZE 10 // 定义栈的最大大小

int stack[MAX_SIZE]; // 定义栈
int top = -1; // 初始化栈顶指针

压入操作(push)就是将元素放到栈顶,并将栈顶指针加一。如果栈已满,则无法执行该操作。

void push(int element) {
    if (top >= MAX_SIZE - 1) { // 栈已满
        printf("Stack is full\n");
    } else { // 栈未满
        top++; // 将栈顶指针加一
        stack[top] = element; // 将元素放到栈顶
    }
}

弹出操作(pop)就是将栈顶元素取出,并将栈顶指针减一。如果栈为空,则无法执行该操作。

int pop() {
    if (top < 0) { // 栈为空
        printf("Stack is empty\n");
        return -1;
    } else { // 栈非空
        int element = stack[top]; // 取出栈顶元素
        top--; // 将栈顶指针减一
        return element;
    }
}

通过数组实现队列

队列需要支持两个操作:入队(enqueue)和出队(dequeue)。我们可以使用一个数组来实现它。

首先,我们需要定义一个数组、一个队头指针和一个队尾指针。队头指针指向当前队头元素的位置,队尾指针指向下一个可用的位置。

#define MAX_SIZE 10 // 定义队列的最大大小

int queue[MAX_SIZE]; // 定义队列
int front = 0; // 初始化队头指针
int rear = 0; // 初始化队尾指针

入队操作(enqueue)就是将元素放到队尾,并将队尾指针加一。如果队列已满,则无法执行该操作。`

void enqueue(int element) {
    if ((rear + 1) % MAX_SIZE == front) { // 队列已满
        printf("Queue is full\n");
    } else { // 队列未满
        queue[rear] = element; // 将元素放到队尾
        rear = (rear + 1) % MAX_SIZE; // 将队尾指针加一
    }
}

链表实现

通过链表实现栈

首先,我们需要定义链表节点的结构体,它应该至少包含两个成员:一个数据成员和一个指向下一个节点的指针。

typedef struct Node {
    int data;
    struct Node* next;
} Node;

接下来,我们可以定义一个栈结构体,其中包含一个指向栈顶的指针。要初始化栈,只需把栈顶指针设置为NULL即可。`

typedef struct Stack {
    Node* top;
} Stack;

void init(Stack* stack) {
    stack->top = NULL;
}

当我们要往栈中插入一个元素时,我们需要创建一个新的节点,将数据存储在这个节点中,并把这个节点插入到栈顶。具体操作如下:

void push(Stack* stack, int value) {
    Node* node = (Node*) malloc(sizeof(Node));
    node->data = value;
    node->next = stack->top;
    stack->top = node;
}

当我们要从栈中取出一个元素时,我们只需要从栈顶取出这个节点,并将栈顶指针指向下一个节点即可。

int pop(Stack* stack) {
    if (stack->top == NULL) {
        printf("Stack is empty!\n");
        return -1;
    }
    int value = stack->top->data;
    Node* temp = stack->top;
    stack->top = stack->top->next;
    free(temp);
    return value;
}

通过链表实现队列

定义链表节点的方法与使用链表实现栈时相同。

typedef struct Node {
    int data;
    struct Node* next;
} Node;

我们可以定义一个队列结构体,其中包含两个指针:一个指向队列头部的指针和一个指向队列尾部的指针。要初始化队列,只需把这两个指针都设置为NULL即可。

typedef struct Queue {
    Node* head;
    Node* tail;
} Queue;

void init(Queue* queue) {
    queue->head = NULL;
    queue->tail = NULL;
}

我们往队列中插入一个元素时,需要创建一个新节点,将数据存储在这个节点中,并把这个节点插入到队列尾部。具体操作如下:

void enqueue(Queue* queue, int value) {
    Node* node = (Node*) malloc(sizeof(Node));
    node->data = value;
    node->next = NULL;
    if (queue->tail == NULL) {
        queue->head = node;
        queue->tail = node;
    } else {
        queue->tail->next = node;
        queue->tail = node;
    }
}

从队列中取出元素时,我们需要从队列的头部开始取出节点,并将队列头部指针指向下一个节点即可。

int dequeue(Queue* queue) {
    if (queue->head == NULL) {
        printf("Queue is empty!\n");
        return -1;
    }
    int value = queue->head->data;
    Node* temp = queue->head;

3、栈和队列的应用

栈的应用场景

栈是一种常见的数据结构,其遵循先进后出(LIFO)的原则。栈广泛应用于计算机科学领域的各个方面,如编译器、操作系统、数据结构等。下面给出几个栈在实际应用中的例子,并以C语言为例:

  • 表达式求值:编译器通常使用栈来解析和评估表达式。例如,可以使用栈来实现中缀表达式转换为后缀表达式并进行求值。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define MAX_SIZE 100

int stack[MAX_SIZE];
int top = -1;

void push(int val) {
    if (top >= MAX_SIZE - 1) {
        printf("Stack overflow\n");
        exit(1);
    }
    stack[++top] = val;
}

int pop() {
    if (top == -1) {
        printf("Stack underflow\n");
        exit(1);
    }
    return stack[top--];
}

int evaluate_postfix(char* postfix) {
    int i, val, op1, op2;
    for (i = 0; postfix[i] != '\0'; i++) {
        if (isdigit(postfix[i])) {
            push(postfix[i] - '0');
        } else {
            op2 = pop();
            op1 = pop();
            switch (postfix[i]) {
                case '+':
                    val = op1 + op2;
                    break;
                case '-':
                    val = op1 - op2;
                    break;
                case '*':
                    val = op1 * op2;
                    break;
                case '/':
                    val = op1 / op2;
                    break;
                default:
                    printf("Invalid operator\n");
                    exit(1);
            }
            push(val);
        }
    }
    return pop();
}

int main() {
    char postfix[] = "23+4*5-";
    int result = evaluate_postfix(postfix);
    printf("Result: %d\n", result);
    return 0;
}


  • 函数调用:当一个函数被调用时,CPU将指令指针(程序计数器)压入栈中。当函数返回时,CPU从栈中弹出指令指针并恢复执行。
#include <stdio.h>

void func2() {
    printf("Inside func2()\n");
}

void func1() {
    printf("Inside func1()\n");
    func2();
}

int main() {
    printf("Inside main()\n");
    func1();
    printf("Exiting main()\n");
    return 0;
}

  • 内存分配:许多操作系统使用栈来管理内存分配和释放。例如,当一个函数被调用时,操作系统为其分配一段内存空间,并在函数返回时释放这些内存。
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100

int* create_array(int size) {
    int* arr = (int*) malloc(size * sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed\n");
        exit(1);
    }
    return arr;
}

int main() {
    int size = 10;
    int* arr = create_array(size);
    for (int i = 0; i < size; i++) {
        arr[i] = i + 1;
    }
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    free(arr);
    return 0;
}

队列的应用场景

  • 线程池:假设我们有一个线程池来处理一些任务,我们需要使用队列来管理这些任务。在 C语言中,我们可以定义一个队列结构体来表示队列的属性,然后编写入队和出队函数来操作队列。
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 10

struct Queue {
    int front;
    int rear;
    int data[MAX_SIZE];
};

void enqueue(struct Queue *q, int value) {
    if (q->rear == MAX_SIZE - 1) {
        printf("Queue is full!\n");
    } else {
        if (q->front == -1)
            q->front = 0;
        q->rear++;
        q->data[q->rear] = value;
    }
}

int dequeue(struct Queue *q) {
    if (q->front == -1 || q->front > q->rear) {
        printf("Queue is empty!\n");
        return -1;
    } else {
        int value = q->data[q->front];
        q->front++;
        return value;
    }
}

int main() {
    struct Queue q = {-1, -1, {0}};
    enqueue(&q, 10);
    enqueue(&q, 20);
    enqueue(&q, 30);
    printf("Dequeued value: %d\n", dequeue(&q));
    printf("Dequeued value: %d\n", dequeue(&q));
    return 0;
}

  • 操作系统调度:我们可以使用队列来实现操作系统的进程调度。在 C
    语言中,我们可以使用结构体来表示每个进程的属性,然后将它们存储在队列中,按照优先级顺序排列。然后执行队列头部的进程,直到队列为空。
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 10

struct Process {
    int id;
    int priority;
};

struct Queue {
    int front;
    int rear;
    struct Process data[MAX_SIZE];
};

void enqueue(struct Queue *q, struct Process p) {
    if (q->rear == MAX_SIZE - 1) {
        printf("Queue is full!\n");
    } else {
        if (q->front == -1)
            q->front = 0;
        q->rear++;
        q->data[q->rear] = p;
    }
}

struct Process dequeue(struct Queue *q) {
    if (q->front == -1 || q->front > q->rear) {
        printf("Queue is empty!\n");
        struct Process p = {-1, -1};
        return p;
    } else {
        struct Process p = q->data[q->front];
        q->front++;
        return p;
    }
}

int main() {
    struct Process p1 = {100, 3};
    struct Process p2 = {101, 1};
    struct Process p3 = {102, 2};
    struct Queue q = {-1, -1, {{0}}};
    enqueue(&q, p1);
    enqueue(&q, p2);
    enqueue(&q, p3);
    while (q.front <= q.rear) {
        struct Process p = dequeue(&q);
        printf("Process id: %d, priority: %d\n", p.id, p.priority);
    }
    return 0;
}

  • 计算机网络:我们可以使用队列来管理网络协议栈中的数据包。在 C
    语言中,我们可以定义一个数据包结构体来表示数据包的属性,然后将它们存储在队列中,按照发送顺序排列。然后从队列头部取出数据包并进行处理,直到队列为空。
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 10

struct Packet {
    int id;
    int size;
};

struct Queue {
    int front;
    int rear;
    struct Packet data[MAX_SIZE];
};

void enqueue(struct Queue *q, struct Packet p) {
    if (q->rear == MAX_SIZE - 1) {
        printf("Queue is full!\n");
    } else {
        if (q->front == -1)
            q->front = 0;
        q->rear++;
        q->data[q->rear] = p;
    }
}

struct Packet dequeue(struct Queue *q) {
    if (q->front == -1 || q->front > q->rear) {
        printf("Queue is empty!\n");
        struct Packet p = {-1, -1};
        return p;
    } else {
        struct Packet p = q->data[q->front];
        q->front++;
        return p;
    }
}

int main() {
    struct Packet p1 = {100, 1024};
    struct Packet p2 = {101, 512};
    struct Packet p3 = {102, 2048};
    struct Queue q = {-1, -1, {{0}}};
    enqueue(&q, p1);
    enqueue(&q, p2);
    enqueue(&q, p3);
    while (q.front <= q.rear) {
        struct Packet p = dequeue(&q);
        printf("Packet id: %d, size: %d\n", p.id, p.size);
    }
    return 0;
}

4、栈和队列的区别和联系

栈和队列的区别

  • 栈和队列是两种常见的数据结构,都是线性结构。它们之间最主要的区别在于元素的添加和删除顺序不同。
  • 栈是一种后进先出(LIFO)的数据结构。这意味着最后被添加到栈中的元素最先被取出。栈只允许在栈顶进行元素的添加和删除操作,因此称为后进先出。当需要对某个元素进行处理时,只能从栈顶开始弹出元素。栈的基本操作包括push、pop、peek、is_empty和is_full。其中,push方法将元素压入栈顶,pop方法将栈顶元素弹出并返回,peek方法返回栈顶元素但不弹出,is_empty方法判断栈是否为空,is_full方法判断栈是否已满(仅针对固定大小的数组实现)。
  • 队列是一种先进先出(FIFO)的数据结构。这意味着最先被添加到队列中的元素最先被取出。队列允许在队尾进行元素的添加,在队首进行元素的删除操作,因此称为先进先出。队列的基本操作包括enqueue、dequeue、peek、is_empty和is_full。其中,enqueue方法将元素插入队列的末尾,dequeue方法从队列的头部删除一个元素并返回,peek方法返回队列头部的元素但不删除,is_empty方法判断队列是否为空,is_full方法判断队列是否已满(仅针对固定大小的数组实现)。
  • 栈和队列在应用中有着广泛的运用。栈常用于对数据的暂存或延迟处理,以及表达式求值、函数调用等场景。而队列常用于实现缓存、任务调度、消息传递等场景,它们需要按照先进先出的顺序进行处理。
  • 在编写程序时,我们需要根据具体的需求选择合适的数据结构。如果需要在某个操作后再次处理该操作的结果,可以使用栈;如果需要按照先进先出的顺序处理元素,可以使用队列。

栈和队列的联系

虽然栈和队列在很多方面有不同的特点和操作方法,但它们也有一些共同点,下面是它们的联系:

  • 线性结构:栈和队列都是线性结构,它们的元素都可以通过一定的顺序排列。栈中的元素只能从栈顶进出,而队列中的元素只能从队头出,从队尾进。
  • 存储方式:栈和队列都可以使用数组或链表来实现。
  • 操作方法:栈和队列都有基本的操作方法,如插入、删除、查看等。虽然它们的方法名称可能不同,但它们的作用大致相同。
  • 应用场景:栈和队列都有广泛的应用场景,如计算机程序设计、操作系统、数据压缩、网络通信等。
  • 计算机底层实现:在计算机底层,栈和队列都是非常重要的数据结构。比如函数调用就是通过栈来实现的,操作系统的任务调度也是通过队列来实现的。
  • 数据结构基础:栈和队列都是数据结构中比较基础的部分。其他许多复杂的数据结构如图、树等都与它们相关联。
  • 限制性操作:栈和队列都具有一定的限制性操作。栈只允许从顶部添加或删除元素,而队列只允许在队尾添加元素,在队首删除元素。这种限制性的操作可以减少程序出错的可能性。
  • 嵌套关系:栈和队列可以互相嵌套使用。比如,在计算某个表达式的值时,可以使用一个栈来保存数字和运算符,再使用一个队列来处理括号。
  • 算法实现:许多算法都涉及到栈和队列的使用。例如,深度优先搜索(DFS)中使用栈,广度优先搜索(BFS)中使用队列。
  • 可扩展性:虽然数组实现的栈和队列大小固定,但是对于链表实现的栈和队列来说,它们的大小是可扩展的。

希望这篇博客能够给大家带来收获,如果有错误的地方还希望各位大佬多多指正,欢迎评论!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
栈和队列都是常用的数据结构,用于存储和操作一系列的元素。下面是关于C语言栈和队列的概要设计: 1. 栈的概要设计: - 栈是一种后进先出(LIFO)的数据结构,类似于我们日常生活的堆栈,只能从顶部进行插入和删除操作。 - 声明栈结构体,其包含一个数组用于存储元素,以及一个整数用于记录栈顶的位置。 - 设计栈的初始化函数,用于初始化栈的大小和栈顶的位置。 - 实现栈的push函数,即将一个元素放入栈,需要更新栈顶的位置。 - 实现栈的pop函数,即从栈弹出一个元素,也需要更新栈顶的位置。 - 可以选择添加其他辅助函数,如栈是否为空、栈是否已满等等。 2. 队列的概要设计: - 队列是一种先进先出(FIFO)的数据结构,类似于我们日常生活排队的场景,只能从队列尾部插入元素,从队列头部删除元素。 - 声明队列结构体,其包含一个数组用于存储元素,以及两个整数用于记录队列头部和尾部的位置。 - 设计队列的初始化函数,用于初始化队列的大小和头尾指针的位置。 - 实现队列的enqueue函数,即将一个元素插入到队列的尾部,同时更新尾部指针。 - 实现队列的dequeue函数,即删除队列头部的元素,同时更新头部指针。 - 可以选择添加其他辅助函数,如队列是否为空、队列是否已满等等。 总结: 栈和队列是常用的数据结构,用于在程序实现对元素的存储和操作。栈是一种后进先出的数据结构,而队列则是一种先进先出的数据结构。在C语言,可以通过结构体和数组的组合来实现栈和队列的概要设计,包括初始化函数、插入/删除操作函数以及一些辅助函数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值