Go源碼學習之雙向鏈表

雙向鏈表的定義

雙向鏈表也叫雙鏈表,是鏈表的一種,它的每一個數據結點中都有兩個指針,分別指向直接後繼和直接前驅。因此,從雙向鏈表中的任意一個結點開始,均可以很方便地訪問它的前驅結點和後繼結點。通常咱們都構造雙向循環鏈表html

這裏記錄一下本身學習理解的過程git

圖解

Doubly linked list - wikipedia

Go的源碼實現

1.首先看一下鏈表中存儲的元素(Element)的定義:github

// 雙向鏈表的一個元素 
type Element struct {
  // 前驅指針和後繼指針
  prev, next *Element

  // 該元素屬於哪一個鏈表list
  list *List

  // 該元素存儲的值
  Value interface{} 
}
複製代碼

2.爲Element這個結構體定義兩個方法:golang

// 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定義一個初始化方法markdown

// Init 初始化一個鏈表,或者重置一個鏈表 
func (l *List) Init() (*List) {
  l.root.prev = &l.root
  l.root.next = &l.root
  l.len = 0
  return l 
}
複製代碼

5.爲鏈表List定義一個工廠方法,用來生成一個鏈表:數據結構

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

插入操做:oop

  • 先將元素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不能爲nil
func (l *List) Remove(e *Element) interface{} {
	if e.list == l {
		l.remove(e)
	}
	return e.Value
}
複製代碼

在鏈表頭部或尾部插入元素:

// PushFront 插入一個包含值v的新元素e到鏈表l的頭部,並返回該元素e
func (l *List) PushFront(v interface{}) *Element {
	l.lazyInit()
	return l.insertValue(v, &l.root)
}

// PushBack 插入一個包含值v的新元素e到鏈表l的尾部,並返回這個新元素e
func (l *List) PushBack(v interface{}) *Element {
	l.lazyInit()
	return l.insertValue(v, l.root.prev)
}
複製代碼

在某個元素以前或以後插入一個新元素:

// InsertBefore 在元素mark以前插入一個值爲v的新元素
// 若是mark不屬於鏈表l,則不會更新鏈表l,mark也不能爲nil
func (l *List) InsertBefore(v interface{}, mark *Element) *Element {
	if mark.list != l {
		return nil
	}
	return l.insertValue(v, mark.prev)
}

// InsertAfter 在元素mark以後插入一個值爲v的新元素
// 若是mark不屬於鏈表l,則不會更新鏈表l,mark也不能爲nil
func (l *List) InsertAfter(v interface{}, mark *Element) *Element {
	if mark.list != l {
		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

總結

雙向鏈表並不難理解,只要了理解了其數據結構和插入、刪除的原理,就能迅速掌握。

參考:

原文地址:popwalker.github.io/article/c78…

相關文章
相關標籤/搜索