動圖演示:手擼堆疊的兩種實現方法!
阿新 • • 發佈:2020-09-24
正式開始之前,先和各位朋友聊聊後期的一些打算,後面的文章計劃**寫一些關於資料結構和演算法的內容**,原因很簡單底層結構決定上層建築嘛,對於框架滿天飛的今天,我們不止要學習如何使用框架,更要了解它的原理以及底層資料結構,只有這樣我們才能更好的應用它。
當然,除了上述原因之外,還有一個重要因素是為了搞定面試。
![演算法配圖-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 分練**,只看不練是沒辦法學好演算法的,而且**學習演算法和資料結構是一個循序漸進的過程,短時間內不會有明顯的收效**。因為這些演算法經過了幾百年的發展和積累才得以流傳下來的,所以想要“玩得轉”還需要一點耐心。
這裡給你講一個學習演算法的“祕訣”:**看不懂的知識要反覆看,如果反覆看還是看不懂,那麼彆著急,休息一下再繼續看!**相信我,對於學習演算法這件事,所有人的過程都是一樣的。**