LinkedList 鏈表
1、什麼是鏈表
鏈表,使用“鏈子”將數據組合起來,這裏的鏈子指的就是引用或者指針。鏈子存儲在哪裏呢?節點(Node)中,我們把節點封裝在類中。即
class Node{
E e;
Node next;
}
也就形成了這樣的數據結構
1.1、同數組的區別
上面就是鏈表,同動態數組不同,鏈表纔算是真正的動態。
動態數組
- 優點:具備隨機訪問能力,直接根據地址尋址一步完成
- 缺點:不具備真正的動態性,底層依然依靠固定容量的數組
鏈表
- 優點:具有真正的動態性,完全根據用戶的數據建立節點
- 缺點:缺少了隨機訪問能力,不適合應對查詢元素頻繁的場景
1.2、節點的實現
由於設計鏈表類的時候,節點作爲鏈表存儲數據的核心。那麼節點類就要作爲鏈表類的子類,所以具體的Java實現如下:
public class LinkedList<E> {
private class Node{ //
public Node(E e, Node node) {
this.e = e;
this.next = node;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return e.toString();
}
E e;
Node next;
}
}
2、鏈表的方法實現
幾乎所有的數據結構的實現都避免不了增刪改查四中基本操作,下面分別實現一下。
寫在前面: 爲了更優化添加元素或者刪除中對頭結點的另外操作,這裏我引入虛擬頭結點(dummyNode)。
2.1、增加元素
增加元素最基本的操作就是向某一個“索引”位置添加元素。
步驟:
- 找到要插入位置的前一個節點pre
- 新增節點,並指向pre節點的下一個節點
- 將pre節點指向新的節點
程序實現
public void add(int index, E e) {
if (index < 0 || index > size) //越界判斷
throw new IllegalArgumentException("add is error, index need index < 0 || index > size ");
table pre = dummyHead; //起始從虛頭開始
for (int i = 0; i < index; ++i) //找到插入位置的前一個節點
pre = pre.next;
pre.next = new table(e, pre.next);
size++;
}
2.2、刪除元素
步驟:
- 找到索引位置的前一個節點pre
- pre節點指向pre節點的下一節點的下一個節點
- 斷開待刪除節點與其下一個節點的連接,刪除的節點會被內存管理機制自動銷燬
程序實現:
public E remove(int index){
if (index < 0 || index > size)
throw new IllegalArgumentException("set is error, index need index < 0 || index > size ");
table pre = dummyHead;
for (int i = 0; i < index; ++i) //找到待刪除節點的前一個節點
pre = pre.next;
size--;
table retNode = pre.next; //保存待刪除節點,後面還要返回
pre.next = retNode.next;
retNode.next = null; //斷開連接
return retNode.e;
}
寫在後面:
在某些場景中,我們並不需要保留這個節點,爲了使代碼更加簡潔易懂我們可以做以下處理。
這種可以應用在LeetCode中,因爲在代碼執行完成以後,所有都會被銷燬。
public void remove(int index){
if (index < 0 || index > size)
throw new IllegalArgumentException("set is error, index need index < 0 || index > size ");
table pre = dummyHead;
for (int i = 0; i < index; ++i)
pre = pre.next;
size--;
pre.next = pre.next.next; //直接刪除
}
2.3、改變元素
改變元素就比較簡單,直接找到位置進行更改元素就可以。同增加元素和刪除元素不同,改變元素不需找到前一個位置,所以起始位置就是虛擬頭節點的下一個位置。
程序實現:
public void set(int index, E e) {
if (index < 0 || index > size)
throw new IllegalArgumentException("set is error, index need index < 0 || index > size ");
table pre = dummyHead.next; //起始位置
for (int i = 0; i < index; ++i) //找到元素
pre = pre.next;
pre.e = e; //改變元素
}
2.4、查詢元素
和其他數據結構相同,包含三種查詢。
- find(E e) 查詢元素e所在位置
- contains(E e) 查詢是否包含元素,同find代碼邏輯相同
- get(int index) 查詢index位置上的元素
/** 是否包含元素 **/
public boolean contains(E e) {
table currentTable = dummyHead.next;
while (currentTable != null) {
if (currentTable.e == e)
return true;
currentTable = currentTable.next;
}
return false;
}
/** 查找元素所在位置 **/
public int find(E e) {
int index = 0;
table currentTable = dummyHead.next;
while (currentTable != null) {
if (currentTable.e == e)
return index;
index++;
currentTable = currentTable.next;
}
return -1;
}
/** 查詢index位置的元素 **/
public E get(int index) {
if (index < 0 || index > size)
throw new IllegalArgumentException("get is error, index need index < 0 || index > size ");
table pre = dummyHead.next;
for (int i = 0; i < index; ++i)
pre = pre.next;
return pre.e;
}
3、時間複雜度分析
對於鏈表來說,最大的優勢在於其動態性,所以在頭部進行的所有操作時間複雜度最低O(1)級別,其他位置的操作均與索引位置位置有關(因爲要先找到要操作的位置)。由此特性,我們可以清楚的看出,用鏈表實現棧的數據結構再合適不過啦。
最後
更多有關數據結構與算法的精彩內容,歡迎大家查看我的主頁:曲怪曲怪
也歡迎大家關注我的微信公衆號:TeaUrn,我在那裏等你哦。
源碼地址:可在公衆號內回覆 數據結構與算法源碼 即可獲得。