数据结构与算法(二):栈和队列

star2017 1年前 ⋅ 355 阅读

栈 和 队列 也是常见的数据结构。Java 里面的方法栈,消息队列 是 栈 和 队列 数据结构的实际应用。

概念

(stack) 是一种线性数据结构。可以把栈想像成只有一端开口的圆筒容器。栈的元素只能先进后出(FILO:first int last out),最早进入的元素存放的位置叫 栈底(bottom),最后进入的元素的位置叫作 栈顶(top)。

基本操作

的基本操作是 入栈出栈

  1. 入栈

    入栈操作(push) 是把新元素放入栈中,只能从栈顶一侧放入元素,新元素的位置将成为新的 栈顶。

  2. 出栈

    出栈操作(pop) 是把元素从栈中弹出,只有栈顶的元素才允许出栈,出栈元素的前一个元素的位置将成为新的 栈顶。

入栈出栈 只会影响最后一个元素,不涉及其它元数的整体移动,所以无论是数组还是以链表实现,入栈、出栈的时间复杂度都是 Ο(1)

队列

概念

队列(queue) 是一种线性结构。可以把队列想像成单行隧道。队列容器有两个端口,一个是出口端,叫作 队头(front),一个是入口端,叫作 队尾(rear)。队列中的元素只能先入先出(FIFO:first in first out)。

队列 即可用数组实现,也可用链表来实现。用数组实现,为了入队操作的方便,最后入队元素的下一个位置规定为队尾位置。

基本操作

  1. 入队

    入队(enqueue) 是把新元素放入队列中,只允许在队尾的位置放入元素,新元素的下一个位置将成为新的队尾。

  2. 出队

    出队(dequeue) 是把元素移出队列,只允许在队头一侧移出元素,出队元素的后一个元素将成为新的队头。

数组实现

public class ArrayQueue {
    /**
     * 最大容量
     */
    private int maxSize;
    /**
     * 数组
     */
    private Object[] array;
    /**
     * 头标记
     */
    private int front;
    /**
     * 尾索引
     */
    private int rear;

    //构建函数
    public ArrayQueue(int maxSize) {
        this.maxSize = maxSize;
        array = new Object[this.maxSize];
        front = -1;
        rear = -1;
    }

    /**
     * 判空
     *
     * @return
     */
    public boolean isEmpty() {
        return front == rear;
    }

    /**
     * 判满
     *
     * @return
     */
    public boolean isFull() {
        return rear == maxSize - 1;
    }

    /**
     * 队尾插入
     *
     * @param e
     */
    public void add(int e) {
        if (isFull()) {
            throw new BusinessException("the array queue is full!");
        }
        rear++;
        array[rear] = e;
    }

    /**
     * 队头弹出
     *
     * @return
     */
    public Object getHead() {
        if (isEmpty()) {
            throw new RuntimeException("the array queue is empty!");
        }
        Object result = array[0];
        // 弹出头,后面元素全部前移
//        System.arraycopy(array, 1, array, 0, array.length - 1);
        for (int i = 0; i < array.length - 1; i++) {
            array[i] = array[i + 1];
        }
        // 最后元素置空
        array[rear] = null;
        // 最后元素索引前移一位
        rear--;
        return result;
    }
}

循环队列

队列元素不断出队,队头空间失去作用浪费掉,队列的容量会越来越小。可以用数组实现的队列可采用循环队列(loop queue) 的方式来维持队列容量的恒定。

在数组不做扩容的前提下,让新元素入队并确定新的队尾位置,利用已出队元素留下的空间,让队尾指针重新指回数组的首((准确的描述是让队尾指针指向队头元素出队空出的位置)。

循环队列在特理存储上,队尾的位置也可以在队头之前。当再有元素入队时,将其放入数组的首位,队尾指针继承后移即可。

一直到 (队尾下标 + 1) % 数组长度 = 队头下标,代表此队列直的已经满了。

注意:队尾指针指向的位置永远空出 1 位,所以队列最大容量比数组长度小 1 。

循环队列 不但充分利用了数组的空间,还避免了数组元素整体移动的麻烦,入队和出队的时间复杂度也是 Ο(1) 。

循环队列实现

package com.datastructure.linkedlist;

public class LoopQueue {

    private int[] array;
    private int front;
    private int rear;

    public LoopQueue(int size) {
        this.array = new int[size];
    }

    /**
     * 入队
     *
     * @param element 入队的元素
     */
    public void enQueue(int element) throws Exception {

        if ((rear + 1) % array.length == front) {
            throw new Exception("队列已满");
        }
        //入队元素
        array[rear] = element;
        //队尾指针指向出队元素的位置
        rear = (rear + 1) % array.length;

    }

    /**
     * 出队
     *
     * @return
     */
    public int deQueue() throws Exception {
        if (rear == front) {
            throw new Exception("队列已空");
        }
        //队头元素出队
        int deQueueElement = array[front];
        //队头指针后移
        front = (front + 1) % array.length;
        return deQueueElement;
    }

    /**
     * 输出队列
     */
    public void output() {
        //如果队头指针与队尾指针不相等,则队列仍有元素
        for (int i = front; i != rear; i = (i + 1) % array.length) {
            //输出队头到队尾之间的元素
            System.out.println(array[i]);
        }
    }

    public static void main(String[] args) throws Exception {
        LoopQueue loopQueue = new LoopQueue(6);
        //注意:入队必须留有一个空位作为队尾指针指向的位置
        loopQueue.enQueue(1);
        loopQueue.enQueue(3);
        loopQueue.enQueue(7);
        loopQueue.enQueue(2);
        loopQueue.enQueue(4);

        loopQueue.output();
        System.out.println("-----------------------");

        loopQueue.deQueue();
        loopQueue.output();

        loopQueue.deQueue();
        loopQueue.deQueue();
        loopQueue.deQueue();
        loopQueue.output();
    }
}

双端队列

双端队列数据结构,集合了栈和队列的优点,允许两端都可以进行入队或出队操作,其元素的逻辑仍是线性结构。

双端队列的实现可参考 Java 实现 数据结构-双端队列

优先队列

优先队列 遵循的是谁的优先级高,谁先出队。

优先队列不属于线性数据结构的范畴,通常采用堆数据结构来实现。

栈和队列的应用

栈的应用

栈的是先入后出的顺序,通常用于对 “历史” 回溯,即逆流而上追溯 “历史”。

例如 实现递归的逻辑,就可以用栈来代替,因为栈可以回溯方法的调用链。

队列的应用

队列有入队端口和出队端口,出队顺序是先进先出,返队列的入队顺序依次访问。

应用场景如消息队列;在多线程中,争夺锁的等待队列。

更多内容请访问:IT源点

全部评论: 0

    我有话说: