数据结构(栈)

1栈的定义和操作

栈(Stack)是一种线性数据结构,遵循后进先出(LIFO, Last In First Out)的原则。这意味着最后一个被放入栈中的元素会第一个被取出。栈通常用于需要在其中临时存储数据并按照特定顺序访问这些数据的场景。

栈的基本操作

  1. Push(压栈)

    • 将一个元素放入栈的顶部。
    • 如果栈已经满了,可能会导致栈溢出(Stack Overflow)。
  2. Pop(弹栈)

    • 从栈的顶部移除一个元素,并返回该元素。
    • 如果栈是空的,可能会导致栈下溢(Stack Underflow)。
  3. Peek(查看栈顶)

    • 查看栈顶的元素,但不移除它。
    • 这个操作不会改变栈的状态。
  4. IsEmpty(判断栈是否为空)

    • 检查栈是否为空。
    • 返回一个布尔值,通常是 truefalse
  5. IsFull(判断栈是否已满)

    • 检查栈是否已满。
    • 返回一个布尔值,通常是 truefalse

栈的实现方式

栈可以通过数组或链表来实现:

  1. 数组实现

    • 需要预先分配固定大小的数组。
    • 优点是存取时间快,但缺点是大小固定。
  2. 链表实现

    • 使用链表节点动态分配内存。
    • 优点是大小灵活,但存取时间可能稍慢。

栈的应用

栈在计算机科学中有广泛的应用,例如:

  1. 函数调用

    • 在程序执行过程中,函数调用和返回的顺序与栈的操作模式一致。
  2. 表达式求值

    • 使用栈来解析和计算数学表达式,例如中缀表达式转换为后缀表达式。
  3. 括号匹配

    • 使用栈来检查括号是否正确匹配。
  4. 浏览器历史记录

    • 浏览器的前进和后退功能可以使用两个栈来实现。
  5. 回溯算法

    • 在需要回溯的算法中,栈用于保存当前状态以便回退。

2栈的存储和实现

2.1顺序栈

1顺序栈的定义和初始化

固定长度顺序栈的定义

我们首先定义一个结构体 SeqStack,其中包含一个固定长度的数组 data 来存储栈的元素,以及一个整型变量 top 来表示栈顶的位置。

cs 复制代码
#define MAX_SIZE 5  // 定义栈的最大容量

typedef struct {
    int data[MAX_SIZE];  // 存储栈元素的数组
    int top;             // 栈顶指针
} SeqStack;

固定长度顺序栈的初始化

初始化顺序栈时,我们需要将栈顶指针 top 设置为 -1,表示栈为空。

cs 复制代码
void initStack(SeqStack* stack) {
    stack->top = -1;  // 初始化栈顶指针为 -1,表示栈为空
}

2动态分配顺序栈初始化

cs 复制代码
#define INITIAL_SIZE 10 // 初始容量

// 顺序栈结构
typedef struct {
    int *data; // 存储栈元素的动态数组
    int top;   // 当前栈顶元素的索引
    int size;  // 栈的当前容量
} SeqStack;

// 初始化顺序栈
SeqStack* initStack() {
    SeqStack* stack = (SeqStack*)malloc(sizeof(SeqStack)); // 分配栈结构的内存
    if (!stack) {
        printf("内存分配失败\n");
        return NULL;
    }
    stack->data = (int*)malloc(INITIAL_SIZE * sizeof(int)); // 分配栈数据的内存
    if (!stack->data) {
        printf("内存分配失败\n");
        free(stack); // 如果数据分配失败,释放栈结构的内存
        return NULL;
    }
    stack->top = -1;  // 栈顶索引初始化为 -1(表示栈为空)
    stack->size = INITIAL_SIZE; // 初始容量设置
    return stack;
}

2顺序栈的基本操作

判断栈是否为空

cs 复制代码
// 判断栈是否为空的函数
// 返回值: 如果栈为空,返回 1;否则返回 0
int isEmpty(SeqStack* stack) {
    if (stack->top == -1) {
        // 当栈顶索引为 -1 时,表示栈中没有元素,栈为空
        return 1; // 栈为空
    } else {
        return 0; // 栈非空
    }
}

判断栈是否已满

cs 复制代码
// 判断栈是否已满的函数
// 参数: stack - 指向顺序栈的指针
// 返回值: 如果栈已满,返回 1;否则返回 0
int isFull(SeqStack* stack) {
    if (stack->top >= stack->size - 1) {
        // 当栈顶索引大于等于栈的最大容量减 1 时,表示栈已满
        return 1; // 栈已满
    } else {
        return 0; // 栈未满
    }
}

顺序栈的进栈

cs 复制代码
int push(SeqStack* stack, int value) {
    // 检查是否已满
    if (isFull(stack)) {
        // 如果已满,扩展栈的容量
        int newSize = stack->size * 2; // 新容量为当前容量的两倍
        int* newData = (int*)realloc(stack->data, newSize * sizeof(int)); // 扩容
        if (!newData) {
            printf("内存重新分配失败\n");
            return 0; // 扩容失败,返回 0
        }
        stack->data = newData; // 更新数据指针
        stack->size = newSize; // 更新栈的容量
    }
    
    // 先更新栈顶索引,再将元素添加到栈顶
    stack->top++; // 将栈顶索引加 1
    stack->data[stack->top] = value; // 将新元素赋值给栈顶
    return 1; // 返回成功标志
}

顺序栈的出栈

cs 复制代码
int pop(SeqStack* stack) {
    // 检查栈是否为空
    if (isEmpty(stack)) {
        printf("栈为空,无法出栈\n");
        return -1; // 栈为空,返回 -1
    }
    
    // 1. 获取当前栈顶元素
    int topElement = stack->data[stack->top];
    
    // 2. 更新栈顶索引,表示出栈操作
    stack->top--;
    
    // 返回栈顶元素
    return topElement;
}

获取栈顶元素

cs 复制代码
// 获取栈顶元素
int top(SeqStack* stack) {
    // 检查栈是否为空
    if (isEmpty(stack)) {
        printf("栈为空,无法获取栈顶元素\n");
        return -1; // 栈为空,返回 -1
    }
    
    // 返回当前栈顶元素
    return stack->data[stack->top]; // 直接访问栈顶元素
}

2.2链式栈

1链式栈的定义与初始化

.链式栈的节点定义

首先,我们需要定义链式栈的节点结构体。每个节点将包含一个数据字段和一个指向下一个节点的指针。

cs 复制代码
#include <stdio.h>
#include <stdlib.h>

// 链式栈节点结构
typedef struct Node {
    int data;               // 节点存储的数据
    struct Node* next;     // 指向下一个节点的指针
} Node;

链式栈的结构定义

接下来,我们定义链式栈的结构体。它将包含一个指向栈顶节点的指针。

cs 复制代码
// 链式栈结构
typedef struct {
    Node* top;  // 指向栈顶节点的指针
} LinkStack;

初始化链式栈

初始化链式栈时,我们只需将栈顶指针设置为 NULL,表示栈初始为空。

cs 复制代码
// 初始化链式栈
LinkStack* initLinkStack() {
    LinkStack* stack = (LinkStack*)malloc(sizeof(LinkStack)); // 分配内存
    if (stack == NULL) {
        printf("内存分配失败\n");
        return NULL; // 初始化失败
    }
    stack->top = NULL; // 初始化栈顶指针为 NULL
    return stack; // 返回初始化后的栈
}

2链式栈的基本操作

判断栈是否为空

cs 复制代码
// 判断栈是否为空
int isEmpty(LinkStack* stack) {
    return stack->top == NULL; // 返回栈顶指针是否为 NULL
}

入栈操作(push)

cs 复制代码
// 入栈操作
void push(LinkStack* stack, int value) {
    // 1. 创建新节点
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        printf("内存分配失败\n");
        return;
    }
    
    // 2. 设置新节点的数据和指针
    newNode->data = value;   // 设置新节点的数据
    newNode->next = stack->top; // 新节点的下一个节点指向当前的栈顶节点
    
    // 3. 更新栈顶指针
    stack->top = newNode;    // 栈顶指针指向新节点
}

出栈操作(pop)

cs 复制代码
// 出栈操作
int pop(LinkStack* stack) {
    // 1. 检查栈是否为空
    if (isEmpty(stack)) {
        printf("栈为空,无法出栈\n");
        return -1; // 栈为空,返回 -1
    }
    
    // 2. 获取当前栈顶节点的数据
    Node* topNode = stack->top; // 获取当前栈顶节点
    int value = topNode->data;  // 获取当前栈顶节点的数据
    
    // 3. 更新栈顶指针
    stack->top = topNode->next; // 栈顶指针指向下一个节点
    
    // 4. 释放原栈顶节点
    free(topNode);              // 释放原栈顶节点的内存
    
    // 5. 返回出栈的元素
    return value;
}

完整示例代码:

cs 复制代码
#include <stdio.h>
#include <stdlib.h>

// 链式栈节点结构
typedef struct Node {
    int data;               // 节点存储的数据
    struct Node* next;     // 指向下一个节点的指针
} Node;

// 链式栈结构
typedef struct {
    Node* top;  // 指向栈顶节点的指针
} LinkStack;

// 初始化链式栈
LinkStack* initLinkStack() {
    LinkStack* stack = (LinkStack*)malloc(sizeof(LinkStack)); // 分配内存
    if (stack == NULL) {
        printf("内存分配失败\n");
        return NULL; // 初始化失败
    }
    stack->top = NULL; // 初始化栈顶指针为 NULL
    return stack; // 返回初始化后的栈
}

// 判断栈是否为空
int isEmpty(LinkStack* stack) {
    return stack->top == NULL; // 返回栈顶指针是否为 NULL
}

// 入栈操作
void push(LinkStack* stack, int value) {
    // 1. 创建新节点
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        printf("内存分配失败\n");
        return;
    }
    
    // 2. 设置新节点的数据和指针
    newNode->data = value;   // 设置新节点的数据
    newNode->next = stack->top; // 新节点的下一个节点指向当前的栈顶节点
    
    // 3. 更新栈顶指针
    stack->top = newNode;    // 栈顶指针指向新节点
}

// 出栈操作
int pop(LinkStack* stack) {
    // 1. 检查栈是否为空
    if (isEmpty(stack)) {
        printf("栈为空,无法出栈\n");
        return -1; // 栈为空,返回 -1
    }
    
    // 2. 获取当前栈顶节点的数据
    Node* topNode = stack->top; // 获取当前栈顶节点
    int value = topNode->data;  // 获取当前栈顶节点的数据
    
    // 3. 更新栈顶指针
    stack->top = topNode->next; // 栈顶指针指向下一个节点
    
    // 4. 释放原栈顶节点
    free(topNode);              // 释放原栈顶节点的内存
    
    // 5. 返回出栈的元素
    return value;
}

// 主函数示例
int main() {
    LinkStack* stack = initLinkStack(); // 初始化链式栈
    if (stack == NULL) {
        return -1; // 初始化失败
    }

    // 进行入栈操作
    push(stack, 10); // 入栈 10
    push(stack, 20); // 入栈 20
    push(stack, 30); // 入栈 30

    // 进行出栈操作
    int poppedValue = pop(stack);
    if (poppedValue != -1) {
        printf("出栈元素: %d\n", poppedValue); // 打印出栈元素
    }

    // 打印剩余的栈元素
    Node* current = stack->top;
    while (current != NULL) {
        printf("栈元素: %d\n", current->data);
        current = current->next;
    }

    return 0;
}

3栈的应用举例

3.1进制转换

进制转换是一种常见的应用场景,特别是在需要将一个数从一种进制(例如十进制)转换为另一种进制(例如二进制、八进制或十六进制)时。栈的先进后出(LIFO)特性非常适合用于这种转换,因为我们需要从高位到低位依次处理数字。

进制转换的基本原理

假设我们有一个十进制数 N,我们希望将其转换为 B 进制表示。进制转换的基本步骤如下:

  1. 使用 N 除以 B,得到商和余数。
  2. 将余数压入栈中(因为我们需要从低位到高位依次处理)。
  3. 将商作为新的 N,重复步骤 1 和 2,直到 N 变为 0。
  4. 依次出栈,得到的结果就是 NB 进制表示。

使用栈进行进制转换的示例代码

以下是一个将十进制数转换为二进制的示例代码,使用顺序栈来存储余数。

cs 复制代码
void conversion(int n) {
    SeqStack stack; // 创建顺序栈
    initStack(&stack); // 初始化栈

    // 处理十进制数的转换
    while (n > 0) {
        int abse = n % 2; // 计算余数
        push(&stack, abse); // 将余数入栈
        n /= 2; // 更新 n 的值
    }

    // 输出转换结果
    printf("二进制表示: ");
    while (!isEmpty(&stack)) {
        printf("%d", pop(&stack)); // 出栈并打印
    }
    printf("\n");
}

完整代码:

cs 复制代码
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100 // 定义栈的最大容量

// 顺序栈结构
typedef struct {
    int data[MAX_SIZE]; // 存储元素的数组
    int top;            // 栈顶指针
} SeqStack;

// 初始化顺序栈
void initStack(SeqStack* stack) {
    stack->top = -1; // 栈指针初始化为 -1,表示栈空
}

// 判断栈是否为空
int isEmpty(SeqStack* stack) {
    return stack->top == -1; // 栈空返回 1,否则返回 0
}

// 入栈操作
int push(SeqStack* stack, int value) {
    if (stack->top == MAX_SIZE - 1) {
        printf("栈满,无法入栈\n");
        return -1; // 栈满,返回 -1
    }
    stack->top++; // 先增加栈顶指针
    stack->data[stack->top] = value; // 然后将值存入栈中
    return 0; // 成功入栈
}

// 出栈操作
int pop(SeqStack* stack) {
    if (isEmpty(stack)) {
        printf("栈空,无法出栈\n");
        return -1; // 栈空,返回 -1
    }
    int value = stack->data[stack->top]; // 先获取栈顶数据
    stack->top--; // 然后减少栈顶指针
    return value; // 返回栈顶数据; // 返回栈顶数据并减少栈顶指针
}

// 十进制转二进制函数
void conversion(int n) {
    SeqStack stack; // 创建顺序栈
    initStack(&stack); // 初始化栈

    // 处理十进制数的转换
    while (n > 0) {
        int abse = n % 2; // 计算余数
        push(&stack, abse); // 将余数入栈
        n /= 2; // 更新 n 的值
    }

    // 输出转换结果
    printf("二进制表示: ");
    while (!isEmpty(&stack)) {
        printf("%d", pop(&stack)); // 出栈并打印
    }
    printf("\n");
}

// 主函数
int main() {
    int number;
    printf("请输入一个十进制整数: ");
    scanf("%d", &number);

    conversion(number); // 调用进制转换函数

    return 0;
}

3.2表达式转换和求值

使用栈来实现表达式转换和求值是一个常见的编程任务。我们可以通过栈来实现中缀表达式到后缀表达式(逆波兰表达式)的转换,然后再使用栈来计算后缀表达式的值。

中缀表达式转后缀表达式

中缀表达式是我们通常使用的表达式形式,例如 3 + 4 * 2 / (1 - 5)^2。后缀表达式(逆波兰表达式)是一种不需要括号的表达式形式,例如 3 4 2 * 1 5 - 2 ^ / +

逆波兰表达式求值

给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法 表示的算术表达式。

请你计算该表达式。返回一个表示表达式值的整数。

注意:

  • 有效的算符为 '+''-''*''/'
  • 每个操作数(运算对象)都可以是一个整数或者另一个表达式。
  • 两个整数之间的除法总是 向零截断
  • 表达式中不含除零运算。
  • 输入是一个根据逆波兰表示法表示的算术表达式。
  • 答案及所有中间计算结果可以用 32 位 整数表示。

示例 1:

复制代码
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

示例 2:

复制代码
输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6

示例 3:

复制代码
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

提示:

  • 1 <= tokens.length <= 104
  • tokens[i] 是一个算符("+""-""*""/"),或是在范围 [-200, 200] 内的一个整数

逆波兰表达式:

逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。

  • 平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 )
  • 该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * )

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

思路:

逆波兰表达式部分代码:

cs 复制代码
// 计算逆波兰表达式的值
int evalRPN(char** tokens, int tokensSize) {
    int stack[tokensSize]; // 定义栈,用于存储操作数
    int top = -1;          // 栈顶指针,初始值为 -1 表示栈为空

    // 遍历给定的令牌数组
    for (int i = 0; i < tokensSize; ++i) {
        // 判断当前令牌是否为运算符
        if (strcmp(tokens[i], "+") == 0 || 
            strcmp(tokens[i], "-") == 0 || 
            strcmp(tokens[i], "*") == 0 || 
            strcmp(tokens[i], "/") == 0) {
            // 弹出栈顶的两个操作数
            int a = stack[top--]; // 第一个操作数
            int b = stack[top--]; // 第二个操作数

            // 执行相应的运算并将结果压入栈中
            if (strcmp(tokens[i], "+") == 0) {
                stack[++top] = b + a; // 加法
            }
            if (strcmp(tokens[i], "-") == 0) {
                stack[++top] = b - a; // 减法
            }
            if (strcmp(tokens[i], "*") == 0) {
                stack[++top] = b * a; // 乘法
            }
            if (strcmp(tokens[i], "/") == 0) {
                stack[++top] = b / a; // 除法
            }
        } 
        // 如果当前令牌是数字
        else {
            // 将字符串转换为整数并压入栈中
            stack[++top] = atoi(tokens[i]);
        }
    }

    // 返回栈顶的结果,即逆波兰表达式的值
    return stack[top];
}

完整代码:

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 计算逆波兰表达式的值
int evalRPN(char** tokens, int tokensSize) {
    int stack[tokensSize]; // 定义栈,用于存储操作数
    int top = -1;          // 栈顶指针,初始值为 -1 表示栈为空

    // 遍历给定的令牌数组
    for (int i = 0; i < tokensSize; ++i) {
        // 判断当前令牌是否为运算符
        if (strcmp(tokens[i], "+") == 0 || 
            strcmp(tokens[i], "-") == 0 || 
            strcmp(tokens[i], "*") == 0 || 
            strcmp(tokens[i], "/") == 0) {
            // 弹出栈顶的两个操作数
            int a = stack[top--]; // 第一个操作数
            int b = stack[top--]; // 第二个操作数

            // 执行相应的运算并将结果压入栈中
            if (strcmp(tokens[i], "+") == 0) {
                stack[++top] = b + a; // 加法
            }
            if (strcmp(tokens[i], "-") == 0) {
                stack[++top] = b - a; // 减法
            }
            if (strcmp(tokens[i], "*") == 0) {
                stack[++top] = b * a; // 乘法
            }
            if (strcmp(tokens[i], "/") == 0) {
                stack[++top] = b / a; // 除法
            }
        } 
        // 如果当前令牌是数字
        else {
            // 将字符串转换为整数并压入栈中
            stack[++top] = atoi(tokens[i]);
        }
    }

    // 返回栈顶的结果,即逆波兰表达式的值
    return stack[top];
}

// 示例主函数
int main() {
    char* tokens[] = {"2", "1", "+", "3", "*"}; // 示例逆波兰表达式: (2 + 1) * 3
    int tokensSize = sizeof(tokens) / sizeof(tokens[0]);
    int result = evalRPN(tokens, tokensSize);
    printf("计算结果: %d\n", result); // 输出: 9
    return 0;
}
相关推荐
IronmanJay1 小时前
【LeetCode每日一题】——862.和至少为 K 的最短子数组
数据结构·算法·leetcode·前缀和·双端队列·1024程序员节·和至少为 k 的最短子数组
IT规划师4 小时前
数据结构 - 散列表,三探之代码实现
数据结构·散列表·哈希表
武子康5 小时前
大数据-187 Elasticsearch - ELK 家族 Logstash Filter 插件 使用详解
大数据·数据结构·elk·elasticsearch·搜索引擎·全文检索·1024程序员节
韭菜盖饭7 小时前
LeetCode每日一题3211---生成不含相邻零的二进制字符串
数据结构·算法·leetcode
czme7 小时前
线程和进程
linux·数据结构·计算机网络
J_admin9 小时前
数据结构 之 二叉树的遍历------先根遍历(五)
数据结构
黑不拉几的小白兔14 小时前
PTA L1系列题解(C语言)(L1_097 -- L1_104)
数据结构·算法·1024程序员节
秋说14 小时前
【数据结构 | PTA】懂蛇语
数据结构·c++
ChaoZiLL15 小时前
关于我的数据结构与算法——初阶第二篇(排序)
数据结构·算法
single59416 小时前
【综合算法学习】(第十篇)
java·数据结构·c++·vscode·学习·算法·leetcode