1. 程式人生 > >棧與佇列的區別及自定義實現

棧與佇列的區別及自定義實現

一、棧(Stack)和佇列(Queue)的特點

棧(Stack)和佇列(Queue)是兩種基於陣列實現、操作受限的線性表,即棧和佇列都是陣列的子集。

線性表:線性表是一種線性結構,它是一個含有n≥0個結點的有限序列,同一個線性表中的元素資料型別相同並且滿足“一對一”的邏輯關係。

“一對一”的邏輯關係:指的是對於線性表中的結點,有且僅有一個開始結點沒有前驅但有一個後繼結點,有且僅有一個終端結點沒有後繼但有一個前驅結點,其它的結點都有且僅有一個前驅和一個後繼結點。

這種受限表現在:

  • 棧的插入和刪除操作只允許在表的尾端進行(在棧中成為“棧頂”),滿足LOFI(Last Out First In )
  • 佇列只允許在表尾插入資料元素,在表頭刪除資料元素,滿足FIFO(First In First Out)

棧與佇列的相同點:

1.都是線性結構。

2.插入操作都是限定在表尾進行。

3.都可以通過順序結構和鏈式結構實現。

4.插入與刪除的時間複雜度都是O(1),在空間複雜度上兩者也一樣。

5.多鏈棧和多鏈佇列的管理模式可以相同。

棧與佇列的不同點:

1.刪除資料元素的位置不同,棧的刪除操作在表尾進行,佇列的刪除操作在表頭進行。

2.應用場景不同;棧的常見應用場景包括括號問題的匹配,表示式的轉換和求值,函式呼叫和遞迴實現,深度優先搜尋遍歷等;佇列的常見應用場景包括計算機系統中各種資源的管理,訊息緩衝器的管理和廣度優先搜尋遍歷等。

3.順序棧能夠實現多棧空間共享,而順序佇列不能。

二、自定義棧的實現

1、定義Stack<E>介面

public interface Stack<E> {

    // 入棧(壓棧),將元素放入棧中
    void push(E e);

    // 出棧(彈棧),從棧中取出元素
    E pop();

    // 檢視棧頂的元素
    E peek();

    // 獲取棧的大小
    int getSize();

    // 判斷棧是否為空
    boolean isEmpty();
}

2、實現類ArrayStack<E>

public class ArrayStack<E> implements Stack<E> {

    MyArray<E> array;

    public ArrayStack() {
        // 預設容量為10
        this.array = new MyArray<E>();
    }

    public ArrayStack(int capacity) {
        this.array = new MyArray<E>(capacity);
    }

    // 壓棧
    public void push(E e) {
        array.addLast(e);
    }

    // 取出棧頂元素
    public E pop() {
        return array.removeLast();
    }

    // 檢視棧頂的元素
    public E peek() {
        return array.get(array.getSize() - 1);
    }

    // 獲取棧的大小
    public int getSize() {
        return array.getSize();
    }

    // 判斷棧是否為空
    public boolean isEmpty() {
        return array.isEmpty();
    }

    // 獲取棧的容量
    public int getCapacity() {
        return array.getCapacity();
    }

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

        }
        sb.append("] top");
        return sb.toString();
    }
}

應用場景:編輯器的do/undo操作、JVM虛擬機器棧+程式計數器,確定方法執行的順序

三、自定義佇列的實現

分別實現兩種佇列,即普通佇列、和迴圈佇列

主要區別在時間複雜度上,在實現佇列的動態擴減容後,普通佇列每次操作deQueue()方法,其時間複雜度都是O(n),而迴圈佇列每次操作deQueue()方法,其時間複雜度都是O(1)。

1、自定義介面

public interface Queue<E> {

    // 獲取佇列的大小
    int getSize();

    // 判斷佇列是否為空
    boolean isEmplt();

    // 將元素放入佇列
    void enqueue(E e);

    // 取出佇列元素
    E dequeue();

    E getFront();

}

2、普通佇列的實現

public class ArrayQueue<E> implements Queue<E> {

    private MyArray<E> array;

    public ArrayQueue() {
        this.array = new MyArray<E>();
    }

    public ArrayQueue(int capacity) {
        this.array = new MyArray<E>(capacity);
    }

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

    public boolean isEmplt() {
        return array.isEmpty();
    }

    public void enqueue(E e) {
        // 此操作的時間複雜度,均攤
        array.addLast(e);
    }

    public E dequeue() {
        // 根據輸入的特性,此操作的時間複雜度為O(n)
        return array.removeFirst();
    }

    public E getFront() {
        return array.get(0);
    }

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

3、迴圈佇列的實現

public class LoopQueue<E> implements Queue<E> {

    private E[] data;
    private int front;
    private int tail;
    private int size;

    // 須預留佇列中的一個位置,因此根據需要,佇列長度加一
    public LoopQueue(int capacity) {
        data = (E[]) new Object[capacity + 1];
        front = 0;
        tail = 0;
        size = 0;
    }

    // 預設佇列的容量為10
    public LoopQueue() {
        this(10);
    }

    // 獲取佇列大小
    public int getSize() {
        return size;
    }

    // 獲取佇列容量,因為根據需要資料長度加一,此時返回陣列容量需要減一
    public int getCapacity() {
        return data.length - 1;
    }

    // 判斷佇列是否為空,若front、tail指向同一個下標,即陣列為空
    public boolean isEmplt() {
        return front == tail;
    }

    // 向佇列中新增元素
    public void enqueue(E e) {
        if ((tail + 1) % data.length == front) {
            resize(getCapacity() * 2);
        }
        data[tail] = e;
        tail = (tail + 1) % data.length;
        size++;
    }

    // 出隊操作
    public E dequeue() {
        if (this.isEmplt()) {
            throw new IllegalArgumentException("The Queue is empty,can not dequeue any element");
        }
        // 獲取佇列第一個元素
        E frontEle = data[front];
        // 將被取出的元素置為null
        data[front] = null;
        // 更新front,記錄佇列的下一個元素的下標為front
        front = (front + 1) % data.length;
        size--;
        // 實現動態減容
        if (size == getCapacity() / 4 && getCapacity() / 2 != 0) {
            resize(getCapacity() / 2);
        }
        return frontEle;
    }

    // 實現陣列動態擴減容
    private void resize(int newCapacity) {
        E[] newArray = (E[]) new Object[newCapacity + 1];
        for (int i = 0; i < size; i++) {
            newArray[i] = data[(i + front) % data.length];
        }
        data = newArray;
        front = 0;
        tail = size;

    }

    // 獲取佇列的元素
    public E getFront() {
        if (this.isEmplt()) {
            throw new IllegalArgumentException("Queue is empty");
        }
        return data[front];
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity()));
        sb.append("front [");
        // 當front != tail時,表示佇列中存在元素
        for (int i = front; i != tail; i = (i + 1) % data.length) {
            sb.append(data[i]);
            if ((i + 1) % data.length != tail) {
                sb.append(", ");
            }
        }
        sb.append("] tail");
        return sb.toString();
    }
}

4、時間複雜度測試

public class QueueTest {

    public static double testQueue(Queue<Integer> queue, int count) {
        // 開始時間,單位納米
        long startTime = System.nanoTime();
        Random random = new Random();
        for (int i = 0; i < count; i++)
            queue.enqueue(random.nextInt(Integer.MAX_VALUE));
        for (int i = 0; i < count; i++) {
            queue.dequeue();
        }
        // 結束時間,單位納米
        long endTime = System.nanoTime();
        // 程式執行消耗時間
        return (endTime - startTime) / 1000000000.0;
    }

    public static void main(String[] args) {
        // 定義操作的次數
        int opNumber = 100000;
        ArrayQueue<Integer> arrayQueue = new ArrayQueue<Integer>();
        // 普通佇列
        double simQueue = testQueue(arrayQueue, opNumber);
        System.out.println("ArrayQueue, time: " + simQueue + " s");
        // 虛幻佇列
        LoopQueue<Integer> loopQueue = new LoopQueue<Integer>();
        double lpQueue = testQueue(loopQueue, opNumber);
        System.out.println("LoopQueue, time: " + lpQueue + " s");

        System.out.println("執行時間相差倍數為times = " + (int) (simQueue / lpQueue));
    }
}

測試結果:

針對執行環境的不同,可能測試結果會有些出處,但是執行時間的差異已經顯現無疑