golang算法练习:单链表/双链表/环形链表

以你之姓@ 2021-12-03 15:05 517阅读 0赞

需求

链表,常见且非常灵活的数据模型,可定制性强,可根据需求调整满足不同的使用需求,如FIFO\LIFO,快速查找等,这里分别列举基础的单向链表和双向链表增删改查操作
备注:需求和运行输出结果均已在代码中注释

单向链表

代码

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. )
  6. type Node struct {
  7. id int
  8. name string
  9. next *Node
  10. }
  11. type SingleLink struct {
  12. head *Node
  13. }
  14. func (singleLink *SingleLink) orderInsert(node *Node) {
  15. // 按Node.id从小到大的顺序,插入链表中
  16. curNode := singleLink.head
  17. if curNode.id == 0 {
  18. // 第一次插入元素的空链表
  19. singleLink.head = node
  20. } else {
  21. // 如果node.id比队首id小,则队首让位后移,要保证队首最小。因为取值比较的时候, 是取curNode.next节点与node进行比较,如果满足大小顺序,
  22. // 则将node插入curNode和curNode.next之间
  23. if curNode.id > node.id {
  24. node.next = curNode
  25. singleLink.head = node
  26. } else {
  27. // 如果node.id不比队首id小,则往后取一位,用后一位与node比较,匹配则插入当前位与后一位中间
  28. for {
  29. if curNode.next == nil {
  30. // 循环到最后一位都没有插入成功,说明node.id比现有链表中的所有node的id都大,则node成为新的队尾
  31. curNode.next = node
  32. break
  33. }
  34. if curNode.next.id > node.id {
  35. node.next = curNode.next
  36. curNode.next = node
  37. break
  38. }
  39. curNode = curNode.next
  40. }
  41. }
  42. }
  43. }
  44. func (singleLink *SingleLink) pop(id int) (node *Node, err error) {
  45. // 弹出链表中指定id的node
  46. curNode := singleLink.head
  47. if curNode.id == id {
  48. // 要取的是队首
  49. singleLink.head = singleLink.head.next
  50. return curNode, nil
  51. }
  52. flag := false // 是否查找到的标志
  53. for {
  54. if curNode.next == nil {
  55. break
  56. }
  57. if curNode.next.id == id {
  58. flag = true
  59. selectNode := curNode.next
  60. curNode.next = curNode.next.next
  61. return selectNode, nil
  62. }
  63. curNode = curNode.next
  64. }
  65. if !flag {
  66. // 如果循环结束还没有查找到,输出信息
  67. return nil, errors.New("node not found")
  68. }
  69. return
  70. }
  71. func (singleLink *SingleLink) getLength() (number int) {
  72. curNode := singleLink.head
  73. if singleLink.head.id == 0 || singleLink.head == nil {
  74. // 空链表
  75. return
  76. } else {
  77. for {
  78. number += 1
  79. curNode = curNode.next
  80. if curNode == nil {
  81. break
  82. }
  83. }
  84. }
  85. fmt.Println("current link length:", number)
  86. return number
  87. }
  88. func (singleLink *SingleLink) list() {
  89. if singleLink.head == nil || singleLink.head.id == 0 {
  90. fmt.Println("link is empty")
  91. } else {
  92. curNode := singleLink.head
  93. for {
  94. if curNode == nil {
  95. break
  96. }
  97. fmt.Print(*curNode, "==>")
  98. curNode = curNode.next
  99. }
  100. }
  101. fmt.Println()
  102. }
  103. func main() {
  104. // 约定空链表的header初始id为0,后面加入的实例id必须大于0
  105. var originalHead = Node{
  106. id: 0,
  107. }
  108. var s = SingleLink{
  109. head: &originalHead,
  110. }
  111. var node1 = Node{
  112. id: 1,
  113. name: "001",
  114. }
  115. var node2 = Node{
  116. id: 2,
  117. name: "002",
  118. }
  119. var node3 = Node{
  120. id: 3,
  121. name: "003",
  122. }
  123. var node4 = Node{
  124. id: 4,
  125. name: "004",
  126. }
  127. var node5 = Node{
  128. id: 5,
  129. name: "005",
  130. }
  131. s.orderInsert(&node4)
  132. s.list()
  133. s.orderInsert(&node2)
  134. s.list()
  135. s.orderInsert(&node3)
  136. s.list()
  137. s.orderInsert(&node5)
  138. s.list()
  139. s.orderInsert(&node1)
  140. s.list()
  141. s.getLength()
  142. popNode, err := s.pop(3)
  143. if err != nil {
  144. fmt.Println(err)
  145. }
  146. fmt.Println("pop node successfully:", *popNode)
  147. s.list()
  148. /* output: {4 004 <nil>}==> {2 002 0xc00000a0e0}==>{4 004 <nil>}==> {2 002 0xc00000a0c0}==>{3 003 0xc00000a0e0}==>{4 004 <nil>}==> {2 002 0xc00000a0c0}==>{3 003 0xc00000a0e0}==>{4 004 0xc00000a100}==>{5 005 <nil>}==> {1 001 0xc00000a0a0}==>{2 002 0xc00000a0c0}==>{3 003 0xc00000a0e0}==>{4 004 0xc00000a100}==>{5 005 <nil>}==> current link length: 5 pop node successfully: {3 003 0xc00000a0e0} {1 001 0xc00000a0a0}==>{2 002 0xc00000a0e0}==>{4 004 0xc00000a100}==>{5 005 <nil>}==> */
  149. }

问题
单向链表的操作必须从首部开始,那么可不可以灵活一些,比如从尾部开始呢?当然可以,改造成双向链表即可满足

双向链表

代码:
为了以示区别,增删改查全部改为从尾部开始循环

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. )
  6. type Node struct {
  7. id int
  8. name string
  9. next *Node
  10. prev *Node
  11. }
  12. type CircleLink struct {
  13. head *Node
  14. tail *Node
  15. }
  16. func (circleLink *CircleLink) orderInsert(node *Node) {
  17. // 按Node.id从小到大的顺序,插入链表中
  18. curNode := circleLink.head
  19. if curNode.id == 0 {
  20. // 第一次插入元素的空链表
  21. circleLink.head = node
  22. circleLink.tail = node
  23. } else {
  24. // 如果node.id比队首id小,则队首让位后移,要保证队首最小。因为取值比较的时候, 是取curNode.next节点与node进行比较,如果满足大小顺序,
  25. // 则将node插入curNode和curNode.next之间
  26. if curNode.id > node.id {
  27. node.next = curNode
  28. curNode.prev = node
  29. circleLink.head = node
  30. } else {
  31. // 如果node.id不比队首id小,则往后取一位,用后一位与node比较,匹配则插入当前位与后一位中间
  32. for {
  33. if curNode.next == nil {
  34. // 循环到最后一位都没有插入成功,说明node.id比现有链表中的所有node的id都大,则node成为新的队尾
  35. curNode.next = node
  36. node.prev = curNode
  37. circleLink.tail = node
  38. break
  39. }
  40. if curNode.next.id > node.id {
  41. node.next = curNode.next
  42. node.prev = curNode
  43. curNode.next.prev = node
  44. curNode.next = node
  45. break
  46. }
  47. curNode = curNode.next
  48. }
  49. }
  50. }
  51. }
  52. func (circleLink *CircleLink) pop(id int) (node *Node, err error) {
  53. // 弹出链表中指定id的node.这次从链表尾部往首部循环
  54. curNode := circleLink.tail
  55. if curNode.id == id {
  56. // 要取的是队尾
  57. circleLink.tail = circleLink.tail.prev
  58. return curNode, nil
  59. }
  60. flag := false // 是否查找到的标志
  61. for {
  62. if curNode.prev == nil {
  63. // 已经从队尾达到队首,全部都没命中
  64. break
  65. }
  66. if curNode.id == id {
  67. flag = true
  68. selectNode := curNode
  69. if curNode == circleLink.head {
  70. // 如果命中的节点正好是队首
  71. if circleLink.head.next != nil {
  72. // 如果队首后面还有节点,则第二个节点置为新的首节点,且第二个节点的prev指向置为nil,这样原head没有被引用,内存会释放出来
  73. circleLink.head.next.prev = nil
  74. circleLink.head = circleLink.head.next
  75. } else {
  76. // 如果队首之后没有节点了,那么直接首位都置空
  77. circleLink.head = nil
  78. circleLink.tail = nil
  79. }
  80. } else {
  81. // 命中的节点在队中间,则取出命中的节点,并维护好其前后指向关系
  82. curNode.prev.next = curNode.next
  83. curNode.next.prev = curNode.prev
  84. }
  85. return selectNode, nil
  86. }
  87. curNode = curNode.prev
  88. }
  89. if !flag {
  90. // 如果循环结束还没有查找到,输出信息
  91. return nil, errors.New("node not found")
  92. }
  93. return
  94. }
  95. func (circleLink *CircleLink) getLength() (number int) {
  96. curNode := circleLink.tail
  97. if circleLink.tail.id == 0 || circleLink.tail == nil {
  98. // 空链表
  99. return
  100. } else {
  101. for {
  102. number += 1
  103. curNode = curNode.prev
  104. if curNode == nil {
  105. break
  106. }
  107. }
  108. }
  109. fmt.Println("current link length:", number)
  110. return number
  111. }
  112. func (circleLink *CircleLink) list() {
  113. if circleLink.tail == nil || circleLink.tail.id == 0 {
  114. fmt.Println("link is empty")
  115. } else {
  116. curNode := circleLink.tail
  117. var nodes []*Node
  118. for {
  119. if curNode == nil {
  120. break
  121. }
  122. //fmt.Print("<==", *curNode)
  123. // 从尾部到首部的循环,为了展示链接效果,将节点先加入一个slice中,稍后从slice的后面开始循环展示数据.此时slice是从尾部到首部排序的
  124. nodes = append(nodes, curNode)
  125. curNode = curNode.prev
  126. }
  127. for i := 0; i < len(nodes); i++ {
  128. index := len(nodes) - i - 1 // 颠倒一下顺序,现在slice中的顺序和链表顺序恰好是相反的,为了打印的效果,让前面的节点先输出
  129. fmt.Print("<==>", nodes[index])
  130. }
  131. }
  132. fmt.Println()
  133. }
  134. func main() {
  135. // 约定空链表的header初始id为0,后面加入的实例id必须大于0
  136. var originalHead = Node{
  137. id: 0,
  138. }
  139. var s = CircleLink{
  140. head: &originalHead,
  141. }
  142. var node1 = Node{
  143. id: 1,
  144. name: "001",
  145. }
  146. var node2 = Node{
  147. id: 2,
  148. name: "002",
  149. }
  150. var node3 = Node{
  151. id: 3,
  152. name: "003",
  153. }
  154. var node4 = Node{
  155. id: 4,
  156. name: "004",
  157. }
  158. var node5 = Node{
  159. id: 5,
  160. name: "005",
  161. }
  162. s.orderInsert(&node4)
  163. s.list()
  164. s.orderInsert(&node2)
  165. s.list()
  166. s.orderInsert(&node3)
  167. s.list()
  168. s.orderInsert(&node5)
  169. s.list()
  170. s.orderInsert(&node1)
  171. s.list()
  172. s.getLength()
  173. popNode, err := s.pop(3)
  174. if err != nil {
  175. fmt.Println(err)
  176. }
  177. fmt.Println("pop node successfully:", *popNode)
  178. s.list()
  179. /* output: <==&{4 004 <nil> <nil>} <==&{2 002 0xc0000761e0 <nil>}<==&{4 004 <nil> 0xc000076180} <==&{2 002 0xc0000761b0 <nil>}<==&{3 003 0xc0000761e0 0xc000076180}<==&{4 004 <nil> 0xc0000761b0} <==&{2 002 0xc0000761b0 <nil>}<==&{3 003 0xc0000761e0 0xc000076180}<==&{4 004 0xc000076210 0xc0000761b0}<==&{5 005 <nil> 0xc0000761e0} <==&{1 001 0xc000076180 <nil>}<==&{2 002 0xc0000761b0 0xc000076150}<==&{3 003 0xc0000761e0 0xc000076180}<==&{4 004 0xc000076210 0xc0000761b0}<==&{5 005 <nil> 0xc0000761e0} current link length: 5 pop node successfully: {3 003 0xc0000761e0 0xc000076180} <==&{1 001 0xc000076180 <nil>}<==&{2 002 0xc0000761e0 0xc000076150}<==&{4 004 0xc000076210 0xc000076180}<==&{5 005 <nil> 0xc0000761e0} */
  180. }

问题
从尾部开始查询的操作复杂度依旧与首部开始一样,为O(n),有没有办法降低复杂度?当然可以,按照自定义链节点串联规则,完全可以使用二分等其他查找的方法降低复杂度,这个后面再补充

环形链表

直接以约瑟夫问题,又称丢手帕游戏举例,此场景非常适用于使用环形链表模型,游戏规则见代码顶部注释
代码:

  1. package main
  2. import (
  3. "fmt"
  4. "math/rand"
  5. )
  6. /* 约瑟夫问题,又叫丢手帕游戏,若干人围成一圈,从头开始,每次递进若干位随机数,然后出列一人,接着再从出列者的下一位开始继续循环, 直到圈内只剩最后一人,游戏结束 */
  7. type Child struct {
  8. Id int
  9. next *Child
  10. }
  11. type Ring struct {
  12. Head *Child
  13. Tail *Child
  14. }
  15. func (ring *Ring) list() {
  16. curChild := ring.Head
  17. for {
  18. fmt.Printf("child %d ==>\t", curChild.Id)
  19. if curChild.next == ring.Head {
  20. break
  21. }
  22. curChild = curChild.next
  23. }
  24. fmt.Println()
  25. }
  26. func (ring *Ring) play() {
  27. // 开始转圈,第一次是从头开始
  28. startIndex := 1
  29. n := 0
  30. startChild := ring.Head
  31. for {
  32. if startChild.next == startChild {
  33. fmt.Printf("游戏结束,圈圈中的最后一位child是%d号\n", startChild.Id)
  34. break
  35. }
  36. n += 1
  37. randInt := rand.Intn(10)
  38. curChild := startChild
  39. for i := 0; i < randInt-1; i++ {
  40. // 在出列child的前一位停下,因为是链表关系,所以需要维护好出列child的前一位的指向关系
  41. curChild = curChild.next
  42. }
  43. chooseChild := curChild.next
  44. if chooseChild == ring.Head {
  45. ring.Head = chooseChild.next
  46. }
  47. curChild.next = chooseChild.next // chooseChild的指针取消,chooseChild会被回收,出列
  48. fmt.Printf("第%d轮,从第%d号开始,前进%d位,出列的是%d号child,game continue!\n", n, startIndex, randInt, chooseChild.Id)
  49. startIndex = (randInt + startIndex + 1) % 20
  50. startChild = chooseChild.next // 下一轮,从chooseChild.next节点开始
  51. }
  52. }
  53. func ringInit(number int) (ring Ring) {
  54. // 构建一个指定成员数量的环形链表
  55. for i := 1; i <= number; i++ {
  56. var child = Child{
  57. Id: i,
  58. }
  59. fmt.Println(child)
  60. if i == 1 {
  61. // 插入第一个节点
  62. ring.Head = &child
  63. ring.Tail = &child
  64. ring.Tail.next = ring.Head // 尾节点下一个指针指向首节点
  65. } else {
  66. // 后面的节点,陆续成为新的尾结点
  67. child.next = ring.Head
  68. ring.Tail.next = &child
  69. ring.Tail = &child
  70. }
  71. }
  72. return
  73. }
  74. func main() {
  75. ring := ringInit(20)
  76. fmt.Println(*ring.Head, *ring.Tail)
  77. ring.list()
  78. ring.play()
  79. }
  80. /* output: 第1轮,从第1号开始,前进1位,出列的是2号child,game continue! 第2轮,从第3号开始,前进7位,出列的是10号child,game continue! 第3轮,从第11号开始,前进7位,出列的是18号child,game continue! 第4轮,从第19号开始,前进9位,出列的是9号child,game continue! 第5轮,从第9号开始,前进1位,出列的是12号child,game continue! 第6轮,从第11号开始,前进8位,出列的是3号child,game continue! 第7轮,从第0号开始,前进5位,出列的是11号child,game continue! 第8轮,从第6号开始,前进0位,出列的是14号child,game continue! 第9轮,从第7号开始,前进6位,出列的是4号child,game continue! 第10轮,从第14号开始,前进0位,出列的是6号child,game continue! 第11轮,从第15号开始,前进4位,出列的是16号child,game continue! 第12轮,从第0号开始,前进1位,出列的是19号child,game continue! 第13轮,从第2号开始,前进2位,出列的是5号child,game continue! 第14轮,从第5号开始,前进9位,出列的是13号child,game continue! 第15轮,从第15号开始,前进8位,出列的是20号child,game continue! 第16轮,从第4号开始,前进4位,出列的是17号child,game continue! 第17轮,从第9号开始,前进1位,出列的是7号child,game continue! 第18轮,从第11号开始,前进5位,出列的是1号child,game continue! 第19轮,从第17号开始,前进7位,出列的是15号child,game continue! 游戏结束,圈圈中的最后一位child是8号 */

发表评论

表情:
评论列表 (有 0 条评论,517人围观)

还没有评论,来说两句吧...

相关阅读

    相关 数据结构--环形

    数据结构-链表-环形链表 环形链表就是将单链表的尾部指向头部,从而形成一个单方向的环形结构,环形链表中每个元素都可以是head,也都可以是尾部,这样就不用担心链表头指针遗

    相关 ----141. 环形

    【题目】给定一个链表,判断链表中是否有环。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来

    相关 环形

    【一】环形单链表 > 单项环形链表解决约瑟夫环 > 1.定义节点 > 2.创建环形链表 > 3.遍历环形链表 > 4.环形链表添加节点 【二】环形单链表的图