深入浅出:栈的原理与实现

栈的原理与C语言实现

一、栈

(1)、基本概念

  • 说明:栈是一种逻辑结构,是特殊的线性表,特殊在只能在固定的一端操作:只要满足上述条件,那么这种特殊的线性表就会呈现一种"后进先出"的逻辑。这种逻辑就被称为栈,栈在生活中随处可见,比如:堆叠的盘子、弹匣、电梯中的人们、嵌套函数的参数等
  • 栈在生活中的示例

  • 特点:由于约定了只能线性表固定的一端进行操作,于是栈这种特殊的线性表的"插入"、删除,另起了下面这些特定的名称
    • 栈顶:可以进行插入删除的一端
    • 栈底:栈顶的对端
    • 入栈:将节点插入栈顶之上,也称为压栈,函数名通常为push() --- 增加数据(栈顶)
    • 出栈:将节点从栈顶删除,也称为弹栈,函数名通常为pop() --- 删除数据(栈顶)
    • 取栈顶:取得栈顶元素,但不出栈,函数名通常为top() --- 查改数据(一般从栈顶到栈底开始遍历)

(2)、栈的存储形式

  • 说明:栈只是一种数据逻辑,如果将数据存储于内存则是另一回事。一般而言,可以采用顺序存储形成顺序栈,或采用链式存储形成链式栈

(3)、栈的基本操作

1、顺序栈的管理结构体设计

  • 说明:栈顶元素是数组的最后一个元素,而栈底是数组的第一个元素;存储数据的数组,用于实际存放栈中的元素;栈顶下标,用于指示当前栈顶元素的位置;栈的容量,用于记录数组的最大长度,防止溢出。
  • 图解:

  • 示例代码
// 顺序栈的管理结构体
typedef struct sequential_stack
{
    datatype_p data_p;  // 指向顺序栈内存的指针
    int capacity;       // 顺序栈的容量
    int top;            // 顺序栈的栈顶
    
}sq_stack_t, *sq_stack_p;

2、初始化顺序栈

  • 说明:对顺序栈进行初始化,data_p指向存储数据的地方,让栈顶等于-1
  • 图解

  • 示例代码
sq_stack_p SEQUENTIAL_STACK_Init(int cap_size)
{
    // 申请顺序栈管理结构体的堆内存空间
    sq_stack_p p = malloc(sizeof(sq_stack_t));
    bzero(p, sizeof(sq_stack_t));

    // 如果申请的堆内存空间成功,给堆内存空间赋值
    if ( p!= NULL)
    {
        // 指向顺序栈内存的指针
        p->data_p = malloc(sizeof(datatype)*cap_size);  
        if (p->data_p == NULL)
        {
            free(p);
            return NULL;
        }
        
        // 顺序栈的容量
        p->capacity = cap_size;

        // 顺序栈的栈顶
        p->top = -1;        
    }

    return p;
}

3、判断顺序栈是否为空

  • 说明:如果栈顶值 ==  -1,则说明顺序栈目前为空
  • 图解

  • 示例代码
bool SEQUENTIAL_STACK_IfEmpty(sq_stack_p p)
{
    return (p->top ==-1);
}

4、判断顺序栈是否满了

  • 说明:如果栈顶值 == capacity - 1,说明顺序栈已经满了
  • 图解:

  • 示例代码
bool SEQUENTIAL_STACK_IfFull(sq_stack_p p)
{
    return (p->top ==p->capacity-1);
}

5、入栈(增加数据)

  • 说明:说明:每增加一个数据,栈顶值 + 1,一直加到栈满为止
  • 图解:

  • 示例代码
int SEQUENTIAL_STACK_Push(sq_stack_p p, datatype data)
{
    // 判断顺序栈是否满了
    if (SEQUENTIAL_STACK_IfFull(p))
        return -1;

    // 数据入栈
    p->top++;                   // 每入栈一个数据,顺序栈的栈顶值+1
    p->data_p[p->top] =  data;  // 将数据进行赋值操作

    return 0;
}

6、出栈(删除数据)

  • 说明:删除栈里面的数据,直到 top == -1 为止
  • 图解:

  • 示例代码
int SEQUENTIAL_STACK_Pop(sq_stack_p p)
{
    // 判断顺序栈是否为空
    if (SEQUENTIAL_STACK_IfEmpty(p))
        return -1;

    // 数据出栈
    bzero(&p->data_p[p->top], sizeof(datatype));
    p->top--;
    
    return 0;
}

7、取栈顶值(获取栈顶数据)

  • 说明:获取栈顶数据
  • 图解

  • 示例代码
datatype SEQUENTIAL_STACK_GetTopData(sq_stack_p p)
{
    // 判断顺序栈是否为空
    if (SEQUENTIAL_STACK_IfEmpty(p))
        return *((datatype_p)-1);

    // 获取栈顶的值(返回指向栈顶数据的地址)
    return p->data_p[p->top];
}

8、修改顺序栈的的数据

  • 说明:修改栈顶数据
  • 图解

  • 示例代码
int SEQUENTIAL_STACK_ChangeTopData(sq_stack_p p, datatype data)
{
    // 判断顺序栈是否为空
    if (SEQUENTIAL_STACK_IfEmpty(p))
        return -1;

    // 2、修改栈顶的数据
    p->data_p[p->top] = data;

    return 0;
}

9、遍历数据

  • 说明:根据栈顶值遍历,直到top 值小于零为止
  • 图解

  • 示例代码
int SEQUENTIAL_STACK_Show(sq_stack_p p)
{
    // 判断顺序栈是否为空
    if (SEQUENTIAL_STACK_IfEmpty(p))
        return -1;

    // 遍历整个顺序栈
    printf("=====================顺序栈里面的数据======================\n");
    for (int i = p->top; i>=0; i--)
    {
        printf("顺序栈里面的第[%d]个数据为 == %d\n", i, p->data_p[i].data);
    }

    return 0;
}

10、销毁顺序栈

  • 说明:先销毁堆区二,再释放堆区一的内存,不然找不到
  • 图解

(4)、顺序栈的使用

/**
  ******************************************************************************
  * @file    main.c
  * @author  MChine慕青
  * @version V0.0.1
  * @date    2025.09.10
  * @brief   使用顺序栈实现数据的增删查改功能
  *          环境:ubuntu16.04
  *          编译:gcc main.c sequential_stack.c
  *          执行:./a.out
  *      
  ******************************************************************************
  * @attention
  *
  *  本文档只供学习使用,不得商用,违者必究
  *
  *  有疑问或者建议:3211735057@qq.com
  * 
  ******************************************************************************
  */

#include "sequential_stack.h"

int main(int argc, char const *argv[])
{
    // (1)、初始化顺序栈
    sq_stack_p p = SEQUENTIAL_STACK_Init(128);
    if (p == NULL)
    {
        printf("初始化顺序栈失败!\n");
        return -1;
    }

    // (2)、选择功能(增删查改、退出(销毁顺序栈))
    int select           = 0;
    datatype new_data    = {0};
    datatype get_data    = {0};  
    datatype change_data = {0};  

    while (1)
    {
        // 1、显示整个顺序栈的数据
        SEQUENTIAL_STACK_Show(p);

        // 2、显示功能选项
        printf("请选择以下功能!\n");
        printf("1、入栈(增加数据)\n");
        printf("2、出栈(删除数据)\n");
        printf("3、获取栈顶数据\n");
        printf("4、修改栈顶数据\n");
        printf("5、退出!\n");

        // 3、选择要做的功能
        scanf("%d", &select);
        while(getchar()!='\n');

        switch (select)
        {
            case 1:
                // 提示
                printf("请输入要插入的数据:\n");

                // 输入数据
                scanf("%d", &new_data.data);
                while(getchar()!='\n');

                // 添加数据到顺序栈中
                SEQUENTIAL_STACK_Push(p, new_data);

                break;

            case 2:
                // 删除顺序栈中的数据(栈顶)
                SEQUENTIAL_STACK_Pop(p);
                break;

            case 3:
                // 提示
                printf("正在获取栈顶数据\n");

                // 获取栈顶的数据
                get_data =  SEQUENTIAL_STACK_GetTopData(p);
                printf("get_data.data == %d\n", get_data.data);
                break;

            case 4:
                // 提示
                printf("请输入要修改的栈顶数据\n");

                // 输入数据
                scanf("%d", &change_data.data);
                while(getchar()!='\n');

                // 修改栈顶的数据
                SEQUENTIAL_STACK_ChangeTopData(p, change_data);
                break;

           case 5:
                SEQUENTIAL_STACK_UnInit(p);
                printf("系统已退出!\n");
                goto exit_sys_label;
                break;
                
        }
    }
exit_sys_label:
    return 0;
}

二、链式栈

(1)、链式栈的设计

1、链式栈的管理结构体设计
2、初始化链式栈管理结构体
3、初始化数据节点
4、判断链式栈是否为空
5、入栈(增加数据)
6、出栈(删除数据)
7、修改栈顶元素
8、遍历数据
9、销毁链式栈

1、链式栈的管理结构体设计

  • 图解

  • 示例代码
// 节点设计
typedef struct node
{
    // 数据域
	int data;

	// 指针域
	struct node *next_p;
}node_t, *node_p;

// 链式栈管理结构体设计
typedef struct link_cir_stack
{
   // 链式栈的栈顶指针(头节点、head_node)
   node_p top_p;

   // 链式栈的当前的元素个数
   int num;

}lc_stack_t, *lc_stack_p;

2、初始化链式栈管理结构体

  • 图解

  • 示例代码
lc_stack_p LINK_CIR_STACK_Init(void)
{
    // 给管理结构体申请堆内存空间
    lc_stack_p p = malloc(sizeof(lc_stack_t));
    bzero(p, sizeof(lc_stack_t));

    // 给堆内存空间赋值
    if ( p!= NULL)
    {
        // 链式栈栈顶指针(初始化头节点)
        p->top_p = malloc(sizeof(node_t));
        if (p->top_p != NULL)
        {
            p->top_p->next_p = p->top_p;
        }
        else
        {
            free(p);
            return NULL;
        }
        
        // 链式栈当前的元素个数(记录链表有几个数据节点)
        p->num = 0;
    }
    else
    {
        return NULL;
    }

    return p;
}

3、初始化数据节点

  • 图解

  • 示例代码
node_p LINK_CIR_STACK_InitDataNode(datatype data)
{
    // 给数据节点申请堆内存空间
    node_p p = malloc(sizeof(node_t));
    bzero(p, sizeof(node_t));

    // 给堆内存空间赋值
    if (p!=NULL)
    {
        // 数据域
        p->data = data;

        // 指针域
        p->next_p = p;
    }

    return p;
}

4、判断链式栈是否为空

  • 图解

  • 示例代码
bool LINK_CIR_STACK_IfEmpty(lc_stack_p p)
{
    // 管理结构体里面的top_p就是栈顶(头节点)
    node_p head_node = p->top_p;
    return head_node->next_p == head_node;

}

5、入栈(增加数据)

  • 图解

  • 示例代码
void LINK_CIR_STACK_Push(lc_stack_p p, node_p new_node)
{
    // 管理结构体里面的top_p就是栈顶(头节点)
    node_p head_node = p->top_p;

    // 先让new_node里面的next_p指向data_node
    new_node->next_p = head_node->next_p;

    // 再让head_node里面的next_p指向new_node
    head_node->next_p = new_node;

    // 最后让链表中的数据节点个数+1
    p->num++;
}

6、出栈

  • 说明:出栈也叫"弹栈",只能删除栈顶数据
  • 图解

  • 示例代码
int LINK_CIR_STACK_Pop(lc_stack_p p)
{
    if (LINK_CIR_STACK_IfEmpty(p))
        return -1;

    // 管理结构体里面的top_p就是栈顶(头节点)
    node_p head_node = p->top_p;

    // 相关中间变量赋值
    node_p last_node = head_node;
    node_p del_node  = head_node->next_p;
    node_p next_node = del_node->next_p;

    // 绕过原链表要删除的节点
    last_node->next_p = next_node;
    
    // 释放要删除节点的资源
    del_node->next_p  = NULL;
    free(del_node);
    
    // 管理结构体中的num需要-1
    p->num--;

    return 0;
}

7、遍历整个整个栈

  • 说明:对整个链式栈进行遍历输出
  • 图解:

  • 示例代码
int LINK_CIR_STACK_Show(lc_stack_p p)
{
    if (LINK_CIR_STACK_IfEmpty(p))
        return -1;   

    // 管理结构体里面的top_p就是栈顶(头节点)
    node_p head_node = p->top_p;

    // 遍历整个链式栈,并逐个打印里面的数据
    node_p tmp_p = NULL;
    int i = 0;

    printf("=====================链式栈中的数据===================\n");
    for (tmp_p = head_node->next_p; tmp_p!=head_node; tmp_p=tmp_p->next_p)
    {
        printf("链式栈中的第%d的节点,数据为:%d\n", i, tmp_p->data.data);
        i++;
    }
    printf("====================================================\n");

    return 0;
    
}

8、销毁整个链式栈

  • 说明:
  • 图解:

  • 示例代码:
void LINK_CIR_STACK_UnInit(lc_stack_p p)
{
    // 1、如果链式栈为空,那么直接释放管理结构体内存即可
    if (LINK_CIR_STACK_IfEmpty(p))
    {
        free(p->top_p); // 释放堆区2(释放头节点数据)
        free(p);        // 释放堆区1
        return;
    }

    // 管理结构体里面的top_p就是栈顶(头节点)
    node_p head_node = p->top_p;

    // 2、销毁链式栈中的数据节点
    node_p tmp_p  = NULL;
    node_p tmp2_p = NULL;
    for (tmp_p=head_node->next_p; tmp_p!=head_node; tmp_p=tmp2_p)
    {
        tmp2_p = tmp_p->next_p;
        free(tmp_p);
    }
    
    // 3、释放堆区2(释放头节点数据)
    free(head_node);

    // 4、释放堆区1
    free(p);        
    
}

9、修改栈顶数据

  • 说明:由于是栈,只能修改栈顶的数据
  • 图解

  • 示例代码
datatype_p LINK_CIR_STACK_GetTopData(lc_stack_p p)
{
    if (LINK_CIR_STACK_IfEmpty(p))
        return NULL;

    // 管理结构体里面的top_p就是栈顶(头节点)
    node_p head_node = p->top_p;

    // 获取栈顶数据
    return &(head_node->next_p->data);
}

10、链式栈的使用

  • 示例代码
/**
  ******************************************************************************
  * @file    main.c
  * @author  MChine慕青
  * @version V0.0.2
  * @date    2025.09.17
  * @brief   使用链式栈实现数据的增删查改功能
  *          前提:需要设置link_cir_stack.h里面的参数
  *          环境:ubuntu22.04
  *          编译:gcc main.c link_cir_stack.c
  *          执行:./a.out
  *      
  ******************************************************************************
  * @attention
  *
  *  本文档只供学习使用,不得商用,违者必究
  * 
  *  博客网站:https://blog.csdn.net/2402_83622972?type=blog
  *  有疑问或者建议:3211735057@qq.com
  * 
  ******************************************************************************
  */

#include "link_cir_stack.h"

int main(int argc, char const *argv[])
{
    // (1)、初始化管理结构体
    lc_stack_p p = LINK_CIR_STACK_Init();
    if ( p == NULL)
    {
        printf("初始化链式栈管理结构体失败!\n");
        return -1;
    }

    // (2)、选择功能(增删查改、退出(销毁链式栈))
    node_p new_node        = NULL;
    int select             = 0;
    datatype   new_data    = {0};
    datatype_p get_data    = NULL;
    datatype   change_data = {0};

    while (1)
    {
        // 1、显示整个链式栈的数据
        LINK_CIR_STACK_Show(p);

        // 2、显示功能选项
        printf("请输入以下功能:\n");
        printf("1、入栈(增加数据)\n");
        printf("2、出栈(删除数据)\n");
        printf("3、获取栈顶数据\n");
        printf("4、修改栈顶数据\n");
        printf("5、退出!\n");

        // 3、选择要做的功能
        scanf("%d", &select);
        while(getchar()!='\n');

        switch (select)
        {
            case 1:
                // 提示
                printf("请输入要入栈的数据:\n");

                // 输入数据
                scanf("%d", &new_data.data);
                while(getchar()!='\n');

                // 生成一个数据节点
                new_node = LINK_CIR_STACK_InitDataNode(new_data);

                // 将新生成的数据节点添加到链式栈中
                LINK_CIR_STACK_Push(p, new_node);

                break;
        
            
            case 2:
                // 删除链式栈中的数据(栈顶)
                LINK_CIR_STACK_Pop(p);
                break;

            case 3:
                // 提示
                printf("正在获取栈顶数据\n");

                // 获取栈顶数据
                get_data =  LINK_CIR_STACK_GetTopData(p);
                if (get_data != NULL)
                    printf("get_data.data == %d\n", get_data->data);
                
                break;

            case 4:
                // 提示
                printf("请输入要修改的栈顶数据\n");

                // 输入数据
                scanf("%d", &change_data.data);
                while(getchar()!='\n');

                // 修改栈顶的数据
                LINK_CIR_STACK_ChangeTopData(p, change_data);
                break;

           case 5:
                LINK_CIR_STACK_UnInit(p);
                printf("系统已退出!\n");
                goto exit_sys_label;
                break;
        }
    }
exit_sys_label:
    return 0;
    
    
    

    return 0;
}

本期内容到这里就结束了,希望这些干货能对大家有所帮助。如果觉得有用的话,不妨动动手指点个赞支持一下,你们的每一个赞都是博主持续创作的动力。

喜欢这类内容的小伙伴也可以关注一下,后续会持续更新更多实用技巧和深度解析。有想了解的主题或问题,欢迎在评论区留言互动,说不定下期内容就是为你量身定制的哦!

最后别忘了转发分享给更多需要的朋友,知识因分享而更有价值。我们下期再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值