栈的简单理解和应用

一、栈的基础概念和理解

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 的栈顶指针初始化为相应的初始值。

判空和判满函数
  • isStack1EmptyisStack2Empty 分别用于判断栈 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。
查看栈顶元素函数
  • peekStack1peekStack2 分别用于查看栈 1 和栈 2 的栈顶元素,但不进行出栈操作。

(2.)两栈共享空间的优缺点

优点
  1. 提高内存利用

传统的做法是为每个栈单独分配一块连续的内存空间,这可能会导致一个栈空间已满,而另一个栈还有大量空闲空间的情况,造成内存的浪费。而两栈共享空间让两个栈从数组的两端向中间生长,一个栈的空闲空间可以被另一个栈使用,使得内存空间得到更充分的利用。例如,在一个程序中,两个栈的使用频率和数据量大小是动态变化的,使用两栈共享空间就能根据实际情况灵活分配内存,减少内存的闲置。

  1. 节省内存开销

相比于为每个栈分别分配独立的内存空间,两栈共享空间只需要一个数组来存储两个栈的数据,减少了额外的内存管理开销。这在内存资源有限的系统中尤为重要,例如嵌入式系统,能够有效节省宝贵的内存资源,提高系统的整体性能。

缺点
  1. 管理复杂度增加

虽然两栈共享空间的基本实现相对简单,但在实际应用中,对栈的管理会变得更加复杂。例如,在判断栈是否已满时,需要同时考虑两个栈的栈顶指针位置;在进行入栈和出栈操作时,也需要额外的逻辑来确保操作的正确性。这增加了代码的复杂度和出错的可能性,对开发者的编程能力和逻辑思维要求较高。

  1. 扩展性较差

两栈共享空间的方案是基于固定大小的数组实现的,一旦数组的大小确定,栈的最大容量也就固定了。如果后续需要增加栈的容量,就需要重新分配更大的数组,并将原数组中的数据复制到新数组中,这会带来较大的开销。而且,这种方案只能同时处理两个栈的情况,如果需要处理更多的栈,就需要重新设计和实现,扩展性较差。

  1. 数据独立性降低

两个栈共享同一块内存空间,使得它们的数据独立性降低。如果在程序运行过程中出现错误,可能会影响到两个栈的数据,增加了调试和维护的难度。例如,一个栈的操作出现越界错误,可能会破坏另一个栈的数据,导致程序出现不可预期的结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值