概要
之前我們分別學習瞭解了動態陣列、棧、佇列,其實他們的底層都是依託靜態陣列來實現的、只是通過我們定義的resize
方法來動態擴容解決固定容量的問題,那麼我們即將學習的連結串列,它其實是一種真正的動態資料結構。
介紹
連結串列是一種最簡單的動態資料結構,它能夠輔助組成其它的資料結構,連結串列中的元素可儲存在記憶體中的任何地方(不需要連續的記憶體,這一點和我們的陣列具有很大的區別,陣列需要連續的記憶體),連結串列中的每個元素都儲存了下一個元素的地址,從而使一系列隨機的記憶體地址串接在一起
。
儲存連結串列的資料的我們一般稱為節點(Node)
,節點一般分為兩部分,一部分儲存我們真正的資料,而另外一部分儲存的是下一個節點的引用地址。
class Node{
private E e; // 儲存的真正元素
private Node next; // 儲存下一個node的引用地址(指向下一個node)
}
複製程式碼
比如現在我們將元素A、B、C三個節點新增到連結串列中,示意圖如下:
從圖中節點A
到節點B
之間的箭頭代表,節點A
指向了節點B
(NodeA.next = NodeB),因為在實際業務中我們的連結串列長度不可能是無窮無盡的,基本上都是有限個節點,通常定義連結串列中的最後一個元素它的next
儲存的是NULL
(空),換句話說,如果在連結串列中,一個節點的next
是空(NULL
)的話,那麼它一定是最後一個節點(對應我們圖中的C
節點)。
根據我們上面介紹的連結串列基本結構,下面我們用程式碼定義一下連結串列的基本骨架(這裡我們引入了一個虛擬的頭結點
,下面我們會作說明)
public class LinkedList<E> {
/**
* 虛擬的頭結點
*/
private Node dummyHead;
/**
* 連結串列中節點的個數
*/
private int size;
public LinkedList() {
// 建立一個虛擬的頭結點
dummyHead = new Node();
}
// 節點定義
private class Node {
// 儲存節點的元素
public E e;
// 儲存下一個節點的地址
public Node next;
public Node() {
}
public Node(E e) {
this.e = e;
}
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"e=" + e +
", next=" + next +
'}';
}
}
}
複製程式碼
後面我們對連結串列的新增節點、刪除節點以及查詢節點,程式碼實現都會基於這個基本骨架
向連結串列中新增節點
思路分析:
一般我們向連結串列中新增節點,基本思路:找到新增節點位置的前一個節點preNode
,然後再改變連結串列的地址引用;由於連結串列的第一個節點也就是頭結點沒有前節點,此時我們為了操作方便,為連結串列新增了不儲存任何元素的一個虛擬的頭結點dummyHead
(不是必須的,對使用者來講是不可見的),其實連結串列中真正的第一個節點是節點A
(dummyHead.next),這樣我們就能保證了,連結串列中儲存元素的節點都有前一個節點。
下面我們來看一下,如果現在有一個節點D
,我們準備把它插入到節點B
的位置,我們需要做哪些操作
第一步:我們首先要找到節點B
的前一個節點,也就是節點A
第二步:將新節點D
的指向指到節點B
(NodeD.next = NodeA.next
),然後再將節點A
的指向,指到節點D
(NodeA.next = NodeD
),這樣我們的節點就能串接起來了
程式碼實現:
/**
* 向連結串列中指定位置插入節點(學習使用,真正插入不會指定索引)
*
* @param index 索引的位置
* @param e 節點元素
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("不是有效的索引");
}
Node prev = dummyHead;
// 找到index位置的前一個節點
for (int i = 0; i < index; i++) {
prev = prev.next;
}
// 新建一個節點,進行掛接
Node node = new Node(e);
node.next = prev.next;
prev.next = node;
size++;
}
複製程式碼
連結串列的遍歷
進行連結串列遍歷,我們需要從連結串列中真正的第一個元素開始,也就是dummyHead.next
/**
* 獲取連結串列中index位置的元素
*
* @param index 索引的位置
* @return 節點的元素
*/
public E get(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("不是有效的索引");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
return cur.e;
}
複製程式碼
修改連結串列中元素
/**
* 修改連結串列中index位置節點的元素
*
* @param index 索引的位置
* @param e 節點的元素
*/
public void set(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("不是有效的索引");
}
Node cur = dummyHead.next;
for (int i = 0; i < index; i++) {
cur = cur.next;
}
cur.e = e;
}
複製程式碼
查詢連結串列中是否包含某元素
/**
* 查詢連結串列中是否包含元素e
*
* @param e
* @return
*/
public boolean contains(E e) {
Node cur = dummyHead.next;
while (cur != null) {
if (cur.e.equals(e)) {
return true;
}
cur = cur.next;
}
return false;
}
複製程式碼
刪除連結串列中的元素
在連結串列中刪除元素,與在連結串列中新增元素有點類似
第一步:我們首先找到刪除節點位置的前一個節點,我們用prev
表示,被刪除的節點我們用delNode
表示
第二步:改變連結串列的引用地址:prev.next = delNode.next
(等同於,將節點在連結串列中刪除)
/**
* 刪除連結串列中index位置的節點
*
* @param index
*/
public void remove(int index) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("不是有效的索引");
}
Node prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
Node delNode = prev.next;
prev.next = delNode.next;
delNode.next = null;
size--;
}
複製程式碼
完整版程式碼GitHub倉庫地址:Java版資料結構-連結串列 歡迎大家【關注】和【Star】
至此筆者已經為大家帶來了資料結構:靜態陣列、動態陣列、棧、陣列佇列、迴圈佇列、連結串列;接下來,筆者還會一一的實現其它常見的陣列結構,大家一起加油!
- 靜態陣列
- 動態陣列
- 棧
- 陣列佇列
- 迴圈佇列
- 連結串列
- 迴圈連結串列
- 二分搜尋樹
- 優先佇列
- 堆
- 線段樹
- 字典樹
- AVL
- 紅黑樹
- 雜湊表
- ....
持續更新中,歡迎大家關注公眾號:小白程式之路(whiteontheroad),第一時間獲取最新資訊!!!