Golang標準庫深入 - 雙向鏈表(container/list)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

 

1. 什麼是雙向鏈表

(引用)

        和單鏈表比較,雙向鏈表的元素不但知道自己的下線,還知道自己的上線(越來越像傳銷組織了)。小煤車開起來,圖裡面可以看出,每個車廂除了一個指向後面車廂的箭頭外,還有一個指向前面車廂的箭頭(車頭、車尾除外)。車頭只有指向後面車廂的箭頭,車尾只有指向前面車廂的箭頭。

2. 和單向鏈表相比的優勢

    1. 插入刪除不需要移動元素外,可以原地插入刪除

    2. 可以雙向遍曆

    插入資料到中間

刪除中間資料

3、雙向鏈表與Go的對應結構

1.節點分析

我們先把車廂分解開來。每節車廂都由煤炭、車體、拉前車廂繩索、拉後車廂繩索這4部分組成。車體是我們的運輸工具,在Go語言裡我們用結構提DNode表示;煤炭代表運的貨物,用data變數表示;拉前車廂繩索和拉後車廂繩索我們分別用指標prev和next表示。這樣一節車廂,用Go語言描述如下:

type DNode struct { data Object prev *DNode next *DNode}

2、雙向鏈表

一個運煤車隊就是一個雙向鏈表。車隊要有車頭、車廂、車尾,作為車隊的負責人還得知道車隊有多長。在Go語言裡,車隊用結構體DList表示,車頭用head變數表示,車位用tail變數表示,車隊長度就用size來表示,把這些表示合起來:

type DList struct { size uint64 head *DNode tail *DNode}

通過找到其中一個節點,就可以通過prev 或 next指向得到指向的資料。

 

4. Go自訂實現鏈表

1.初始化Init

    雙向鏈表的初始化,可以理解成大衛哥準備買一個車隊準備運煤。第一步,得獲得國家有關部門的批准,有了批准大衛哥就可以買車廂運煤了。但是,批准下來的時候,大衛哥的車隊啥都沒有,沒有車頭、車尾,連一節車廂也沒有。Go語言代碼實現:

package main//節點資料結構type DNode struct {data interface{}prev *DNodenext *DNode}// 鏈表資料結構type DList struct {size uint64head *DNodetail *DNode}// 鏈表的初始化func InitList() (list *DList) {list = *(DList)list.size = 0list.head = nillist.tail = nilreturn}// 新增資料func (dlist *DList) Append(data interface{}) {// 建立一個節點newNode := &DNode{data: data}if (*dlist).GetSize() == 0 { //只有一個節點(*dlist).head = newNode(*dlist).tail = newNode // 頭尾節點都是自己(*newNode).prev = nil   // 但是頭尾的指向是nil(*newNode).next = nil} else { // 接到尾部// 新節點的指向修改(*newNode).prev = (*dlist).tail(*newNode).next = nil// 之前尾節點的指向修改(*(*dlist).tail).next = newNode // 將之前的尾節點的next指向新增節點// 更新鏈表的尾節點(*dlist).tail = newNode}// 更新鏈表的節點計數(*dlist).size++}/* 在節點後面插入資料InsertNextparam- ele 所要插入的位置- data 所要插入的節點資料*/func (dlist *DList) InsertNext(ele *DNode, data interface{}) bool {if ele == nil {return false}if dlist.isTail(ele) { //正好在尾部dlist.Append(data)} else { // 在中間插入//構造新節點newNode := new(DNode)(*newNode).data = data(*newNode).prev = ele         // 上一個節點就是ele(*newNode).next = (*ele).next // 下一個節點就是ele原來的下一個節點// ele的下一個指向新節點(*ele).next = newNode// ele之前下節點的prev重新指向新節點*((*newNode).next).prev = newNode// 更新鏈表計數(*dlist).size++}}/**節點之前插入介面都是類似的方式:1. 首先根據資料建立新節點, 並設定指向2. 更新位置節點的指向資料3. 更新鏈表head , tail ,size資料刪除節點:1. 首先擷取要刪除節點指向資料(驗證頭尾)2. 更新要刪除節點的prev節點的next為要刪除節點的next節點( 有點亂啊!!)3. 更新鏈表資料記得return要刪除的節點資料(否則資料丟失)尋找節點:type MatchFun func (data1 interface{}, data2 interface{}) intfunc (dList *DList) Search(data Object, yourMatch MatchFun) *DNode*/// 擷取鏈表長度GetSizefunc (dList *DList) GetSize() uint64 {return (*dList).size}//擷取頭部節點GetHeadfunc (dList *DList) GetHead() *DNode {return (*dList).head}//擷取尾部節點GetTailfunc (dList *DList) GetTail() *DNode {return (*dList).tail}

 

通過自己實現鏈表,來更深入瞭解鏈表的結構後, 我們使用go的 container/list 庫實現。

 

5.Go庫container/list 實現鏈表操作

關於庫的成員函數,我就不一一列舉了, 看一看文檔很詳細,也很簡單。

下面直接上案例:

func main() {link := list.New()// 迴圈插入到頭部for i := 0; i <= 10; i++ {link.PushBack(i)}// 遍曆鏈表for p := link.Front(); p != link.Back(); p = p.Next() {fmt.Println("Number", p.Value)}}

 

6. slice和list的效能比較

1. 建立、 添加元素的比較

package mainimport ("container/list""fmt""time")func T1() {t := time.Now()//1億建立添加測試// slice 建立slice := make([]int, 10)for i := 0; i < 1*100000*1000; i++ {slice = append(slice, i)}fmt.Println("slice 建立成功:", time.Now().Sub(t).String())// list建立添加t = time.Now()l := list.New()for i := 0; i < 1*100000*1000; i++ {l.PushBack(i)}fmt.Println("list建立成功:", time.Now().Sub(t).String())}func T2() {sli := make([]int, 10)for i := 0; i < 1*100000*1000; i++ {sli = append(sli, 1)}l := list.New()for i := 0; i < 1*100000*1000; i++ {l.PushBack(1)}// 比較遍曆t := time.Now()for _, _ = range sli {//fmt.Printf("values[%d]=%d\n", i, item)}fmt.Println("遍曆slice的速度:" + time.Now().Sub(t).String())t = time.Now()for e := l.Front(); e != nil; e = e.Next() {//fmt.Println(e.Value)}fmt.Println("遍曆list的速度:" + time.Now().Sub(t).String())}func T3() {sli := make([]int, 10)for i := 0; i < 1*100000*1000; i++ {sli = append(sli, 1)}l := list.New()for i := 0; i < 1*100000*1000; i++ {l.PushBack(1)}//比較插入t := time.Now()slif := sli[:100000*500]slib := sli[100000*500:]slif = append(slif, 10)slif = append(slif, slib...)fmt.Println("slice 的插入速度" + time.Now().Sub(t).String())var em *list.Elementlen := l.Len()var i intfor e := l.Front(); e != nil; e = e.Next() {i++if i == len/2 {em = ebreak}}//忽略掉找中間元素的速度。t = time.Now()ef := l.PushBack(2)l.MoveBefore(ef, em)fmt.Println("list: " + time.Now().Sub(t).String())}

簡單的測試下,如果頻繁的插入和刪除建議用list,頻繁的遍曆查詢選slice。

由於container/list不是並發安全的,所以需要自己手動添加一層並發的封裝。

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.