【数据结构与算法】之栈

一、什么是栈

前言:放假在家里看书的感觉真好呀~。栈作为一种特殊的线性表,操作较为简单,但是有着很多的应用场景。

(一)栈的定义

栈 (stack) 是限定仅在栈顶进行插入和删除操作的线性表。

(二)栈的特点

  • 遵循后进先出 (LIFO) 的原则。
  • 栈只允许访问一个数据(最后插入的数据),即只能对栈顶的数据进行操作。
  • 一般的,栈的插入操作称为进栈 (push),栈的删除操作称为出栈 (pop)。

二、为什么要用栈

栈的引人简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题核心。反之,像数组等,因为要分散精力去考虑数组的下标增减等细节问题,反而可能掩盖了问题的本质。

三、如何操作栈

栈作为一种抽象数据类型 (ADT),可通过数组链表来实现。
首先提供的是Stack接口,其内部定义了栈的抽象方法。

/**
 * 栈接口,遵循后进先出的原则。
 *
 * @author likezhen
 * @version 1.0
 */
public interface Stack<E> {

    /**
     * 返回栈中存放的数据个数
     *
     * @return 栈中存放的数据个数
     */
    int size();

    /**
     * 判断是否为空栈
     *
     * @return 是否为空栈
     */
    boolean isEmpty();

    /**
     * 将数据插入栈顶
     * @param e 待入栈的数据
     */
    void push(E e);

    /**
     * 移除并返回栈顶数据
     *
     * @return 栈顶数据(如果为空栈则返回Null)
     */
    E pop();

    /**
     * 返回栈顶数据
     *
     * @return 栈顶数据(如果为空栈则返回Null)
     */
    E peek();
}

(一)顺序栈

用数组来实现的栈称为顺序栈。特别地,数组的尾部为栈顶,索引为0的位置为栈底

顺序栈

图一、顺序栈的结构

顺序栈类 ArrayStack 实现栈接口。顺序栈和数组一样,需要先指明其容量,一旦确定,就不能更改。

/**
 * 顺序栈类,通过数组来实现栈结构
 * 提供了pop(), push(), peek()等方法
 *
 * @author likezhen
 * @version 1.0
 */
class ArrayStack<E> implements Stack<E> {
    public final static int CAPACITY = 10; //默认容量为10
    private E[] data; //存放数据的数组
    private int t = -1; //空栈时计数器为-1

    public ArrayStack() {
        this(CAPACITY);
    }

    public ArrayStack(int capacity) { //可自定义栈的容量
        data = (E[]) new Object[capacity];
    }

    @Override
    public int size() {
        return (t + 1);
    }

    @Override
    public boolean isEmpty() {
        return t == -1;
    }

    @Override
    public void push(E e) throws IllegalStateException {
        if (size() == data.length) throw new IllegalStateException("栈已满");
        data[++t] = e; //先自增,后赋值
    }

    @Override
    public E peek() {
        if (isEmpty()) return null;
        return data[t];
    }

    @Override
    public E pop() {
        if (isEmpty()) return null;
        E answer = data[t]; //先赋值
        data[t] = null; //让垃圾回收器回收
        t--; //后自减
        return answer;
    }
}

在顺序栈的测试类中,和我之前的一篇总结文章(【数据结构与算法】之链表)一样,用Person类作为待储存的数据。进栈顺序为 A->B->C,栈顶数据为C,出栈顺序为 C->B->A,符合栈的后进先出原则。

/**
 * 顺序栈的测试类
 *
 * @author likezhen
 * @version 1.0
 */
public class ArrayStackApp {
    public static void main(String[] args) {
        ArrayStack<Person> arrayStack = new ArrayStack<>(3); //空栈,容量为3
        arrayStack.push(new Person("A", 1)); //进栈
        arrayStack.push(new Person("B", 2)); //进栈
        arrayStack.push(new Person("C", 3)); //进栈
        System.out.println("--------进栈顺序-------\n" +
                "Person{name='A', age=1}\n" +
                "Person{name='B', age=2}\n" +
                "Person{name='C', age=3}");
        System.out.println("--------栈顶元素--------");
        System.out.println(arrayStack.peek());
        System.out.println("--------出栈顺序--------");
        while (!arrayStack.isEmpty()) {
            System.out.println(arrayStack.peek());
            arrayStack.pop();
        }
    }
}

测试结果:

D:\Java\jdk\bin\java.exe 
--------进栈顺序-------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}
--------栈顶元素--------
Person{name='C', age=3}
--------出栈顺序--------
Person{name='C', age=3}
Person{name='B', age=2}
Person{name='A', age=1}

Process finished with exit code 0

(二)链栈

用单链表来实现的栈称为链栈。特别地,与顺序栈相反,单链表的表头为栈顶,表尾为栈底。原因之一是在栈的所有操作中时间复杂度都为 O(1),在单链表表头增加和删除一个结点的时间复杂度也都为 O(1),但在单链表的表尾删除一个结点的时间复杂度为 O(N),所以表头为栈顶更适合。
链栈

图二、链栈的结构

链栈类 LinkedStack 实现了栈接口,封装了一个单链表对象。通过调用单链表的各种方法,即可实现链栈的操作。关于单链表的源代码,可参考我之前总结的一篇关于链表的文章——【数据结构与算法】之链表

栈方法单链表方法描述
size()list.getSize()获取栈(或链表)中的数据个数
isEmpty()list.isEmpty()判断是否为空栈(或空链表)
push(E e)list.addFirst(E e)在栈顶(或表头)添加数据
pop()list.removeFirst()移除栈顶(或表头)的数据
peek()list.first()获取栈顶(或表头)存放的数据
表一、链栈与单链表的对应方法

/**
 * 链栈类,通过链表来实现栈结构
 * 提供了pop(), push(), peek()等方法
 *
 * @author likezhen
 * @version 1.0
 */
class LinkedStack<E> implements Stack<E> {
    private SinglyLinkedList<E> list = new SinglyLinkedList<>(); //私有的单链表对象

    public LinkedStack() {
    }

    @Override
    public int size() {
        return list.getSize();
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public void push(E e) {
        list.addFirst(e);
    }

    @Override
    public E pop() {
        return list.removeFirst();
    }

    @Override
    public E peek() {
        return list.first();
    }
}

在链栈的测试类中,用Person类作为待储存的数据。进栈顺序为 A->B->C,栈顶数据为C,出栈顺序为 C->B->A,符合栈的后进先出原则。

/**
 * 链栈的测试类
 *
 * @author likezhen
 * @version 1.0
 */
public class LinkedStackApp {
    public static void main(String[] args) {
        LinkedStack<Person> linkedStack = new LinkedStack<>(); //空栈
        linkedStack.push(new Person("A", 1)); //进栈
        linkedStack.push(new Person("B", 2)); //进栈
        linkedStack.push(new Person("C", 3)); //进栈
        System.out.println("--------进栈顺序-------\n" +
                "Person{name='A', age=1}\n" +
                "Person{name='B', age=2}\n" +
                "Person{name='C', age=3}");
        System.out.println("--------栈顶元素--------");
        System.out.println(linkedStack.peek());
        System.out.println("--------出栈顺序--------");
        while (!linkedStack.isEmpty()) {
            System.out.println(linkedStack.peek());
            linkedStack.pop();
        }
    }
}

测试结果:

D:\Java\jdk\bin\java.exe
--------进栈顺序-------
Person{name='A', age=1}
Person{name='B', age=2}
Person{name='C', age=3}
--------栈顶元素--------
Person{name='C', age=3}
--------出栈顺序--------
Person{name='C', age=3}
Person{name='B', age=2}
Person{name='A', age=1}

Process finished with exit code 0
  • 时间复杂度分析:
    栈因为其特殊的结构,永远只能操作一个数据,所以其进出栈等所有栈操作的时间复杂度都为 O(1),与数据量无关。
  • 空间复杂度分析:
    顺序栈的空间复杂度为 O(N),与数组的长度成正比。
    链栈的空间复杂度为 O(N),与数据量成正比。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值