1. 程式人生 > >資料結構 1 線性表詳解 連結串列、 棧 、 佇列 結合JAVA 詳解

資料結構 1 線性表詳解 連結串列、 棧 、 佇列 結合JAVA 詳解

## 前言 其實在學習資料結構之前,我也是從來都沒了解過這門課,但是隨著工作的慢慢深入,之前學習的東西實在是不夠用,並且太皮毛了。太淺,只是懂得一些淺層的,我知道這個東西怎麼用,但是要優化、或者是解析,就不知道該咋弄了。比如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