04 线性结构——栈(特性、进栈与出栈、栈顶指针、顺序栈和链式栈、相关功能的定义与代码实现)

目录

1 栈的定义

2 相关概念

2.1 栈顶(Top)

2.2 栈底(Bottom)

2.3 空栈

2.4 栈的序列

2.5 出栈入栈与栈顶指针移动的关系

2.5.1 初始情况 top = -1

2.5.2 初始情况 top = 0

3 栈的应用:函数调用

4 栈的存储结构

4.1 顺序栈

4.2 链式栈

4.2.1 头插法

4.2.2 尾插法

5 功能定义

6 功能实现

6.1 初始化栈

6.2 返回栈内元素个数

6.3 添加新元素至栈顶

6.4 弹出栈顶元素并返回

6.5 释放栈内存

6.6 遍历栈中的所有元素

6.7 完整代码 


1 栈的定义

        栈(stack)是一种特殊的线性表,其特点在于只能在表的一端(称为栈顶)进行插入和删除操作。栈的应用范围极为广泛,从算法设计到日常生活场景,如堆叠的盘子、报纸、电梯中人群的进出顺序、邮局的邮筒等,都体现了栈的基本思想。

        特点:栈是一种后进先出(LIFO,Last In First Out)或先进后出(FILO,First In Last Out)的线性表。这意味着最后插入的元素会最先被删除,而最先插入的元素会最后被删除


2 相关概念

2.1 栈顶(Top)

        栈顶是栈中允许进行插入和删除操作的一端,也称为表尾。栈顶的位置由一个称为栈顶指针的变量来指示,该变量随着栈中元素的增减而动态变化。栈顶指针通常指向栈顶元素的下一个位置(在数组实现中)或栈顶元素本身(在链表实现中)

2.2 栈底(Bottom)

        栈底是栈中固定不变的一端,不允许进行插入和删除操作,也称为表头。栈底通常位于栈的起始位置,其元素是栈中最早插入的元素

2.3 空栈

        空栈是指不包含任何元素的栈。在空栈中,栈顶指针通常指向栈的起始位置或某个特定的空值标记

2.4 栈的序列

        设栈 S = (a1, a2, ..., an),则 a1 称为栈底元素,an 为栈顶元素。栈中元素按 a1, a2, ..., an 的次序依次进栈(压栈、push),而出栈(弹栈、pop)时,第一个被删除的元素应为栈顶元素 an,随后是 an-1, ..., a2, a1,即按照后进先出的顺序进行。

2.5 出栈入栈与栈顶指针移动的关系

        不同的参考书对栈的操作有不同的描述,以下是两种常见的描述:

2.5.1 初始情况 top = -1

        在这种情况下,栈顶指针 (top) 初始值设为 -1,表示栈为空。当有新元素入栈时,先将栈顶指针加 1(即 top++),然后将元素放入栈顶位置。出栈时,先取出栈顶元素,再将栈顶指针减 1(即 top--)。 

  • 初始状态:栈为空,栈顶指针(top)为 -1。
  • 元素 A 入栈:首先将栈顶指针加 1(即 top++),使得 top 变为 0,然后将元素 A 放入新的栈顶位置。
  • 元素 BCD 依次入栈:同样遵循上述步骤,每次新元素进入栈时,先更新栈顶指针(top++),然后再放置元素。此时栈内顺序为 A->B->C->D,栈顶指针指向 D,top 等于 3。
  • 元素 D 出栈:执行 pop 操作时,首先弹出栈顶元素 D,然后将栈顶指针下移一位(top--),指向原来的次栈顶元素 C,top 等于 2。
  • 最终栈的状态变为 A->B->C,栈顶指针指向 C,top 等于 2。

2.5.2 初始情况 top = 0

        在这种情况下,栈顶指针 (top) 初始值设为 0 ,表示栈为空。当有新元素入栈时,先将元素放入栈顶位置,然后将栈顶指针加 1(即 top++)。出栈时,先将栈顶指针减 1(即 top--),再取出栈顶元素。 

  • 初始状态:栈为空,栈顶指针(top)为 0。
  • 元素 A 入栈:首先将元素 A 放入栈顶位置,然后将栈顶指针加 1(即 top++),使得 top 变为 1。
  • 元素 BCD 依次入栈:同样遵循上述步骤,每次新元素进入栈时,先放置元素,然后再更新栈顶指针(top++)。此时栈内顺序为 A->B->C->D,栈顶指针指向 D 后面一个位置(但不存储元素), top 等于 4。
  • 元素 D 出栈:执行 pop 操作时,首先将栈顶指针下移一位,指向 C 后面一个位置(但不存储元素),即 top 变为 3,然后弹出栈顶元素 D。
  • 最终栈的状态变为 A->B->C,栈顶指针指向 C 后面一个位置(但不存储元素),top 等于 3。

3 栈的应用:函数调用

        栈在算法和数据结构中有广泛的应用,如函数调用栈、深度优先搜索(DFS)、表达式求值、括号匹配等。此外,栈还可以用于实现队列、优先队列等其他数据结构的功能。

        栈最常用的地方就是计算机的函数调用,不管何种语言,最先被调用的函数将会最后返回,而最后被调用的函数则会最先返回其结果。举例:

void funcA() {
    funcB();
}

void funcB() {
    funcC();
}

void funcC() {
    // 执行某些操作
}

        在这个例子中,函数调用的顺序是 funcA() -> funcB() -> funcC()。当程序执行到 funcA() 时,它会调用 funcB(),而 funcB() 又会调用 funcC()。每次函数调用时,都会在调用栈上创建一个新的栈帧(或称为活动记录),用于存储该函数的局部变量、参数以及返回地址等信息

        当 funcC() 执行完毕后,它会将控制权返回给 funcB(),后者继续执行剩余的部分,完成后也将控制权返回给 funcA()。因此,返回的顺序是 funcC() -> funcB() -> funcA()。这个过程清楚地展示了栈如何管理函数调用的顺序,确保每个函数都能正确地返回到调用它的函数处。

        通过这种方式,即使是在复杂的程序中,栈也能有效地管理和跟踪大量的函数调用,保证程序的正常运行。


4 栈的存储结构

        栈可以使用不同的数据结构来实现,最常见的两种方法是使用顺序表(数组)和链表。根据所使用的存储结构,栈可以分为两种类型:顺序栈和链式栈。在实际应用中,选择哪种实现方式取决于具体的需求和场景。

4.1 顺序栈

        顺序栈是使用数组来实现的栈。在这种实现方式中,栈中的元素存储在一个固定大小的数组中,通常使用一个整型变量(如 top)来指示栈顶的位置。具有以下特点:

  • 存储效率高:由于使用数组,内存分配连续,访问速度快。
  • 空间利用率有限:数组的大小是固定的,如果栈中元素数量超过数组的容量,需要进行扩容操作,这可能会导致额外的时间开销。
  • 实现简单:数组的索引操作简单直观,易于理解和实现。

4.2 链式栈

        链式栈是使用链表来实现的栈。在这种实现方式中,栈中的元素存储在一个链表中,每个节点包含一个数据元素和一个指向下一个节点的指针。通常使用一个指针(如 top)来指示栈顶的位置。具有以下特点:

  • 动态分配内存:链表的节点可以在需要时动态分配和释放,不受固定大小的限制。
  • 空间利用率高:可以根据需要动态扩展和收缩,避免了空间浪费。
  • 实现稍微复杂:链表的插入和删除操作涉及指针操作,相对数组来说稍微复杂一些。

4.2.1 头插法

优点:

  • 插入和删除操作高效在链表头部插入和删除节点的时间复杂度为 O(1),因为只需要修改头指针和新节点的指针。
  • 栈顶元素访问方便栈顶元素始终位于链表的头部,可以直接通过头指针访问,无需遍历整个链表

缺点:

  • 插入和删除操作会影响其他节点:虽然头插法的时间复杂度为 O(1),但在某些情况下,频繁的插入和删除操作可能会影响链表的稳定性,尤其是在多线程环境中。

4.2.2 尾插法

优点:

  • 逻辑直观:对于习惯于从尾部操作的人来说,尾插法可能更符合直觉。

缺点:

  • 插入和删除操作效率低在链表尾部插入和删除节点的时间复杂度为 O(n),因为每次操作都需要遍历整个链表找到最后一个节点。
  • 栈顶元素访问不便:栈顶元素位于链表的尾部,访问栈顶元素需要遍历整个链表,效率较低。

总结:

        顺序栈适用于栈的大小已知且变化不大,或者对访问速度有较高要求的场景。
        链式栈适用于栈的大小不确定或频繁变化,或者需要灵活管理内存的场景。

        由于栈的主要操作是入栈(push)和出栈(pop),而这些操作都涉及到栈顶元素,因此使用头插法可以显著提高效率。头插法不仅插入和删除操作的时间复杂度为 O(1),而且访问栈顶元素也非常方便。


5 功能定义

        前文提到过,栈的底层实现既可以使用数组也可以使用链表。本节基于动态数组实现一个栈,并需要实现如下函数方法:

功能函数签名描述
初始化栈void initStack(Stack *stack, size_t capacity)初始化一个栈,并设置初始容量。
返回栈内元素个数size_t getSize(const Stack *stack)返回栈中当前的元素个数。
添加新元素至栈顶void push(Stack *stack, int element)将一个新元素压入栈顶。如果栈满,则自动扩容。
弹出栈顶元素并返回int pop(Stack *stack)移除并返回栈顶元素(假设是 int 类型)。如果栈为空,返回错误码(如 -1)。
释放栈内存void destroyStack(Stack *stack)释放栈占用的所有内存资源。
遍历栈中的所有元素

void traverseStack(Stack *stack)

void printfStack(Stack *stack)

方法一:使用循环和一个临时栈,通过 push 和 pop 操作,遍历栈中的所有元素,遍历后栈的状态与遍历前相同。

方法二:直接通过循环动态数组的操作遍历栈中的所有元素。


6 功能实现

        本节基于动态数组来实现一个栈。

6.1 初始化栈

#include <stdio.h>
#include <stdlib.h>

// 定义栈结构体
// 使用顺序存储结构(动态数组)来实现 —> 顺序栈
typedef struct
{
    int *data;       // 动态数组存储栈元素,以 int 类型为例
    size_t size;     // 当前栈内元素个数
    size_t capacity; // 动态数组的容量
} Stack;

/**
 * 初始化栈
 *
 * 此函数用于初始化一个栈结构体,分配初始容量的内存,并设置栈的初始状态。
 *
 * @param stack 指向栈结构体的指针
 * @param initialCapacity 栈的初始容量
 */
void initStack(Stack *stack, size_t initialCapacity)
{
    if (stack == NULL) // 检查传入的指针是否为空
    {
        puts("Error: 传入的栈为 NULL");
        return; // 如果是空指针,则直接返回,退出函数
    }

    // size_t 是 C 标准库中定义的一种无符号整数类型,不能表示负数
    // 如果传入的 initialCapacity 是负数,可能会表示为一个很大的数(补码、数据回绕处理)
    // 函数内部不方便处理负数行为,只能在函数调用之前,约束传入的参数
    // 所以这里只是检查是否为 0
    if (initialCapacity == 0)
    {
        puts("Warning: 初始容量为 0,请使用一个正数来表示容量!");
        return; // 如果容量为 0,退出函数
    }

    // 使用 malloc 分配初始容量的内存
    stack->data = (int *)malloc(initialCapacity * sizeof(int));
    if (stack->data == NULL)
    {
        puts("Error: 内存分配失败,初始化失败!");
        return; // 如果分配失败,退出函数
    }
    stack->size = 0;                   // 初始元素个数为 0
    stack->capacity = initialCapacity; // 设置初始容量
    
    printf("Success:成功初始化动态数组,容量为:%zu\n", initialCapacity);
}

int main()
{
    // 声明结构体变量
    Stack myStack;

    puts("----------------------1.初始化栈----------------------");
    initStack(&myStack, 2); // 初始化栈,初始容量为 2
    initStack(NULL, 2);     // 错误测试
    initStack(&myStack, 0); // 错误测试

    return 0;
}

        输出结果如下所示:

6.2 返回栈内元素个数

/**
 * 获取栈内元素个数
 *
 * 此函数用于获取栈中当前的元素个数。
 *
 * @param stack 指向栈结构体的指针
 * @return 栈内元素个数,如果传入的指针为 NULL,则返回 0
 */
size_t getSize(const Stack *stack)
{
    // 检查传入的指针是否为空
    if (stack == NULL)
    {
        puts("Error: 传入的栈为 NULL");
        return 0; // 如果是空指针,返回 0
    }

    // 返回栈内元素个数
    return stack->size;
}

6.3 添加新元素至栈顶

/**
 * 添加新元素到栈顶
 *
 * 此函数用于将新元素添加到栈顶。如果栈已满,会自动扩展栈的容量。
 * 新元素只能添加到栈顶(即动态数组的尾部),所以不用传入索引。
 *
 * @param stack 指向栈结构体的指针
 * @param element 要添加的新元素
 */
void push(Stack *stack, int element)
{
    // 检查传入的指针是否为空
    if (stack == NULL)
    {
        puts("Error: 传入的栈为 NULL");
        return; // 如果是空指针,则直接返回,退出函数
    }

    // 检查顺序表(动态数组)是否存满了
    if (stack->size == stack->capacity)
    {
        printf("栈空间已满,当前容量为 %zu ,需要扩容,正在扩容中~\n", stack->capacity);
        // 如果栈已满,需要扩展容量
        size_t newCapacity = stack->capacity * 2; // 扩容到原来的 2 倍
        // 重新分配内存
        int *newData = (int *)realloc(stack->data, newCapacity * sizeof(int));

        if (newData == NULL)
        {
            puts("Error: 扩容失败!");
            return; // 如果内存分配失败,退出函数
        }

        stack->data = newData;
        stack->capacity = newCapacity;
        printf("扩容成功(容量扩大一倍),现在的容量为 %zu\n", newCapacity);
    }

    stack->data[stack->size] = element; // 将元素压入栈顶
    stack->size++;                      // 更新元素个数
    printf("Success:新元素 %d 成功进栈(栈顶)\n", element);
}

6.4 弹出栈顶元素并返回

/**
 * 弹出栈顶元素并返回
 *
 * 此函数用于从栈顶弹出一个元素,并返回该元素。如果栈为空,会输出错误信息并返回一个无效值。
 *
 * @param stack 指向栈结构体的指针
 * @return 栈顶元素,如果栈为空则返回 -1
 */
int pop(Stack *stack)
{
    // 检查栈是否为空
    if (stack->size == 0)
    {
        puts("当前栈空,弹栈失败");
        return -1; // 栈为空,返回无效值
    }

    // 方法 1
    // int popElement = stack->data[stack->size - 1]; // 使用数组加下标返回,注意需要 -1 ,不然会越界访问
    // stack->size--;                                 // 更新元素个数
    // return popElement;

    // 方法 2
    // stack->size--;                   // 更新元素个数
    // return stack->data[stack->size]; // 返回栈顶元素

    // 方法 3
    return stack->data[--stack->size]; // 返回栈顶元素并更新元素个数(自减操作)
}

6.5 释放栈内存

/**
 * 释放栈内存
 *
 * 此函数用于释放栈所占用的内存,并将栈结构体的成员重置为初始状态。
 *
 * @param stack 指向栈结构体的指针
 */
void destroyStack(Stack *stack)
{
    // 检查传入的指针是否为空
    if (stack == NULL)
    {
        puts("Error: 传入的栈为 NULL");
        return; // 如果是空指针,则直接返回,退出函数
    }

    // 释放动态数组内存
    free(stack->data);  // 释放动态数组内存
    stack->data = NULL; // 防止悬挂指针

    // 重置栈结构体的成员
    stack->size = 0;
    stack->capacity = 0;

    puts("Success: 栈内存已成功释放");
}

6.6 遍历栈中的所有元素

/**
 * 遍历栈中的所有元素
 *
 * 此函数用于遍历栈中的所有元素,打印每个元素的值。遍历后栈的状态与遍历前相同。
 *
 * @param stack 指向栈结构体的指针
 */
void traverseStack(Stack *stack)
{
    // 检查传入的指针是否为空
    if (stack == NULL)
    {
        puts("Error: 传入的栈为 NULL");
        return; // 如果是空指针,则直接返回,退出函数
    }

    Stack tempStack;                        // 创建一个临时栈用于保存弹出的元素
    initStack(&tempStack, stack->capacity); // 初始化临时栈,容量与原栈相同

    puts("使用临时栈的方式遍历栈栈中的所有元素:");

    // 循环直到原栈为空
    while (stack->size > 0)
    {
        int element = pop(stack);  // 从原栈弹出元素
        printf("%d\t", element);   // 处理元素(这里是打印)
        push(&tempStack, element); // 将元素压入临时栈
    }
    printf("\n");

    // 将所有元素从临时栈重新压入原栈
    while (tempStack.size > 0)
    {
        int element = pop(&tempStack); // 从临时栈弹出元素
        push(stack, element);          // 将元素压回原栈
    }

    // 释放临时栈占用的资源
    destroyStack(&tempStack);
}

        对于顺序栈,可以直接使用循环遍历动态数组进行栈元素的访问,如下所示:

/**
 * 打印栈中的所有元素
 *
 * 此函数用于打印栈中的所有元素。通过直接访问栈的内部数组,从栈底到栈顶依次打印每个元素。
 *
 * @param stack 指向栈结构体的指针
 */
void printfStack(Stack *stack)
{
    // 检查传入的指针是否为空
    if (stack == NULL)
    {
        puts("Error: 传入的栈为 NULL");
        return; // 如果是空指针,则直接返回,退出函数
    }

    // 检查栈是否为空
    if (stack->size == 0)
    {
        puts("Warning:当前栈为空,没有元素可以打印");
        return; // 如果栈为空,直接返回
    }

    for (size_t i = 0; i < stack->size; i++)
    {
        printf("%d\t", stack->data[i]); // 打印每个元素,使用制表符分隔
    }

    // 注意,倒着遍历的时候,就不能使用 size_t 定义循环变量
    // size_t 无法取到负数,会将负数(计算机中存储的是补码)解释成一个很大的数
    // 所以得用 int 定义循环变量
    // for (int i = stack->size - 1; i >= 0; i--)
    // {
    //     printf("%d\t", stack->data[i]); // 打印每个元素,使用制表符分隔
    // }
    printf("\n"); // 打印一个换行,结束一行
}

6.7 完整代码 

#include <stdio.h>
#include <stdlib.h>

// 定义栈结构体
// 使用顺序存储结构(动态数组)来实现 —> 顺序栈
typedef struct
{
    int *data;       // 动态数组存储栈元素,以 int 类型为例
    size_t size;     // 当前栈内元素个数
    size_t capacity; // 动态数组的容量
} Stack;

/**
 * 初始化栈
 *
 * 此函数用于初始化一个栈结构体,分配初始容量的内存,并设置栈的初始状态。
 *
 * @param stack 指向栈结构体的指针
 * @param initialCapacity 栈的初始容量
 */
void initStack(Stack *stack, size_t initialCapacity)
{
    if (stack == NULL) // 检查传入的指针是否为空
    {
        puts("Error: 传入的栈为 NULL");
        return; // 如果是空指针,则直接返回,退出函数
    }

    // size_t 是 C 标准库中定义的一种无符号整数类型,不能表示负数
    // 如果传入的 initialCapacity 是负数,可能会表示为一个很大的数(补码、数据回绕处理)
    // 函数内部不方便处理负数行为,只能在函数调用之前,约束传入的参数
    // 所以这里只是检查是否为 0
    if (initialCapacity == 0)
    {
        puts("Warning: 初始容量为 0,请使用一个正数来表示容量!");
        return; // 如果容量为 0,退出函数
    }

    // 使用 malloc 分配初始容量的内存
    stack->data = (int *)malloc(initialCapacity * sizeof(int));
    if (stack->data == NULL)
    {
        puts("Error: 内存分配失败,初始化失败!");
        return; // 如果分配失败,退出函数
    }
    stack->size = 0;                   // 初始元素个数为 0
    stack->capacity = initialCapacity; // 设置初始容量

    printf("Success:成功初始化动态数组,容量为:%zu\n", initialCapacity);
}

/**
 * 获取栈内元素个数
 *
 * 此函数用于获取栈中当前的元素个数。
 *
 * @param stack 指向栈结构体的指针
 * @return 栈内元素个数,如果传入的指针为 NULL,则返回 0
 */
size_t getSize(const Stack *stack)
{
    // 检查传入的指针是否为空
    if (stack == NULL)
    {
        puts("Error: 传入的栈为 NULL");
        return 0; // 如果是空指针,返回 0
    }

    // 返回栈内元素个数
    return stack->size;
}

/**
 * 添加新元素到栈顶
 *
 * 此函数用于将新元素添加到栈顶。如果栈已满,会自动扩展栈的容量。
 * 新元素只能添加到栈顶(即动态数组的尾部),所以不用传入索引。
 *
 * @param stack 指向栈结构体的指针
 * @param element 要添加的新元素
 */
void push(Stack *stack, int element)
{
    // 检查传入的指针是否为空
    if (stack == NULL)
    {
        puts("Error: 传入的栈为 NULL");
        return; // 如果是空指针,则直接返回,退出函数
    }

    // 检查顺序表(动态数组)是否存满了
    if (stack->size == stack->capacity)
    {
        printf("栈空间已满,当前容量为 %zu ,需要扩容,正在扩容中~\n", stack->capacity);
        // 如果栈已满,需要扩展容量
        size_t newCapacity = stack->capacity * 2; // 扩容到原来的 2 倍
        // 重新分配内存
        int *newData = (int *)realloc(stack->data, newCapacity * sizeof(int));

        if (newData == NULL)
        {
            puts("Error: 扩容失败!");
            return; // 如果内存分配失败,退出函数
        }

        stack->data = newData;
        stack->capacity = newCapacity;
        printf("扩容成功(容量扩大一倍),现在的容量为 %zu\n", newCapacity);
    }

    stack->data[stack->size] = element; // 将元素压入栈顶
    stack->size++;                      // 更新元素个数
    printf("Success:新元素 %d 成功进栈(栈顶)\n", element);
}

/**
 * 弹出栈顶元素并返回
 *
 * 此函数用于从栈顶弹出一个元素,并返回该元素。如果栈为空,会输出错误信息并返回一个无效值。
 *
 * @param stack 指向栈结构体的指针
 * @return 栈顶元素,如果栈为空则返回 -1
 */
int pop(Stack *stack)
{
    // 检查栈是否为空
    if (stack->size == 0)
    {
        puts("当前栈空,弹栈失败");
        return -1; // 栈为空,返回无效值
    }

    // 方法 1
    // int popElement = stack->data[stack->size - 1]; // 使用数组加下标返回,注意需要 -1 ,不然会越界访问
    // stack->size--;                                 // 更新元素个数
    // return popElement;

    // 方法 2
    // stack->size--;                   // 更新元素个数
    // return stack->data[stack->size]; // 返回栈顶元素

    // 方法 3
    return stack->data[--stack->size]; // 返回栈顶元素并更新元素个数(自减操作)
}

/**
 * 释放栈内存
 *
 * 此函数用于释放栈所占用的内存,并将栈结构体的成员重置为初始状态。
 *
 * @param stack 指向栈结构体的指针
 */
void destroyStack(Stack *stack)
{
    // 检查传入的指针是否为空
    if (stack == NULL)
    {
        puts("Error: 传入的栈为 NULL");
        return; // 如果是空指针,则直接返回,退出函数
    }

    // 释放动态数组内存
    free(stack->data);  // 释放动态数组内存
    stack->data = NULL; // 防止悬挂指针

    // 重置栈结构体的成员
    stack->size = 0;
    stack->capacity = 0;

    puts("Success: 栈内存已成功释放");
}

/**
 * 遍历栈中的所有元素
 *
 * 此函数用于遍历栈中的所有元素,打印每个元素的值。遍历后栈的状态与遍历前相同。
 *
 * @param stack 指向栈结构体的指针
 */
void traverseStack(Stack *stack)
{
    // 检查传入的指针是否为空
    if (stack == NULL)
    {
        puts("Error: 传入的栈为 NULL");
        return; // 如果是空指针,则直接返回,退出函数
    }

    Stack tempStack;                        // 创建一个临时栈用于保存弹出的元素
    initStack(&tempStack, stack->capacity); // 初始化临时栈,容量与原栈相同

    puts("使用临时栈的方式遍历栈栈中的所有元素:");

    // 循环直到原栈为空
    while (stack->size > 0)
    {
        int element = pop(stack);  // 从原栈弹出元素
        printf("%d\t", element);   // 处理元素(这里是打印)
        push(&tempStack, element); // 将元素压入临时栈
    }
    printf("\n");

    // 将所有元素从临时栈重新压入原栈
    while (tempStack.size > 0)
    {
        int element = pop(&tempStack); // 从临时栈弹出元素
        push(stack, element);          // 将元素压回原栈
    }

    // 释放临时栈占用的资源
    destroyStack(&tempStack);
}

/**
 * 打印栈中的所有元素
 *
 * 此函数用于打印栈中的所有元素。通过直接访问栈的内部数组,从栈底到栈顶依次打印每个元素。
 *
 * @param stack 指向栈结构体的指针
 */
void printfStack(Stack *stack)
{
    // 检查传入的指针是否为空
    if (stack == NULL)
    {
        puts("Error: 传入的栈为 NULL");
        return; // 如果是空指针,则直接返回,退出函数
    }

    // 检查栈是否为空
    if (stack->size == 0)
    {
        puts("Warning:当前栈为空,没有元素可以打印");
        return; // 如果栈为空,直接返回
    }

    for (size_t i = 0; i < stack->size; i++)
    {
        printf("%d\t", stack->data[i]); // 打印每个元素,使用制表符分隔
    }

    // 注意,倒着遍历的时候,就不能使用 size_t 定义循环变量
    // size_t 无法取到负数,会将负数(计算机中存储的是补码)解释成一个很大的数
    // 所以得用 int 定义循环变量
    // for (int i = stack->size - 1; i >= 0; i--)
    // {
    //     printf("%d\t", stack->data[i]); // 打印每个元素,使用制表符分隔
    // }
    printf("\n"); // 打印一个换行,结束一行
}

int main()
{
    // 声明结构体变量
    Stack myStack;

    puts("----------------------1.初始化栈----------------------");
    initStack(&myStack, 2); // 初始化栈,初始容量为 2
    initStack(NULL, 2);     // 错误测试
    initStack(&myStack, 0); // 错误测试

    puts("-------------------2.新元素进栈(栈顶)------------------");
    push(NULL, 1);     // 错误测试
    push(&myStack, 1); // 元素 1 压栈
    push(&myStack, 2); // 元素 2 压栈
    push(&myStack, 3); // 元素 3 压栈(需要扩容)
    printf("栈内元素个数为:%zu\n", getSize(&myStack));
    printf("栈内元素个数为:%zu\n", getSize(NULL)); // 错误测试 返回 0

    puts("---------------------3.循环遍历栈元素--------------------");
    // traverseStack(&myStack);
    printfStack(&myStack);
    printfStack(NULL); // 错误测试

    puts("---------------------4.栈顶元素出栈--------------------");
    printf("弹出的栈顶元素是:%d\n", pop(&myStack));
    printf("弹出的栈顶元素是:%d\n", pop(&myStack));
    printf("弹出的栈顶元素是:%d\n", pop(&myStack));
    printf("栈内元素个数为:%zu\n", getSize(&myStack));
    printfStack(&myStack); // 没有元素可打印,警告

    puts("---------------------4.释放栈内存--------------------");
    destroyStack(&myStack);

    return 0;
}

        输出结果如下所示:

是一种线性的数据结构,通常采用两种主要的存储结构实现:顺序存储(数组)链接存储(链表)。以下是这两种情况下的基本操作: **顺序存储(Array Stack):** 1. 初始化:创建一个固定大小的数组作为,如果需要动态调整大小,可以预先设定数组长度。 ```python def init_stack(array_size): stack = [None] * array_size top = -1 return stack, top ``` 2. 进栈(Push):将元素添加到。 ```python def push(stack, item): if len(stack) == top + 1: raise OverflowError("Stack is full") stack[top + 1] = item top += 1 ``` 3. 出栈(Pop):移除并返回元素,若空则抛异常。 ```python def pop(stack): if top < 0: raise IndexError("Stack is empty") item = stack[top] stack[top] = None top -= 1 return item ``` 4. 取元素(Peek):查看但不移除元素。 ```python def peek(stack): if top < 0: return None return stack[top] ``` 5. 判断是否为空(Is Empty): ```python def is_empty(stack): return top == -1 ``` 6. 遍历(通常不适用于顺序栈,因为顺序栈只能访问,无法随机访问中间元素): **顺序栈一般不适合循环遍历,除非使用辅助。** **链式存储(Linked List Stack):** 1. 初始化:创建一个空节点作为头结点。 ```python class Node: def __init__(self, data=None): self.data = data self.next = None def init_stack(): return Node() ``` 2. 进栈:在当前节点后插入新节点。 ```python def push(linked_stack, item): new_node = Node(item) new_node.next = linked_stack.top linked_stack.top = new_node ``` 3. 出栈:删除并返回节点。 ```python def pop(linked_stack): if linked_stack.is_empty(): raise IndexError("Stack is empty") popped_data = linked_stack.top.data linked_stack.top = linked_stack.top.next return popped_data ``` 4. 其他操作类似顺序栈
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thanks_ks

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值