手寫棧和隊列底層實現(java實現)

打造屬於自己的棧和隊列

​ 前一篇博客介紹瞭如何打造屬於自己的數組,在實現數組的基礎上,我們來完成對棧和隊列的實現。

棧

  • 在棧頂壓棧
  • 在棧頂出棧

棧的應用

  • 無處不在的Undo操作(撤銷操作)
  • 程序調用的系統棧,函數A中調用另外的函數B,先將函數B入棧,執行完畢函數B出棧,在執行函數A。

實現一個棧的接口

實現一個Stack的接口:
package design.stack;

/**
 * @author cyj
 */
public interface Stack<E> {
    /**
     * 獲取棧的長度
     *
     * @return
     */
    int getSize();

    /**
     * 判斷棧是否爲空
     *
     * @return
     */
    boolean isEmpty();

    /**
     * 將一個數壓入棧中
     *
     * @param e
     */
    void push(E e);

    /**
     * 移除棧頂元素,返回被刪除的元素
     *
     * @return
     */
    E pop();

    /**
     * 獲取棧頂元素
     *
     * @return
     */
    E peek();
}

實現一個棧(基於數組)

package design.stack;

import design.array.ArrayObject;

/**
 * @program: design
 * @description: 數組棧具體實現類
 * @author: cyj
 * @create: 2019-03-20 10:42
 **/
public class ArrayStack<E> implements Stack<E> {

    ArrayObject<E> array;

    public ArrayStack(int capacity) {
        array = new ArrayObject<>(capacity);
    }

    public ArrayStack() {
        array = new ArrayObject<>();
    }

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

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

    public int getCapacity() {
        return array.getCapacity();
    }

    @Override
    public void push(E e) {
        array.addLast(e);
    }

    @Override
    public E pop() {
        return array.removeLast();
    }

    @Override
    public E peek() {
        return array.getLast();
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("Stack: ");
        res.append('[');
        for (int i = 0; i < array.getSize(); i++) {
            res.append(array.get(i));
            if (i != array.getSize() - 1) {
                res.append(", ");
            }
        }
        res.append("] top");
        return res.toString();
    }
}

測試棧類

package design.stack;

/**
 * @program: design
 * @description: 棧測試類
 * @author: cyj
 * @create: 2019-03-20 11:03
 **/
public class StackTest {
    public static void main(String[] args) {
        ArrayStack<Integer> stack = new ArrayStack<>();

        //循環賦值,將整數壓入棧中
        for (int i = 0; i < 5; i++) {
            stack.push(i);
            System.out.println(stack);
        }
		//在去除棧頂元素
        stack.pop();
        System.out.println(stack);
    }
}

運行結果:(top)爲棧頂元素
Stack: [0] top
Stack: [0, 1] top
Stack: [0, 1, 2] top
Stack: [0, 1, 2, 3] top
Stack: [0, 1, 2, 3, 4] top
Stack: [0, 1, 2, 3] top

隊列

隊列

  • 在隊尾入隊
  • 在隊首出隊

實現一個數組隊列

​ 在數組的基礎上實現一個隊列,需要實現以下方法

在這裏插入圖片描述

  • 先定義一個隊列接口:
package design.queue;

/**
 * @author cyj
 */
public interface Queue<E> {
    /**
     * 獲取隊列的大小
     *
     * @return
     */
    int getSize();

    /**
     * 判斷隊列是否爲空
     *
     * @return
     */
    boolean isEmpty();

    /**
     * 將一個數添加入隊尾中
     *
     * @param e
     */
    void enqueue(E e);

    /**
     * 去除隊首元素,返回被刪除的元素
     *
     * @return
     */
    E dequeue();

    /**
     * 獲取隊列元素
     *
     * @return
     */
    E getFront();
}
  • 實現Queue接口
package design.queue;

import design.array.ArrayObject;

/**
 * @program: design
 * @description: Array的隊列實現類
 * @author: cyj
 * @create: 2019-03-20 14:16
 **/
public class ArrayQueue<E> implements Queue<E> {
    private ArrayObject<E> array;

    public ArrayQueue(int capacity) {
        array = new ArrayObject<>(capacity);
    }

    public ArrayQueue() {
        array = new ArrayObject<>();
    }

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

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

    public int getCapacity() {
        return array.getCapacity();
    }

    @Override
    public void enqueue(E e) {
        //入隊在隊尾入隊
        array.addLast(e);
    }

    @Override
    public E dequeue() {
        //時間複雜度爲O(n),刪除隊首元素之後,需要將隊列元素往前移一位,數據量一上去就會非常耗時間,因此需要改進
        //下文會對循環隊列進行介紹
        return array.removeFirst();
    }

    @Override
    public E getFront() {
        return array.getFirst();
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append("Queue: ");
        res.append("front [");
        for (int i = 0; i < array.getSize(); i++) {
            res.append(array.get(i));
            if (i != array.getSize() - 1) {
                res.append(", ");
            }
        }
        res.append("] tail");
        return res.toString();
    }

    public static void main(String[] args) {
        ArrayQueue<Integer> queue = new ArrayQueue<>();
        for (int i = 0; i < 10; i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}

//每在隊尾入隊3個元素之後再出隊一個隊首元素
運行結果:
Queue: front [0] tail
Queue: front [0, 1] tail
Queue: front [0, 1, 2] tail
Queue: front [1, 2] tail
Queue: front [1, 2, 3] tail
Queue: front [1, 2, 3, 4] tail
Queue: front [1, 2, 3, 4, 5] tail
Queue: front [2, 3, 4, 5] tail
Queue: front [2, 3, 4, 5, 6] tail
Queue: front [2, 3, 4, 5, 6, 7] tail
Queue: front [2, 3, 4, 5, 6, 7, 8] tail
Queue: front [3, 4, 5, 6, 7, 8] tail
Queue: front [3, 4, 5, 6, 7, 8, 9] tail

實現一個循環隊列

​ 因爲在數組隊列中,每出隊一個隊首元素,都需要將之後的元素往前位移,這相當的耗時間,時間複雜度爲O(n)。那有沒有方法降低時間複雜度呢?

​ 這個時候就輪到循環隊列閃亮登場,循環隊列通過雙指針的方式維護整個隊列,出隊的情況下不用整個位移,而是將指向隊首的指針往後移一位就可以。那麼在入隊的時候,則將指向隊尾的指針向後移。那麼問題來了,指向隊尾的指針如果已經到了隊列的最長長度,但是隊首之前由於執行過出隊操作而騰出來空間,這時就可以把指向隊尾的指針指向隊列的前端,形成一個閉環,因此也有了判斷隊列是否爲空爲滿的判斷依據。

  • 判斷循環隊列爲空:front==tail

  • 判斷循環隊列爲滿:(tail+1)%length=front 進行取餘操作

  • 根據判斷爲滿的條件可以看出,循環隊列實際上浪費了一個空間,因此在實例化中需要將循環隊列的空間+1
    在這裏插入圖片描述

package design.queue;

/**
 * @program: design
 * @description: 循環隊列實現類
 * @author: cyj
 * @create: 2019-03-20 14:46
 **/
public class LoopQueue<E> implements Queue<E> {
    private E[] data;
    private int front, tail;
    private int size;

    public LoopQueue(int capacity) {
        //循環隊列判斷條件,有意的會浪費一個單位,這樣才能容納下用戶期望的隊列長度
        data = (E[]) new Object[capacity + 1];
        front = 0;
        tail = 0;
        size = 0;
    }

    public LoopQueue() {
        this(10);
    }

    public int getCapacity() {
        return data.length - 1;
    }

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

    @Override
    public boolean isEmpty() {
        return front == tail;
    }

    @Override
    public void enqueue(E e) {
        if ((tail + 1) % data.length == front) {
            resize(getCapacity() * 2);
        }
        data[tail] = e;
        tail = (tail + 1) % data.length;
        size++;
    }

    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity + 1];
        for (int i = 0; i < size; i++) {
            newData[i] = data[(i + front) % data.length];
        }
        data = newData;
        front = 0;
        tail = size;
    }

    /**
     * O(1) 均攤複雜度
     *
     * @return
     */
    @Override
    public E dequeue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
        }
        E ret = data[front];
        data[front] = null;
        front = (front + 1) % data.length;
        size--;
        if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
            resize(getCapacity() / 2);
        }
        return ret;
    }

    @Override
    public E getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("Cannot dequeue from an empty queue.");
        }
        return data[front];
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("Queue: size= %d , capacity= %d\n", size, getCapacity()));
        res.append("front [");
        //重點!!判斷條件多想想
        for (int i = front; i != tail; i = (i + 1) % data.length) {
            res.append(data[i]);
            if ((i + 1) % data.length != tail) {
                res.append(", ");
            }
        }
        res.append("] tail");
        return res.toString();
    }

    public static void main(String[] args) {
        LoopQueue<Integer> queue = new LoopQueue<>();
        for (int i = 0; i < 10; i++) {
            queue.enqueue(i);
            System.out.println(queue);

            if (i % 3 == 2) {
                queue.dequeue();
                System.out.println(queue);
            }
        }
    }
}


//每在隊尾入隊3個元素之後再出隊一個隊首元素
//注意觀察循環隊列的capacity
輸出結果:
Queue: size= 1 , capacity= 10
front [0] tail
Queue: size= 2 , capacity= 10
front [0, 1] tail
Queue: size= 3 , capacity= 10
front [0, 1, 2] tail
Queue: size= 2 , capacity= 5
front [1, 2] tail
Queue: size= 3 , capacity= 5
front [1, 2, 3] tail
Queue: size= 4 , capacity= 5
front [1, 2, 3, 4] tail
Queue: size= 5 , capacity= 5
front [1, 2, 3, 4, 5] tail
Queue: size= 4 , capacity= 5
front [2, 3, 4, 5] tail
Queue: size= 5 , capacity= 5  //實際開閉了6個空間,所以在下一步才進行擴容
front [2, 3, 4, 5, 6] tail
Queue: size= 6 , capacity= 10
front [2, 3, 4, 5, 6, 7] tail
Queue: size= 7 , capacity= 10
front [2, 3, 4, 5, 6, 7, 8] tail
Queue: size= 6 , capacity= 10
front [3, 4, 5, 6, 7, 8] tail
Queue: size= 7 , capacity= 10
front [3, 4, 5, 6, 7, 8, 9] tail

數組隊列與循環隊列的比較

分別執行100000次的入隊操作和100000次的出隊操作,查看運行時間

package design.queue;

import java.util.Random;

/**
 * @program: design
 * @description: 數組隊列和循環隊列的比較
 * @author: cyj
 * @create: 2019-03-20 16:20
 **/
public class QueueTest {
    /**
     * 測試使用q運行opCount個enqueue和dequeue操作所需要的時間,單位:秒
     *
     * @param q
     * @param opCount
     * @return
     */
    private static double testQueue(Queue<Integer> q, int opCount) {
        long startTime = System.nanoTime();
        Random random = new Random();
        for (int i = 0; i < opCount; i++) {
            q.enqueue(random.nextInt(Integer.MAX_VALUE));
        }
        for (int i = 0; i < opCount; i++) {
            q.dequeue();
        }
        long endTime = System.nanoTime();
        return (endTime - startTime) / 1000000000.0;
    }

    public static void main(String[] args) {
        int opCount = 100000;

        ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
        double time1 = testQueue(arrayQueue, opCount);
        System.out.println("ArrayQueue , time: " + time1 + " s");

        LoopQueue<Integer> loopQueue = new LoopQueue<>();
        double time2 = testQueue(loopQueue, opCount);
        System.out.println("LoopQueue , time: " + time2 + " s");

    }
}



運行結果:
ArrayQueue , time: 3.311457298 s
LoopQueue , time: 0.012101963 s


可以看出有明顯的區別,甚至可以說100倍的區別,主要是因爲數組隊列在出隊操作上耗費了太多的時間。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章