雙向鏈表的定義
雙向鏈表也叫雙鏈表,是鏈表的一種,它的每個資料結點中都有兩個指標,分別指向直接後繼和直接前驅。所以,從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。一般我們都構造雙向迴圈鏈表。
這裡記錄一下自己學習理解的過程
圖解
[圖片上傳失敗...(image-afe880-1531019243291)]
Go的源碼實現
1.首先看一下鏈表中儲存的元素(Element)的定義:
// 雙向鏈表的一個元素 type Element struct { // 前驅指標和後繼指標 prev, next *Element // 該元素屬於哪個鏈表list list *List // 該元素儲存的值 Value interface{} }
2.為Element這個結構體定義兩個方法:
// Next 返回元素e的後一個元素 func (e *Element) Next() *Element { if p := e.next; e.list != nil && &e.list.root != p { return p } return nil }// Prev 返回元素e的前一個元素 func (e *Element) Prev() *Element { if p := e.prev; e.list != nil && &e.list.root != p { return p } return nil }
3.再看鏈表list的定義:
// List 代表一個雙向鏈表 // List的零值是一個空的列表 type List struct { // 根節點 root Element // 當前鏈表的長度 len int }
4.為鏈表List定義一個初始化方法
// Init 初始化一個鏈表,或者重設一個鏈表 func (l *List) Init() (*List) { l.root.prev = &l.root l.root.next = &l.root l.len = 0 return l }
5.為鏈表List定義一個Factory 方法,用來產生一個鏈表:
func New() *List { return new(List).Init() }
6.下面看鏈表核心的兩個方法:插入和刪除,鏈表的其他動作方式基本都是基於這兩個方法
// insert 在元素at後面插入元素e,將list的長度遞增,返回該元素 func (l *List) insert(e, at *Element) *Element { n := at.next at.next = e e.prev = at e.next = n n.prev = e e.list = l l.len ++ return e }// remove 從雙向鏈表中移除一個元素e,遞減鏈表的長度,返回該元素e func (l *List) remove(e *Element) *Element { e.prev.next = e.next e.next.prev = e.prev e.next = nil // 防止記憶體流失 e.prev = nil // 防止記憶體流失 e.list = nil l.len -- return e }
doubly linked list insert operate
插入操作:
- 先將元素b的後繼指標和元素c的前驅指標刪除
- 然後將元素b的後繼指標指向新元素new,將新元素new 的前驅指標指向元素b
- 再講元素c的前驅指標指向新元素new,將新元素new 的後繼指標指向元素c
- 最後將鏈表長度加一
double linked list remove operate
刪除操作:
- 現將元素b的後繼指標指向元素d,將元素d的前驅指標指向b
- 再講元素c的前驅指標和後繼指標刪除
- 將鏈表長度減一
7.理解了鏈表的插入和刪除操作,就可以在此基礎上封裝出豐富的鏈表操作函數:
// insertValue 是對l.insert(&Element{Value:v}, at)的封裝func (l *List) insertValue(v interface{}, at *Element) *Element { return l.insert(&Element{Value: v}, at)}// Remove 如果元素e是鏈表l的一個元素,則移除e// 返回元素e的值e.Value// 該元素e不能為nilfunc (l *List) Remove(e *Element) interface{} { if e.list == l { l.remove(e) } return e.Value}
在鏈表頭部或尾部插入元素:
// PushFront 插入一個包含值v的新元素e到鏈表l的頭部,並返回該元素efunc (l *List) PushFront(v interface{}) *Element { l.lazyInit() return l.insertValue(v, &l.root)}// PushBack 插入一個包含值v的新元素e到鏈表l的尾部,並返回這個新元素efunc (l *List) PushBack(v interface{}) *Element { l.lazyInit() return l.insertValue(v, l.root.prev)}
在某個元素之前或之後插入一個新元素:
// InsertBefore 在元素mark之前插入一個值為v的新元素// 如果mark不屬於鏈表l,則不會更新鏈表l,mark也不能為nilfunc (l *List) InsertBefore(v interface{}, mark *Element) *Element { if mark.list != nil { return nil } return l.insertValue(v, mark.prev)}// InsertAfter 在元素mark之後插入一個值為v的新元素// 如果mark不屬於鏈表l,則不會更新鏈表l,mark也不能為nilfunc (l *List) InsertAfter(v interface{}, mark *Element) *Element { if mark.list != nil { return nil } return l.insertValue(v, mark)}
將某個元素移動到鏈表頭部或尾部:
// MoveToFront 將元素e移動到鏈表頭部// 如果元素e不是鏈表的元素,則不會更新鏈表func (l *List) MoveToFront(e *Element) { if e.list != l || l.root.next == e { return } l.insert(l.remove(e), &l.root)}// MoveToBack 將元素e移動到鏈表尾部// 如果元素e不是鏈表的元素,則不會更新鏈表func (l *List) MoveToBack(e *Element) { if e.list != l || e == l.root.prev { return } l.insert(l.remove(e), l.root.prev)}
將元素A移動到元素B之前 或 之後:
// MoveBefore 移動元素e到元素mark之前func (l *List) MoveBefore(e, mark *Element) { if e.list != l || mark.list != l || e == mark { return } l.insert(l.remove(e), mark.prev)}// MoveAfter 移動元素e到元素mark之後func (l *List) MoveAfter(e, mark *Element) { if e.list != l || e == mark || mark.list != l { return } l.insert(l.remove(e), mark)}
上述代碼均來自golang源碼,詳見Go Doc
總結
雙向鏈表並不難理解,只要了理解了其資料結構和插入、刪除的原理,就能迅速掌握。
參考:
- Doubly linked list - wikipedia
- container/list - Go Doc
- 數組、單鏈表和雙鏈表介紹 以及 雙向鏈表的C/C++/Java實現
原文地址:https://popwalker.github.io/article/c78375f9/