1. 栈是什么?答:像一叠盘子
想象你面前有一叠盘子,每次你只能:
放一个新盘子到最上面(压栈 / push)
拿走最上面的盘子(出栈 / pop)
看一眼最上面的盘子是什么,但不拿走(查看栈顶 / peek)
这就叫“后进先出”,英文是 LIFO(Last In First Out)。
在程序里,栈就是一个只能在一端操作的容器。我们要做的是用一个“数组”来存这些盘子,并且这个数组能自动变大,这样我们就永远不用担心盘子放不下了。
2. 为什么要“动态”?
普通的数组一旦定义好大小就不能变了。比如 int arr[10] 就只能放10个数字。如果突然来了第11个盘子,就放不下了。
动态数组的意思是:一开始我们只申请一点点内存(比如能放4个数字),等它放满了,我们再申请一个更大的内存(比如能放8个),然后把原来的4个数字搬过去,之后还可以继续变大。这样一来,你用的时候完全不需要事先知道要存多少个元素。
3. 动态数组栈需要哪些零件?
我们需要用一个结构体把三样东西捆在一起:
typedef struct {
int *data; // 指向存放元素的数组(用指针,方便重新分配)
int top; // 栈顶索引,表示最后一个元素的位置,-1 代表栈空
int capacity; // 当前分配的内存最多能放几个元素
} ArrayStack;
data 是个指针,我们用它指向动态分配的那块内存。
top 记录栈顶在哪里。比如栈里已有两个数,那 top = 1(索引从0开始)。
capacity 记录现在分配的空间大小,不是实际元素个数。
4. 一步一步实现各个功能
4.1 初始化
刚开始栈是空的,我们要申请一小块内存,把 top 设成 -1,capacity 设成初始大小。
#define INIT_SIZE 4 // 刚开始分配4个位置
void initStack(ArrayStack *s) {
s->data = (int*)malloc(INIT_SIZE * sizeof(int));
s->top = -1;
s->capacity = INIT_SIZE;
}
这里的 malloc 就是在内存里要了一块地,能放4个 int。
4.2 判空、查大小
int isEmpty(ArrayStack *s) {
return s->top == -1; // 如果 top 还是 -1,说明没有盘子
}
int size(ArrayStack *s) {
return s->top + 1; // 因为索引从0开始,数量 = 索引 + 1
}
4.3 扩容(最关键的一步)
当栈满的时候,我们需要更大的地方。这里用 realloc 函数,它可以把之前 malloc 出来的地盘变大(实际上可能找一块新地,把你原来的东西拷贝过去,老地自动释放)。
void expand(ArrayStack *s) {
s->capacity = s->capacity * 2; // 容量扩成两倍
s->data = (int*)realloc(s->data, s->capacity * sizeof(int));
}
realloc(原指针, 新大小) 返回新内存的地址。我们把它重新赋给 s->data 就行了。
4.4 压栈(push)—— 放盘子
每次放盘子之前,先看看是不是满了。满了就先扩容,然后 top 加1,把新元素放在 top 位置上。
void push(ArrayStack *s, int value) {
if (s->top + 1 == s->capacity) { // 真的满了
expand(s); // 扩大地盘
}
s->top++; // 栈顶上移
s->data[s->top] = value; // 把新数字放到栈顶
}
4.5 出栈(pop)—— 拿走最上面的盘子
拿出最上面的盘子,同时 top 减1,表示盘子少了。注意检查是否为空。
int pop(ArrayStack *s) {
if (isEmpty(s)) {
printf("栈已经空了,不能再拿!\n");
exit(-1); // 异常退出,或者你可以返回一个特殊值
}
int val = s->data[s->top]; // 拿到栈顶的值
s->top--; // 栈顶向下移
return val;
}
4.6 查看栈顶(peek)—— 只看不拿
和 pop 一样,但不移动 top。
int peek(ArrayStack *s) {
if (isEmpty(s)) {
printf("栈是空的,没东西可看!\n");
exit(-1);
}
return s->data[s->top];
}
4.7 释放内存
最后不用时,一定要把 malloc 出来的内存还回去,不然会内存泄漏。
void destroyStack(ArrayStack *s) {
free(s->data);
s->data = NULL;
s->top = -1;
s->capacity = 0;
}
5. 完整代码(可以直接跑起来)
#include <stdio.h>
#include <stdlib.h>
#define INIT_SIZE 4
typedef struct {
int *data;
int top;
int capacity;
} ArrayStack;
// 初始化
void initStack(ArrayStack *s) {
s->data = (int*)malloc(INIT_SIZE * sizeof(int));
s->top = -1;
s->capacity = INIT_SIZE;
}
// 判空
int isEmpty(ArrayStack *s) {
return s->top == -1;
}
// 元素个数
int size(ArrayStack *s) {
return s->top + 1;
}
// 扩容
void expand(ArrayStack *s) {
s->capacity *= 2;
s->data = (int*)realloc(s->data, s->capacity * sizeof(int));
printf("(内部扩容:容量变为了 %d)\n", s->capacity);
}
// 压栈
void push(ArrayStack *s, int value) {
if (s->top + 1 == s->capacity) {
expand(s);
}
s->data[++(s->top)] = value; // 等价于 top++; data[top]=value;
}
// 出栈
int pop(ArrayStack *s) {
if (isEmpty(s)) {
printf("栈空,无法出栈\n");
exit(1);
}
return s->data[(s->top)--]; // 先返回 data[top],然后 top--
}
// 查看栈顶
int peek(ArrayStack *s) {
if (isEmpty(s)) {
printf("栈空\n");
exit(1);
}
return s->data[s->top];
}
// 打印整个栈(从底到顶)
void printStack(ArrayStack *s) {
printf("栈内容(底->顶):");
for (int i = 0; i <= s->top; i++) {
printf("%d ", s->data[i]);
}
printf("\n");
}
// 销毁
void destroyStack(ArrayStack *s) {
free(s->data);
s->data = NULL;
s->top = -1;
s->capacity = 0;
}
// 测试一下
int main() {
ArrayStack stack;
initStack(&stack);
printf("压入 10, 20, 30, 40\n");
push(&stack, 10);
push(&stack, 20);
push(&stack, 30);
push(&stack, 40);
printStack(&stack);
printf("当前栈顶: %d\n", peek(&stack));
printf("元素个数: %d\n", size(&stack));
printf("\n再压入 50(会触发扩容)\n");
push(&stack, 50); // 这里会看到扩容提示
printStack(&stack);
printf("元素个数: %d\n", size(&stack));
printf("\n连续出栈两个:\n");
printf("弹出: %d\n", pop(&stack));
printf("弹出: %d\n", pop(&stack));
printStack(&stack);
destroyStack(&stack);
return 0;
}
6. 复盘一下关键点
我们用 malloc 申请初始数组,用 realloc 在空间不够时自动扩大。
top 既表示最后一个元素的索引,也表示下一次压栈时要放的位置(只要先把 top+1 再赋值)。
扩容通常扩容为原来的两倍,这样可以减少频繁的重新分配。
7.用链表实现栈
除了用数组来实现以外,用链表也可以实现。
以下是用链表来实现的代码。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 链表节点
typedef struct Node {
int data;
struct Node *next;
} Node;
// 栈结构(保存栈顶指针和节点数)
typedef struct {
Node *top;
int count;
} LinkedStack;
// 初始化
void initStack(LinkedStack *s) {
s->top = NULL;
s->count = 0;
}
// 判空
bool isEmpty(LinkedStack *s) {
return s->top == NULL;
}
// 获取大小
int size(LinkedStack *s) {
return s->count;
}
// 压栈(头插法)
void push(LinkedStack *s, int value) {
Node *newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败\n");
exit(EXIT_FAILURE);
}
newNode->data = value;
newNode->next = s->top;
s->top = newNode;
s->count++;
}
// 出栈
int pop(LinkedStack *s) {
if (isEmpty(s)) {
printf("栈空,无法出栈\n");
exit(EXIT_FAILURE);
}
Node *temp = s->top;
int value = temp->data;
s->top = s->top->next;
free(temp);
s->count--;
return value;
}
// 获取栈顶
int peek(LinkedStack *s) {
if (isEmpty(s)) {
printf("栈空\n");
exit(EXIT_FAILURE);
}
return s->top->data;
}
// 打印栈(从顶到底)
void printStack(LinkedStack *s) {
printf("栈 (顶->底): ");
Node *cur = s->top;
while (cur != NULL) {
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
// 销毁栈
void destroyStack(LinkedStack *s) {
while (!isEmpty(s)) {
pop(s);
}
}
// ---------- 测试 ----------
int main() {
LinkedStack stack;
initStack(&stack);
push(&stack, 100);
push(&stack, 200);
push(&stack, 300);
printStack(&stack); // 300 200 100
printf("栈顶: %d\n", peek(&stack)); // 300
printf("出栈: %d\n", pop(&stack)); // 300
printf("出栈: %d\n", pop(&stack)); // 200
printf("大小: %d\n", size(&stack)); // 1
push(&stack, 400);
push(&stack, 500);
printStack(&stack); // 500 400 100
destroyStack(&stack);
return 0;
}
7172



被折叠的 条评论
为什么被折叠?



