1、什麼是雙向鏈表node
和單鏈表比較,雙向鏈表的元素不但知道本身的下線,還知道本身的上線(愈來愈像傳銷組織了)。小煤車開起來,圖裏面能夠看出,每一個車箱除了一個指向後面車箱的箭頭外,還有一個指向前面車箱的箭頭(車頭、車尾除外)。車頭只有指向後面車箱的箭頭,車尾只有指向前面車箱的箭頭。git
2、雙向鏈表與Go的對應結構github
一、節點函數
咱們先把車箱分解開來。每節車箱都由煤炭、車體、拉前車箱繩索、拉後車箱繩索這4部分組成。車體是咱們的運輸工具,在Go語言裏咱們用結構提DNode表示;煤炭表明運的貨物,用data變量表示;拉前車箱繩索和拉後車箱繩索咱們分別用指針prev和next表示。這樣一節車箱,用Go語言描述以下:工具
type DNode struct { data Object prev *DNode next *DNode }
二、雙向鏈表ui
一個運煤車隊就是一個雙向鏈表。車隊要有車頭、車箱、車尾,做爲車隊的負責人還得知道車隊有多長。在Go語言裏,車隊用結構體DList表示,車頭用head變量表示,車位用tail變量表示,車隊長度就用size來表示,把這些表示合起來:spa
type DList struct { size uint64 head *DNode tail *DNode }
結構講完了,下面講講如何增長、減小車箱,也就是雙向鏈表的接口。指針
3、接口說明及實現code
接口主要分爲這幾類。一個是雙向鏈表自己的,還有一類是節點的。鏈表自己的還分爲公開和私有兩種。下面咱們就詳細聊聊這些接口。blog
一、初始化鏈表Init
雙向鏈表的初始化,能夠理解成大衛哥準備買一個車隊準備運煤。第一步,得得到國家有關部門的批准,有了批准大衛哥就能夠買車箱運煤了。可是,批准下來的時候,大衛哥的車隊啥都沒有,沒有車頭、車尾,連一節車箱也沒有。Go語言代碼實現:
func (dList *DList) Init() { _dList := *(dList) _dList.size = 0 // 沒車箱 _dList.head = nil // 沒車頭 _dList.tail = nil // 沒車尾 }
二、新增數據Append
大衛哥新買了車箱,買好的車箱要掛到車隊後面。第一節車箱就是車頭。
func (dList *DList) Append(data Object) { newNode := new(DNode) (*newNode).data = data if (*dList).GetSize() == 0 { // 買個車頭 (*dList).head = newNode (*dList).tail = newNode (*newNode).prev = nil (*newNode).next = nil } else { // 掛在車隊尾部 (*newNode).prev = (*dList).tail (*newNode).next = nil (*((*dList).tail)).next = newNode (*dList).tail = newNode } (*dList).size++; }
三、在節點後面插入數據InsertNext
有時候,車箱不是放在車隊尾巴,而是要放在中間,好比都是運蘋果的車箱最好放一塊兒。
func (dList *DList) InsertNext(elmt *DNode, data Object) bool { if elmt == nil { // apend return false } if dList.isTail(elmt) { // 剛好在車隊尾巴 dList.Append(data) } else { newNode := new(DNode) (*newNode).data = data (*newNode).prev = elmt (*newNode).next = (*elmt).next (*elmt).next = newNode (*((*newNode).next)).prev = newNode (*dList).size++; } return true }
五、在節點前面插入數據InsertPrev
在節點前面插入數據,能夠理解爲在當前節點前一個節點的後面插入數據。
func (dList *DList) InsertPrev(elmt *DNode, data Object) bool { if elmt == nil { return false } if dList.isHead(elmt) { // 若是是新增一個車頭就特殊處理 newNode := new(DNode) (*newNode).data = data (*newNode).next = dList.GetHead() (*newNode).prev = nil (*(dList.head)).prev = newNode dList.head = newNode dList.size++ return true } else { prev := (*elmt).prev return dList.InsertNext(prev, data) } }
這裏的isHead就是判斷節點是不是車頭,後面大衛哥會介紹。
六、刪除一個節點Remove
有些車箱出現問題須要維修,就要把它從車隊裏卸下來。
func (dList *DList) Remove(elmt *DNode) Object { if elmt == nil { return false } prev := (*elmt).prev next := (*elmt).next if dList.isHead(elmt) { dList.head = next } else { (*prev).next = next } if dList.isTail(elmt) { dList.tail = prev } else { (*next).prev = prev } dList.size-- return (*elmt).GetData() }
卸下來後,車箱裏的數據仍是要保留的。
七、查找指定數據所在的節點Search
好比說,要找到蘋果在哪節車箱。就要用到查找功能了。
func (dList *DList) Search(data Object, yourMatch ...MatchFun) *DNode { if dList.GetSize() == 0 { return nil } match := defaultMatch if len(yourMatch) > 0 { match = yourMatch[0] } node := dList.GetHead() for ; node != nil; node = node.GetNext() { if match(node.GetData(), data) == 0 { break } } return node }
match是匹配函數,定義以下:
type MatchFun func (data1 Object, data2 Object) int
若是data1和data2相等就返回0,data1大於data2就返回正數,小於就返回負數。
八、獲取鏈表長度GetSize
func (dList *DList) GetSize() uint64 { return (*dList).size }
九、獲取頭部節點GetHead
func (dList *DList) GetHead() *DNode { return (*dList).head }
十、獲取尾部節點GetTail
func (dList *DList) GetTail() *DNode { return (*dList).tail }
十一、節點是不是頭部節點isHead
func (dList *DList) isHead(elmt *DNode) bool { return dList.GetHead() == elmt }
十二、節點是不是列表尾部isTail
func (dList *DList) isTail(elmt *DNode) bool { return dList.GetTail() == elmt }
1三、獲取節點內數據GetData
func (dNode *DNode) GetData() Object { return (*dNode).data }
這個是節點的方法,不是鏈表的。用來獲取車箱內裝的是什麼。
1四、獲取下一個節點GetNext
func (dNode *DNode) GetNext() *DNode { return (*dNode).next }
這個也是節點的方法,幫助車箱找到下一節車箱。
1五、獲取前一個節點GetPrev
func (dNode *DNode) GetPrev() *DNode { return (*dNode).prev }
這裏一樣是節點的方法。用來找到上一節車箱。