連結串列以及golang介入式連結串列的實現
連結串列以及golang介入式連結串列的實現
今天看tcp/ip協議棧的程式碼時看到一個雙向連結串列,連結串列嗎?聽過它的頂頂大名,知道它是由節點構成的,每個節點還有個指標指向下一個節點,但是從來沒自己實現過一個,沒有實踐就不能深刻理解,遂有此文。
以下所有觀點都是個人愚見,有不同建議或補充的的歡迎emial我aboutme
何為連結串列?
連結串列(Linked list)是一種常見的基礎資料結構,是一種線性表,但是並不會按線性的順序儲存資料,而是在每一個節點裡存到下一個節點的指標(Pointer)。由於不必須按順序儲存,連結串列在插入的時候可以達到O(1)的複雜度,比另一種線性表順序錶快得多,但是查詢一個節點或者訪問特定編號的節點則需要O(n)的時間,而順序表相應的時間複雜度分別是O(logn)和O(1)。
簡單的說連結串列是一個具有邏輯順序的線性表,每一個節點裡存到下一個節點的指標。
圖示
單連結串列
雙向連結串列
連結串列有啥用?
因為連結串列插入很快,而且具有動態性,想新增幾個元素就新增幾個(記憶體空間足夠),不像陣列一樣那麼死板,正因為連結串列的靈活性,所有連結串列的用處是大大的有啊。
連結串列最適合用於頻繁更新變化
的資料,比如一個需要非同步執行並且不可丟失的命令序列、一個需要進行實時載入與解除安裝的驅動,無序且數量未知,這個時候就需要連結串列結構來協助完成資料的管理。如果不需要過度關注資料的順序,還可以用連結串列方便快捷地在任意一個地方插入或刪除一個元素,並且不會影響到其它的元素。
又或者我在今天看tcp/ip原始碼中,連結串列用來構造佇列,作為資料段的佇列。我想連結串列用於佇列應該是最多的。如果你看過linux核心原始碼,應該會發現linux核心中多處使用連結串列這種結構。
go標準庫的雙向連結串列
golang的標準庫中實現了一個雙向連結串列,該連結串列可以儲存任何資料,先看看使用標準庫連結串列的例子:
package list_test
import (
"container/list"
"fmt"
"testing"
)
func TestList(t *testing.T) {
// Create a new list and put some numbers in it.
l := list.New()
e4 := l.PushBack(4)
e1 := l.PushFront(1)
l.InsertBefore(3, e4)
l.InsertAfter(2, e1)
// Iterate through list and print its contents.
for e := l.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
}
// output
// 1
// 2
// 3
// 4
該連結串列實現了連結串列所有的功能,連結串列的增刪查改。
實現該連結串列的資料結構
// List represents a doubly linked list.
// The zero value for List is an empty list ready to use.
type List struct {
root Element // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}
// Element is an element of a linked list.
type Element struct {
// Next and previous pointers in the doubly-linked list of elements.
// To simplify the implementation, internally a list l is implemented
// as a ring, such that &l.root is both the next element of the last
// list element (l.Back()) and the previous element of the first list
// element (l.Front()).
next, prev *Element
// The list to which this element belongs.
list *List
// The value stored with this element.
Value interface{}
}
可以看到Element
結構體看到了連結串列的結構,next
,prev
分別指向下一個和前一個元素的指標。Value
就是連結串列中的資料段,可以理解為上圖中的object。
介入式連結串列(intrusive list)
前面的連結串列都是普通連結串列,記得<<c語言程式設計>>
上講的連結串列也是一樣,就是連結串列的節點指標和資料段是放在同一個struct,每實現一個不同的struct就得重新實現一遍連結串列的功能,這對於“懶惰”的程式設計師來說是不可忍受的,所以就出來了介入式連結串列,將資料段和連結串列的功能區別開來。最經典的例子莫過於linux核心的list_head,詳情請看連結klist or Linked List in Linux Kernel,linux中是用c實現的,我想用go實現一個介入式連結串列。
實現程式碼
package list
type Intrusive interface {
Next() Intrusive
Prev() Intrusive
AddNext(Intrusive)
AddPrev(Intrusive)
}
// List provides the implementation of intrusive linked lists
type List struct {
prev Intrusive
next Intrusive
}
func (l *List) Next() Intrusive {
return l.next
}
func (l *List) Prev() Intrusive {
return l.prev
}
func (l *List) AddNext(i Intrusive) {
l.next = i
}
func (l *List) AddPrev(i Intrusive) {
l.prev = i
}
func (l *List) Reset() {
l.prev = nil
l.next = nil
}
func (l *List) Empty() bool {
return l.prev == nil
}
// Front returns the first element of list l or nil.
func (l *List) Front() Intrusive {
return l.prev
}
// Back returns the last element of list l or nil.
func (l *List) Back() Intrusive {
return l.next
}
// PushFront inserts the element e at the front of list l.
func (l *List) PushFront(e Intrusive) {
e.AddPrev(nil)
e.AddNext(l.prev)
if l.prev != nil {
l.prev.AddPrev(e)
} else {
l.next = e
}
l.prev = e
}
// PushBack inserts the element e at the back of list l.
func (l *List) PushBack(e Intrusive) {
e.AddNext(nil)
e.AddPrev(l.next)
if l.next != nil {
l.next.AddNext(e)
} else {
l.prev = e
}
l.next = e
}
// InsertAfter inserts e after b.
func (l *List) InsertAfter(e, b Intrusive) {
a := b.Next()
e.AddNext(a)
e.AddPrev(b)
b.AddNext(e)
if a != nil {
a.AddPrev(e)
} else {
l.next = e
}
}
// InsertBefore inserts e before a.
func (l *List) InsertBefore(e, a Intrusive) {
b := a.Prev()
e.AddNext(a)
e.AddPrev(b)
a.AddPrev(e)
if b != nil {
b.AddNext(e)
} else {
l.prev = e
}
}
// Remove removes e from l.
func (l *List) Remove(e Intrusive) {
prev := e.Prev()
next := e.Next()
if prev != nil {
prev.AddNext(next)
} else {
l.prev = next
}
if next != nil {
next.AddPrev(prev)
} else {
l.next = prev
}
}
我們這裡用List
表示實現了Intrusive
介面,也實現了連結串列的基本功能,所以任何實現了Intrusive
介面的物件都是可以作為連結串列的節點,利用這個介入式連結串列就很簡單了,只要在現有的struct嵌入List
這個結構體即可,在舉個例子:
package list
import (
"container/list"
"fmt"
"testing"
)
func TestIntrusiveList(t *testing.T) {
type E struct {
List
data int
}
// Create a new list and put some numbers in it.
l := List{}
e4 := &E{data: 4}
e1 := &E{data: 1}
l.PushBack(e4)
l.PushFront(e1)
l.InsertBefore(&E{data: 3}, e4)
l.InsertAfter(&E{data: 2}, e1)
for e := l.Front(); e != nil; e = e.Next() {
fmt.Printf("e: %#v\n", e)
fmt.Printf("data: %#v\n", e.(*E).data)
}
}
在E
裡嵌入List
即可作為連結串列的節點,是不是很方便,其實當我寫完介入式連結串列的栗子後,發現其實標準庫的連結串列更方便,哈哈。。因為golang有interface{}
。
參考
https://blog.goquxiao.com/posts/2013/07/06/intrusive-list/
http://blog.nlogn.cn/linked-list-in-linux-kernel/
相關文章
- 連結串列-單連結串列實現
- golang 實現連結串列爽不爽?Golang
- 013 透過連結串列學習Rust之實現連結串列的通用函式Rust函式
- 013 通過連結串列學習Rust之實現連結串列的通用函式Rust函式
- FreeRTOS連結串列實現
- 連結串列 - 單向連結串列
- 連結串列-迴圈連結串列
- 連結串列-雙向連結串列
- 單連結串列建立連結串列出現問題
- 【資料結構】連結串列(單連結串列實現+詳解+原碼)資料結構
- 連結串列4: 迴圈連結串列
- 連結串列-雙向通用連結串列
- Python實現單連結串列Python
- 實現雙向連結串列
- 019 透過連結串列學Rust之雙連結串列實現PeekRust
- 019 通過連結串列學Rust之雙連結串列實現PeekRust
- pta重排連結串列(一個很清晰的實現,完全模擬連結串列的實現)
- 連結串列-雙向非通用連結串列
- 【LeetCode】->連結串列->通向連結串列自由之路LeetCode
- 連結串列入門與插入連結串列
- Leetcode_86_分割連結串列_連結串列LeetCode
- 資料結構-單連結串列、雙連結串列資料結構
- 連結串列
- 資料結構實驗之連結串列三:連結串列的逆置資料結構
- 資料結構實驗之連結串列五:單連結串列的拆分資料結構
- 資料結構實驗之連結串列六:有序連結串列的建立資料結構
- LeetCode-Python-86. 分隔連結串列(連結串列)LeetCodePython
- 資料結構實驗之連結串列二:逆序建立連結串列資料結構
- 資料結構實驗之連結串列九:雙向連結串列資料結構
- TypeScript 實現連結串列反轉TypeScript
- 連結串列找環(python實現)Python
- Go實現雙向連結串列Go
- go 實現單向連結串列Go
- java實現連結串列反轉Java
- java實現雙向連結串列Java
- Redis筆記 — 連結串列和連結串列節點的API函式(三)Redis筆記API函式
- 023 透過連結串列學Rust之非安全方式實現連結串列1Rust
- 024 透過連結串列學Rust之非安全方式實現連結串列2Rust
- 023 通過連結串列學Rust之非安全方式實現連結串列1Rust