1. 程式人生 > >動圖演示:手擼堆疊的兩種實現方法!

動圖演示:手擼堆疊的兩種實現方法!

正式開始之前,先和各位朋友聊聊後期的一些打算,後面的文章計劃**寫一些關於資料結構和演算法的內容**,原因很簡單底層結構決定上層建築嘛,對於框架滿天飛的今天,我們不止要學習如何使用框架,更要了解它的原理以及底層資料結構,只有這樣我們才能更好的應用它。 當然,除了上述原因之外,還有一個重要因素是為了搞定面試。 ![演算法配圖-1.gif](https://cdn.nlark.com/yuque/0/2020/gif/92791/1600781431900-6c7d1cf3-f015-4bdf-85f9-87cbeffc1143.gif#align=left&display=inline&height=186&margin=%5Bobject%20Object%5D&name=%E7%AE%97%E6%B3%95%E9%85%8D%E5%9B%BE-1.gif&originHeight=186&originWidth=300&size=934073&status=done&style=none&width=300) 隨著軟體開發行業競爭的日益激烈,面試的難度也在逐漸增加,因為企業要從眾多的面試人中選出最優秀的人,只能提高面試的難度,而演算法和資料結構比較燒腦的硬核技能之一,自然也就成了面試的首選科目。並且隨著時間的推移,演算法和資料結構出現的頻率和佔比也會不斷增加,因此為了順應時代發展的潮流,我們也要做一些調整,所以在後面的一些文章中,我會陸續更新一些關於演算法和資料結構的文章,希望大家能夠喜歡。 > PS:當然隨著智慧系統的普及(如今日頭條和抖音),演算法和資料結構在企業中應用也越來越多,因此學習演算法和資料結構也是迫在眉睫的事了。 ### 棧 棧(Stack)又叫堆疊(簡稱棧),它是在同一端進行插入和刪除資料的線性表。 棧是最基礎也是最常見的資料結構之一,它的資料結構和操作流程如下圖所示: ![stack-4.gif](https://cdn.nlark.com/yuque/0/2020/gif/92791/1600683976688-66c3b99f-8a77-4313-866a-8adfa48c0a77.gif#align=left&display=inline&height=576&margin=%5Bobject%20Object%5D&name=stack-4.gif&originHeight=576&originWidth=1090&size=96969&status=done&style=none&width=1090) 其中,允許進行插入和刪除的一端叫作棧頂(Top),另一端叫作棧底(Bottom),棧底固定,棧頂浮動。 當棧中的元素為零時,該棧叫作空棧。新增資料時一般叫作入棧或進棧(Push),刪除資料叫作出棧或退棧(Pop)。**棧是後進先出(Last In First Out,LIFO)的線性表**。 ![image.png](https://cdn.nlark.com/yuque/0/2020/png/92791/1600671956068-9c18a2f6-f57e-4409-a9f9-1a0d400d5efb.png#align=left&display=inline&height=269&margin=%5Bobject%20Object%5D&name=image.png&originHeight=537&originWidth=558&size=28965&status=done&style=none&width=279) ### 物理結構 & 邏輯結構 在手擼演算法之前,我們先來認識一下資料結構中的兩個重要概念:**物理結構和邏輯結構**。 當談到“物理”和“邏輯”一詞時,我們可以會想到資料庫中的邏輯刪除和物理刪除。 所謂的**物理刪除是指通過刪除命令真實的將資料從物理結構中刪除的過程;而邏輯刪除是指通過修改命令將資料更改為“已刪除”的狀態,並非真實的刪除資料。** 這裡的邏輯結構和物理結構和上面的概念類似,所謂的**物理結構是指可以將資料儲存在物理空間中**,比如陣列和連結串列都屬於物理資料結構;而**邏輯結構則是用於描述資料間的邏輯關係的**,比如本文要講的棧就屬於邏輯結構。 ![af5f4e292b7f10819b018be38865f268.gif](https://cdn.nlark.com/yuque/0/2020/gif/92791/1600782556021-def3d721-3373-429b-8ffc-31f0d5d3a338.gif#align=left&display=inline&height=165&margin=%5Bobject%20Object%5D&name=af5f4e292b7f10819b018be38865f268.gif&originHeight=165&originWidth=150&size=48270&status=done&style=none&width=150) 可能有些人看到這裡就蒙了,沒關係,我這裡舉一個例子你就明白了。 如果用人來表示物理結構和邏輯結構的話,那麼**真實存在的有血有肉的人就屬於物理結構,而人的思想和信念就屬於邏輯結構了**。 ![image.png](https://cdn.nlark.com/yuque/0/2020/png/92791/1600596032811-1eb42942-29de-40ba-9d69-15351d3cd5b2.png#align=left&display=inline&height=224&margin=%5Bobject%20Object%5D&name=image.png&originHeight=448&originWidth=886&size=48422&status=done&style=none&width=443) ### 自定義棧I:陣列實現 通過上面的內容,我們知道了棧屬於邏輯結構,因此它的實現方式就可以有很多種了,比如陣列的實現方式或者是連結串列的實現方式。那麼我們就先用陣列實現一下,棧的主要方法有: ![image.png](https://cdn.nlark.com/yuque/0/2020/png/92791/1600683039640-a6e62527-0bf4-4be6-a972-c12276034efd.png#align=left&display=inline&height=205&margin=%5Bobject%20Object%5D&name=image.png&originHeight=409&originWidth=647&size=27161&status=done&style=none&width=323.5) #### ① 定義結構 那麼我們先來定義它的結構: ```java public class MyStack { private Object[] value = null; // 棧儲存容器 private int top = -1; // 棧頂(的指標) private int maxSize = 0; // 棧容量 // 建構函式(初始化預設容量) MyStack() { this.maxSize = 10; } // 有參建構函式 MyStack(int initSize) throws Exception { if (initSize <= 0) { throw new Exception("棧容量必須大於 0"); } else { value = new Object[initSize]; maxSize = initSize; top = -1; } } } ``` 其中棧中資料會儲存在 `Object[] value` 陣列中,`top` 變數代表棧頂的指標,它其實儲存的是棧頂元素的下標,會隨著入棧不斷變化(後進先出),`maxSize` 表示棧的最大容量。 #### ② 入棧 此方法是給棧新增資料的,實現程式碼如下: ```java // 入棧(資料新增) public boolean push(E e) throws Exception { if (maxSize - 1 == top) { throw new Exception("入棧失敗,棧已滿"); } else { value[++top] = e; return true; } } ``` 每次當有資料插入時,只需在陣列中新增一個值,並將棧頂的下標 +1 即可。 入棧操作如下圖所示: ![stack-5.gif](https://cdn.nlark.com/yuque/0/2020/gif/92791/1600684173602-34c64969-6e85-48ce-a960-04d842a06aa1.gif#align=left&display=inline&height=360&margin=%5Bobject%20Object%5D&name=stack-5.gif&originHeight=360&originWidth=1196&size=90057&status=done&style=none&width=1196) #### ③ 出棧 此方法是刪除棧中的資料的,實現程式碼如下: ```java // 資料移除(出棧) public E pop() throws Exception { if (top <= -1) { throw new Exception("移除失敗,棧中已無資料"); } else { return (E) value[top--]; } } ``` 出棧只需刪除陣列中棧頂資料(最後加入的資料),並修改棧頂下標 -1 即可。 出棧操作如下圖所示: ![stack-6.gif](https://cdn.nlark.com/yuque/0/2020/gif/92791/1600684265226-0fe99ef3-c529-4820-a86a-f4eee62050d1.gif#align=left&display=inline&height=360&margin=%5Bobject%20Object%5D&name=stack-6.gif&originHeight=360&originWidth=1196&size=77597&status=done&style=none&width=1196) #### ④ 資料查詢 除了以上操作方法之外,我們還需要新增一個查詢棧頂資料的方法: ```java // 資料查詢 public E peep() throws Exception { if (top <= -1) { throw new Exception("移除失敗,棧中已無資料"); } else { return (E) value[top]; } } ``` #### ⑤ 程式碼測試 到此為止棧的資料結構就已經實現完了,接下來我們來測試一下: ```java // 程式碼測試 public static void main(String[] args) throws Exception { MyStack stack = new MyStack(10); stack.push("Hello"); stack.push("Java"); System.out.println(stack.peep()); stack.pop(); System.out.println(stack.pop()); } ``` 以上程式的執行結果為: >
Java > > Hello 從上述程式碼可以看出,我們新增棧的順序是 `Hello`、`Java` 而輸出的順序是 `Java`、 `Hello` 符合棧的定義(後進先出)。 ### 自定義棧II:連結串列實現 除了陣列之外,我們可以還可使用連結串列來實現棧結構,它的實現稍微複雜一些,我們先來看連結串列本身的資料結構: ![image.png](https://cdn.nlark.com/yuque/0/2020/png/92791/1600684420870-04df518d-31e9-4a24-9705-6db08c98df8e.png#align=left&display=inline&height=102&margin=%5Bobject%20Object%5D&name=image.png&originHeight=203&originWidth=858&size=15764&status=done&style=none&width=429) 使用連結串列實現棧的流程如下: ![stack-7.gif](https://cdn.nlark.com/yuque/0/2020/gif/92791/1600684708281-55afdc81-de5d-43a2-bbf4-ca277e031974.gif#align=left&display=inline&height=334&margin=%5Bobject%20Object%5D&name=stack-7.gif&originHeight=334&originWidth=692&size=172446&status=done&style=none&width=692) 也就是說,入棧時我們將資料儲存在連結串列的頭部,出棧時我們從頭部進行移除,並將棧頂指標指向原頭部元素的下一個元素,實現程式碼如下。 我們先來定義一個連結串列節點: ```java public class Node { Object value; // 每個節點的資料 Node next; // 下一個節點 public Node(Object value) { this(value, null); } /** * 建立新節點 * @param value 當前節點資料 * @param next 指向下一個節點(頭插法) */ public Node(Object value, Node next) { this.value = value; this.next = next; } } ``` 接下來我們使用連結串列來實現一個完整的棧: ```java public class StackByLinked { private Node top = null; // 棧頂資料 private int maxSize = 0; // 棧最大容量 private int leng = 0; // 棧實際容量 public StackByLinked(int initSize) throws Exception { if (initSize <= 0) { throw new Exception("棧容量不能小於等於0"); } top = null; maxSize = initSize; leng = 0; } /** * 容量是否已滿 * @return */ public boolean isFull() { return leng >
= maxSize; } /** * 是否為空 * @return */ public boolean isEmpty() { return leng <= 0; } /** * 入棧 * @param val * @return * @throws Exception */ public boolean push(Object val) throws Exception { if (this.isFull()) { // 容量已滿 throw new Exception("容量已滿"); } top = new Node(val, top); // 存入資訊,並將當前節點設定為頭節點 leng++; return true; } /** * 出棧(移除) * @return * @throws Exception */ public Node pop() throws Exception { if (this.isEmpty()) { throw new Exception("棧為空,無法進行移除操作"); } Node item = top; // 返回當前元素 top = top.next; leng--; return item; } /** * 查詢棧頂資訊 * @return */ public Node peek() throws Exception { if (isEmpty()) { throw new Exception("你操作的是一個空棧"); } return top; } // 程式碼測試 public static void main(String[] args) throws Exception { StackByLinked stack = new StackByLinked(10); stack.push("Hello"); stack.push("Java"); System.out.println(stack.peek().value); stack.pop(); System.out.println(stack.pop().value); } } ``` 以上程式的執行結果是: > Java > > Hello ### 總結 本文我們使用了陣列和連結串列等物理結構來實現了棧,當然我們也可以使用其他容器來實現,比如 Java 中的 `List`,我們只需要保證在操作棧時是後進先出的執行順序,並且至少包含 3 個重要方法:入棧、出棧和查詢棧頂元素就可以了。 ### 最後 **演算法和資料結構的學習是 3 分學 7 分練**,只看不練是沒辦法學好演算法的,而且**學習演算法和資料結構是一個循序漸進的過程,短時間內不會有明顯的收效**。因為這些演算法經過了幾百年的發展和積累才得以流傳下來的,所以想要“玩得轉”還需要一點耐心。 這裡給你講一個學習演算法的“祕訣”:**看不懂的知識要反覆看,如果反覆看還是看不懂,那麼彆著急,休息一下再繼續看!**相信我,對於學習演算法這件事,所有人的過程都是一樣的。**