双向链表的GO语言实现

1、什么是双向链表node

%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8.png

和单链表比较,双向链表的元素不但知道本身的下线,还知道本身的上线(愈来愈像传销组织了)。小煤车开起来,图里面能够看出,每一个车箱除了一个指向后面车箱的箭头外,还有一个指向前面车箱的箭头(车头、车尾除外)。车头只有指向后面车箱的箭头,车尾只有指向前面车箱的箭头。git

2、双向链表与Go的对应结构github

一、节点函数

%E8%8A%82%E7%82%B9.png

咱们先把车箱分解开来。每节车箱都由煤炭、车体、拉前车箱绳索、拉后车箱绳索这4部分组成。车体是咱们的运输工具,在Go语言里咱们用结构提DNode表示;煤炭表明运的货物,用data变量表示;拉前车箱绳索和拉后车箱绳索咱们分别用指针prev和next表示。这样一节车箱,用Go语言描述以下:工具

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

二、双向链表ui

%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8%E8%A1%A8%E7%A4%BA.png

一个运煤车队就是一个双向链表。车队要有车头、车箱、车尾,做为车队的负责人还得知道车队有多长。在Go语言里,车队用结构体DList表示,车头用head变量表示,车位用tail变量表示,车队长度就用size来表示,把这些表示合起来:spa

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

结构讲完了,下面讲讲如何增长、减小车箱,也就是双向链表的接口。指针

3、接口说明及实现code

%E5%8F%8C%E5%90%91%E9%93%BE%E8%A1%A8%E7%9A%84%E6%8E%A5%E5%8F%A3.png

接口主要分为这几类。一个是双向链表自己的,还有一类是节点的。链表自己的还分为公开和私有两种。下面咱们就详细聊聊这些接口。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
}

这里一样是节点的方法。用来找到上一节车箱。

代码下载

相关文章
相关标签/搜索