c语言实现栈【由浅入深-数据结构】

文章目录


前言

本文介绍c语言实现栈的相关内容。

(【由浅入深】是一个系列文章,它记录了我个人作为一个小白,在学习c++技术开发方向计相关知识过程中的笔记,欢迎各位彭于晏刘亦菲从中指出我的错误并且与我共同学习进步,作为该系列的第一部曲-c语言,大部分知识会根据本人所学和我的助手------通义,DeepSeek等以及合并网络上所找到的相关资料进行核实誊抄,每一篇文章都可能会因为一些错误在后续时间增删改查,因为该系列按照我的网络课程学习笔记形式编写,我会使用绝大多数人使用的讲解顺序编写,所以基础框架和大部分内容案例会与他人一样,基础知识不会过于详细讲述)


C语言实现栈的详细解析

栈(Stack)一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端,称为栈顶,另一端称为栈底。具有后进先出 (LIFO, Last In First Out)的特性。它在函数调用、表达式求值、括号匹配等场景有广泛应用。下面我将详细讲解C语言中实现栈的两种主要方式:顺序栈 (基于数组-最常用)和链式栈(基于链表)。

一、栈的基本概念

栈的核心特性

  • 后进先出:最后进入的元素最先被取出
  • 操作限制 :只能在栈顶进行插入(push)和删除(pop)操作
  • 基本操作时间复杂度:均为O(1)

栈的两个经典操作

  1. 压栈(Push):栈的插入操作,将元素放入栈顶
  2. 出栈(Pop):栈的删除操作,将栈顶元素移除

二、栈的实现方式

1. 顺序栈(基于数组实现)

结构体定义
c 复制代码
typedef struct {
    int* data;      // 存储元素的数组
    int size;       // 栈的总容量
    int top;        // 栈顶指针(初始为-1,表示空栈)
} Stack;

关键点

  • top初始值为-1表示空栈
  • size限制栈的最大容量
  • 使用动态数组实现顺序存储
代码实现

初始化栈

c 复制代码
Stack* initStack(int n) {
    Stack* s = (Stack*)malloc(sizeof(Stack));
    s->data = (int*)malloc(sizeof(int) * n);
    s->size = n;
    s->top = -1;  // 初始化栈顶指针
    return s;
}

入栈操作

c 复制代码
int push(Stack* s, int val) {
    if (s->top == s->size - 1) {
        // 栈已满,返回错误
        return -1;
    }
    s->top++;
    s->data[s->top] = val;
    return 0;  // 成功
}

出栈操作

c 复制代码
int pop(Stack* s, int* val) {
    if (s->top == -1) {
        // 栈为空,返回错误
        return -1;
    }
    *val = s->data[s->top];
    s->top--;
    return 0;  // 成功
}

判空操作

c 复制代码
int empty(Stack* s) {
    return s->top == -1;
}

获取栈顶元素

c 复制代码
int top(Stack* s) {
    if (empty(s)) {
        return -1;  // 空栈返回错误值
    }
    return s->data[s->top];
}
顺序栈的特点
  • 优点:实现简单,内存连续,访问速度快
  • 缺点:容量固定,可能有空间浪费;满栈时需要扩容(增加复杂性)

完整数组实现

c 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>

// 定义栈中存储的数据类型为整型
typedef int STDataType;

// 定义栈结构体
typedef struct Stack
{
    STDataType* a;      // 动态数组,用于存储栈元素
    int top;            // 栈顶指针(指向栈顶元素的下一个位置)
    int capacity;       // 栈的容量(当前分配的数组大小)
}ST;

// 函数声明(栈操作接口)
void STInit(ST* ps);                // 初始化栈
void STDestroy(ST* ps);              // 销毁栈(释放内存)

void STPush(ST* ps, STDataType x);  // 入栈(压栈)
void STPop(ST* ps);                 // 出栈(弹栈)
STDataType STTop(ST* ps);           // 获取栈顶元素
int STSize(ST* ps);                 // 获取栈中元素个数
bool STEmpty(ST* ps);               // 判断栈是否为空

// 初始化栈
void STInit(ST* ps)
{
    // 确保传入的指针有效
    assert(ps);
    
    // 初始化栈成员:
    // 1. 将动态数组指针置为NULL(表示尚未分配内存)
    // 2. 栈顶指针初始化为0(表示栈为空,栈顶元素在位置-1,但实际存储从0开始)
    // 3. 栈容量初始化为0
    ps->a = NULL;
    ps->top = 0;
    ps->capacity = 0;
}





// 销毁栈(释放内存并重置状态)
void STDestroy(ST* ps)
{
    // 确保传入的指针有效
    assert(ps);
    
    // 释放动态数组内存
    free(ps->a);
    
    // 重置栈状态(避免野指针)
    ps->a = NULL;
    ps->top = 0;
    ps->capacity = 0;
}





// 入栈操作(压栈)
void STPush(ST* ps, STDataType x)
{
    // 确保传入的指针有效
    assert(ps);
    
    // 检查栈是否已满(top等于容量表示已无可用空间)
    if (ps->top == ps->capacity)
    {
        // 计算新容量:如果当前容量为0(空栈),则分配4个元素空间
        // 否则,容量翻倍(避免频繁扩容,提高效率)
        int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
        
        // 重新分配内存,扩展栈容量
        STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
        
        // 检查内存分配是否成功
        if (tmp == NULL)
        {
            perror("realloc fail"); // 打印错误信息
            return; // 分配失败,退出函数
        }
        
        // 更新栈的数组指针和容量
        ps->a = tmp;
        ps->capacity = newcapacity;
    }
    
    // 将元素放入栈顶位置(top指向的位置)
    ps->a[ps->top] = x;
    
    // 栈顶指针后移(指向下一个空位置)
    ps->top++;
}





// 出栈操作(弹栈)
void STPop(ST* ps)
{
    // 确保传入的指针有效
    assert(ps);
    
    // 检查栈是否为空(不能从空栈弹出元素)
    assert(!STEmpty(ps));
    
    // 栈顶指针前移(相当于移除栈顶元素)
    ps->top--;
}





// 获取栈顶元素
STDataType STTop(ST* ps)
{
    // 确保传入的指针有效
    assert(ps);
    
    // 检查栈是否为空
    assert(!STEmpty(ps));
    
    // 栈顶元素位于top-1位置(因为top指向下一个空位置)
    return ps->a[ps->top - 1];
}





// 获取栈中元素个数
int STSize(ST* ps)
{
    // 确保传入的指针有效
    assert(ps);
    
    // 栈中元素个数 = top(因为top表示已使用的元素数量)
    return ps->top;
}




// 判断栈是否为空
bool STEmpty(ST* ps)
{
    // 确保传入的指针有效
    assert(ps);
    
    // 如果栈顶指针为0,则栈为空
    return ps->top == 0;
}



int main()
{
	ST s;
	STInit(&s);
	STPush(&s, 1);
	STPush(&s, 2);
	STPush(&s, 3);

	int top = STTop(&s);
	printf("%d ", top);
	STPop(&s);

	STPush(&s, 4);
	STPush(&s, 5);

	while (!STEmpty(&s))
	{
		int top = STTop(&s);
		printf("%d ", top);
		STPop(&s);
	}

	STDestroy(&s);

	return 0;
}

主要掌握上面数组实现即可


2. 链式栈(基于链表实现)

结构体定义
c 复制代码
typedef struct Node {
    int data;
    struct Node* next;
} Node;

typedef struct {
    Node* top;  // 栈顶指针
    int size;   // 栈的大小
} Stack;

关键点

  • 栈顶即为链表的头节点
  • top指向栈顶元素
  • size记录栈中元素个数
代码实现

初始化栈

c 复制代码
Stack* initStack() {
    Stack* s = (Stack*)malloc(sizeof(Stack));
    s->top = NULL;
    s->size = 0;
    return s;
}

入栈操作

c 复制代码
void push(Stack* s, int val) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = val;
    newNode->next = s->top;
    s->top = newNode;
    s->size++;
}

出栈操作

c 复制代码
int pop(Stack* s, int* val) {
    if (s->top == NULL) {
        return -1;  // 栈为空
    }
    Node* temp = s->top;
    *val = temp->data;
    s->top = temp->next;
    free(temp);
    s->size--;
    return 0;
}

判空操作

c 复制代码
int empty(Stack* s) {
    return s->top == NULL;
}

获取栈顶元素

c 复制代码
int top(Stack* s) {
    if (empty(s)) {
        return -1;  // 空栈返回错误值
    }
    return s->top->data;
}
链式栈的特点
  • 优点:空间利用率高,无需扩容,动态增长
  • 缺点:实现相对复杂,内存不连续,访问速度稍慢

三、两种实现方式的对比

特性 顺序栈 链式栈
实现基础 数组 链表
空间利用率 低(可能有浪费) 高(动态分配)
扩容 需要扩容(可能有性能开销) 无需扩容
插入/删除效率 O(1) O(1)
代码复杂度 较低 较高
适用场景 已知栈大小,需要频繁操作 不知道栈大小,需要动态增长

四、栈的应用场景

栈在C语言中有以下广泛应用:

  1. 表达式求值

    • 栈可以用于存储运算符和操作数
    • 实现表达式的求值算法,如中缀表达式转后缀表达式并计算结果
  2. 函数调用

    • 函数调用时,需要保存函数的返回地址、参数和局部变量等信息
    • 这些信息使用栈来保存和管理
  3. 括号匹配

    • 栈可以用于检查括号是否匹配
    • 遇到左括号入栈,遇到右括号出栈,最终检查栈是否为空
  4. 逆波兰表达式求值

    • 逆波兰表达式是一种后缀表达式
    • 栈可以实现逆波兰表达式的求值
  5. 递归算法

    • 递归算法中,每次递归调用时需要保存当前函数的状态
    • 这些状态可以使用栈来保存和管理
  6. 深度优先搜索

    • 栈可以用于实现图的深度优先搜索算法

五、实际应用示例

括号匹配检查

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

typedef struct {
    char* data;
    int size;
    int top;
} Stack;

Stack* initStack(int n) {
    Stack* s = (Stack*)malloc(sizeof(Stack));
    s->data = (char*)malloc(sizeof(char) * n);
    s->size = n;
    s->top = -1;
    return s;
}

int push(Stack* s, char c) {
    if (s->top == s->size - 1) return -1;
    s->top++;
    s->data[s->top] = c;
    return 0;
}

int pop(Stack* s, char* c) {
    if (s->top == -1) return -1;
    *c = s->data[s->top];
    s->top--;
    return 0;
}

int empty(Stack* s) {
    return s->top == -1;
}

int isMatching(char c1, char c2) {
    return (c1 == '(' && c2 == ')') ||
           (c1 == '[' && c2 == ']') ||
           (c1 == '{' && c2 == '}');
}

int checkBrackets(char* str) {
    Stack* s = initStack(100);
    for (int i = 0; str[i] != '\0'; i++) {
        if (str[i] == '(' || str[i] == '[' || str[i] == '{') {
            push(s, str[i]);
        } else if (str[i] == ')' || str[i] == ']' || str[i] == '}') {
            char c;
            if (empty(s) || !isMatching(pop(s, &c), str[i])) {
                return 0;  // 括号不匹配
            }
        }
    }
    return empty(s);  // 检查栈是否为空
}

int main() {
    char str1[] = "({[()])}";
    char str2[] = "({[]})";
    
    printf("str1: %s\n", checkBrackets(str1) ? "匹配" : "不匹配");
    printf("str2: %s\n", checkBrackets(str2) ? "匹配" : "不匹配");
    
    return 0;
}

六、总结

  1. 栈的实现

    • 顺序栈:基于数组,实现简单,但容量固定
    • 链式栈:基于链表,空间利用率高,动态增长
  2. 选择建议

    • 如果已知栈的大小,且对性能要求高,选择顺序栈
    • 如果栈的大小不确定,需要动态增长,选择链式栈
  3. 栈的核心价值

    • 通过后进先出的特性,简化了复杂问题的解决
    • 为表达式求值、括号匹配、函数调用等提供了高效解决方案
  4. 重要原则

    • 所有栈操作必须检查栈是否为空(避免空栈出栈错误)
    • 顺序栈需注意容量限制,满栈时需要处理扩容
    • 链式栈需注意内存管理,出栈时需释放节点内存

栈作为基础数据结构,理解其原理和实现方式对学习更复杂的数据结构和算法至关重要。希望这篇详细解析能帮助你深入理解C语言中栈的实现和应用。

相关推荐
熬了夜的程序员4 小时前
【LeetCode】88. 合并两个有序数组
数据结构·算法·leetcode·职场和发展·深度优先
胖咕噜的稞达鸭4 小时前
封装map和set(红黑树作为底层结构如何实现map和set插入遍历)
c语言·数据结构·c++·算法·gitee·哈希算法
草莓工作室4 小时前
C语言基础1:C语言简单程序
c语言
宁清明6 小时前
【小宁的学习日记2 C语言】C语言判断
c语言·学习·算法
2401_841495647 小时前
【数据结构】基于Prim算法的最小生成树
java·数据结构·c++·python·算法·最小生成树·prim
木井巳9 小时前
[Java数据结构和算法] HashMap 和 HashSet
java·数据结构·1024程序员节
祈祷苍天赐我java之术10 小时前
解析常见的限流算法
java·数据结构·算法
小猪咪piggy10 小时前
【算法】day10 分治
数据结构·算法·排序算法
又是忙碌的一天10 小时前
算法学习 13
数据结构·学习·算法