算法动画图解 | 被 “废弃“ 的 Java 栈,为什么还在用
公眾號:ByteCode,致力于分享最新技術原創文章,涉及 Kotlin、Jetpack、譯文、系統源碼、 LeetCode / 劍指 Offer / 多線程 / 國內外大廠算法題 等等一系列文章。
在 LeetCode 上不知不覺已經刷了 210+ 題,總提交次數 1000+ 次,從這篇文章開始,每篇算法類型的文章,將會做成動畫的形式,每篇文章都會用 Java 和 kotlin 去實現,并且每道題目都有解題思路、時間復雜度、空間復雜度和源代碼,更多內容點擊下方鏈接前去查看。
- 劍指 Offer 及國內外大廠面試題解:在線閱讀
- LeetCode 系列題解:在線閱讀
通過這篇文章你將學習到以下內容:
- 棧的定義
- 棧的實現
- 為什么不推薦使用 Java 棧
- 性能低
- 破壞了原有的數據結構
- 不推薦使用了,為什么現在還在用
- 為什么推薦使用 Deque 接口替換棧
- 效率比 Java 棧快
- 屏蔽掉無關的方法
- Stack 和 ArrayDeque 區別
- 棧的時間復雜度
- 為什么不推薦使用 Java 棧
- 棧的應用:有效的括號
棧的定義
棧是 后入先出(LIFO) 的數據結構,入棧通常使用 push 操作,往棧中插入數據到棧底,出棧使用 pop 操作,從棧頂刪除數據。入棧和出棧操作動畫如下所示。
棧的實現
棧常用的實現方式是通過動態數組來實現的,在 Java 和 Kotlin 中也內置了棧庫 Stack,但是 Stack 已經不推薦使用了。
為什么不推薦使用
- 性能低
性能低是因為 Stack 繼承自 Vector, 而 Vector 在每個方法中都加了鎖,如下所示:
...... public synchronized void trimToSize() { }public synchronized void ensureCapacity(int minCapacity) { }public synchronized void setSize(int newSize) { }public synchronized int capacity() { }public synchronized int size() { }public synchronized boolean isEmpty() { } ......由于需要兼容老的項目,很難在原有的基礎上進行優化,因此 Vector 就被淘汰掉了,使用 ArrayList 和 CopyOnWriteArrayList 來代替,如果在非線程安全的情況下可以使用 ArrayList,線程安全的情況下可以使用 CopyOnWriteArrayList 。
- 破壞了原有的數據結構
棧的定義是在一端進行 push 和 pop 操作,除此之外不應該包含其他 入棧和出棧 的方法,但是 Stack 繼承自 Vector,使得 Stack 可以使用父類 Vector 公有的方法,如下所示。
val stack = Stack<Int>() stack.push(6) stack.add(1,10) stack.removeAt(1) stack.pop() stack.addAll(arrayListOf()) ......正如你所見,除了調用 push() 和 pop() 方法之外,還可以調用 addXXX() 、 removeXXX() 等等方法,但是這樣會破壞棧原有的結構。所以對于棧的數據結構,不應該有可以在任何位置添加或者刪除元素的能力。
為什么現在還在用
但是為什么在實際項目中還有很多小伙伴在使用 Stack。如果你經常刷 LeetCode 應該會見到很多小伙伴使用 Stack 做相關的算法題。總結了一下主要有兩個原因。
- JDK 官方是不推薦使用 Stack,之所以還有很多人在使用,是因為 JDK 并沒有加 deprecation 注解,只是在文檔和注釋中聲明不建議使用,但是很少有人會去關注其實現細節
- 在做算法題的時候,關注點在解決問題的算法邏輯思路上,并不會關注在不同語言下 Stack 實現細節,但是對于使用 Java 語言的開發者,不僅需要關注算法邏輯本身,也需要關注它的實現細節
為什么推薦使用 Deque 接口替換棧
如果 JDK 不推薦使用 Stack,那應該使用什么集合類來替換棧,一起看看官方的文檔。
正如圖中標注部分所示,棧的相關操作應該由 Deque 接口來提供,推薦使用 Deque 這種數據結構, 以及它的子類,例如 ArrayDeque。
val stack: Deque<Int> = ArrayDeque()使用 Deque 接口來實現棧的功能有什么好處:
- 速度比 Stack 快
這個類作為棧使用時可能比 Stack 快,作為隊列使用時可能比 LinkedList 快。因為原來的 Java 的 Stack 繼承自 Vector,而 Vector 在每個方法中都加了鎖,而 Deque 的子類 ArrayDeque 并沒有鎖的開銷。
- 屏蔽掉無關的方法
原來的 Java 的 Stack,包含了在任何位置添加或者刪除元素的方法,這些不是棧應該有的方法,所以需要屏蔽掉這些無關的方法。
聲明為 Deque 接口可以解決這個問題,在接口中聲明棧需要用到的方法,無需管子類是如何是實現的,對于上層使用者來說,只可以調用和棧相關的方法。
Stack 和 ArrayDeque 區別如下所示。
| Stack | 數組 | 是 |
| ArrayDeque | 數組 | 否 |
Stack 常用的方法如下所示。
| 入棧 | push(E item) |
| 出棧 | pop() |
| 查看棧頂 | peek() 為空時返回 null |
ArrayDeque 常用的方法如下所示。
| 入棧 | push(E item) |
| 出棧 | poll() 棧為空時返回 null pop() 棧為空時會拋出異常 |
| 查看棧頂 | peek() 為空時返回 null |
棧的時間復雜度
棧的核心實現是通過動態數組來實現的,所以在擴容的時候,時間復雜度為 O(n),其他操作例如 push(E item) 和 pop() 、 peek() 等等時間復雜度為 O(1)。
棧的應用:有效的括號
題解已收藏于 https://github.com/hi-dhl/Leetcode-Solutions-with-Java-And-Kotlin。每道題目都會用 Java 和 kotlin 去實現,并且每道題目都有解題思路、時間復雜度、空間復雜度和源代碼,
題目描述
給定一個字符串, 只包括 ‘(’,’)’,’{’,’}’,’[’,’]’,判斷字符串是否有效
有效字符串需要滿足以下條件:
- 左括號必須用相同類型的右括號閉合
- 左括號必須以正確的順序閉合
注意空字符串可被認為是有效字符串。
Example 1:Input: "()"Output: trueExample 2:Input: "()[]{}"Output: trueExample 3:Input: "(]"Output: falseExample 4:Input: "([)]"Output: falseExample 5:Input: "{[]}"Output: true算法流程
- 判斷當前棧是否為空
- 如果不為空,判斷當前元素是否和棧頂元素相等
- 如果不相等,發現了不符合的括號,提前返回 false,結束循環
復雜度分析
假設字符串的長度為 N 則:
- 時間復雜度:O(N)。正確有效的括號需要遍歷了一次字符串,所需要的時間復雜度為 O(N)。
- 空間復雜度:O(N)。如果輸入字符串全是左括號,例如 (((((((,棧的大小即為輸入字符串的長度,所需要的空間復雜度為 O(N)
Kotlin 實現
class Solution {fun isValid(s: String): Boolean {val stack = ArrayDeque<Char>()// 開始遍歷字符串for (c in s) {when (c) {// 遇到左括號,將對應的右括號壓入棧中'(' -> stack.push(')')'[' -> stack.push(']')'{' -> stack.push('}')else -> {// 遇到右括號,判斷當前元素是否和棧頂元素相等,不相等提前返回,結束循環if (stack.isEmpty() || stack.poll() != c) {return false}}}}// 通過判斷棧是否為空,來檢查是否是有效的括號return stack.isEmpty()} }Java 實現
class Solution {public boolean isValid(String s) {Deque<Character> stack = new ArrayDeque<Character>();// 開始遍歷字符串for (int i = 0; i < s.length(); i++) {char c = s.charAt(i);// 遇到左括號,則將其對應的右括號壓入棧中if (c == '(') {stack.push(')');} else if (c == '[') {stack.push(']');} else if (c == '{') {stack.push('}');} else {// 遇到右括號,判斷當前元素是否和棧頂元素相等,不相等提前返回,結束循環if (stack.isEmpty() || stack.poll() != c) {return false;}}}// 通過判斷棧是否為空,來檢查是否是有效的括號return stack.isEmpty();} }倉庫 KtKit 是用 Kotlin 語言編寫的小巧而實用的工具庫,包含了項目中常用的一系列工具, 正在逐漸完善中。
- KtKit 倉庫地址:https://github.com/hi-dhl/KtKit
- KtKit 在線閱讀:https://ktkit.hi-dhl.com
如果這個倉庫對你有幫助,請在倉庫右上角幫我 star 一下,非常感謝你的支持,同時也歡迎你提交 PR
如果有幫助 點個贊 就是對我最大的鼓勵
代碼不止,文章不停
歡迎關注公眾號:ByteCode,持續分享最新的技術
最后推薦我一直在更新維護的項目和網站:
-
個人博客,將所有文章進行分類,歡迎前去查看 https://hi-dhl.com
-
計劃建立一個最全、最新的 AndroidX Jetpack 相關組件的實戰項目 以及 相關組件原理分析文章,正在逐漸增加 Jetpack 新成員,倉庫持續更新,歡迎前去查看:AndroidX-Jetpack-Practice
-
LeetCode / 劍指 offer / 國內外大廠面試題 / 多線程 題解,語言 Java 和 kotlin,包含多種解法、解題思路、時間復雜度、空間復雜度分析
- 劍指 offer 及國內外大廠面試題解:在線閱讀
- LeetCode 系列題解:在線閱讀
-
最新 Android 10 源碼分析系列文章,了解系統源碼,不僅有助于分析問題,在面試過程中,對我們也是非常有幫助的,倉庫持續更新,歡迎前去查看 Android10-Source-Analysis
-
整理和翻譯一系列精選國外的技術文章,每篇文章都會有譯者思考部分,對原文的更加深入的解讀,倉庫持續更新,歡迎前去查看 Technical-Article-Translation
總結
以上是生活随笔為你收集整理的算法动画图解 | 被 “废弃“ 的 Java 栈,为什么还在用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: D-S envidence theory
- 下一篇: DS证据理论基础研究--主要将其应用于旅