摘要訊息 : 連結串列是前向連結串列的擴展, 它允許我們在尋訪的時候向後倒退.
0. 前言
對於前向連結串列 α={(α1,p1),(α2,p2),...,(αn,pn)}, 在《【資料結構】前向連結串列》中, 它只有一個向後的指向型標記. 因此, 我們在尋訪前向連結串列的時候, 只能向前進, 而不可以倒退. 連結串列是加強型的前向連結串列, 它允許尋訪的時候向後倒退.
更新紀錄 :
- 2022 年 4 月 3 日進行第一次更新和修正.
閱讀提示 : 部分在《【資料結構】前向連結串列》中介紹過的細節我們在這篇文章中不再累贅, 如果閣下沒有學習過前向連結串列, 請優先學習前向連結串列.
1. 定義
定義 1. 我們稱向量 α0={α1,α2,...,αn} 的擴展 α={(α1,p1,p1),(α2,p2,p2),...,(αn,pn,pn)} 為連結串列 (list), 又稱為雙向連結串列 (bidirectional list). 其中, pi 是後指向的指向型標記, pi 是前指向的指向型標記, i=1,2,...,n.
指向型標記的定義見《【資料結構】前向連結串列》第 1 節.
在前向連結串列中, 我們通常習慣設定 pn=∞, 而在連結串列中, 我們習慣設定 pn=1 且 p1=n.
Figure 1. 連結串列
2. 插入
在前項連結串列中, 插入操作只需要更新 pi 即可, 但是由於連結串列中有兩個指向型標記, 所以這兩個指向型標記都需要進行更新. 假設我們在連結串列 α={(α1,p1,p1),(α2,p2,p2),...,(αn,pn,pn)} 的第 i 個位置插入 m 個元素 β1,β2,...,βm. 其中, 1≤i≤n. 插入操作可以描述為 :
- 配置 m 個結點 (β1,pn+1,pn+1),(β1,pn+2,pn+2),...,(βm,pn+m,pn+m);
- 令 pn+1=i+1,pn+1=i+2,...,pn+1=i+m;
- 令 pn+1=i−1,pn+1=i,...,pn+1=i+m−1
但是在程式設計中, 結點中的指向型標記儲存的基本都是記憶體位址, 所以上面的指派操作都應該替換為具體的記憶體位址. 另外, 我們需要將 pi−1 指向新的結點 (β1,pn+1,pn+1), 還需要將 pi 指向 (βm,pn+m,pn+m).
Algorithm 1. 向連結串列中插入元素
和前向連結串列類似, 雖然向連結串列中插入元素會比前向連結串列插入元素要慢一些, 當 m=1 的時候, 單純插入一個元素操作的時間複雜度仍然是 Θ(1), 空間複雜度也是 Θ(1) 的. 這裡, 我們同樣忽略了可能的尋訪操作.
3. 移除
同樣地, 從連結串列中移除元素不僅要更新向後的指向型標記, 也需要更新向前的指向型標記. 假設我們要移除連結串列 α={(α1,p1,p1),(α2,p2,p2),...,(αn,pn,pn)} 第 i 個位置起的 m 個元素 (i≥1 且 i+m≤n), 具體的步驟是這樣 :
- 令 pi−1=i+m+1;
- 令 pi+m+1=i−1;
- 回收結點 (αi,pi,pi),(αi+1,pi+1,pi+1),...,(αi+m,pi+m,pi+m).
Algorithm 2. 從連結串列中移除元素
對於 m=1 的情況, 從連結串列中移除元素的時間複雜度和空間複雜度都是 Θ(1). 對於移除, 我們也是和插入操作一樣不將可能的尋訪考慮進時間複雜度中.
4. 搜尋和字典比較
在連結串列中進行搜尋或者比較兩個連結串列是和向量類似的, 因為連結串列 α={(α1,p1,p1),(α2,p2,p2),...,(αn,pn,pn)} 經過退化便可以得到向量 α0={α1,α2,...,αn}. 這其中只是尋訪結點的操作和向量不同罷了. 因此, 連結串列上的搜尋和字典比較可以參考向量的搜尋和字典比較 (《【資料結構】向量 (順序表)》), 此處不再累贅.
5. 和連結串列有關的演算法
所有和前項連結串列有關的演算法同樣可以用在連結串列上, 只是現在我們可能還需要考慮更新前指向的指向型標記. 因此, 所有演算法都可以參考《【資料結構】前向連結串列》第 5 節, 此處不再累贅.
6. 實作
我自己用 C++ 實作了連結串列 : https://github.com/Jonny0201/data_structure/blob/master/data_structure/forward_list.hpp, 大家可以參考後自行實作.