顺序栈+链式栈


一、基本概念

栈是一种逻辑结构,是特殊的线性表。特殊在:

只能在固定的一端操作

只要满足上述条件,那么这种特殊的线性表就会呈现一种"后进先出"的逻辑,这种逻辑就被称为栈。栈在生活中到处可见,比如堆叠的盘子、电梯中的人们、嵌套函数的参数等等。

栈示意图

由于约定了只能在线性表固定的一端进行操作,于是给栈这种特殊的线性表的"插入"、"删除",另起了下面这些特定的名称:

  • 栈顶:可以进行插入删除的一端
  • 栈底:栈顶的对端(不能进行任何的插入和删除操作)
  • 入栈:将节点插入栈顶之上,也称为压栈,函数名通常为push()
  • 出栈:将节点从栈顶剔除,也称为弹栈,函数名通常为pop()
  • 取栈顶取得栈顶元素,但不出栈,函数名通常为top()

基于这种固定一端操作的简单约定,栈获得了"后进先出"的基本特性,如下图所示,最后一个放入的元素,最先被拿出来:

栈的后进先出特性

二、存储形式

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

顺序栈

顺序存储意味着开辟一块连续的内存来存储数据节点,一般而言,管理栈数据除了需要一块连续的内存之外,还需要记录栈的总容量、当前栈的元素个数、当前栈顶元素位置(下标),如果有多线程还需要配互斥锁(互斥机制)和信号量(同步机制)等信息,为了便于管理,通常将这些信息统一于在一个管理结构体之中:

struct seqStack
{
    datatype *data;  // 顺序栈入口
    int size;        // 顺序栈总容量
    int top;         // 顺序栈栈顶元素下标
};
顺序栈

链式栈

链式栈的组织形式与链表无异,只不过插入删除被约束在固定的一端。为了便于操作,通常也会创建所谓管理结构体,用来存储栈顶指针、栈元素个数等信息:

// 链式栈节点
typedef struct node
{
    datatype data;
    struct node *next;
}node;

// 链式栈管理结构体
struct linkStack
{
    node *top; // 链式栈栈顶指针
    int  size; // 链式栈当前元素个数
};
链式栈

三、基本操作

顺序栈

设计管理结构体并初始化

// 设计数据类型
typedef int dataType ;
//设计管理结构体
struct seqStack
{
    datatype *data; // 顺序栈入口
    int size;       // 顺序栈总容量
    int top;        // 顺序栈栈顶元素下标
};
顺序栈管理结构体
//初始化
Cntl_t * StackInit( unsigned int size )
{
    Cntl_t * cntl = calloc(1, sizeof(Cntl_t));
    if (cntl == NULL)
    {
        perror("calloc cntl error ");
        return NULL ;
    }
    
    cntl->Data = calloc( size , sizeof(dataType));
    if (cntl->Data == NULL)
    {
        perror("calloc Data error");
        return NULL ;
    }
    
    cntl->Size = size ;
    cntl->Top =  -1 ;

    return cntl ;
}

入栈

入栈操作
int push( Cntl_t * Stack , dataType * NewData )
{

    // 0
    if (NewData == NULL)
    {
        // 栈顶下标+1  = 当前有效数据量
        return Stack->Top+1 ;
    }

    // 1
    if (Stack->Top + 1 >= Stack->Size)
    {
        printf("当前栈已满..\n");
        return -1 ;
    }

    // 2
    Stack->Top ++ ;

    // 3
    Stack->Data [Stack->Top] = *NewData ;

    // 4 
    return Stack->Top + 1 ;
}

出栈

dataType * pop( Cntl_t * Stack )
{
    if (Stack->Top == -1)
    {
        printf("当前栈为空..\n");
        return NULL ;
    }

    // 使用临时指针指向栈顶元素的数据地址
    dataType * tmp = Stack->Data + Stack->Top ; 
    
    // 更新栈顶下标
    Stack->Top -- ;

    return tmp ;
}

销毁

void StackDestroy(Cntl_t * Stack)
{
    if (Stack == NULL) return;

    if (Stack->Data != NULL)
    {
        free(Stack->Data);
        Stack->Data = NULL;
    }

    free(Stack);
}

链式栈

设计管理结构体并初始化

对于链表一般情况下我们更倾向于带有头节点的链表,因为他操作起来更为便利,而链式栈的管理结构体便可以充当这个头节点的角色。
再思考可以发现对于栈这种逻辑,链表的随意性实际中在栈中都被禁用了,因此可以选用最简单的方案就是单向不循环链表。

链式栈初始化
P_Cntl_t InitStack( void )
{

    // 申请管理结构体
    P_Cntl_t cntl = calloc(1, sizeof(Cntl_t));
    if (cntl == NULL)
    {
        perror("calloc Cntl erorr");
        return NULL ;
    }
    
    // 初始化成员
    cntl->top = NULL ;
    cntl->Count = 0 ;

    return cntl ;
}

入栈

链式栈入栈
int push( P_Cntl_t cntl , Node_t * NewNode )
{
    NewNode->Next = cntl->top ;
    cntl->top = NewNode ;
    cntl->Count ++ ;
}

出栈

链式栈入栈
Node_t * pop( P_Cntl_t cntl )
{
    if (cntl->Count <= 0 )
    {
        printf("链式栈为空..\n") ;
        return NULL ;
    }
    
    Node_t * tmp = cntl->top ;
    
    cntl->top = tmp->Next ;
    
    tmp->Next = NULL ;
    
    cntl->Count -- ;

    return tmp ;
}

销毁

/* 销毁整个链式栈 */
void DestroyStack(P_Cntl_t * pCntl)   /* 传二级指针,方便把实参置空 */
{
    if (pCntl == NULL || *pCntl == NULL)
        return;

    /* 1. 清空所有节点 */
    while ((*pCntl)->Count > 0)
    {
        Node_t * node = pop(*pCntl);
        free(node);
    }

    /* 2. 释放管理结构体 */
    free(*pCntl);

    /* 3. 把实参指针置空 */
    *pCntl = NULL;
}
posted @ 2025-10-30 08:27  林明杰  阅读(8)  评论(0)    收藏  举报