一.栈的基本概念
栈(Stack)是一种线性数据结构,遵循**后进先出(LIFO, Last In First Out)**原则。最后插入的元素最先被访问或删除。看栈顶等)。
二.栈的核心操作
设计结构体并初始化
入栈(Push)操作:
将元素添加到栈顶。若栈已满(固定容量实现时即顺序栈),此操作需要检查栈满否则可能导致栈溢出(Stack Overflow)。
出栈(Pop)操作:
移除并返回栈顶元素。若栈为空,此操作需要检查栈是否为空,否则可能导致栈下溢(Stack Underflow)。
查看栈顶(Top)操作
返回栈顶元素但不移除它。也需要判断栈是否为空,因为空栈时操作无效。
三.栈的实现方式
1.顺序表实现栈
结构体设计
struct seqStack //定义结构体类型
{
int *data; //存储入栈的数据
int size; //顺序栈的容量
int top; //栈顶位置
};
初始化
顺序栈(seqStack)的初始化函数 initStack,为栈结构分配内存并设置初始状态。
以下是逐步分析:
-
动态分配
struct seqStack类型的内存空间,并将指针赋给stack。若分配失败,malloc返回NULL。 -
若栈结构内存分配成功,继续为栈的存储数组
data分配空间。这里使用calloc分配size个int大小的空间,并初始化为 0。 -
若
data分配失败,释放之前分配的stack内存,避免内存泄漏,并返回NULL表示初始化失败。 -
初始化栈的容量
size和栈顶指针top(-1 表示空栈)。 -
最终返回栈指针
stack(为NULL表示失败)。
//初始化栈,传入栈的容量
struct seqStack * initStack(int size)
{
struct seqStack *stack = malloc(sizeof(struct seqStack)); //申请内存空间
if(stack != NULL) //确保申请成功
{
stack->data = calloc(size, sizeof(int)); //申请数据存储空间
if(stack->data == NULL) //申请失败释放空间,返回空避免内存泄漏
{
free(stack);
return NULL;
}
stack->size = size; //容量赋初值
stack->top = -1; //栈顶指针赋初值
}
return stack;
}
入栈
实现顺序栈的入栈操作(push)
以下是逐步分析:
入栈操作前,要检查栈满
isFULL函数实现
-
判断栈满,通过栈顶指针(top),当栈顶指针等于栈的容量-1则栈满(初始栈顶指针为-1,从零开始,因此这里需要减1) -
//判断栈满 bool isFULL(struct seqStack *stack) { return stack->top >=stack->size-1; //0-size计数 }
push函数实现:
-
函数返回布尔类型,表示操作是否成功;参数
stack是指向顺序栈结构的指针;参数data是要入栈的整型数据。 -
调用
isFULL函数检查栈是否已满。如果栈满,输出错误信息并通过perror打印错误,并返回false表示入栈失败。 -
栈顶指针
top先自增,指向新的栈顶位置然后将数据data存储在新的栈顶位置data[stack->top]。这里使用的是前自增操作符++,确保先移动指针再存储数据。 -
操作成功完成,返回
true。
//入栈
bool push(struct seqStack *stack, int data)
{
if(isFULL(stack))
{
perror("isfull");return false;
}
stack->data[++stack->top] = data; //存储传入的数据,修改栈顶指针的位置
return true;
}
出栈
出栈和取栈顶元素需要判断栈空
isEmpty函数实现:
//判断栈空
bool isEmpty(struct seqStack *stack)
{
return stack->top == -1;
}
top函数实现:
-
参数struct seqStack *stack:指向顺序栈结构的指针; -
参数int *m:用于存储获取到的栈顶元素的指针;
1.调用isEmpty(stack)检查栈是否为空 如果栈为空,输出错误信息"isempty"并返回false
2.当栈不为空时,通过stack->data[stack->top]访问栈顶元素 将栈顶元素的值赋给指针m所指向的内存位置,最后返回true表示操作成功。
//取栈顶元素
bool top(struct seqStack *stack, int *m)
{
if(isEmpty(stack))
{
perror("isempty\n");
return false;
}
*m = stack->data[stack->top];
return true;
}
取栈顶元素
pop函数实现:
接收两个参数:指向栈结构的指针 stack 和用于返回栈顶元素的指针 m。
调用 top 函数检查栈是否为空并获取栈顶元素。
如果栈为空,top 返回 false,pop 也直接返回 false。
如果栈不为空,栈顶指针 top 减一,完成出栈操作,并返回 true 表示出栈成功。
//出栈
bool pop(struct seqStack *stack, int *m)
{
if(!top(stack, m))
{
return false;
}
stack->top--;
return true;
}
主函数
//主函数
int main(void)
{
struct seqStack *stack = initStack(10);
if(stack == NULL)
{
perror("初始化顺序栈失败\n");
exit(0);
}
else
printf("初始化成功\n");
push(stack, 3);
push(stack, 4);
push(stack, 5);
printf("\n");
while(!isEmpty(stack))
{
int m;
pop(stack, &m);
printf("%d\n", m);
}
return 0;
完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
struct seqStack //定义结构体类型
{
int *data; //存储入栈的数据
int size; //顺序栈的容量
int top; //栈顶位置
};
//初始化栈,传入栈的容量
struct seqStack * initStack(int size)
{
struct seqStack *stack = malloc(sizeof(struct seqStack)); //申请内存空间
if(stack != NULL) //确保申请成功
{
stack->data = calloc(size, sizeof(int)); //申请数据存储空间
if(stack->data == NULL) //申请失败释放空间,返回空避免内存泄漏
{
free(stack);
return NULL;
}
stack->size = size; //容量赋初值
stack->top = -1; //栈顶指针赋初值
}
return stack;
}
//判断栈满
bool isFULL(struct seqStack *stack)
{
return stack->top >=stack->size-1; //0-size计数
}
//入栈
bool push(struct seqStack *stack, int data)
{
if(isFULL(stack))
{
perror("isfull");return false;
}
stack->data[++stack->top] = data; //存储传入的数据,修改栈顶指针的位置
return true;
}
//判断栈空
bool isEmpty(struct seqStack *stack)
{
return stack->top == -1;
}
//取栈顶元素
bool top(struct seqStack *stack, int *m)
{
if(isEmpty(stack))
{
perror("isempty\n");
return false;
}
*m = stack->data[stack->top];
return true;
}
//出栈
bool pop(struct seqStack *stack, int *m)
{
if(!top(stack, m))
{
return false;
}
stack->top--;
return true;
}
//主函数
int main(void)
{
struct seqStack *stack = initStack(10);
if(stack == NULL)
{
perror("初始化顺序栈失败\n");
exit(0);
}
else
printf("初始化成功\n");
push(stack, 3);
push(stack, 4);
push(stack, 5);
printf("\n");
while(!isEmpty(stack))
{
int m;
pop(stack, &m);
printf("%d\n", m);
}
return 0;
}
顺序栈的优缺点
优点
-
内存连续:顺序栈基于数组实现,内存连续访问效率高,CPU缓存命中率更高。
-
操作简单:入栈(
push)和出栈(pop)仅需操作栈顶指针,时间复杂度为 O(1)。 -
节省空间:无需存储额外指针,仅需一个数组和栈顶指针即可实现。
缺点
-
固定容量:栈大小需预先定义,可能导致空间浪费或栈溢出(需动态扩容,但扩容有性能开销)。
-
扩容成本高:动态扩容需重新分配内存并复制数据,时间复杂度为 O(n)。
链表实现栈
栈是一种后进先出(LIFO)的数据结构,链表是一种动态数据结构,通过指针连接节点。用链表实现栈时,栈顶通常是链表的头节点,入栈和出栈操作均在链表头部进行,保证时间复杂度为 O(1)。
结构体设计
- 链式存储:通过
next指针实现动态内存分配,无需预先定义栈容量。 - 组合设计:将栈顶指针
top和栈大小size封装在linkstack结构中,便于状态管理。 - 指针类型别名:使用
typedef将struct linkstack*定义为Linkstack,简化代码书写。
struct nodestack //节点设计
{
int data;
struct nodestack* next;
};
typedef struct linkstack //栈结构设计
{
struct nodestack* top;
int size;
}*Linkstack;
初始化
Linkstack initlinkstack()实现初始化链式栈。
内存分配
-
使用
malloc动态分配struct linkstack大小的内存空间 -
检查分配是否成功(
stack!=NULL)
初始化成员
-
将栈顶指针
top初始化为NULL,表示空栈 -
将栈大小
size初始化为0,表示没有元素
//初始化链式栈
Linkstack initlinkstack()
{
Linkstack stack = malloc(sizeof(struct linkstack));
if(stack!=NULL)
{
stack->top = NULL;
stack->size = 0;
}
return stack;
}
入栈
实现链式栈的入栈操作(push)
push函数实现:
动态分配新节点内存
通过 malloc 创建新节点 node,并检查内存分配是否成功, 处理分配失败的情况。
节点数据赋值
将参数 data 存入新节点的 data 字段,完成数据初始化。
调整链表指针
新节点的 next 指针指向当前栈顶节点 stack->top,建立链表连接关系。
更新栈顶指针
栈的 top 指针指向新节点,使新节点成为新的栈顶元素。
栈大小维护
递增 stack->size ,保持栈的当前元素数量同步更新。
//入栈,创建新的节点,链接链表
bool push(Linkstack stack, int data)
{
struct nodestack *node = malloc(sizeof(struct nodestack));
if (node == NULL)
return false;
node->data = data;
node->next = stack->top;
stack->top = node;
stack->size++;
return true;
}
出栈
出栈和取栈顶元素需要判断栈空
isEmpty函数实现:
//判断栈空
bool isEmpty(Linkstack stack)
{
return stack->size == 0;
}
top函数实现:
- 检查栈是否为空(调用
isEmpty(stack)),若非空则执行下一步; - 通过
stack->top->data获取栈顶节点的数据,并将其赋值给*data; - 返回
true表示操作成功; - 若栈为空,直接返回
false表示操作失败。
//取栈顶元素
bool top(Linkstack stack, int *data)
{
if(!isEmpty(stack))
{
*data = stack->top->data;
return true;
}
return false;
}
取栈顶元素
pop函数实现:
-
空栈检查
调用isEmpty(stack)判断栈是否为空,避免对空栈执行出栈操作。 -
出栈操作
- 临时指针
p保存当前栈顶节点。 - 将
stack->top指向下一个节点(stack->top->next)。 - 释放
p指向的节点内存。 - 更新栈大小(
stack->size--)。
- 临时指针
-
-
返回值
- 成功出栈返回
true。 - 栈空时返回
false。
- 成功出栈返回
-
//出栈
bool pop(Linkstack stack)
{
if(!isEmpty(stack))
{
struct nodestack* p = stack->top; //记录出栈指针,以便后续释放
stack->top = stack->top->next; //指针移位,指向释放结构体指针的后面
free(p); //出栈需要释放内存
stack->size--;
return true;
}
return false;
}
主函数
int main(void)
{
Linkstack stack = initlinkstack();
if(stack == NULL)printf("初始化失败\n");
else printf("初始化成功\n");
printf("请输入一个数\n");
int n=0;
scanf("%d", &n);
while(n)//n != 0
{
push(stack, n--);
}
while(1)
{
int data = 0;
if(!top(stack, &data))
break;
printf("%d ",data);
printf("\n");
pop(stack);
}
return 0;
}
完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
struct nodestack //节点设计
{
int data;
struct nodestack* next;
};
typedef struct linkstack //栈结构设计
{
struct nodestack* top;
int size;
}*Linkstack;
//初始化链式栈
Linkstack initlinkstack()
{
Linkstack stack = malloc(sizeof(struct linkstack));
if(stack!=NULL)
{
stack->top = NULL;
stack->size = 0;
}
return stack;
}
//入栈,创建新的节点,链接链表
bool push(Linkstack stack, int data)
{
struct nodestack *node = malloc(sizeof(struct nodestack));
if (node == NULL)
return false;
node->data = data;
node->next = stack->top;
stack->top = node;
stack->size++;
return true;
}
//判断栈空
bool isEmpty(Linkstack stack)
{
return stack->size == 0;
}
//取栈顶元素
bool top(Linkstack stack, int *data)
{
if(!isEmpty(stack))
{
*data = stack->top->data;
return true;
}
return false;
}
//出栈
bool pop(Linkstack stack)
{
if(!isEmpty(stack))
{
struct nodestack* p = stack->top; //记录出栈指针,以便后续释放
stack->top = stack->top->next; //指针移位,指向释放结构体指针的后面
free(p); //出栈需要释放内存
stack->size--;
return true;
}
return false;
}
int main(void)
{
Linkstack stack = initlinkstack();
if(stack == NULL)printf("初始化失败\n");
else printf("初始化成功\n");
printf("请输入一个数\n");
int n=0;
scanf("%d", &n);
while(n)//n != 0
{
push(stack, n--);
}
while(1)
{
int data = 0;
if(!top(stack, &data))
break;
printf("%d ",data);
printf("\n");
pop(stack);
}
return 0;
}
链式栈的优缺点
优点
-
动态大小:基于链表实现,可动态增长,无需预先定义容量。
-
灵活内存:节点分散存储,适合内存碎片化场景。
-
无溢出风险:只要内存足够,理论上可无限扩展。
缺点
-
额外开销:每个节点需存储指针,占用更多内存。
-
访问效率低:内存非连续,访问节点时缓存命中率低,操作速度略慢于顺序栈。
-
代码复杂度:需处理链表节点的动态分配与释放,实现稍复杂。
对比总结
| 特性 | 顺序栈 | 链式栈 |
|---|---|---|
| 内存布局 | 连续 | 非连续 |
| 容量限制 | 固定(可动态扩容) | 动态增长 |
| 时间效率 | $O(1)$(无扩容时) | $O(1)$(但常数项更高) |
| 空间效率 | 更优(无指针开销) | 较差(需存储指针) |
| 适用场景 | 已知最大容量或频繁操作 | 容量不可预知或内存碎片多 |
四.栈的应用场景
- 函数调用栈:保存函数调用时的上下文。
- 表达式求值:处理括号匹配、运算符优先级。
- 浏览器历史:后退功能基于栈实现。
- 撤销操作:文本编辑器的撤销机制。
数据结构-栈的实现与应用
702

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



