C++ 数据结构之--栈(数组和链表实现)

        栈是一种遵循后进先出(LIFO)的原则的数据结构。栈通常用于存储和管理函数调用、表达式求值、内存分配等操作中的临时数据。栈有两个基本操作:压栈(push),将数据放入栈顶;出栈(pop),从栈顶移出数据。除了栈顶的元素外,其他元素都不可直接访问或修改。栈可以通过数组链表来实现。在计算机科学中,栈被广泛应用于编程语言的解析、内存管理和算法实现等方面。

数组实现

        代码

/**
 * 数组实现stack
 */
#include <cstdlib> // INT_MAX包含在cstdlib 头文件中
#include <cstring> // 使用cstring中的memcpy函数
#ifndef STACK_ARRAY_STACK_H
#define STACK_ARRAY_STACK_H

const int EXPAND_CAPACITY = 20; // 每次扩容大小
const int MAX_CAPACITY = INT_MAX; // 最大容量为INT_MAX

class Stack{
public:
    void push(int e);
    int top(){return data[size-1];} // 返回顶部数据
    void pop(){data[size-1] = 0;size --;} // 弹出顶部数据

    void clear(){
        free(this->data);
        data = (int *)malloc(sizeof(int)*10);
        this->size = 0;
    }

    bool empty(){return size == 0;} // 栈是否为空
    Stack(){
        data = (int *)malloc(sizeof(int)*10); // data初始容量为10
    }
    ~Stack(){free(this->data);}

private:
    int *data; // 栈数据
    int size = 0; // 元素个数
    int capacity = 10; // data容量

};

/**
 * 元素入栈
 * @param e要入栈的元素
 */
void Stack::push(int e) {
    // 如果元素个数>=栈容量,先扩容EXPAND_CAPACITY
    if(size >= capacity){
        capacity += EXPAND_CAPACITY;
        // 如果扩容后的容量大于INT_MAX,直接扩容到INT_MAX
        if(capacity >= MAX_CAPACITY){
            capacity = MAX_CAPACITY;
        }

        int *t = (int *) malloc(sizeof (int) * capacity);
        memcpy(t,data, sizeof(int) * size);
        delete data;
        data = t;
    }
    // size已达最大容量,不再执行push操作
    if(size == MAX_CAPACITY) return;
    data[size] = e;
    size ++;
}

#endif //STACK_ARRAY_STACK_H

        结构图

        

        代码分析

        上述代码实现了一个基于数组的栈数据结构。

        其中,push() 方法实现了将元素入栈操作,如果当前栈已满,会自动对栈进行扩容。每次扩容的大小是 EXPAND_CAPACITY,如果在扩容后的容量超过 INT_MAX,则容量被限制为 INT_MAX

   top() 方法用于返回栈顶元素。

   pop() 方法则是弹出栈顶元素,并更新栈的大小。

   clear() 方法用于清空栈元素。该方法首先释放原来的 data 数组,然后重新分配一个大小为10的数组,并将栈的大小更新为0。

         该栈数据结构在构造函数和析构函数中分别使用了 malloc() 和 free() 方法来动态分配和释放内存,保证了随着程序的执行,栈可以正常添加和删除元素,同时也可以正确地释放占用的内存,避免了内存泄漏问题。

链表实现

        代码

/**
 * 链表实现stack
 */
#ifndef STACK_LINK_STACK_H
#define STACK_LINK_STACK_H

/**
 * 链表节点定义
 */
class Node{
public:
    int val; // data
    Node *next; // 下一个节点
};

class LinkStack{
public:
    void push(int e); // 从链表头部入栈
    int top(){return this->first->next->val;} // 读取第一个元素
    void pop(){Node *t = first->next;first->next = t->next;delete t;} // 弹出第一个元素
    bool empty(){return this->first->next == nullptr;} // 是否为空

    void clear(){
        while(first->next != nullptr){
            Node* head = first;
            Node* temp = first->next;
            delete head;
            first = temp;
        }
    }

    LinkStack(){first = new Node();} // 初始化first指针
    ~LinkStack(){this->clear();} // 释放内存
private:
    Node *first; // 头节点,不存储数据
};

/**
 * 入栈方法
 * 新建val为e的节点node
 * node->next 指向 first->next
 * first->next 指向 node
 * s++
 * @param e 要入栈的元素
 */
void LinkStack::push(int e) {
    Node *node = new Node();
    node->next = nullptr;
    node->val = e;
    node->next = first->next;
    first->next = node;
}

#endif //STACK_LINK_STACK_H

       结构图

        代码分析

        上述代码实现了使用链表实现栈的数据结构。

        首先定义了一个链表节点类 `Node`,包含一个整数值 `val` 和一个指向下一个节点的指针 `next`。

        然后定义了一个链表栈类 `LinkStack`,其中包含了以下几个方法:

        - `push(int e)`:从链表头部入栈,即在链表的首节点之后插入一个新的节点。新节点的值为 `e`,其指针指向原首节点,然后将首节点的指针指向新节点。栈的大小增加。
        - `top()`:返回栈顶元素,即返回首节点的下一个节点。
        - `pop()`:弹出栈顶元素,即将首节点的指针指向栈顶元素的下一个节点,然后释放原栈顶元素节点的内存。栈的大小减少。
        - `empty()`:判断栈是否为空,即栈的大小是否为0。

        类的构造函数 `LinkStack()` 初始化了首节点 `first`,构造函数创建了一个空的链表栈实例。
类的析构函数 `~LinkStack()` 释放了首节点 `first` 的内存。

不同实现的比较

数组实现栈

        优点
  1. 简单且易于实现:数组在内存中是连续存储的,因此可以通过索引快速访问和操作元素。
  2. 随机访问:由于数组的连续存储方式,可以随机访问任意位置的元素,例如查询栈中的某个元素。
  3. 占用空间较小:数组中只需要存储元素本身,不需要额外的指针来连接节点。
        缺点
  1. 固定大小:数组在创建时需要指定大小,无法动态调整。如果栈的大小超过了数组的容量,需要重新创建一个更大的数组并复制元素,效率较低。
  2. 空间浪费:如果栈的大小较小,而数组的容量较大,会导致部分空间被浪费。

链表实现栈

        优点
  1. 动态大小:链表的大小可以动态增减,不需要事先指定容量。可以根据实际情况灵活地分配内存。
  2. 内存利用率高:链表在需要增加或删除元素时,可以动态分配或释放内存,避免了固定容量带来的空间浪费。
  3. 高效的增删操作:在链表中插入或删除元素的时间复杂度为O(1),效率较高。
        缺点
  1. 需要额外的指针:链表节点除了存储元素本身的值,还需要一个指针来连接下一个节点,占用了额外的空间。
  2. 随机访问较慢:链表中的元素不是连续存储的,无法像数组一样通过索引进行快速随机访问。

        综上所述,如果对栈的频繁插入和删除操作较多,并且栈的大小不确定,可以选择使用链表来实现栈。如果对随机访问有较高的需求,或者栈的大小是固定的,并且对空间利用率有较高要求,可以选择使用数组来实现栈。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

who_am_i__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值