数据结构中用数组或者链表实现栈

 

引言

栈是一种线性数据结构,遵循“后进先出”(LIFO)原则,即最后添加的元素最先被移除。栈的实现方式有多种,其中基于数组和基于链表是两种常见方法。本文将详细介绍基于链表的栈实现,并与基于数组的栈进行对比,帮助初学者理解两种实现方式的区别和适用场景。

栈遵循先入后出的原则,因此我们只能在栈顶添加或删除元素。然而,数组和链表都可以在任意位置添加和
删除元素,因此栈可以视为一种受限制的数组或链表。换句话说,我们可以“屏蔽”数组或链表的部分无关
操作,使其对外表现的逻辑符合栈的特性。
栈的操作

在深入实现之前,先定义栈的关键操作:

  • Push(入栈):在栈顶添加元素。

  • Pop(出栈):移除栈顶元素。

  • Peek(查看栈顶):查看栈顶元素但不移除。

  • IsEmpty(是否为空):检查栈是否为空。

  • IsFull(是否已满):检查栈是否已达到最大容量(对固定大小的栈相关)。

基于数组的栈实现

基于数组的栈使用固定大小的数组存储元素,top 变量跟踪栈顶元素的索引,初始为 -1 表示空栈。

#define MAXSIZE 1024 // 栈的最大容量
typedef struct {
    int data[MAXSIZE]; // 存储元素的数组
    int top; // 栈顶元素的索引,初始为-1表示空栈
} Stack;

// 初始化栈
void initStack(Stack* s) {
    s->top = -1; // 设置top为-1,表示栈为空
}

// 入栈
int push(Stack* s, int element) {
    if (s->top + 1 >= MAXSIZE) {
        return 0; // 栈已满,返回0表示失败
    }
    s->top++; // top加1,指向下一个位置
    s->data[s->top] = element; // 在top位置放元素
    return 1; // 成功返回1
}

// 出栈
int pop(Stack* s) {
    if (s->top == -1) {
        return -1; // 栈为空,返回-1表示失败
    }
    int element = s->data[s->top]; // 获取栈顶元素
    s->top--; // top减1,移除元素
    return element; // 返回移除的元素
}

// 查看栈顶
int peek(Stack* s) {
    if (s->top == -1) {
        return -1; // 栈为空,返回-1
    }
    return s->data[s->top]; // 返回栈顶元素
}

// 是否为空
int isEmpty(Stack* s) {
    return s->top == -1; // 如果top为-1,返回真
}

// 是否已满
int isFull(Stack* s) {
    return s->top + 1 >= MAXSIZE; // 如果top+1等于最大容量,返回真
}
为什么 last = -1 表示栈为空?

last 跟踪最后一个元素的索引。当 last = -1 时,说明没有元素,栈为空。-1 是一个特殊值,因为数组索引从 0 开始,-1 表示“没有元素”,与标准实践一致。

p->last 是否是栈的长度?

不是,p->last 是最后一个元素的索引,栈的长度是 p->last + 1。比如:

  • last = -1,长度 = 0(空栈)。

  • last = 0,长度 = 1(一个元素)。

  • last = 1,长度 = 2(两个元素)。

return -1 是什么意思?
  • get 函数中,return -1 表示没找到元素。

  • find 函数中,return -1 表示位置无效。

  • -1 是一个特殊值,表示操作失败,因为索引不能为负。

 

基于链表的栈实现

基于链表的栈使用节点集合,每个节点包含数据和指向下一个节点的指针,链表的头部(栈顶)由 top 指针管理。这种方法允许动态大小,因为节点按需分配。

#include <stdio.h>
#include <stdlib.h>

// 定义节点结构
typedef struct Node {
    int data;          // 存储数据
    struct Node* next; // 指向下一个节点的指针
} Node;

// 定义栈结构
typedef struct {
    Node* top; // 栈顶指针,初始为 NULL
} LinkedListStack;

// 初始化栈
void initLinkedListStack(LinkedListStack* s) {
    s->top = NULL; // 栈为空时,栈顶指针为 NULL
}

// 入栈操作
int push(LinkedListStack* s, int element) {
    Node* newNode = (Node*)malloc(sizeof(Node)); // 分配新节点内存
    if (newNode == NULL) {
        return 0; // 内存分配失败,返回 0 表示失败
    }
    newNode->data = element; // 设置新节点的数据
    newNode->next = s->top;  // 新节点的 next 指向当前栈顶
    s->top = newNode;        // 更新栈顶指针为新节点
    return 1; // 成功返回 1
}

// 出栈操作
int pop(LinkedListStack* s) {
    if (s->top == NULL) {
        return -1; // 栈为空,返回 -1 表示失败
    }
    Node* temp = s->top;     // 保存当前栈顶节点
    int element = temp->data; // 获取栈顶数据
    s->top = s->top->next;   // 更新栈顶指针为下一个节点
    free(temp);              // 释放原栈顶节点的内存
    return element;          // 返回出栈的元素
}

// 查看栈顶元素
int peek(LinkedListStack* s) {
    if (s->top == NULL) {
        return -1; // 栈为空,返回 -1
    }
    return s->top->data; // 返回栈顶元素的数据
}

// 检查栈是否为空
int isEmpty(LinkedListStack* s) {
    return s->top == NULL; // 如果栈顶指针为 NULL,栈为空
}
  • Node结构体

    • int data:存储节点中的数据,这里是整数类型,但可以根据需求定义其他类型。

    • struct Node* next:一个指针,指向下一个Node结构体。如果是链表的最后一个节点,next指向NULL,表示链表结束。

  • LinkedListStack结构体

    • Node* top:一个指针,指向栈顶的节点(即链表的头节点)。当栈为空时,topNULL

详细解释

  • 每个Node就像一个书架上的书,data是书的内容,next是书的标签,写着“下一本书在哪里”。

  • LinkedListStack是整个书架的管理者,top是当前你拿的书的入口,指向最上面的书。

变量的含义

以下是代码中变量的详细含义:

  • dataNode结构体中的成员,存储节点的数据,比如一个整数。这是节点的核心内容,代表栈中存储的元素。

  • nextNode结构体中的成员,是一个指针,指向下一个Node。如果当前节点是最后一个,nextNULL,表示链表结束。

  • topLinkedListStack结构体中的成员,是一个指针,指向栈顶的Node。当栈为空时,topNULL

通俗解释

  • data是书的内容,next是书的“下一页”标签,top是你当前拿的书的入口。

NULL的含义

NULL是C语言中的一个宏,表示空指针(Null Pointer),不指向任何有效内存地址。它用于表示“没有东西”或“不存在”。

在链表栈中,NULL有以下具体用途:

  • 栈为空:当栈中没有元素时,LinkedListStacktop被设置为NULL,表示栈顶不存在。

  • 链表结束:每个Nodenext指针指向下一个节点,当到达最后一个节点时,next被设置为NULL,表示没有更多节点。

详细解释

  • top == NULL时,说明栈里没有节点,相当于书架上没有书。

  • 当某个节点的next == NULL时,说明这是最后一个节点,后面没有书了。

->操作符的解释

->操作符是C语言中用于通过指针访问结构体成员的运算符。其语法为:如果有一个指针ptr指向结构体Sptr->member表示通过ptr访问S中的member成员。

在链表栈中,->操作符频繁用于访问节点的成员,因为节点是通过指针连接的。

单个->的例子

  • s->tops是一个指向LinkedListStack的指针,s->top访问的是LinkedListStack中的top成员(即栈顶指针)。

  • node->data:如果node是一个指向Node的指针,node->data访问的是该节点中的数据。

两个->的解释

  • 当你看到像s->top->data这样的表达式时,它表示:

    • s->top:先通过s访问LinkedListStack中的top指针(它是一个指向Node的指针)。

    • (s->top)->data:再通过top指针访问Node结构体中的data成员。

  • 因此,s->top->data的意思是“获取栈顶节点中的数据”。

  • 另一个例子:s->top->next

    • s->top:获取栈顶指针。

    • s->top->next:通过栈顶指针访问Node结构体中的next成员(即下一个节点的指针)。

    • 这在出栈操作中常用,例如:

    • s->top = s->top->next;
      

      这里,s->top->next获取栈顶节点的下一个节点,然后将栈顶指针更新为这个下一个节点,从而移除原来的栈顶节点。

通俗解释

  • 两个->就像是“先找到一个东西,再从这个东西里找另一个东西”。比如s->top->data是“先找到栈顶(小助手),然后看栈顶上的数据(机器人的手)”。

操作示例与两个->的实际使用

为了让你更清楚地理解,让我们看一下链表栈的基本操作(入栈和出栈),特别关注两个->的用法:

  • 入栈(Push)

  • int push(LinkedListStack* s, int element) {
        Node* newNode = (Node*)malloc(sizeof(Node)); // 创建新节点
        if (newNode == NULL) return 0; // 内存分配失败
        newNode->data = element; // 设置新节点的数据
        newNode->next = s->top;  // 新节点的next指向当前栈顶
        s->top = newNode;        // 更新栈顶为新节点
        return 1; // 成功
    }
    
  • 这里没有两个->,但可以看到s->topnewNode->next的用法。
  • 出栈(Pop)

  • int pop(LinkedListStack* s) {
        if (s->top == NULL) return -1; // 栈为空
        Node* temp = s->top;     // 保存当前栈顶
        int element = temp->data; // 获取栈顶数据
        s->top = s->top->next;   // 更新栈顶为下一个节点,这里用了两个`->`
        free(temp);              // 释放内存
        return element;          // 返回出栈的元素
    }
    
    • s->top = s->top->next;中,s->top->next是一个两个->的例子:

      • s->top:获取栈顶指针。

      • s->top->next:通过栈顶指针访问next成员,获取下一个节点的指针。

性能与限制
  • 基于链表的栈

    • 优点:动态大小,无浪费空间,适合未知大小。

    • 缺点:动态内存分配可能稍慢,需手动管理内存。

  • 两个->的频率:在栈操作中,两个->主要出现在访问栈顶节点的成员(如s->top->next),通常用于更新栈顶或遍历。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值