ADT: Stack 栈

本文介绍了抽象数据结构ADT中的栈(Stack),阐述了栈的基本概念、操作接口(压栈、出栈、查看栈顶元素)以及其作为后进先出数据结构的应用。栈可以用数组或链表实现,文中提供了Java和C++的实现示例,并通过测试验证了其正确性。栈结构简单但应用广泛,如单调栈、最小最大栈、中缀表达式转换等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ADT: Stack 栈

简介

本篇开始介绍 ADT(Abstract Data Type 抽象数据结构),在计算机科学中除了基本类型如 int、long、char 等,大多数的程序还需要用到其他集成更多细节的抽象数据结构,如栈(Stack)队列(Queue)树(Tree)图(Graph) 等,许多复杂算法的实现必须借助特定的数据结构才能够实现。

本篇将从最简单的栈(Stack)结构开始介绍,虽然栈的结构非常简单,但是透过简单的变形或是扩展能够运用在非常多的场景:

  1. 单调栈:维护升(降)序排列的栈
  2. 最小(大)栈:维护当前栈中最小(大)的元素
  3. 中缀表达式转后缀表达式
  4. 成对符号(括号、标签)匹配

接下来就来看看栈的概念和实现细节

参考

正文

栈结构

图片来源:google图片

栈结构是一个只有单边开放的容器,入栈(添加元素)出栈(取出元素)都是从同一边执行,属于后进先出(LIFO=Last-In-First-Out)的数据结构。

抽象接口

栈数据元素作为一个抽象的数据结构其实并不关心实现细节是使用数组还是链表,对于栈结构的操作统一透过约定好的公共接口来调用,以下是接口方法的说明

PUSH(x)
压(入)栈,将元素 x 加入到栈结构当中

POP()
出栈,将栈顶元素弹出并返回

TOP()
获取栈顶元素,但是不将其弹出栈结构

这边介绍的是最基本款的栈结构,如单调栈或是最大最小栈在实现细节上有额外的限制,这里就不多加说明。

实现要素

栈可以使用数组实现,也能够使用链表实现,这边介绍数组实现的细节。

使用数组来实现栈需要:

  1. 维护一个保存元素的数组 T[] array
  2. 记录当前数组的大小 int size
  3. 记录当前栈顶位置 int top
  4. 当数组被填满时扩展数组的方法 void expand()

Java 实现

首先提供 Java 的实现,使用了范型来增加数据结构的通用性:

  • Stack.java
/**
 * 栈
 * @param <T>
 */
public interface Stack<T> {
    /**
     * 压栈
     * @param t
     */
    void push(T t);
    /**
     * 出栈
     * @return
     */
    T pop();
    /**
     * 获得栈顶元素
     * @return
     */
    T top();
}
  • ``
public class StackWithArray<T> implements Stack<T> {
    public StackWithArray() {
        this(1); // 默认大小为 1
    }

    public StackWithArray(int size) {
        this.top = -1;
        this.size = size;
        this.array = (T[]) new Object[size];
    }

    private T[] array;
    private int size;
    private int top;

    /**
     * 压栈
     *
     * @param t
     */
    public void push(T t) {
        if (top == size - 1) extend();
        array[++top] = t;
    }

    /**
     * 出栈
     *
     * @return
     */
    public T pop() {
        if (top < 0) return null;
        return array[top--];
    }

    /**
     * 获得栈顶元素
     *
     * @return
     */
    public T top() { 
        return array[top]; 
    }

    /**
     * 扩展数组大小,每次扩展为当前大小的两倍
     */
    private void extend() {
        T[] newArray = (T[]) new Object[size * 2];
        for (int i = 0; i < size; i++) newArray[i] = array[i];
        array = newArray;
        size *= 2;
    }
}
  • StackTest.java
public class StackTest {
    @Test
    public void test_stack_with_array() {
        Stack<Integer> stack = new StackWithArray<Integer>();
        Integer[] nums = new Integer[]{0, 4, 1, 5, 2, 6, 3, 0, 4, 7};
        for(int i=0 ; i<10 ; i++) stack.push(nums[i]);
        // 检查是否符合 LIFO 的顺序
        for(int i=9 ; i>=4 ; i--) {
            assertEquals(nums[i], stack.pop());
        }
        // 检查栈顶
        assertEquals((Integer)5, stack.top());
    }
}

C++ 实现

使用了 C++ 的模版类语法,所以声明定义都写在 header 文件内

  • Stack.h
template<class T>
class Stack {
public:
    Stack();
    Stack(int);
    ~Stack();
    // 压栈
    void push(const T &);
    // 出栈
    T pop();

private:
    // 扩展数组
    void extend();

    T *_array; // 保存元素的数组
    int size; // 栈大小
    int top; // 栈顶指针
};

template<class T>
Stack<T>::Stack(): _array(new T[1]), size(1), top(-1) {}

template<class T>
Stack<T>::Stack(int size) :_array(new T[size]), size(size), top(-1) {}

template<class T>
Stack<T>::~Stack<T>() {
    delete[] _array;
}

template<class T>
void Stack<T>::push(const T &t) {
    if (top == size - 1) extend();
    _array[++top] = t;
}

template<class T>
T Stack<T>::pop() {
    if (top < 0) return nullptr;
    return _array[top--];
}

template<class T>
void Stack<T>::extend() {
    T *array = new T[size * 2];
    for (int i = 0; i < size; i++) {
        array[i] = _array[i];
    }
    _array = array;
    size *= 2;
}

template<class T>
void Stack<T>::info() {
    cout << "Stack: {size=" << size << ", nums: ";
    std::print(_array, size);
    cout << ", top=" << top << "}" << endl;
}

结语

栈结构非常的简单,但是又非常使用,大多数语言在内置库或第三方库(Java 中有 java.util.Stack 类)都提供性能优化后的栈结构,如果需要特别形式的栈或是对于初入栈有额外的定义则可以自己动手实现一个。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值