算法通关村第四关——栈的链表、数组实现(详细注释+简单图示)

38 阅读4分钟

链表实现

🔑栈顶指针指向最新(头)元素,如果空栈,head==null。
所有pop peek push都需要分两种情况(空/非空栈)讨论。

截屏2024-01-01 16.03.53.png

[pic from link]

注释

// 定义一个泛型栈,T是泛型类型参数
class ListStack<T> {
    
    // 内部节点类,用于链表结构
    class Node<T> {
        public T t; // 节点存储的数据
        public Node next; // 指向下一个节点的指针
    }
    
    public Node<T> head; // 栈顶指针,初始化时指向链表的头节点

    // 构造方法,初始化一个空栈
    ListStack() {
        head = null;
    }

    // 入栈方法,将元素t推入栈顶
    public void push(T t) {
        // 如果传入的元素是null,抛出空指针异常
        if (t == null) {
            throw new NullPointerException("参数不能为null");
        }
        // 如果栈为空,即头节点为null,创建新节点作为头节点
        if (head == null) {
            head = new Node<T>(); // 创建新节点
            head.t = t; // 设置节点数据
            head.next = null; // 新节点的下一个节点为null
        } else {
            // 如果栈不为空,创建新节点并将其链接到链表前端
            Node<T> temp = head; // 临时保存当前头节点
            head = new Node<>(); // 创建新的头节点
            head.t = t; // 设置新节点的数据
            head.next = temp; // 新节点指向原来的头节点
        }
    }

    // 出栈方法,返回并移除栈顶元素
    public T pop() {
        // 如果栈为空,则返回null
        if (head == null) {
            return null;
        }
        T t = head.t; // 临时保存栈顶元素的数据
        head = head.next; // 将头节点指针移动到下一个节点,即移除当前栈顶
        return t; // 返回栈顶元素的数据
    }

    // 查看栈顶元素,但不从栈中移除它
    public T peek() {
        // 如果栈为空,则返回null
        if (head == null) {
            return null;
        }
        return head.t; // 返回栈顶元素的数据,不移除元素
    }

    // 检查栈是否为空
    public boolean isEmpty() {
        // 如果头节点为null,表示栈为空
        return head == null;
    }
}

数组实现

top 指向下一个可用vs.最新

选择top指针指向最新元素还是下一个可用位置,通常取决于个人偏好和具体情况,因为功能上它们是等效的。然而,每种方式都有其特定的优缺点:

  1. 指向最新元素(Top points to the last element) :

    • 优点:

      • pop()操作简单,直接返回stack[top],然后top--
      • 更直观地表示栈顶元素的位置。
    • 缺点:

      • 必须先增加top,然后再进行push()操作。
      • 栈空时需要处理top-1的情况。
  2. 指向下一个可用位置(Top points to the next available position) :

    • 优点:

      • push()操作简单,先将元素赋给stack[top],然后top++
      • top的初始值为0,这在某些情况下可能更符合从0开始的数组索引习惯。
    • 缺点:

      • pop()操作前需要先减少top
      • top的值等于数组长度时,表示栈满,可能需要额外逻辑来处理。

一般来说,在计算机科学中,栈通常被视为从0开始索引的,因此第二种方法(top指向下一个可用位置)在教科书和示例代码中更常见。这种方法的isEmpty()实现也更直观,因为当top == 0时,很明显栈是空的。

截屏2024-01-01 16.12.50.png

注释

import java.util.Arrays;

// 泛型栈的定义,可以存储任意类型的对象
class Mystack<T> {
    // 栈元素存储数组
    private Object[] stack;
    // 栈顶指针,指向下一个可用位置
    private int top;

    // 构造器,初始化栈的默认大小为10
    Mystack() {
        stack = new Object[10]; // 初始化数组大小为10
        top = 0; // 初始化栈顶指针为0
    }

    // 检查栈是否为空
    public boolean isEmpty() {
        return top == 0; // 如果栈顶指针为0,栈为空
    }

    // 查看栈顶元素但不移除
    public T peek() {
        T t = null; // 初始化返回元素为null
        if (top > 0) { // 如果栈不为空
            t = (T) stack[top - 1]; // 取出栈顶元素,但不修改栈顶指针
        }
        return t; // 返回栈顶元素
    }

    // 元素入栈
    public void push(T t) {
        expandCapacity(top + 1); // 确保容量足够
        stack[top] = t; // 将元素放在栈顶指针指向的位置
        top++; // 栈顶指针递增
    }

    // 元素出栈
    public T pop() {
        T t = peek(); // 使用peek()方法获取栈顶元素
        if (top > 0) { // 如果栈不为空
            stack[top - 1] = null; // 清除栈顶元素
            top--; // 栈顶指针递减
        }
        return t; // 返回栈顶元素
    }

    // 扩展栈的容量
    public void expandCapacity(int size) {
        int len = stack.length; // 当前栈数组的长度
        if (size > len) { // 如果所需容量大于当前长度
            size = size * 3 / 2 + 1; // 扩容策略:当前需求大小的1.5倍再加1
            stack = Arrays.copyOf(stack, size); // 使用Arrays.copyOf进行数组扩容
        }
    }
}