資料結構 1 線性表詳解 連結串列、 棧 、 佇列 結合JAVA 詳解
阿新 • • 發佈:2020-03-06
## 前言
其實在學習資料結構之前,我也是從來都沒了解過這門課,但是隨著工作的慢慢深入,之前學習的東西實在是不夠用,並且太皮毛了。太淺,只是懂得一些淺層的,我知道這個東西怎麼用,但是要優化、或者是解析,就不知道該咋弄了。比如JAVA 最有名的幾個容器:
- List
- Set
- MAP
- Queue
這些都是涉及到有關資料結構的,以及一些簡單的演算法。排序、氣泡排序、二分法這些,都要涉及到時間複雜度、以及資料結構的知識,這門課,還是很重要的。
## 為了啥
其實資料結構,結構這個詞,就是將我們原本的一些資料,按照某種結構放到一起,為了更加便利以及後期對於這些資料的利用。不能胡來,亂放一遭,那樣整理起來很麻煩,並且不方便以後的二次利用。
平時使用的資料,要麼是基本型別、要麼就是引用型別、陣列、這些就是最基本的。加入需要存一個比如層級結構的崗位,那普通的陣列就沒有辦法了。
![image.png](https://file.chaobei.xyz/blogs/image_1583466904155.png_imagess)
這裡我們所涉及到的內容其實就是 資料的 `結構`
## 結構分類
資料結構的分類,到底有哪些呢,如何去理解他們,就是我們本節課的內容。這裡我們將接觸到線性表、樹狀圖、圖儲存結構等
### 線性表
線性表其實和陣列有些類似。我們都知道,所有資料的型別都可以通過
最基本的 `陣列` `指標(引用型別)` 這兩種最基本的型別構造。
線性表可以細分為:
- 順序表
- 連結串列
- 棧
- 佇列
本節課就圍繞線性表,將這幾種型別依次解釋清楚
## 順序表
順序表最常見的,當然就是陣列(不等同陣列),滿足 `一對一` 何謂一對一呢,就是其裡面儲存的元素,他們的型別,都是存在相同型別的關係,並且緊挨著連線起來的。例如:
```java
String [] array = new String[] {"a","b","c"};
```
類似於這種,除掉首元素和尾元素,每個元素前後都有相鄰的元素。這樣的我們就叫做順序表
![image.png](https://file.chaobei.xyz/blogs/image_1583475589373.png_imagess)
JAVA 裡面我們知道最基本的List 介面,下面有一個 `ArrayList`
ArrayList 底層就是以一個數組,其就是一個順序表。
### 基本操作
我這裡全部以JAVA 為例。
```java
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
```
通過這個建構函式,我們可以發現,傳入一個指定的大小數,大於0,則指定基本陣列的大小為傳入大小。 雖然這個陣列是支援自動擴容的,我們還是研究一下
### add() 增加元素到尾部
```java
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
```
`ensureCapacityInternal`是對陣列的監測,若大小不足以容納,則擴容的機制
這裡的增加元素其實很簡單,就將元素放到size ,也就是容器當中元素數的位置,首次放入元素的時候,size 初始化就是0 而後自增,很簡單
### add() 插入元素
```java
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
```
`rangeCheckForAdd` 檢查插入位置有沒有超過陣列大小。則直接丟擲異常
擴容後,將插入點後面的元素往後移動一個位置,通過`System.arraycopy`複製方式實現
![image.png](https://file.chaobei.xyz/blogs/image_1583478124630.png_imagess)
### 查詢指定下標 get()
```java
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
```
這個就不細說了,太簡單了。
### remove() 移除元素
```
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
```
這個和上面指定位置插入一個元素剛好相反,把指定位置的元素移除掉後,後面的元素往前移動一位,而後將最後元素的位置進行清理。
### 總結
順序表最大的特點是:查詢快,因為是陣列,直接下標出。插入和移除就比較慢了。因為要移動、複製陣列,很麻煩
## 連結串列
在上面我們已經說過了,任何的資料型別都可以通過最基本的陣列和指標構造。連結串列也不例外,相比於陣列,陣列則是定長的,不管儲存的滿否,都申請了一定大小的記憶體空間,而連結串列則不是,連結串列的空間是隨用隨申請,資料的位置相比於陣列,其實不連續的,一般來說,需要在元素上指定下一個元素的指標,來達成連結關係。
![image.png](https://file.chaobei.xyz/blogs/image_1583476164210.png_imagess)
每個元素上都有一塊位置用於指向下一個元素(指標)
這裡我畫的不連續也就是為了表示元素的不連續性
```java
private static class Node {
E item;
Node next;
Node prev;
Node(Node prev, E element, Node next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
```
連結串列在JAVA 當中最具代表性的就是 LinkedList(雙向連結串列),就是每個元素會帶有它的上一個節點和下一個節點的指標,我們圖上畫出來的是單向連結串列。
### add(E e)
```java
void linkLast(E e) {
final Node l = last;
final Node newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
```
新下掛一個節點的時候,將最後一個節點(null)儲存到 l 下,然後構造出一個新節點,將本節點作為最後一個最後一個節點。
![image.png](https://file.chaobei.xyz/blogs/image_1583480182481.png_imagess)
### add(int Index,E e)
```java
void linkBefore(E e, Node succ) {
// assert succ != null;
final Node pred = succ.prev;
final Node newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
```
這裡兩個引數,E 表示將要插入的元素,
![image.png](https://file.chaobei.xyz/blogs/image_1583481243811.png_imagess)
兩邊連結串列斷開,new 一個新的節點,連線即可。
![image.png](https://file.chaobei.xyz/blogs/image_1583481330801.png_imagess)
### get(i) 查詢
```java
Node node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
```
連結串列的查詢指定下標就比較費時了。需要一個個遍歷。其實是很麻煩的。
### remove(i)
```java
E unlink(Node x) {
// assert x != null;
final E element = x.item;
final Node next = x.next;
final Node prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
```
移除一個指定位置的節點,這個其實和增加一個節點時候其實是類似的。將上下節點對於這個節點的引用進行修改即可。
### 小結
連結串列還是比較適合於快速增加、刪除、不適合於索引。因為需要全盤遍歷
## 棧 Stack
堆還是按照陣列為基礎實現的,只不過它是一個半開的陣列,怎麼理解這個半開的陣列呢,如圖,就好像一個瓶子一樣,往裡面丟元素,`先進後出原則`
![image.png](https://file.chaobei.xyz/blogs/image_1583483829545.png_imagess)
### 入棧 push
將一個元素加入的棧裡面,此時的元素是最外層的一個元素,此時執行出棧命令,則這個元素會被刪除並返回
### 出棧 pop
刪除此堆疊頂部的物件,並將該物件作為此函式的值返回。
### 檢視 peek
通過 peek 檢視當前棧頂的元素,只是檢視,並不執行刪除
## 佇列 Queue
佇列遵循`先進先出`原則
佇列還提供額外的插入,提取和檢查操作。 這些方法中的每一種都有兩種形式:如果操作失敗,則丟擲一個異常,另一種返回一個特殊值( null或false ,具體取決於操作)。
![image.png](https://file.chaobei.xyz/blogs/image_1583485650139.png_imagess)
這裡使用 `ArrayBlockingQueue` 以陣列實現的阻塞佇列
```java
BlockingQueue strings = new ArrayBlockingQueue(2);
```
一個有限的blocking queue由陣列支援。 這個佇列排列元素FIFO(先進先出)。 佇列的頭部是佇列中最長的元素。 佇列的尾部是佇列中最短時間的元素。 新元素插入佇列的尾部,佇列檢索操作獲取佇列頭部的元素。
這是一個經典的“有界緩衝區”,其中固定大小的陣列儲存由生產者插入的元素並由消費者提取。 建立後,容量無法更改
### 入隊 add()/offer()/put()
add 和 offer 都可以將元素加入到佇列中。但是add 在超過佇列容量的時候會丟擲異常,offer 則會返回false
而put 操作則會在佇列沒有空間的時候阻塞,直到佇列有空間執行
### 出隊 poll()/take()
- poll檢索並刪除此佇列的頭,如果此佇列為空,則返回 null 。
- take 在沒有元素的時候則會阻塞
## 小結
通過本小結,我們已經學習到了最基本的線性表,而線性表又包含哪些呢
- 順序表 ArrayList
- 連結串列 LinkedList
- 棧 Stack
- 佇列 Queue
下一節我們將繼續學習有關於字串、陣列、廣義表等內容
## 參考:
http://c.biancheng.net/view/33