Go 中 List 的实现方式

Posted Golang语言社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go 中 List 的实现方式相关的知识,希望对你有一定的参考价值。

为了快速回顾Go基本的语法知识,打算用Go中的基本语法以及特性来实现一些常见的数据结构和排序算法,通过分析如何实现一些基本的数据结构,可以很快学习Go的语法特性。记忆更加深刻,掌握更加迅速。这是我认为学习一门新语言入门最好的方式。这也是方便自己以后需要用Go来写东西的一种前期准备,到时候就不用去翻一些教程了。系列博文的第一篇就从如何实现List开始。

需求

大家都知道基本链表得有以下特性:链表的初始化、链表的长度、节点的插入、删除、查找等一些常见的基本操作,最后写好之后,需要测试。关于测试,我之前写过Go的系列笔记中有叙述,不再重复。

实现初始化

  1. type Node struct {

  2.         Value      interface{} 

  3.         next, prev *Node       

  4. }

复制代码

下面就是定义List结构体了,有了上面的分析,List结构体的定义就很好实现了:

  1. type List struct {

  2.         root   Node // 头节点

  3.         length int  // list长度

  4. }

复制代码

那么在构建好基本的数据结构之后,如何去获取一个List对象呢。先不着急实现,想想在Java语言中怎么实现的:

  1. Person p  = new Man();

复制代码

  1. // 返回List的指针

  2. func New() *List {

  3.         l.length = 0// list初始长度为0

  4.         l.root.next = &l.root

  5.         l.root.prev = &l.root

  6.         return l

  7. }

复制代码

判空和长度

  1. func (l *List) IsEmpty() bool {

  2.         return l.root.next == &l.root

  3. }

复制代码

分析完毕之后,获取list的长度就简单很多了:

  1. func (l *List) Length() int {

  2.         return l.length

  3. }

复制代码

头插和尾插

因为在定义List数据结构的时候,就定义了一个root头节点。所以此时,可以很方便的实现头插入和尾插入。考虑能够同时插入多个/一个Node节点,利用Go中的变长参数实现该特性。对插入的Node节点进行循环处理,新节点的指针域和root节点的指针域做相应改变,具体实现方式以及说明在代码中说明:

  1. func (l *List) PushFront(elements ...interface{}) {

  2.         for _, element := range elements {

  3.                 n := &Node{Value: element} // 注释一

  4.                 n.next = l.root.next // 新节点的next是root节点的next

  5.                 l.root.next.prev = n // 原来root节点的next的prev是新节点

  6.                 l.root.next = n // 头插法 root 之后始终是新节点

  7.                 l.length++ // list 长度加1

  8.         }

  9. }

复制代码

  1. func (l *List) PushBack(elements ...interface{}) {

  2.         for _, element := range elements {

  3.                 n := &Node{Value: element}

  4.                 n.next = &l.root     // since n is the last element, its next should be the head

  5.                 n.prev = l.root.prev // n's prev should be the tail

  6.                 l.root.prev.next = n // tail's next should be n

  7.                 l.root.prev = n      // head's prev should be n

  8.                 l.length++

  9.         }

  10. }

复制代码

查找

查找最终的效果是返回指定数值的索引,如果不存在的话返回-1即可。对于链表的查找是一个遍历的过程,在此时就需要考虑遍历的起始和终止区间了,不能越界出错。因为是循环链表,终止节点也很好办。具体代码如下所示:

  1. func (l *List) Find(element interface{}) int {

  2.         index := 0

  3.         p := l.root.next

  4.         for p != &l.root && p.Value != element {

  5.                 p = p.next

  6.                 index++

  7.         }

  8.         // p不是root

  9.         if p != &l.root {

  10.                 return index

  11.         }


  12.         return -1

  13. }

复制代码

删除

链表的删除操作逻辑很清晰,将一个Node的节点与前后节点断开即可,同时前后节点和Node节点本身指针域也要做相应修改,最后别忘记将链表的长度减少相应长度。

  1. func (l *List) remove(n *Node) {

  2.         n.prev.next = n.next

  3.         n.next.prev = n.prev

  4.         n.next = nil

  5.         n.prev = nil

  6.         l.length--

  7. }

复制代码

删除并返回List中的第一个数据:

  1. func (l *List) Lpop() interface{} {

  2.         if l.length == 0 {

  3.         // null的表现形式nil

  4.                 return nil

  5.         }

  6.         n := l.root.next

  7.         l.remove(n)

  8.         return n.Value

  9. }

复制代码

遍历

下面normalIndex函数的作用返回一个正常逻辑的Index,例如处理好一些越界问题:

  1. func (l *List) normalIndex(index int) int {

  2.         if index > l.length-1 {

  3.                 index = l.length - 1

  4.         }


  5.         if index < -l.length {

  6.                 index = 0

  7.         }

  8.         // 将给定的index与length做取余处理

  9.         index = (l.length + index) % l.length

  10.         return index

  11. }

复制代码

如下的函数为获取指定范围内的数据,根据传入的参数需要指定start和end,最后返回的应该是一个切片或者数组,具体类型未知:

  1. func (l *List) Range(start, end int) []interface{} {

  2.         // 获取正常的start和end

  3.         start = l.normalIndex(start)

  4.         end = l.normalIndex(end)

  5.    // 声明一个interface类型的数组

  6.         res := []interface{}{}

  7.           // 如果上下界不符合逻辑,返回空res

  8.         if start > end {

  9.                 return res

  10.         }

  11.     

  12.         sNode := l.index(start)

  13.         eNode := l.index(end)

  14.     // 起始点和重点遍历

  15.         for n := sNode; n != eNode; {

  16.               // res的append方式

  17.                 res = append(res, n.Value)

  18.                 n = n.next

  19.         }

  20.         res = append(res, eNode.Value)

  21.         return res

  22. }

复制代码

ok,以上即为Go中List的数据结构的实现方式,通过本节,能够学习到许多Go的语法特性。个人认为学习编程,语法是最简单的,应该利用最短的时间在,最有效的掌握。



本文来自:allenwu.itscoder.com

查看原文:Go 中 List 的实现方式



以上是关于Go 中 List 的实现方式的主要内容,如果未能解决你的问题,请参考以下文章

3.8 Go语言中函数可变参数(Variadic Parameter)

Go语言容器—list

Go语言容器—list

go语言学习-接口

go 接口

3.8 Go语言中函数可变参数(Variadic Parameter)