列表

一、列表 List

我们又经常听到 列表 List 数据结构,其实这只是更宏观的统称,表示存放数据的队列。

列表 List:可以存放数据的数据结构,数据按顺序排列,可以依次入队和出队,

列表有序号关系,可以取出某个序号的数据。先进先出的 队列 (queue) 和先进后出的 栈(stack) 都是列表。

大家也经常听说一种叫 线性表 的数据结构,表示具有相同特性的数据元素的有限序列,实际上就是 列表 的同义词。

我们一般写算法进行数据计算,数据处理,都需要有个地方来存数据,我们可以使用封装好的数据结构 List

列表的实现有 顺序表示链式表示

顺序表示:指的是用一组 地址连续的存储单元 依次存储线性表的数据元素,称为线性表的 顺序存储结构。它以 物理位置相邻 来表示线性表中数据元素间的逻辑关系,可随机存取表中任一元素。顺序表示的又叫 顺序表,也就是用数组来实现的列表。

链式表示:指的是用一组 任意的存储单元 存储线性表中的数据元素,称为线性表的 链式存储结构。它的存储单元可以是连续的,也可以是不连续的。在表示数据元素之间的逻辑关系时,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息,也就是用链表来实现的列表。

我们在前面已经实现过这两种表示的数据结构:先进先出的 队列 (queue) 和先进后出的 栈(stack)

接下来我们会来实现链表形式的双端列表,也叫双端队列,这个数据结构应用场景更广泛一点。在实际工程应用上,缓存数据库 Redis列表List 基本类型就是用它来实现的。

二、实现双端列表

双端列表,也可以叫双端队列。

我们会用双向链表来实现这个数据结构:

  1. // DoubleList 双端列表,双端队列
  2. type DoubleList struct {
  3. head *ListNode // 指向链表头部
  4. tail *ListNode // 指向链表尾部
  5. len int // 列表长度
  6. lock sync.Mutex // 为了进行并发安全pop弹出操作
  7. }
  8. // ListNode 列表节点
  9. type ListNode struct {
  10. pre *ListNode // 前驱节点
  11. next *ListNode // 后驱节点
  12. value string // 值
  13. }

设计结构体 DoubleList 指向队列头部 head 和尾部 tail 的指针字段,方便找到链表最前和最后的节点。

链表节点之间是双向链接的,链表的第一个元素的前驱节点为 nil,最后一个元素的后驱节点也为 nil。如图:

列表 - 图1

我们实现的双端列表和 Golang 标准库 container/list 中实现的不一样,感兴趣的可以阅读标准库的实现。

2.1.列表节点普通操作

  1. // GetValue 获取节点值
  2. func (node *ListNode) GetValue() string {
  3. return node.value
  4. }
  5. // GetPre 获取节点前驱节点
  6. func (node *ListNode) GetPre() *ListNode {
  7. return node.pre
  8. }
  9. // GetNext 获取节点后驱节点
  10. func (node *ListNode) GetNext() *ListNode {
  11. return node.next
  12. }
  13. // HashNext 是否存在后驱节点
  14. func (node *ListNode) HashNext() bool {
  15. return node.pre != nil
  16. }
  17. // HashPre 是否存在前驱节点
  18. func (node *ListNode) HashPre() bool {
  19. return node.next != nil
  20. }
  21. // IsNil 是否为空节点
  22. func (node *ListNode) IsNil() bool {
  23. return node == nil
  24. }
  25. // Len 返回列表长度
  26. func (list *DoubleList) Len() int {
  27. return list.len
  28. }

以上是对节点结构体 ListNode 的操作,主要判断节点是否为空,有没有后驱和前驱节点,返回值等,时间复杂度都是 O(1)

2.2.从头部开始某个位置前插入新节点

我们参考数组下标,下标从0开始。从双端列表的头部,插入新的节点。

  1. // AddNodeFromHead 从头部开始,添加节点到第N+1个元素之前,
  2. // N=0表示添加到第一个元素之前,表示新节点成为新的头部,
  3. // N=1表示添加到第二个元素之前,以此类推
  4. func (list *DoubleList) AddNodeFromHead(n int, v string) {
  5. // 加并发锁
  6. list.lock.Lock()
  7. defer list.lock.Unlock()
  8. // 如果索引超过或等于列表长度,一定找不到,直接panic
  9. if n != 0 && n >= list.len {
  10. panic("index out")
  11. }
  12. // 先找出头部
  13. node := list.head
  14. // 往后遍历拿到第 N+1 个位置的元素
  15. for i := 1; i <= n; i++ {
  16. node = node.next
  17. }
  18. // 新节点
  19. newNode := new(ListNode)
  20. newNode.value = v
  21. // 如果定位到的节点为空,表示列表为空,将新节点设置为新头部和新尾部
  22. if node.IsNil() {
  23. list.head = newNode
  24. list.tail = newNode
  25. } else {
  26. // 定位到的节点,它的前驱
  27. pre := node.pre
  28. // 如果定位到的节点前驱为nil,那么定位到的节点为链表头部,需要换头部
  29. if pre.IsNil() {
  30. // 将新节点链接在老头部之前
  31. newNode.next = node
  32. node.pre = newNode
  33. // 新节点成为头部
  34. list.head = newNode
  35. } else {
  36. // 将新节点插入到定位到的节点之前
  37. // 定位到的节点的前驱节点 pre 现在链接到新节点上
  38. pre.next = newNode
  39. newNode.pre = pre
  40. // 定位到的节点的后驱节点 node.next 现在链接到新节点上
  41. node.next.pre = newNode
  42. newNode.next = node.next
  43. }
  44. }
  45. // 列表长度+1
  46. list.len = list.len + 1
  47. }

首先加锁实现并发安全:

  1. // 加并发锁
  2. list.lock.Lock()
  3. defer list.lock.Unlock()

然后判断索引是否超出列表长度,其中 n=0 表示要成为新的头部节点,我们放过它:

  1. // 如果索引超过或等于列表长度,一定找不到,直接panic
  2. if n != 0 && n >= list.len {
  3. panic("index out")
  4. }

如果 n=0 表示要插入到第一个节点之前,也就是新节点想成为新的链表头部。

如果 n=1 表示要插入到第二个节点之前,新节点成为第二个节点,以此类推。

首先,找出列表的头部:

  1. node := list.head

然后往后面遍历,定位到索引 n 指定的节点 node,我们要在这个节点之前插入新的节点:

  1. // 往后遍历拿到第 N+1 个位置的元素
  2. for i := 1; i <= n; i++ {
  3. node = node.next
  4. }

接着初始化新节点:

  1. newNode := new(ListNode)

定位到的节点有三种情况,我们需要在该节点之前插入新节点:

列表 - 图2

第一种情况,判断定位到的节点 node 是否为空,如果为空,表明列表没有元素,将新节点设置为新头部和新尾部即可:

  1. // 如果定位到的节点为空,表示列表为空,将新节点设置为新头部和新尾部
  2. if node.IsNil() {
  3. list.head = newNode
  4. list.tail = newNode
  5. }

否则,我们要插入新的节点到非空的列表上。 我们找到定位到的节点的前驱节点:pre := node.pre,我们要把新节点变成定位到的节点的前驱节点,之前的前驱节点 pre 要往前顺延。

第二种情况,如果前驱节点为空:pre.IsNil(),表明定位到的节点 node 为头部,那么新节点要取代它,成为新的头部:

  1. if pre.IsNil() {
  2. // 将新节点链接在老头部之前
  3. newNode.next = node
  4. node.pre = newNode
  5. // 新节点成为头部
  6. list.head = newNode
  7. }

新节点成为新的头部,需要将新节点的后驱设置为老头部:newNode.next = node,老头部的前驱为新头部:node.pre = newNode,并且新头部变化:list.head = newNode

第三种情况,如果定位到的节点的前驱节点不为空,表明定位到的节点 node 不是头部节点,那么我们只需将新节点链接到节点 node 之前即可:

  1. // 定位到的节点的前驱节点 pre 现在链接到新节点前
  2. pre.next = newNode
  3. newNode.pre = pre
  4. // 定位到的节点链接到新节点之后
  5. newNode.next = node
  6. node.pre = newNode

先将定位到的节点的前驱节点和新节点绑定,因为现在新节点插在前面了,把定位节点的前驱节点的后驱设置为新节点:pre.next = newNode,新节点的前驱设置为定位节点的前驱节点:newNode.pre = pre

同时,定位到的节点现在要链接到新节点之后,所以新节点的后驱设置为:newNode.next = node,定位到的节点的前驱设置为:node.pre = newNode

当然插入新节点的最后,我们要将链表长度加一:

  1. // 列表长度+1
  2. list.len = list.len + 1

大部分时间花在遍历位置上,如果 n=0,那么时间复杂度为 O(1),否则为 O(n)

2.3.从尾部开始某个位置后插入新节点

  1. // AddNodeFromTail 从尾部开始,添加节点到第N+1个元素之后,N=0表示添加到第一个元素之后,表示新节点成为新的尾部,N=1表示添加到第二个元素之后,以此类推
  2. func (list *DoubleList) AddNodeFromTail(n int, v string) {
  3. // 加并发锁
  4. list.lock.Lock()
  5. defer list.lock.Unlock()
  6. // 如果索引超过或等于列表长度,一定找不到,直接panic
  7. if n != 0 && n >= list.len {
  8. panic("index out")
  9. }
  10. // 先找出尾部
  11. node := list.tail
  12. // 往前遍历拿到第 N+1 个位置的元素
  13. for i := 1; i <= n; i++ {
  14. node = node.pre
  15. }
  16. // 新节点
  17. newNode := new(ListNode)
  18. newNode.value = v
  19. // 如果定位到的节点为空,表示列表为空,将新节点设置为新头部和新尾部
  20. if node.IsNil() {
  21. list.head = newNode
  22. list.tail = newNode
  23. } else {
  24. // 定位到的节点,它的后驱
  25. next := node.next
  26. // 如果定位到的节点后驱为nil,那么定位到的节点为链表尾部,需要换尾部
  27. if next.IsNil() {
  28. // 将新节点链接在老尾部之后
  29. node.next = newNode
  30. newNode.pre = node
  31. // 新节点成为尾部
  32. list.tail = newNode
  33. } else {
  34. // 将新节点插入到定位到的节点之后
  35. // 新节点链接到定位到的节点之后
  36. newNode.pre = node
  37. node.next = newNode
  38. // 定位到的节点的后驱节点链接在新节点之后
  39. newNode.next = next
  40. next.pre = newNode
  41. }
  42. }
  43. // 列表长度+1
  44. list.len = list.len + 1
  45. }

操作和头部插入节点相似,自行分析。

2.4.从头部开始某个位置获取列表节点

  1. // IndexFromHead 从头部开始往后找,获取第N+1个位置的节点,索引从0开始。
  2. func (list *DoubleList) IndexFromHead(n int) *ListNode {
  3. // 索引超过或等于列表长度,一定找不到,返回空指针
  4. if n >= list.len {
  5. return nil
  6. }
  7. // 获取头部节点
  8. node := list.head
  9. // 往后遍历拿到第 N+1 个位置的元素
  10. for i := 1; i <= n; i++ {
  11. node = node.next
  12. }
  13. return node
  14. }
  15. `

如果索引超出或等于列表长度,那么找不到节点,返回空。

否则从头部开始遍历,拿到节点。

时间复杂度为:O(n)

2.5.从尾部开始某个位置获取列表节点

  1. // IndexFromTail 从尾部开始往前找,获取第N+1个位置的节点,索引从0开始。
  2. func (list *DoubleList) IndexFromTail(n int) *ListNode {
  3. // 索引超过或等于列表长度,一定找不到,返回空指针
  4. if n >= list.len {
  5. return nil
  6. }
  7. // 获取尾部节点
  8. node := list.tail
  9. // 往前遍历拿到第 N+1 个位置的元素
  10. for i := 1; i <= n; i++ {
  11. node = node.pre
  12. }
  13. return node
  14. }

操作和从头部获取节点一样,请自行分析。

2.6.从头部开始移除并返回某个位置的节点

获取某个位置的元素,并移除它。

  1. // PopFromHead 从头部开始往后找,获取第N+1个位置的节点,并移除返回
  2. func (list *DoubleList) PopFromHead(n int) *ListNode {
  3. // 加并发锁
  4. list.lock.Lock()
  5. defer list.lock.Unlock()
  6. // 索引超过或等于列表长度,一定找不到,返回空指针
  7. if n >= list.len {
  8. return nil
  9. }
  10. // 获取头部
  11. node := list.head
  12. // 往后遍历拿到第 N+1 个位置的元素
  13. for i := 1; i <= n; i++ {
  14. node = node.next
  15. }
  16. // 移除的节点的前驱和后驱
  17. pre := node.pre
  18. next := node.next
  19. // 如果前驱和后驱都为nil,那么移除的节点为链表唯一节点
  20. if pre.IsNil() && next.IsNil() {
  21. list.head = nil
  22. list.tail = nil
  23. } else if pre.IsNil() {
  24. // 表示移除的是头部节点,那么下一个节点成为头节点
  25. list.head = next
  26. next.pre = nil
  27. } else if next.IsNil() {
  28. // 表示移除的是尾部节点,那么上一个节点成为尾节点
  29. list.tail = pre
  30. pre.next = nil
  31. } else {
  32. // 移除的是中间节点
  33. pre.next = next
  34. next.pre = pre
  35. }
  36. // 节点减一
  37. list.len = list.len - 1
  38. return node
  39. }

首先加并发锁实现并发安全:

  1. // 加并发锁
  2. list.lock.Lock()
  3. defer list.lock.Unlock()

先判断索引是否超出列表长度,如果超出直接返回空指针。:

  1. // 索引超过或等于列表长度,一定找不到,返回空指针
  2. if n >= list.len {
  3. return nil
  4. }

获取头部,然后遍历定位到第 N+1 个位置的元素:

  1. // 获取头部
  2. node := list.head
  3. // 往后遍历拿到第 N+1 个位置的元素
  4. for i := 1; i <= n; i++ {
  5. node = node.next
  6. }

获取要移除的节点的前驱和后驱:

  1. // 移除的节点的前驱和后驱
  2. pre := node.pre
  3. next := node.next

定位到的并要移除的节点有三种情况发生,移除的是头部,尾部或者中间节点:

列表 - 图3

我们这么这里:

如果前驱和后驱都为空:

  1. // 如果前驱和后驱都为nil,那么移除的节点为链表唯一节点
  2. if pre.IsNil() && next.IsNil() {
  3. list.head = nil
  4. list.tail = nil
  5. }

那么要移除的节点是链表中唯一的节点,直接将列表头部和尾部置空即可。

如果移除的是头部或尾部节点:

  1. else if pre.IsNil() {
  2. // 表示移除的是头部节点,那么下一个节点成为头节点
  3. list.head = next
  4. next.pre = nil
  5. } else if next.IsNil() {
  6. // 表示移除的是尾部节点,那么上一个节点成为尾节点
  7. list.tail = pre
  8. pre.next = nil
  9. }

当前驱节点为空:pre.IsNil(),表示移除的是头部节点,那么头部节点的下一个节点要成为新的头部:list.head = next,并且这时新的头部前驱要设置为空:next.pre = nil

同理,当后驱节点为空:next.IsNil(),表示移除的是尾部节点,需要将尾部节点的前一个节点设置为新的尾部:list.tail = pre,并且这时新的尾部后驱要设置为空:pre.next = nil

如果移除的节点处于两个节点之间,那么将这两个节点链接起来即可:

  1. // 移除的是中间节点
  2. pre.next = next
  3. next.pre = pre

当然,最后列表长度减一:

  1. // 节点减一
  2. list.len = list.len - 1

主要的耗时用在定位节点上,其他的操作都是链表链接,可以知道时间复杂度为:O(n)

2.7.从尾部开始移除并返回某个位置的节点

  1. // PopFromTail 从尾部开始往前找,获取第N+1个位置的节点,并移除返回
  2. func (list *DoubleList) PopFromTail(n int) *ListNode {
  3. // 加并发锁
  4. list.lock.Lock()
  5. defer list.lock.Unlock()
  6. // 索引超过或等于列表长度,一定找不到,返回空指针
  7. if n >= list.len {
  8. return nil
  9. }
  10. // 获取尾部
  11. node := list.tail
  12. // 往前遍历拿到第 N+1 个位置的元素
  13. for i := 1; i <= n; i++ {
  14. node = node.pre
  15. }
  16. // 移除的节点的前驱和后驱
  17. pre := node.pre
  18. next := node.next
  19. // 如果前驱和后驱都为nil,那么移除的节点为链表唯一节点
  20. if pre.IsNil() && next.IsNil() {
  21. list.head = nil
  22. list.tail = nil
  23. } else if pre.IsNil() {
  24. // 表示移除的是头部节点,那么下一个节点成为头节点
  25. list.head = next
  26. next.pre = nil
  27. } else if next.IsNil() {
  28. // 表示移除的是尾部节点,那么上一个节点成为尾节点
  29. list.tail = pre
  30. pre.next = nil
  31. } else {
  32. // 移除的是中间节点
  33. pre.next = next
  34. next.pre = pre
  35. }
  36. // 节点减一
  37. list.len = list.len - 1
  38. return node
  39. }

操作和从头部移除节点相似,请自行分析。

2.8.完整例子

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. )
  6. // DoubleList 双端列表,双端队列
  7. type DoubleList struct {
  8. head *ListNode // 指向链表头部
  9. tail *ListNode // 指向链表尾部
  10. len int // 列表长度
  11. lock sync.Mutex // 为了进行并发安全pop弹出操作
  12. }
  13. // ListNode 列表节点
  14. type ListNode struct {
  15. pre *ListNode // 前驱节点
  16. next *ListNode // 后驱节点
  17. value string // 值
  18. }
  19. // GetValue 获取节点值
  20. func (node *ListNode) GetValue() string {
  21. return node.value
  22. }
  23. // GetPre 获取节点前驱节点
  24. func (node *ListNode) GetPre() *ListNode {
  25. return node.pre
  26. }
  27. // GetNext 获取节点后驱节点
  28. func (node *ListNode) GetNext() *ListNode {
  29. return node.next
  30. }
  31. // HashNext 是否存在后驱节点
  32. func (node *ListNode) HashNext() bool {
  33. return node.pre != nil
  34. }
  35. // HashPre 是否存在前驱节点
  36. func (node *ListNode) HashPre() bool {
  37. return node.next != nil
  38. }
  39. // IsNil 是否为空节点
  40. func (node *ListNode) IsNil() bool {
  41. return node == nil
  42. }
  43. // Len 返回列表长度
  44. func (list *DoubleList) Len() int {
  45. return list.len
  46. }
  47. // AddNodeFromHead 从头部开始,添加节点到第N+1个元素之前,N=0表示添加到第一个元素之前,表示新节点成为新的头部,N=1表示添加到第二个元素之前,以此类推
  48. func (list *DoubleList) AddNodeFromHead(n int, v string) {
  49. // 加并发锁
  50. list.lock.Lock()
  51. defer list.lock.Unlock()
  52. // 如果索引超过或等于列表长度,一定找不到,直接panic
  53. if n != 0 && n >= list.len {
  54. panic("index out")
  55. }
  56. // 先找出头部
  57. node := list.head
  58. // 往后遍历拿到第 N+1 个位置的元素
  59. for i := 1; i <= n; i++ {
  60. node = node.next
  61. }
  62. // 新节点
  63. newNode := new(ListNode)
  64. newNode.value = v
  65. // 如果定位到的节点为空,表示列表为空,将新节点设置为新头部和新尾部
  66. if node.IsNil() {
  67. list.head = newNode
  68. list.tail = newNode
  69. } else {
  70. // 定位到的节点,它的前驱
  71. pre := node.pre
  72. // 如果定位到的节点前驱为nil,那么定位到的节点为链表头部,需要换头部
  73. if pre.IsNil() {
  74. // 将新节点链接在老头部之前
  75. newNode.next = node
  76. node.pre = newNode
  77. // 新节点成为头部
  78. list.head = newNode
  79. } else {
  80. // 将新节点插入到定位到的节点之前
  81. // 定位到的节点的前驱节点 pre 现在链接到新节点前
  82. pre.next = newNode
  83. newNode.pre = pre
  84. // 定位到的节点链接到新节点之后
  85. newNode.next = node
  86. node.pre = newNode
  87. }
  88. }
  89. // 列表长度+1
  90. list.len = list.len + 1
  91. }
  92. // AddNodeFromTail 从尾部开始,添加节点到第N+1个元素之后,N=0表示添加到第一个元素之后,表示新节点成为新的尾部,N=1表示添加到第二个元素之后,以此类推
  93. func (list *DoubleList) AddNodeFromTail(n int, v string) {
  94. // 加并发锁
  95. list.lock.Lock()
  96. defer list.lock.Unlock()
  97. // 如果索引超过或等于列表长度,一定找不到,直接panic
  98. if n != 0 && n >= list.len {
  99. panic("index out")
  100. }
  101. // 先找出尾部
  102. node := list.tail
  103. // 往前遍历拿到第 N+1 个位置的元素
  104. for i := 1; i <= n; i++ {
  105. node = node.pre
  106. }
  107. // 新节点
  108. newNode := new(ListNode)
  109. newNode.value = v
  110. // 如果定位到的节点为空,表示列表为空,将新节点设置为新头部和新尾部
  111. if node.IsNil() {
  112. list.head = newNode
  113. list.tail = newNode
  114. } else {
  115. // 定位到的节点,它的后驱
  116. next := node.next
  117. // 如果定位到的节点后驱为nil,那么定位到的节点为链表尾部,需要换尾部
  118. if next.IsNil() {
  119. // 将新节点链接在老尾部之后
  120. node.next = newNode
  121. newNode.pre = node
  122. // 新节点成为尾部
  123. list.tail = newNode
  124. } else {
  125. // 将新节点插入到定位到的节点之后
  126. // 新节点链接到定位到的节点之后
  127. newNode.pre = node
  128. node.next = newNode
  129. // 定位到的节点的后驱节点链接在新节点之后
  130. newNode.next = next
  131. next.pre = newNode
  132. }
  133. }
  134. // 列表长度+1
  135. list.len = list.len + 1
  136. }
  137. // First 返回列表链表头结点
  138. func (list *DoubleList) First() *ListNode {
  139. return list.head
  140. }
  141. // Last 返回列表链表尾结点
  142. func (list *DoubleList) Last() *ListNode {
  143. return list.tail
  144. }
  145. // IndexFromHead 从头部开始往后找,获取第N+1个位置的节点,索引从0开始。
  146. func (list *DoubleList) IndexFromHead(n int) *ListNode {
  147. // 索引超过或等于列表长度,一定找不到,返回空指针
  148. if n >= list.len {
  149. return nil
  150. }
  151. // 获取头部节点
  152. node := list.head
  153. // 往后遍历拿到第 N+1 个位置的元素
  154. for i := 1; i <= n; i++ {
  155. node = node.next
  156. }
  157. return node
  158. }
  159. // IndexFromTail 从尾部开始往前找,获取第N+1个位置的节点,索引从0开始。
  160. func (list *DoubleList) IndexFromTail(n int) *ListNode {
  161. // 索引超过或等于列表长度,一定找不到,返回空指针
  162. if n >= list.len {
  163. return nil
  164. }
  165. // 获取尾部节点
  166. node := list.tail
  167. // 往前遍历拿到第 N+1 个位置的元素
  168. for i := 1; i <= n; i++ {
  169. node = node.pre
  170. }
  171. return node
  172. }
  173. // PopFromHead 从头部开始往后找,获取第N+1个位置的节点,并移除返回
  174. func (list *DoubleList) PopFromHead(n int) *ListNode {
  175. // 加并发锁
  176. list.lock.Lock()
  177. defer list.lock.Unlock()
  178. // 索引超过或等于列表长度,一定找不到,返回空指针
  179. if n >= list.len {
  180. return nil
  181. }
  182. // 获取头部
  183. node := list.head
  184. // 往后遍历拿到第 N+1 个位置的元素
  185. for i := 1; i <= n; i++ {
  186. node = node.next
  187. }
  188. // 移除的节点的前驱和后驱
  189. pre := node.pre
  190. next := node.next
  191. // 如果前驱和后驱都为nil,那么移除的节点为链表唯一节点
  192. if pre.IsNil() && next.IsNil() {
  193. list.head = nil
  194. list.tail = nil
  195. } else if pre.IsNil() {
  196. // 表示移除的是头部节点,那么下一个节点成为头节点
  197. list.head = next
  198. next.pre = nil
  199. } else if next.IsNil() {
  200. // 表示移除的是尾部节点,那么上一个节点成为尾节点
  201. list.tail = pre
  202. pre.next = nil
  203. } else {
  204. // 移除的是中间节点
  205. pre.next = next
  206. next.pre = pre
  207. }
  208. // 节点减一
  209. list.len = list.len - 1
  210. return node
  211. }
  212. // PopFromTail 从尾部开始往前找,获取第N+1个位置的节点,并移除返回
  213. func (list *DoubleList) PopFromTail(n int) *ListNode {
  214. // 加并发锁
  215. list.lock.Lock()
  216. defer list.lock.Unlock()
  217. // 索引超过或等于列表长度,一定找不到,返回空指针
  218. if n >= list.len {
  219. return nil
  220. }
  221. // 获取尾部
  222. node := list.tail
  223. // 往前遍历拿到第 N+1 个位置的元素
  224. for i := 1; i <= n; i++ {
  225. node = node.pre
  226. }
  227. // 移除的节点的前驱和后驱
  228. pre := node.pre
  229. next := node.next
  230. // 如果前驱和后驱都为nil,那么移除的节点为链表唯一节点
  231. if pre.IsNil() && next.IsNil() {
  232. list.head = nil
  233. list.tail = nil
  234. } else if pre.IsNil() {
  235. // 表示移除的是头部节点,那么下一个节点成为头节点
  236. list.head = next
  237. next.pre = nil
  238. } else if next.IsNil() {
  239. // 表示移除的是尾部节点,那么上一个节点成为尾节点
  240. list.tail = pre
  241. pre.next = nil
  242. } else {
  243. // 移除的是中间节点
  244. pre.next = next
  245. next.pre = pre
  246. }
  247. // 节点减一
  248. list.len = list.len - 1
  249. return node
  250. }
  251. func main() {
  252. list := new(DoubleList)
  253. // 在列表头部插入新元素
  254. list.AddNodeFromHead(0, "I")
  255. list.AddNodeFromHead(0, "love")
  256. list.AddNodeFromHead(0, "you")
  257. // 在列表尾部插入新元素
  258. list.AddNodeFromTail(0, "may")
  259. list.AddNodeFromTail(0, "happy")
  260. list.AddNodeFromTail(list.Len()-1, "begin second")
  261. list.AddNodeFromHead(list.Len()-1, "end second")
  262. // 正常遍历,比较慢,因为内部会遍历拿到值返回
  263. for i := 0; i < list.Len(); i++ {
  264. // 从头部开始索引
  265. node := list.IndexFromHead(i)
  266. // 节点为空不可能,因为list.Len()使得索引不会越界
  267. if !node.IsNil() {
  268. fmt.Println(node.GetValue())
  269. }
  270. }
  271. fmt.Println("----------")
  272. // 正常遍历,特别快,因为直接拿到的链表节点
  273. // 先取出第一个元素
  274. first := list.First()
  275. for !first.IsNil() {
  276. // 如果非空就一直遍历
  277. fmt.Println(first.GetValue())
  278. // 接着下一个节点
  279. first = first.GetNext()
  280. }
  281. fmt.Println("----------")
  282. // 元素一个个 POP 出来
  283. for {
  284. node := list.PopFromHead(0)
  285. if node.IsNil() {
  286. // 没有元素了,直接返回
  287. break
  288. }
  289. fmt.Println(node.GetValue())
  290. }
  291. fmt.Println("----------")
  292. fmt.Println("len", list.Len())
  293. }

输出:

  1. you
  2. begin second
  3. love
  4. I
  5. may
  6. end second
  7. happy
  8. ----------
  9. you
  10. begin second
  11. love
  12. I
  13. may
  14. end second
  15. happy
  16. ----------
  17. you
  18. begin second
  19. love
  20. I
  21. may
  22. end second
  23. happy
  24. ----------
  25. len 0

首先,先从列表头部插入三个新元素,然后从尾部插入两个新元素,再往头部和尾部插入两个新元素:

  1. list := new(DoubleList)
  2. // 在列表头部插入新元素
  3. list.AddNodeFromHead(0, "I")
  4. list.AddNodeFromHead(0, "love")
  5. list.AddNodeFromHead(0, "you")
  6. // 在列表尾部插入新元素
  7. list.AddNodeFromTail(0, "may")
  8. list.AddNodeFromTail(0, "happy")
  9. list.AddNodeFromTail(list.Len()-1, "begin second")
  10. list.AddNodeFromHead(list.Len()-1, "end second")

最后用三种方式进行遍历,前两种仅仅是查看元素,后一种是 PopFromHead 遍历后移除元素。

附录

代码下载: https://github.com/hunterhug/goa.c/blob/master/code/double_list/double_list.go