图解数据结构:栈和队列

前言

阅读此篇之前,强烈建议先仔细阅读上一篇 图解数据结构:数组和单链表 ,会有事半功倍的效果,并且此篇的代码,基本上是复用上一篇的实现。

上一篇主要讲解了数组和链表这两种线性结构的特点、区别、时间复杂度分析等。对数组和链表的划分,实际上是物理结构(存储结构)的划分。
物理结构有两种基本的结构:顺序存储结构、链式存储结构。而本篇所讲解的栈和队列属于逻辑结构上的划分。逻辑结构分为线性结构、非线性结构。

  • 线性结构:有且仅有一个开始节点和一个终端节点,每个节点最多只有一个直接前驱和一个直接后继。代表结构:栈、队列
  • 非线性结构:一个节点可能有多个直接前驱和多个直接后继。代表结构:树、图

本篇主要讲解栈和队列的特点、区别,以及用数组和链表分别实现栈和队列。

堆栈(英语:stack)又称为栈或堆叠,是计算机科学中的一种抽象数据类型,只允许在有序的线性数据集合的一端(称为堆栈顶端,英语:top)进行加入数据(英语:push)和移除数据(英语:pop)的运算。因而按照后进先出(LIFO, Last In First Out)的原理运作。

栈的主要特点就是LIFO(Last In First Out,后进先出),并且程序只能操作栈的一端,被操作的一端叫做栈顶(Top)。所以栈的使用非常简单,但是实现的功能却非常强大。
栈的主要操作有两个个:入栈(push)、出栈(pop)。

入栈(push)

入栈操作
如图所示,栈就像一个瓶子,只有一个口。三个元素A、B、C先后入栈,先入栈的放在底部,后入栈的放在上面。

出栈(pop)

出栈操作
根据图示,栈顶的元素最先出栈。这与入栈的顺序刚好相反,入栈顺序是A->B->C,出栈顺序是C->B->A。也就是说:栈是LIFO(Last In First Out,后进先出的)。
看似简单的栈,应用十分广泛。操作系统的函数调用、各类编辑器的撤销操作的实现都离不开栈。

栈有两种实现方式:顺序栈链式栈

顺序栈

顺序栈用数组实现,基于上一篇 图解数据结构:数组和单链表 ,我们实现了动态数组,实际上用数组实现栈,就是将数组的增、删操作限制在头部或者尾部,即只能在数组的一端操作元素,就成了顺序栈,复用上一篇的代码,实现顺序栈就很简单了。

// 动态数组实现顺序栈
public class ArrayStack<E> {

	// 此处ArrayList为上一篇博客所实现的
    private ArrayList<E> list;

    public ArrayStack() {
        list = new ArrayList();
    }

    /**
     * 入栈
     * @param e
     * @return
     */
    public E push(E e) {
        list.add(e);
        return e;
    }

    /**
     * 出栈
     * @return
     */
    public E pop() {
        return list.remove();
    }

    /**
     * 查看栈顶元素
     * @return
     */
    public E peek() {
        return list.get(list.size() - 1);
    }
}

注意:pop()peek()方法都能返回栈顶元素,pop()方法会删除栈顶元素,也就是出栈。而peek()方法仅仅是查看栈顶元素,不会删除栈顶元素。
以上几个方法的时间复杂度在动态数组ArrayList中都已经分析过了,此处不再赘述。
完整代码下载地址:
Github:ArrayStack.java

链式栈

链式栈是用链表实现栈,也就是上一篇实现的LinkedList。由于复用了上一篇的代码,所以实现起来也非常简单,基本上只需要把顺序栈中ArrayList换成LinkedList就可以了。

队列

队列,又称为伫列(queue),是先进先出(FIFO, First-In-First-Out)的线性表。在具体应用中通常用链表或者数组来实现。队列只允许在后端(称为rear)进行插入操作,在前端(称为front)进行删除操作。
队列的操作方式和堆栈类似,唯一的区别在于队列只允许新数据在后端进行添加。

与栈(stack)不同的是,队列是FIFO(First In First Out,先进先出),进入队列的一端叫尾部(rear),出队列的一端叫头部(front)。队列的主要操作也有两个:入队(offer)、出队(poll)

入队

入队操作
从图中可以看到,A、B、C三个元素都是从队尾(rear)进入,就像现实生活中的排队,先来的就排在前面。

出队

出队操作
从图中可以看出,出队的顺序是A->B->C,也就是入队的顺序,即说明了队列是遵循FIFO的。队列的引用也十分广泛,锁的实现、生产者-消费者模型等都离不开队列。

队列也有两种实现方式:顺序队列链式队列

顺序队列

顺序队列用数组实现,基于上一篇 图解数据结构:数组和单链表 ,我们实现了动态数组,用数组实现队列,就是将数组的增操作限制在尾部,删操作限制在头部,即分别只能在数组的一端操作元素,就成了顺序队列,复用上一篇的代码。

public class ArrayQueue<E> {

	// 此处ArrayList为上一篇博客所实现的
    private ArrayList<E> list;

    public ArrayQueue() {
        list = new ArrayList();
    }

    /**
     * 出队
     * @param e
     */
    public void offer(E e) {
        list.add(e);
    }

    /**
     * 入队
     * @return
     */
    public E poll() {
        return list.remove(0);
    }

    /**
     * 查看队列头部元素
     * @return
     */
    public E peek() {
        return list.get(0);
    }

}

注意:poll()peek()方法都能返回队列头部元素,poll()方法会删除队列头部元素,也就是出队。而peek()方法仅仅是查看队列头部元素,不会删除队列头部元素。
完整代码下载地址:
Github:ArrayQueue.java

链式队列

链式队列是用链表实现队列,也就是上一篇实现的LinkedList。由于复用了上一篇的代码,所以实现起来也非常简单,基本上只需要把顺序队列中ArrayList换成LinkedList就可以了。

总结

对于栈和队列的简单实现,其实上一篇就已经实现过了,所以本篇的重点是理解栈和队列的工作方式、结构区别、使用区别等。栈和队列是比较简单的线性结构,但是简单不代表用得少。实际上栈和队列的应用非常广泛,理解其工作原理,是使用好栈和队列的第一步,也是最重要的一步。

发布了52 篇原创文章 · 获赞 107 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Baisitao_/article/details/102673498