文章目录
一、栈的基础概念和理解
1. 定义
栈(Stack)是一种特殊的线性数据结构,遵循后进先出(Last In First Out,LIFO)的原则。这意味着最后进入栈的元素会最先被移除。可以把栈想象成一摞盘子,你只能从这摞盘子的顶部添加或拿走盘子,最后放上去的盘子总是第一个被拿走。
2. 基本操作
- 入栈(Push):将一个元素添加到栈的顶部。
- 出栈(Pop):移除并返回栈顶部的元素。
- 查看栈顶元素(Peek 或 Top):返回栈顶部的元素,但不移除它。
- 判断栈是否为空(IsEmpty):检查栈中是否没有元素。
- 获取栈的大小(Size):返回栈中元素的数量。
3. 应用场景
- 函数调用栈:在程序执行过程中,当调用一个函数时,系统会将当前的执行上下文(包括局部变量、返回地址等)压入栈中;当函数返回时,再从栈中弹出这些信息。
- 表达式求值:例如,在计算中缀表达式时,可以使用栈来处理运算符的优先级。
- 括号匹配:检查代码中括号是否匹配,通过栈可以方便地实现。
二、栈的基本操作示例
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
// 定义栈结构体
typedef struct {
int data[MAX_SIZE];
int top;
} Stack;
// 初始化栈
void initStack(Stack *s) {
s->top = -1;
}
// 判断栈是否为空
int isEmpty(Stack *s) {
return s->top == -1;
}
// 判断栈是否已满
int isFull(Stack *s) {
return s->top == MAX_SIZE - 1;
}
// 入栈操作
void push(Stack *s, int item) {
if (isFull(s)) {
printf("Stack is full\n");
return;
}
s->data[++(s->top)] = item;
}
// 出栈操作
int pop(Stack *s) {
if (isEmpty(s)) {
printf("Stack is empty\n");
return -1;
}
return s->data[(s->top)--];
}
// 查看栈顶元素
int peek(Stack *s) {
if (isEmpty(s)) {
printf("Stack is empty\n");
return -1;
}
return s->data[s->top];
}
// 获取栈的大小
int size(Stack *s) {
return s->top + 1;
}
int main() {
Stack s;
initStack(&s);
push(&s, 1);
push(&s, 2);
push(&s, 3);
printf("%d\n", pop(&s)); // 输出: 3
printf("%d\n", peek(&s)); // 输出: 2
printf("%d\n", size(&s)); // 输出: 2
return 0;
}
1.C 语言代码解析
Stack
结构体:包含一个数组data
用于存储栈中的元素,以及一个整数top
表示栈顶的索引。initStack
函数:将栈顶索引top
初始化为 -1,表示栈为空。isEmpty
函数:通过检查top
是否为 -1 来判断栈是否为空。isFull
函数:通过检查top
是否等于MAX_SIZE - 1
来判断栈是否已满。push
函数:如果栈未满,将元素添加到数组中,并将top
加 1。pop
函数:如果栈不为空,返回栈顶元素,并将top
减 1。如果栈为空,输出错误信息并返回 -1。peek
函数:如果栈不为空,返回栈顶元素,但不改变top
的值。如果栈为空,输出错误信息并返回 -1。size
函数:返回top + 1
,即栈中元素的数量。
通过以上代码和解析,你可以对栈这种数据结构有一个更深入的理解,并学会如何实现和使用栈。
三、两栈共享空间
1.两栈共享空间解释
(1.)概念
两栈共享空间是一种对内存空间进行有效利用的存储方式,它主要用于解决两个栈在使用过程中,栈空间的动态变化问题。通常情况下,每个栈会单独分配一块连续的内存空间,但在实际应用中,可能会出现一个栈空间已满,而另一个栈还有大量空闲空间的情况,造成内存的浪费。两栈共享空间的方法就是让两个栈共享同一块连续的内存区域,通过合理安排栈的增长方向,使得两个栈可以在这个有限的空间内动态地使用内存,提高内存的利用率。
(2.)实现原理
两栈共享空间一般使用一个一维数组来实现。两个栈分别==从数组的两端开始向中间增长==,具体来说:
- 栈 1 的栈底位于数组的起始位置(下标为 0),栈顶指针
top1
从数组的起始位置开始向数组中间移动,随着元素入栈,top1
逐渐增大。 - 栈 2 的栈底位于数组的末尾位置(下标为数组长度 - 1),栈顶指针
top2
从数组的末尾位置开始向数组中间移动,随着元素入栈,top2
逐渐减小。
当 top1 + 1 == top2
时,表示整个共享空间已经被两个栈占满,此时再进行入栈操作就会发生栈溢出。
2.代码示例
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
// 定义两栈共享空间的结构体
typedef struct {
int data[MAX_SIZE]; // 用于存储两个栈的元素
int top1; // 栈 1 的栈顶指针
int top2; // 栈 2 的栈顶指针
} DoubleStack;
// 初始化两栈共享空间
void initDoubleStack(DoubleStack *ds) {
ds->top1 = -1; // 栈 1 初始栈顶指针为 -1,表示栈为空
ds->top2 = MAX_SIZE; // 栈 2 初始栈顶指针为数组长度,表示栈为空
}
// 判断栈 1 是否为空
int isStack1Empty(DoubleStack *ds) {
return ds->top1 == -1;
}
// 判断栈 2 是否为空
int isStack2Empty(DoubleStack *ds) {
return ds->top2 == MAX_SIZE;
}
// 判断共享空间是否已满
int isFull(DoubleStack *ds) {
return ds->top1 + 1 == ds->top2;
}
// 栈 1 入栈操作
void pushStack1(DoubleStack *ds, int item) {
if (isFull(ds)) {
printf("Stack is full, cannot push to stack 1.\n");
return;
}
ds->data[++(ds->top1)] = item; // 先将 top1 加 1,再将元素存入数组
}
// 栈 2 入栈操作
void pushStack2(DoubleStack *ds, int item) {
if (isFull(ds)) {
printf("Stack is full, cannot push to stack 2.\n");
return;
}
ds->data[--(ds->top2)] = item; // 先将 top2 减 1,再将元素存入数组
}
// 栈 1 出栈操作
int popStack1(DoubleStack *ds) {
if (isStack1Empty(ds)) {
printf("Stack 1 is empty, cannot pop.\n");
return -1;
}
return ds->data[(ds->top1)--]; // 先返回栈顶元素,再将 top1 减 1
}
// 栈 2 出栈操作
int popStack2(DoubleStack *ds) {
if (isStack2Empty(ds)) {
printf("Stack 2 is empty, cannot pop.\n");
return -1;
}
return ds->data[(ds->top2)++]; // 先返回栈顶元素,再将 top2 加 1
}
// 查看栈 1 的栈顶元素
int peekStack1(DoubleStack *ds) {
if (isStack1Empty(ds)) {
printf("Stack 1 is empty, cannot peek.\n");
return -1;
}
return ds->data[ds->top1];
}
// 查看栈 2 的栈顶元素
int peekStack2(DoubleStack *ds) {
if (isStack2Empty(ds)) {
printf("Stack 2 is empty, cannot peek.\n");
return -1;
}
return ds->data[ds->top2];
}
int main() {
DoubleStack ds;
initDoubleStack(&ds);
// 栈 1 入栈操作
pushStack1(&ds, 1);
pushStack1(&ds, 2);
// 栈 2 入栈操作
pushStack2(&ds, 3);
pushStack2(&ds, 4);
// 栈 1 出栈操作
printf("Popped from stack 1: %d\n", popStack1(&ds));
// 栈 2 出栈操作
printf("Popped from stack 2: %d\n", popStack2(&ds));
return 0;
}
(1.)代码解释
结构体定义
typedef struct {
int data[MAX_SIZE];
int top1;
int top2;
} DoubleStack;
data
数组用于存储两个栈的元素,大小为MAX_SIZE
。top1
是栈 1 的栈顶指针,初始值为 -1 表示栈 1 为空。top2
是栈 2 的栈顶指针,初始值为MAX_SIZE
表示栈 2 为空。
初始化函数
void initDoubleStack(DoubleStack *ds) {
ds->top1 = -1;
ds->top2 = MAX_SIZE;
}
将栈 1 和栈 2 的栈顶指针初始化为相应的初始值。
判空和判满函数
isStack1Empty
和isStack2Empty
分别用于判断栈 1 和栈 2 是否为空。isFull
用于判断共享空间是否已满,当top1 + 1 == top2
时,表示共享空间已满。
入栈函数
pushStack1
:在栈 1 入栈时,先检查共享空间是否已满,如果未满,则将top1
加 1,然后将元素存入data[top1]
。pushStack2
:在栈 2 入栈时,先检查共享空间是否已满,如果未满,则将top2
减 1,然后将元素存入data[top2]
。
出栈函数
popStack1
:在栈 1 出栈时,先检查栈 1 是否为空,如果不为空,则返回data[top1]
,然后将top1
减 1。popStack2
:在栈 2 出栈时,先检查栈 2 是否为空,如果不为空,则返回data[top2]
,然后将top2
加 1。
查看栈顶元素函数
peekStack1
和peekStack2
分别用于查看栈 1 和栈 2 的栈顶元素,但不进行出栈操作。
(2.)两栈共享空间的优缺点
优点
- 提高内存利用
传统的做法是为每个栈单独分配一块连续的内存空间,这可能会导致一个栈空间已满,而另一个栈还有大量空闲空间的情况,造成内存的浪费。而两栈共享空间让两个栈从数组的两端向中间生长,一个栈的空闲空间可以被另一个栈使用,使得内存空间得到更充分的利用。例如,在一个程序中,两个栈的使用频率和数据量大小是动态变化的,使用两栈共享空间就能根据实际情况灵活分配内存,减少内存的闲置。
- 节省内存开销
相比于为每个栈分别分配独立的内存空间,两栈共享空间只需要一个数组来存储两个栈的数据,减少了额外的内存管理开销。这在内存资源有限的系统中尤为重要,例如嵌入式系统,能够有效节省宝贵的内存资源,提高系统的整体性能。
缺点
- 管理复杂度增加
虽然两栈共享空间的基本实现相对简单,但在实际应用中,对栈的管理会变得更加复杂。例如,在判断栈是否已满时,需要同时考虑两个栈的栈顶指针位置;在进行入栈和出栈操作时,也需要额外的逻辑来确保操作的正确性。这增加了代码的复杂度和出错的可能性,对开发者的编程能力和逻辑思维要求较高。
- 扩展性较差
两栈共享空间的方案是基于固定大小的数组实现的,一旦数组的大小确定,栈的最大容量也就固定了。如果后续需要增加栈的容量,就需要重新分配更大的数组,并将原数组中的数据复制到新数组中,这会带来较大的开销。而且,这种方案只能同时处理两个栈的情况,如果需要处理更多的栈,就需要重新设计和实现,扩展性较差。
- 数据独立性降低
两个栈共享同一块内存空间,使得它们的数据独立性降低。如果在程序运行过程中出现错误,可能会影响到两个栈的数据,增加了调试和维护的难度。例如,一个栈的操作出现越界错误,可能会破坏另一个栈的数据,导致程序出现不可预期的结果。