戀上數據結構與算法:隊列(十)

文章目錄

(一)單端隊列(Queue)的簡介&設計
(二)單端隊列(Queue)的實現&測試
(三)練習:用棧實現隊列
(四)單端隊列(Queue)& 雙端隊列(Deque)的源碼分析
(五)雙端隊列(Deque)的簡介&設計
(六)雙端隊列(Deque)的實現&測試
(七)循環單端隊列(Circle Queue)的簡介&設計
(八)循環單端隊列(Circle Queue)的實現&測試
(九)循環單端隊列(Circle Queue):動態擴容
(十)循環單端隊列(Circle Queue):封裝索引映射
(十一)循環雙端隊列(Circle Deque)的實現
(十二)循環雙端隊列(Circle Deque)的測試
(十三)循環雙端隊列(Circle Deque)的模運算優化
(十四)循環雙端隊列(Circle Deque)的clear方法
(十五)作業:用隊列實現棧

(一)單端隊列(Queue)的簡介&設計

在這裏插入圖片描述
在這裏插入圖片描述

(二)單端隊列(Queue)的實現&測試

import com.zzq.list.LinkedList;
import com.zzq.list.List;

public class Queue<E> {
    private List<E> list = new LinkedList<>();

    public int size() {
        return list.size();
    }

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

    public void clear() {
        list.clear();
    }
    
    public void enQueue(E element) {
        list.add(element);
    }

    public E deQueue() {
        return list.remove(0);
    }

    public E front() {
        return list.get(0);
    }
}

測試結果如下:
在這裏插入圖片描述

(三)練習:用棧實現隊列

https://leetcode-cn.com/problems/implement-queue-using-stacks/

準備2個棧: inStackoutStack

入隊:push到inStack
在這裏插入圖片描述
出隊:如果outStack爲空將inStack所有元素逐一彈出,push到outStack,outStack彈出棧頂元素
在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述
出隊:如果outStack不爲空,outStack彈出棧頂元素
在這裏插入圖片描述

假設如下操作:11入隊、 22入隊、出隊、 33入隊、 出隊
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
代碼實現如下:

public class _232_用棧實現隊列 {
    private Stack<Integer> inStack;
    private Stack<Integer> outStack;

    /**
     * Initialize your data structure here.
     */
    public _232_用棧實現隊列() {
        inStack = new Stack<>();
        outStack = new Stack<>();
    }

    /**
     * 入隊
     */
    public void push(int x) {
        inStack.push(x);
    }

    /**
     * 出隊
     */
    public int pop() {
        checkOutStack();
        return outStack.pop();
    }

    /**
     * 獲取隊頭元素
     */
    public int peek() {
        checkOutStack();
        return outStack.peek();
    }

    /**
     * 是否爲空
     */
    public boolean empty() {
        return inStack.isEmpty() && outStack.isEmpty();
    }

    private void checkOutStack() {
        if (outStack.isEmpty()) {
            while (!inStack.isEmpty()) {
                outStack.push(inStack.pop());
            }
        }
    }
}

(四)單端隊列(Queue)& 雙端隊列(Deque)的源碼分析

其實JDK官方提供的LinkedList就間接實現了Queue接口
在這裏插入圖片描述
在這裏插入圖片描述

(五)雙端隊列(Deque)的簡介&設計

在這裏插入圖片描述

(六)雙端隊列(Deque)的實現&測試

代碼實現如下:

import com.zzq.list.LinkedList;
import com.zzq.list.List;

public class Deque<E> {
    private List<E> list = new LinkedList<>();

    public int size() {
        return list.size();
    }

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

    public void clear() {
        list.clear();
    }

    public void enQueueRear(E element) {
        list.add(element);
    }

    public E deQueueFront() {
        return list.remove(0);
    }

    public void enQueueFront(E element) {
        list.add(0, element);
    }

    public E deQueueRear() {
        return list.remove(list.size() - 1);
    }

    public E front() {
        return list.get(0);
    }

    public E rear() {
        return list.get(list.size() - 1);
    }
}

測試結果如下:
在這裏插入圖片描述

(七)循環單端隊列(Circle Queue)的簡介&設計

在這裏插入圖片描述
詳細介紹可以看:進一步優化ArrayList

當數組頭部有空位,而尾部滿了的時候,要添加節點,可以往頭部添加,通過取模運算算出真實位置,所以叫循環隊列
在這裏插入圖片描述

(八)循環單端隊列(Circle Queue)的實現&測試

代碼實現如下:

public class CircleQueue<E> {
    private int front;//記錄隊頭元素的下標
    private int size;
    private E[] elements;
    private static final int DEFAULT_CAPACITY = 10;

    public CircleQueue() {
        elements = (E[]) new Object[DEFAULT_CAPACITY];
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void enQueue(E element) {
//        elements[front + size] = element; //不能直接相加,會有溢出的風險
        elements[(front + size) % elements.length] = element;//要通過取模運算達到循環的目的
        size++;
    }

    public E deQueue() {
        E frontElement = elements[front];
        elements[front] = null;
//        front++; //出隊是指把隊頭元素彈出,所以front要指向下一個
        front = (front + 1) % elements.length; //要通過取模運算達到循環的目的
        size--;
        return frontElement;
    }

    public E front() {
        return elements[front];
    }
}

最後加上toString()方法如下:

    @Override
    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("capcacity=").append(elements.length)
                .append(" size=").append(size)
                .append(" front=").append(front)
                .append(", [");
        for (int i = 0; i < elements.length; i++) {
            if (i != 0) {
                string.append(", ");
            }

            string.append(elements[i]);
        }
        string.append("]");
        return string.toString();
    }

測試結果如下:
在這裏插入圖片描述

(九)循環單端隊列(Circle Queue):動態擴容

擴容過程如下:
在這裏插入圖片描述
代碼實現如下:
在這裏插入圖片描述

(十)循環單端隊列(Circle Queue):封裝索引映射

    private int index(int index) {
        return (front + index) % elements.length;
    }

在這裏插入圖片描述
其它地方的修改也是如此,不再展示了

(十一)循環雙端隊列(Circle Deque)的實現

循環雙端隊列:可以進行兩端添加、刪除操作的循環單端隊列

public class CircleDeque<E> {
    private int front;
    private int size;
    private E[] elements;
    private static final int DEFAULT_CAPACITY = 10;

    public CircleDeque() {
        elements = (E[]) new Object[DEFAULT_CAPACITY];
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void clear() {

    }

    public void enQueueRear(E element) {
        ensureCapacity(size + 1);
        elements[index(size)] = element;
        size++;
    }

    public E deQueueFront() {
        E frontElement = elements[front];
        elements[front] = null;
        front = index(1);
        size--;
        return frontElement;
    }

    public void enQueueFront(E element) {
        ensureCapacity(size + 1);
        front = index(-1);// front-1 --> index(-1)
        elements[front] = element;
        size++;
    }

    public E deQueueRear() {
        int rearIndex = index(size + 1);
        E rear = elements[rearIndex];
        elements[rearIndex] = null;
        size--;
        return rear;
    }

    public E front() {
        return elements[front];
    }

    public E rear() {
        return elements[index(size - 1)];
    }

    private int index(int index) {
        index += front;
        if (index < 0) {
            // 如隊列頭部沒有空位,elements[index(-1)] = element 相當於 -1%10=-1
            // 此時只要在加上數組的長度即可得到正確的位置
            return index + elements.length;
        }
        return index % elements.length;
    }

    private void ensureCapacity(int capacity) {
        int oldCapacity = elements.length;
        if (oldCapacity >= capacity) return;

        // 新容量爲舊容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        E[] newElements = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++) {
//            newElements[i] = elements[(i + front) % elements.length];
            newElements[i] = elements[index(i)];
        }
        elements = newElements;

        // 重置front
        front = 0;
    }

    @Override
    public String toString() {
        StringBuilder string = new StringBuilder();
        string.append("capcacity=").append(elements.length)
                .append(" size=").append(size)
                .append(" front=").append(front)
                .append(", [");
        for (int i = 0; i < elements.length; i++) {
            if (i != 0) {
                string.append(", ");
            }

            string.append(elements[i]);
        }
        string.append("]");
        return string.toString();
    }
}

(十二)循環雙端隊列(Circle Deque)的測試

public class Main {
    public static void main(String[] args) {
        CircleDeque<Integer> queue = new CircleDeque<>();
        // 頭5 4 3 2 1  100 101 102 103 104 105 106 8 7 6 尾
        // 每次擴容,都把真正的0號節點放在0號位置,以此類推
        // 頭 8 7 6  5 4 3 2 1  100 101 102 103 104 105 106 107 108 109 null null 10 9 尾
        for (int i = 0; i < 10; i++) {
            queue.enQueueFront(i + 1);
            queue.enQueueRear(i + 100);
        }

        // 頭 null 7 6  5 4 3 2 1  100 101 102 103 104 105 106 null null null null null null null 尾
        for (int i = 0; i < 3; i++) {
            queue.deQueueFront();
            queue.deQueueRear();
        }

        // 頭 11 7 6  5 4 3 2 1  100 101 102 103 104 105 106 null null null null null null 12 尾
        queue.enQueueFront(11);
        queue.enQueueFront(12);
        System.out.println(queue);
        while (!queue.isEmpty()) {
            System.out.println(queue.deQueueFront());
        }
    }
}

(十三)循環雙端隊列(Circle Deque)的模運算優化

我們要儘量避免使用乘*、 除/、 模%、 浮點數運算,效率低下

比如下面這個取模運算的例子,效率就比較底下:
在這裏插入圖片描述
優化後:
前提條件n < 2m , n >= 0 , m > 0
在這裏插入圖片描述
簡化後:
在這裏插入圖片描述

CircleQueueindex(int index)方法優化後:

    private int index(int index) {
        index += front;
        return index - (index >= elements.length ? elements.length : 0);
    }

CircleDequeindex(int index)方法優化後:

	private int index(int index) {
		index += front;
		if (index < 0) {
			return index + elements.length;
		}
		return index - (index >= elements.length ? elements.length : 0);
	}

(十四)循環雙端隊列(Circle Deque)的clear方法

	public void clear() {
		for (int i = 0; i < size; i++) {
			elements[index(i)] = null;
		}
		front = 0;
		size = 0;
	}

(十五)作業:用隊列實現棧

225. 用隊列實現棧https://leetcode-cn.com/problems/implement-stack-using-queues/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章