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

news2024/10/16 22:24:54

目录

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;
}

        输出结果如下所示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2216598.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一个月学会Java 第17天 常用类(包装类、数学类、日期类、可变长字符串等等)

Day17 常用类(包装类、数学类、日期类、可变长字符串等等) 这一节会非常的简单和轻松&#xff0c;可以当做今天的学习就是在放松自己&#xff0c;因为这些常用类只需要记住类名和大概的方法在里面即可&#xff0c;翻一下看一下方法名都可以看出来的 目录 包装类数学类Math日期…

五、【智能体】满满干货,RAG技术在智能体中的神奇应用,你知道几个?

1. 智能问答系统 RAG技术广泛应用于问答系统中&#xff0c;特别是在需要即时获取最新信息或专业知识的场合。传统生成模型可能无法应对最新或特定领域的复杂问题&#xff0c;而RAG通过检索相关资料并增强生成过程&#xff0c;能够提供准确、具体的回答。 客户服务&#xff1a…

OpenCV-人脸检测

文章目录 一、人脸检测流程二、关键方法三、代码示例四、注意事项 OpenCV是一个开源的计算机视觉和机器学习软件库&#xff0c;它提供了多种人脸检测方法&#xff0c;以下是对OpenCV人脸检测的详细介绍&#xff1a; 一、人脸检测流程 人脸检测是识别图像中人脸位置的过程&…

2023年甘肃省职业院校技能大赛 网络建设与运维赛项 网络搭建及安全部署竞赛报告单

2023年甘肃省职业院校技能大赛 网络建设与运维赛项 网络搭建及安全部署竞赛报告单 示例&#xff1a;(仅供参考&#xff0c;具体以正式赛卷报告单为准) 在SW1上执行show vlan命令收集信息&#xff0c;截图如下&#xff1a; 请注意尽量保证格式、图片排列整齐 请注意尽量窗…

IO进程---day3

1、完成标准io的单字符实现两个文件的拷贝&#xff1b; #include<myhead.h>//标准io的单字符现两个文件的拷贝&#xff1b; int main(int argc, const char *argv[]) {//判断是否有3个文件传入if(3 ! argc){fputs("input file error\n",stderr);return -1;}//打…

测试教程分享

前几年在腾讯课堂上发布了不少课程&#xff0c;后来腾讯课堂改革&#xff0c;要收会员费&#xff0c;课程还要抽提程&#xff0c;这么下来就相当于白干了。就放弃了在上面发课程&#xff0c;再后来腾讯课堂就关闭了&#xff0c;以前发布的视频就没有地方发了&#xff0c;于是我…

IDEA中git如何快捷的使用Cherry-Pick功能

前言 我们在使用IDEA开发时&#xff0c;一般是使用GIT来管理我们的代码&#xff0c;有时候&#xff0c;我们需要在我们开发的主分支上合并其他分支的部分提交代码。注意&#xff0c;是部分&#xff0c;不是那个分支的全部提交&#xff0c;这时候&#xff0c;我们就需要使用Che…

在Linux中搭建WordPress并实现Windows主机远程访问

WordPreWordPress是一个基于PHP开发的开源平台&#xff0c;适用于在支持PHP与MySQL数据库的服务器上搭建个性化博客或网站。同时&#xff0c;它也能够作为功能强大的内容管理系统&#xff08;CMS&#xff09;被广泛应用。 虚拟机&#xff1a;VirtualBox 虚拟机安装&#x1f449…

【更新】中国地区粮食播种、粮食产量、灾害等数据(1990-2023年)

数据为中国地区粮食播种、粮食产量、灾害等数据&#xff0c;包括369个指标&#xff0c;各类农作物播种面积、粮食产量、牲畜饲养、受灾面积等。这些指标综合反映了中国农业生产、粮食安全的相关情况 一、数据介绍 数据名称&#xff1a;中国地区粮食播种、粮食产量、灾害等数据…

RHCE---使用邮箱客户端s-nail

使用邮箱客户端s-nail 方案一&#xff1a;使用网易邮箱 1&#xff0c;挂载虚拟镜像 [rootlocalhost ~]# mount /dev/sr1 /mnt mount: /mnt: /dev/sr1 already mounted on /run/media/root/RHEL-9-3-0-BaseOS-x86_64.2&#xff0c;编辑环境文件 [rootlocalhost ~]# vim /et…

部署harbor问题(缺少ssl认证证书)

在部署harbor服务&#xff0c;/install.sh启动时&#xff0c;缺少ssl认证 1. 创建证书目录 首先&#xff0c;创建 /usr/local/harbor/ssl 目录&#xff1a; mkdir -p /usr/local/harbor/ssl 2. 生成私钥 生成一个 4096 位的 RSA 私钥&#xff1a; openssl genrsa -out /us…

【优秀Python大屏】全球肺癌患病人数分析与可视化展示

1. 项目背景 肺癌是全球范围内影响人类健康的重大疾病之一&#xff0c;了解不同地区、不同收入水平国家的肺癌患病人数分布以及不同年龄段的患病趋势&#xff0c;有助于全球卫生组织和研究人员制定更有效的防治策略。本次数据分析利用全球各洲和国家的肺癌患病数据&#xff0c…

了解专用代理服务器的功能

在当今数字化的环境中&#xff0c;确保安全高效的互联网连接变得至关重要。这种需求催生了专用代理服务器&#xff0c;这是一种确保在线隐私、安全和可访问性的强大工具。了解专用代理服务器的细微差别和功能对于寻求增强在线保护和访问的个人和企业是十分重要的。 一、什么是…

中标麒麟v5安装qt512.12开发软件

注意 需要联网操作 遇到问题1&#xff1a;yum提示没有可用软件包问题 终端执行如下命令 CentOS7将yum源更换为国内源保姆级教程 中标麒麟V7-yum源的更换&#xff08;阿里云源&#xff09; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Cento…

Ingress-nginx中HTTPS的强制转发

文章目录 在使用aws 的NLB转发流量到ingress时&#xff0c;发现NLP上生成的转发配置不符合正常预期&#xff0c;如下图&#xff1a; ingress-nginx service 配置如下&#xff1a; apiVersion: v1 kind: Service metadata:annotations:service.beta.kubernetes.io/aws-load-b…

无人机集群路径规划:5种优化算法(SFOA、APO、GOOSE、CO、PIO)求解无人机集群路径规划,提供MATLAB代码

一、单个无人机路径规划模型介绍 无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径&#xff0c;使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一&#xff0c;它可以通过算法和模型来确定无人机的航迹&#xff0c;以避开障碍物、优化…

CSS网页布局(重塑网页布局)

一、实现两列布局 许多网站有一些特点&#xff0c;如页面顶部放置一个大的导航或广告条&#xff0c;右侧是链接或图片&#xff0c;左侧放置主要内容&#xff0c;页面底部放置版权信息等 一般情况&#xff0c;此类网页布局的两列都有固定的宽度&#xff0c;而且从内容上很容易区…

【C++】—通俗易懂的理解C++中的模板

文章目录 前言&#xff1a;&#x1f49e;1.函数模板&#x1f49e;1.1 函数模板的概念&#x1f49e;1.2 函数模板的格式&#x1f49e;1.3 函数模板的原理&#x1f49e;1.4 函数模板的实例化&#x1f49e;1.5 模板参数的匹配原则 &#x1f49e;2.类模板&#x1f49e;1.1 类模板的…

简单介绍$listeners

$listeners 它可以获取父组件传递过来的所有自定义函数&#xff0c;如下&#xff1a; // 父组件 <template><div class"a"><Child abab"handleAbab" acac"handleAcac"/></div> </template><script> impor…

【v5.3.0】修复订单批量发货提示 isPicUpload is not defined

使用订单批量发货的时候&#xff0c;没有反应&#xff0c;控制台提示 ReferenceError: isPicUpload is not defined 修改文件src/pages/order/orderList/components/tableList.vue 把isPicUpload改成isFileUpload&#xff0c;然后重新打包admin后台上传即可