Java中不可變的佇列如何實現? - XP123
函式式語言通常具有不可變的資料型別——一旦建立了物件,您就無法更改它,儘管您可以將其作為其他物件的一部分包含在內。不可變物件更容易推理,並防止程式碼的兩部分認為它們具有更改資料的獨佔訪問許可權的錯誤。我們將研究一種使用兩個Stack以不可變方式實現佇列Queue的方法。
不可變的Stack
傳統的堆疊Stack實現通常使用一個陣列加上一個到當前頂部的偏移量,或者一個單連結串列,其中指向列表的指標指向頂部。
從不變性的角度來看,陣列不是很好,因為它是一個旨在讀取和更新的資料塊。
基於列表的堆疊是更自然的選擇。堆疊有兩種可能性:
- 空堆疊,由 null 表示,-或-
- 包含內容的堆疊,由對包含值的單元格的引用和對堆疊其餘部分的“下一個”引用表示。
要將物件壓入堆疊,請為新單元分配資料,並將其指向當前的“頂部”單元。這不會影響現有堆疊。然後將頂部更改為指向剛剛分配的單元格。
要移除物件,請將頂部指標移至當前單元格中的“下一個”指標。這隻會改變“頂部”。
因此,我們需要有一個堆疊,它佔用的空間與專案的數量成正比,並且需要 O(1) 時間。(O(1) 意味著它使用固定的最大步數,並且永遠不必迴圈其內容。)
傳統的佇列Queue
與堆疊Stack一樣,傳統佇列有兩種常見的實現方式:陣列版本和列表版本。標記。我意識到這聽起來很混亂。在一個真實的例子上追蹤它,你就會明白它為什麼有效。(有關更深入的討論,請參閱參考資料中的“程式設計:使用隱藏的內容”。)
從不變性的角度來看,這兩種方法都有缺陷:我們要麼更新陣列的內容,要麼在現有單元格中處理指標。兩者都不是一成不變的。
兩種方法都需要 O(1) 來新增或刪除,但都使用可變性。
從堆疊Stack派生除的佇列Queue
我們想要一個不變的佇列,它佔用的空間與佇列中的單元格數量成正比,新增或刪除專案的時間複雜度為 O(1)。下面的兩棧演算法很接近。(我的靈感來自參考文獻中Haskell 書中的版本。)
這個想法是你有兩個堆疊:ins和outs兩個Stack。每當您新增新專案時,就將其新增到ins堆疊中。每當您移除它時,您就將其從outs堆疊中移除。
刪除顯然是一個棘手的問題——物件如何進入outs堆疊以將其刪除?訣竅是:如果你想刪除,而outs堆疊中沒有任何東西,那麼首先從insto彈出所有內容outs,然後從 中刪除outs。
這是 Java 中的一個實現。它使用 Java Stack 類,它實際上是一個基於陣列的實現,但這對我們來說並不重要。我們只關心它是一個 O(1) 堆疊。
public class Queue<T> { private Stack<T> ins = new Stack<>(); private Stack<T> outs = new Stack<>(); public boolean isEmpty() { return ins.isEmpty() && outs.empty(); } public void insert(T item) { ins.push(item); } public T remove() { if (isEmpty()) { throw new NoSuchElementException(); } if (outs.isEmpty()) { Queue.emptyStackTo(ins, outs); } return outs.pop(); } private static <T> void emptyStackTo( Stack<T> ins, Stack<T> outs) { while (!ins.isEmpty()) { outs.push(ins.pop()); } } } |
我們要求佇列不變地工作。在我們的程式碼中,我們持有兩個堆疊。這些都是不可變的,所以我們的佇列也是不可變的。
大小應該與專案的數量成正比。堆疊告訴我們:每條資料都在兩個堆疊之一中。所以使用的空間量是兩個堆疊空間的總和。
最後,我們想要 O(1) 的效能。堆疊為我們提供了它們push()和pop()操作。我們add()只是呼叫了一個堆疊操作,所以它是 O(1)。不過,remove()比較麻煩。
Remove()在呼叫堆疊的 O(1) 之前,可能必須將一個堆疊清空到另一個堆疊中pop()。但是我們的複製操作可能必須在某個時候複製一整堆資料。這個副本與使用的空間成正比——我們稱之為 O(n),其中 n 代表專案的數量。這使得我們的操作 O(n)。
我發現這個演算法在幾個層面上很有趣:
- 我喜歡看到以不同方式實現的佇列,沒有陣列或棘手的連結串列。
- 看看不變性如何改變資料結構很有趣。隨著我們使用更多的函式式語言和更多的多執行緒系統,這樣的技術將變得更加重要。(見Okasaki在參考文獻)。
- 這是攤銷時間界限的一個很好的例子。(這讓我想知道我們是否可以以某種方式降低 O(n)。)
相關文章
- Java中實現不可變MapJava
- Java中如何快捷的建立不可變集合Java
- 【資料結構】佇列(順序佇列、鏈佇列)的JAVA程式碼實現資料結構佇列Java
- AQS原始碼深入分析之條件佇列-你知道Java中的阻塞佇列是如何實現的嗎?AQS原始碼佇列Java
- 阻塞佇列一——java中的阻塞佇列佇列Java
- Java 中佇列同步器 AQS(AbstractQueuedSynchronizer)實現原理Java佇列AQS
- Java中的阻塞佇列Java佇列
- 佇列的一種實現:迴圈佇列佇列
- Python佇列的三種佇列實現方法Python佇列
- Java 阻塞佇列(BlockingQueue)的內部實現原理Java佇列BloC
- Java阻塞佇列中的異類,SynchronousQueue底層實現原理剖析Java佇列
- python多執行緒中訊息佇列如何實現?Python執行緒佇列
- 如何用RabbitMQ實現延遲佇列MQ佇列
- 通過佇列實現棧OR通過棧實現佇列佇列
- 實現不可變類如何禁止子類化?
- 如何實現MQ佇列訊息監控MQ佇列
- 深入理解Java中的不可變物件Java物件
- Java中的不可變資料結構Java資料結構
- 9. 題目:對佇列實現棧&用棧實現佇列佇列
- 四、佇列的概念和實現佇列
- 鏈式佇列的實現方式佇列
- Golang 實現 RabbitMQ 的死信佇列GolangMQ佇列
- 用佇列實現棧佇列
- 用 Rust 實現佇列Rust佇列
- 佇列(Queue)-c實現佇列
- 用棧實現佇列佇列
- java佇列Java佇列
- Java 10中Stream API不可變集合JavaAPI
- 佇列 優先順序佇列 python 程式碼實現佇列Python
- 鏈式佇列—用連結串列來實現佇列佇列
- Day 10| 232.用棧實現佇列 、 225. 用佇列實現棧佇列
- Laravel 在事件監聽中實現佇列的方法以及指定加入的佇列名稱和佇列延遲時間Laravel事件佇列
- Golang 實現 RabbitMQ 的延遲佇列GolangMQ佇列
- GCD之佇列的實現和使用GC佇列
- 詳細分析棧和佇列的資料結構的實現過程(Java 實現)佇列資料結構Java
- 兩個棧實現佇列佇列
- RabbitMQ 實現延遲佇列MQ佇列
- Redis實現訊息佇列Redis佇列