(C语言)算法通关村第四关——如何基于数组或链表实现栈青铜桃战笔记

目录

前言

指针数组

1 用数组(指针数组)的形式构建栈

01 创建结构体,栈大小初始化 

02 内部必须实现的函数

03 peek(查看栈顶元素) pop(出栈)push(入栈)

2 用链表的形式构建栈

01 创建结构体,栈大小初始化 

02 内部必须实现的函数

03 peek(查看栈顶元素) pop(出栈)push(入栈)

总结

使用链表构建栈:

优点:

缺点:

使用数组构建栈:

优点:

缺点:

结论


前言

栈是一种数据结构,它按照后进先出(LIFO)的原则管理数据。这意味着最后放入栈的元素将首先被取出。栈有两个主要操作:压栈(push),将元素放入栈的顶部;出栈(pop),从栈的顶部取出元素。栈常常用于跟踪程序执行过程中的函数调用、表达式求值等操作,也可用于解决一些计算问题。

指针数组

/**
* 数组: 数组是一种数据结构,可以存储相同类型的多个元素,这些元素在内存中是连续排列的。
* 指针: 指针是一个特殊的变量,它存储了一个内存地址,即某个数据的位置。
* 指针数组: 当我们将指针放入数组中,这个数组就成为指针数组。数组的每个元素都是一个指针,指向内存中的某个位置。
*/
int numbers[3];  // 这是一个整数数组
int *ptrArray[3]; // 这是一个指针数组,每个元素是一个整数指针

int a = 10, b = 20, c = 30;

ptrArray[0] = &a;  // 第一个元素指向变量 a
ptrArray[1] = &b;  // 第二个元素指向变量 b
ptrArray[2] = &c;  // 第三个元素指向变量 c

1 用数组(指针数组)的形式构建栈

01 创建结构体,栈大小初始化 

#define INITIAL_CAPACITY 5  // 初始容量
#define GROWTH_FACTOR 2  // 扩容因子

typedef struct {
    void** data ; // 栈中元素
    int top;  // 栈顶指针
    int capacity; // 栈容量
} Stack;

/**
 * @brief 初始化栈
 *
 * 初始化给定的栈结构,为其分配内存空间,并设置初始状态。
 *
 * @param stack 栈指针
 */
void cstack_initStack(Stack* stack) {
    // 为数据部分分配内存空间
    stack->data = (void**)malloc(INITIAL_CAPACITY * sizeof(void*));
    // 初始化栈顶为-1,表示栈为空  这是因为栈的索引是从0开始的,所以当top为-1时,表示栈中没有元素
    stack->top = -1;
    // 初始化栈的容量为初始容量
    stack->capacity = INITIAL_CAPACITY;
}

02 内部必须实现的函数

      
/**
 * @brief 判断栈是否为空
 *
 * 判断给定的栈是否为空,如果栈顶指针为-1,则返回1,否则返回0。
 *
 * @param stack 栈指针
 *
 * @return 如果栈为空,则返回1;否则返回0。
 */
int cstack_isEmpty(Stack* stack) {
    // 判断栈顶指针是否为-1,如果是则返回1,表示栈为空
    return stack->top == -1;
}


/**
 * @brief 判断栈是否已满
 *
 * 判断给定的栈是否已满。如果栈已满,返回1;否则返回0。
 *
 * @param stack 栈指针
 *
 * @return int 如果栈已满,返回1;否则返回0
 */
int cstack_isFull(Stack* stack) {
    // 判断栈顶指针是否等于栈的最大容量减去1,如果是,说明栈已满
    return stack->top == stack->capacity - 1;
}


/**
 * @brief 扩大栈的空间
 *
 * 根据设定的增长因子,扩大给定栈的空间。
 *
 * @param stack 栈指针
 */
void cstack_grow(Stack* stack) {
    // 扩大栈的空间,增长因子为预先设定的常量
    stack->capacity *= GROWTH_FACTOR;
    // 重新分配内存空间,以容纳更多的元素
    stack->data = (void**)realloc(stack->data, stack->capacity * sizeof(void*));
    // 如果内存分配失败,则输出错误信息并退出程序
    if (stack->data == NULL) {
        fprintf(stderr, "内存分配失败\n");
        exit(EXIT_FAILURE);
    }
}


/**
 * @brief 释放堆栈内存
 *
 * 释放给定堆栈的内存资源。
 *
 * @param stack 堆栈指针
 */
void cstack_freeStack(Stack* stack) {
    free(stack->data);
}

03 peek(查看栈顶元素) pop(出栈)push(入栈)

/**
 * @brief 将元素推入堆栈
 *
 * 将指定的元素推入给定的堆栈中。如果堆栈已满,则会扩展堆栈的大小。
 *
 * @param stack 堆栈指针
 * @param value 要推入堆栈的元素指针
 */
void cstack_push(Stack* stack, void* value) {
    if (cstack_isFull(stack)) {
        cstack_grow(stack);
    }
    stack->data[++stack->top] = value;
}

/**
 * @brief 出栈操作
 *
 * 从给定的栈中弹出一个元素,并返回该元素的值。如果栈为空,则无法执行出栈操作,会输出错误信息。
 *
 * @param stack 栈指针
 *
 * @return 成功出栈的元素值,如果栈为空则返回NULL
 */
void* cstack_pop(Stack* stack) {
    if (cstack_isEmpty(stack)) {
        printf("栈为空,无法出栈\n");
        return NULL;
    } else {
        return stack->data[stack->top--];
    }
}

/**
 * @brief 查看栈顶元素
 *
 * 从给定的栈中获取栈顶元素,并返回其地址。如果栈为空,则返回NULL。
 *
 * @param stack 栈指针
 *
 * @return 返回栈顶元素的地址,如果栈为空则返回NULL
 */
void* cstack_peek(Stack* stack) {
    // 检查栈是否为空
    if (cstack_isEmpty(stack)) {
        // 如果栈为空,打印提示信息
        printf("栈为空\n");
        // 返回NULL
        return NULL;
    } else {
        // 如果栈不为空,返回栈顶元素的地址
        return stack->data[stack->top];
    }
}

2 用链表的形式构建栈

01 创建结构体,栈大小初始化 


// 定义链表节点
typedef struct Node {
    void* data;  // 存储任意类型的数据
    struct Node* next;  // 指向下一个节点的指针
} Node;

// 定义栈结构
typedef struct {
    Node* top;  // 栈顶指针
} Stack;

// 初始化栈
void initStack(Stack* stack) {
    stack->top = NULL;
}

02 内部必须实现的函数

// 判断栈是否为空
int isEmpty(Stack* stack) {
    return stack->top == NULL;
}

// 释放栈的内存
void freeStack(Stack* stack) {
    while (!isEmpty(stack)) {
        pop(stack);
    }
}

03 peek(查看栈顶元素) pop(出栈)push(入栈)

// 元素入栈
void push(Stack* stack, void* value) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        fprintf(stderr, "内存分配失败\n");
        exit(EXIT_FAILURE);
    }

    newNode->data = value;
    newNode->next = stack->top;
    stack->top = newNode;
}

// 元素出栈
void* pop(Stack* stack) {
    if (isEmpty(stack)) {
        printf("栈为空,无法出栈\n");
        return NULL;
    }

    Node* topNode = stack->top;
    void* value = topNode->data;
    stack->top = topNode->next;
    free(topNode);

    return value;
}

// 获取栈顶元素但不出栈
void* peek(Stack* stack) {
    if (isEmpty(stack)) {
        printf("栈为空\n");
        return NULL;
    }

    return stack->top->data;
}

总结

  • 栈的数据结构定义: 定义清楚栈的数据结构,包括栈顶指针以及可能需要的其他信息。
  • 栈的大小和扩容: 如果栈的大小是固定的,确保栈不会溢出。如果栈的大小不固定,考虑实现自动扩容的机制。
  • 内存管理: 如果使用动态内存分配,确保在适当的时候释放内存,以防止内存泄漏。
  • 栈的初始化: 在使用栈之前,确保进行了初始化。这通常包括将栈顶指针设置为适当的初始值。
  • 数据类型的一般性: 如果希望栈能够处理不同类型的数据,考虑使用泛型机制或者采用一般性的数据表示方式。
  • 栈的操作: 实现基本的栈操作,如入栈、出栈、获取栈顶元素等。确保这些操作能够正确处理边界条件,例如空栈的情况。
  • 错误处理: 在可能出现错误的地方进行适当的错误处理,例如内存分配失败或尝试在空栈上执行出栈操作。

使用链表构建栈:

优点:

  1. 动态大小: 链表实现的栈可以动态分配内存,因此可以灵活地增加或减少栈的大小。
  2. 内存利用率: 不需要预先定义栈的大小,可以根据需要分配内存,减少内存的浪费。
  3. 插入和删除操作效率高: 在链表中进行插入和删除元素的操作效率较高,不涉及数据的移动。

缺点:

  1. 访问元素速度慢: 在链表中访问元素的效率较低,需要从头节点开始遍历直到找到目标元素。
  2. 额外的空间消耗: 每个节点需要存储数据和指向下一个节点的指针,可能会导致额外的空间消耗。

使用数组构建栈:

优点:

  1. 直接访问元素: 数组实现的栈可以通过索引直接访问元素,因此在访问元素方面效率较高。
  2. 内存消耗小: 不需要额外的指针存储空间,因此在内存使用方面较为紧凑。
  3. 适合小规模固定大小的栈: 对于知道栈大小的情况,使用数组可以更简单和高效。

缺点:

  1. 固定大小: 静态数组实现的栈在创建时需要指定大小,不能动态扩展,可能导致空间浪费或栈溢出。
  2. 插入和删除效率低: 在数组中插入和删除元素的操作可能涉及大量元素的移动,效率较低。
  3. 不灵活: 无法灵活地适应动态变化的栈大小需求,不适用于不确定大小的情况。

结论

  • 如果栈的大小不确定,需要动态变化,或者对于频繁的插入和删除操作,链表实现的栈更为适用。
  • 如果栈的大小是已知的且较小,对于直接访问元素的需求较高,数组实现的栈可能更合适。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值