第一節 如何用Go實現單鏈表

1、概念介紹node

下面這副圖是咱們單鏈表運煤車隊。
%E5%8D%95%E9%93%BE%E8%A1%A8-1.pnggit

每節運煤車就是單鏈表裏的元素,每節車箱裏的煤炭就是元素中保存的數據。先後車經過鎖鏈相連,做爲單鏈表運煤車,從1號車箱開始,每節車箱都知道後面拉着哪一節車箱,殊不知道前面是哪節車箱拉的本身。第一節車箱沒有任何車箱拉它,咱們就叫它車頭,第五節車箱後面拉其餘車箱,咱們稱爲車尾。github

做爲單鏈表它最大的特色就是能隨意增長車隊的長度,也能隨意減小車隊的長度。這是比數組公交車最大的優勢。數組

2、Go語言實現講解框架

一、節點
%E6%AF%8F%E8%8A%82%E8%BD%A6%E5%8E%A2.png
每節車箱都由車體、繩索和煤炭構成。在Go語言中表示這種自定義組合體的類型就是結構,固然爲了通用性,咱們這裏要把車箱轉換成節點也就是元素,煤炭轉換成數據,繩索轉換成指針。ui

type Node struct {
    data Object
    next *Node
}

用張圖來描述這種對應關係。
%E5%AF%B9%E5%BA%94%E5%85%B3%E7%B3%BB.png
這裏結構體Node表示車箱,data表示煤炭用Object類型,next是牽着下節車箱的繩索,用指針表示。spa

對於車箱來講,除了放煤炭外,還能放瓜果、衣物、飯菜等等,因此這裏data的類型必須通用,固然Go裏是沒有Java裏的Object類型的,因此咱們就本身定義了一個。指針

type Object interface{}

二、鏈表
%E9%93%BE%E8%A1%A8.png
光有車箱還不夠,咱們還要描述車箱組成的車隊。每一個單鏈表車隊都有車頭、車尾和車箱數量,咱們一樣以結構來表現。code

type List struct {
    size uint64 // 車輛數量
    head *Node  // 車頭
    tail *Node  // 車尾
}

三、方法索引

(1)初始化

第一步就是組裝單鏈表車隊,這就是初始化。不過開始的時候車隊是個空殼,一個車箱沒有。

func (list *List) Init() {
    (*list).size = 0    // 此時鏈表是空的
    (*list).head = nil  // 沒有車頭
    (*list).tail = nil  // 沒有車尾
}

(2)添加元素

當前的單鏈表是空的,因此咱們要向裏面添加元素。

func (list *List) Append(node *Node) {
    (*list).head = node // 這是單鏈表的第一個元素,也是鏈表的頭部
    (*list).tail = node // 同時是單鏈表的尾部
    (*list).size = 1    // 單鏈表有了第一個元素
}

如今單鏈表有了第一個元素,我還想再添加一個元素,固然是添加到單鏈表尾部。

func (list *List) Append(node *Node) {
    if (*list).size == 0 { // 無元素的時候添加
        (*list).head = node // 這是單鏈表的第一個元素,也是鏈表的頭部 
        (*list).tail = node // 同時是單鏈表的尾部
        (*list).size = 1    // 單鏈表有了第一個元素
    } else { // 有元素了再添加
        oldTail := (*list).tail
        (*oldTail).next = node  // node放到尾部元素後面
        (*list).tail = node     // node成爲新的尾部
        (*list).size++          // 元素數量增長
    }
}

分析上面的代碼存在3處疑點,元芳你怎麼看?屬下認爲
第一,node若是爲空,則添加無任何意義;
第二,代碼中存在重複的地方;
這第三麼,卑職如何才能知道新增結果?
下面大衛哥順着元芳的思路改進下代碼。

func (list *List) Append(node *Node) bool {
    if node == nil {
        return false
    }
    
    (*node).next = nil
    // 將新元素放入單鏈表中
    if (*list).size == 0 { 
        (*list).head = node  
    } else { 
        oldTail := (*list).tail
        (*oldTail).next = node  
    }

    // 調整尾部位置,及鏈表元素數量
    (*list).tail = node // node成爲新的尾部  
    (*list).size++      // 元素數量增長

    return true
}

(3)插入元素

一天領導讓大衛哥把他小舅子安排到第一個,因而大衛哥爲了拍好領導馬屁。絞盡腦汁弄了一個方法。

*func (list List) Insert(node *Node) bool {

if node == nil {
    return false
}

(*node).next = (*list).head   // 領導小舅子排到以前第一名前面
(*list).head = node           // 領導小舅子成爲第一名
(*list).size++
return true

}**

來託關係插隊的人愈來愈多,領導的關係戶不能動,只能插後面人的隊了。因而大衛哥又修改了代碼,增長了位置參數。

func (list *List) Insert(i uint,node *Node) bool {
    // 空的節點、索引超出範圍和空鏈表都沒法作插入操做
    if node == nil || i > (*list).size || (*list).size == 0 {
        return false
    }

    if i == 0 { // 直接排第一,也就領導小舅子才能夠
        (*node).next = (*list).head
        (*list).head = node
    } else {
        // 找到前一個元素
        preItem := (*list).head
        for j := 1 ; j < i; j++ { // 數前面i個元素
            preItem = (*preItem).next
        }
        // 原來元素放到新元素後面,新元素放到前一個元素後面
        (*node).next = (*preItem).next
        (*preItem).next = preItem
    }

        (*list).size++ 

        return true
}

(4)刪除元素

插隊的關係戶太多,影響了正常排隊的人,被人投訴,大衛哥只好想辦法刪除一些。

func (list *List) Remove(i uint, node *Node) bool {
    if i >= (*list).size {
        return false
    }
    
    if i == 0 { // 刪除頭部
        node = (*list).head
        (*list).head = (*node).next
        if (*list).size == 1 { // 若是隻有一個元素,那尾部也要調整
            (*list).tail = nil
        }
    } else {
        preItem := (*list).head
        for j := 1; j < i; j++ {
            preItem = (*preItem).next
        }
    
        node = (*preItem).next
        (*preItem).next = (*node).next

        if i == ((*list).size - 1) { // 若刪除的尾部,尾部指針須要調整
            (*list).tail = preItem
        }
    }
    (*list).size--
    return true
}

(5)獲取

爲了獲取某個位置的元素,咱們須要一個方法。

func (list *List) Get(i uint) *Node {
    if i >= (*list).size {
        return nil
    }

    item := (*list).head
    for j := 0; j < i ; j++ {    // 從head數i個
        item = (*item).next
    }

    return item
}

到這裏基本框架已經出來了,不過還有幾個接口還沒實現,做爲課後做業。

3、小結

單鏈表就和列車相似,一個接着一個,因此本節從列車類比介紹了單鏈表的Go語言實現。在接口實現部分大衛哥以序號做爲鏈表中每一個節點的操做關鍵字。在實際應用中,咱們每每以data中的某一個字段做爲操做關鍵字。因此這也衍生出鏈表的不一樣接口,你們能夠參考大衛哥留的連接中的代碼實現做爲理解。同時有些實現將表頭獨立出來並不存放數據,這在必定程度上簡化了代碼的實現。

代碼下載

4、習題

(1)補全GetSize,RemoveAll,GetHead和GetTail的定義和實現。
(2)以data做爲參數,考慮單鏈表的實現。
(3)將單鏈表的head獨立出來,此時的head是獨立的,不存放data,以下圖,考慮單鏈表的實現,並比較這種實現。
%E7%81%AB%E8%BD%A6%E5%A4%B4.png
(4)若是將head和tail都獨立出來,都不存放data,此時的單鏈表如何實現?這樣實現的代碼在插入刪除操做時候是否是更容易點?
%E7%81%AB%E8%BD%A6%E5%A4%B4%E5%B0%BE.png

相關文章
相關標籤/搜索