Go数据结构与算法—链表
Posted 小圣.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go数据结构与算法—链表相关的知识,希望对你有一定的参考价值。
介绍
链表是一种物理存储单元上的非连续、非顺序的存储结构。链表由一系列节点(链表中的每一个元素)组成。节点可以在运行时动态生成。每个节点包括两个部分:存储数据元素的数据域、存储下一个节点的指针域。 相比于线性表的顺序结构,链表操作复杂。但是由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
单链表
type HeroNode struct
Id int
Name string
NickName string
Next *HeroNode
// 单链表的插入
func Insert(head *HeroNode, newNode *HeroNode)
temp := head
for
if temp.Next == nil
break
temp = temp.Next
temp.Next = newNode
// 按顺序插入
func InsertByOrder(head *HeroNode, newNode *HeroNode)
temp := head
flag := true
for
// 已到链表的最后
if temp.Next == nil
break
else if temp.Next.Id > newNode.Id // 已经找到第一个比当前ID大的节点
break
else if temp.Next.Id == newNode.Id // 已存在相同ID的节点
flag = false
break
temp = temp.Next
if flag
newNode.Next = temp.Next
temp.Next = newNode
else
fmt.Println("已存在相同的节点")
return
// 单链表的删除
func Delete(head *HeroNode, id int)
temp := head
for
if temp.Next.Id == id
break
temp = temp.Next
temp.Next = temp.Next.Next
// 显示所有的节点信息
func List(head *HeroNode)
temp := head
for
if temp.Next == nil
break
temp = temp.Next
fmt.Printf("[%d, %s, %s]\\n", temp.Id, temp.Name, temp.NickName)
// 测试
func main()
head := &HeroNode
hero1 := &HeroNode
Id: 1,
Name: "宋江",
NickName: "及时雨",
hero2 := &HeroNode
Id: 2,
Name: "卢俊义",
NickName: "玉麒麟",
hero3 := &HeroNode
Id: 3,
Name: "林冲",
NickName: "豹子头",
// Insert(head, hero1)
// Insert(head, hero3)
// Insert(head, hero2)
InsertByOrder(head, hero1)
InsertByOrder(head, hero3)
InsertByOrder(head, hero2)
List(head)
Delete(head, 1)
List(head)
循环链表
对于单链表,由于每个节点只存储了向后节点的指针,到了尾标志就停止了向后链的操作,这样,其中的节点无法找到它的前驱节点了。
将单链表中的尾节点的指针指向头节点,就可以使整个单链表形成一个环。这种头尾相接的单链表称为单循环链表(circle linked list)
type CatNode struct
Id int
Name string
Next *CatNode
func Insert(head *CatNode, newNode *CatNode)
// 第一次添加
if head.Next == nil
head.Id = newNode.Id
head.Name = newNode.Name
head.Next = head
return
temp := head
// 找到最后一个节点
for
if temp.Next == head
break
temp = temp.Next
// 加入到链表中
temp.Next = newNode
newNode.Next = head
// 遍历环形链表
func List(head *CatNode)
temp := head
if temp.Next == nil
fmt.Println("the linked is empty")
return
for
fmt.Println("猫的名称:", temp.Name)
if temp.Next == head
break
temp = temp.Next
// 删除
func Delete(head *CatNode, id int) *CatNode
temp := head
helper := head
if temp.Next == nil
fmt.Println("the linked is empty")
return head
// 如果链表只有一个节点
if temp.Next == head
if temp.Id == id
temp.Next = nil
return head
// 把helper放到链表的最后
for
if helper.Next == head
break
helper = helper.Next
// 删除指定的节点
flag := true
for
if temp.Next == head // 最后一个节点,还没找到
break
if temp.Id == id // 找到了指定的节点,进行删除
if temp == head // 删除的是头节点
head = head.Next
helper.Next = temp.Next
fmt.Println("被删除的小猫:", id)
flag = false
break
temp = temp.Next
helper = helper.Next
if flag
if temp.Id == id
helper.Next = temp.Next
fmt.Println("被删除的小猫:", id)
else
fmt.Println("没有这个猫")
return head
双链表
在单链表中,每个节点只有一个指向后继节点的链。若要查找前驱节点,则必须从单链表的头指针开始沿着链表方向逐个检测,操作效率低,这时候就需要采用双链表了。
双链表(doubly linked list)是每个节点有两个地址域的线性链表,两个地址域分别指向前驱节点和后继结点。
type HeroNode struct
Id int
Name string
NickName string
Prev *HeroNode
Next *HeroNode
// 插入
func Insert(head *HeroNode, newNode *HeroNode)
temp := head
for
if temp.Next == nil
break
temp = temp.Next
temp.Next = newNode
newNode.Prev = temp
// 按照id的大小进行顺序插入
func InsertByOrder(head *HeroNode, newNode *HeroNode)
temp := head
flag := true
for
if temp.Next == nil
break
else if temp.Next.Id > newNode.Id
break
else if temp.Next.Id == newNode.Id
flag = false
break
temp = temp.Next
if flag
newNode.Next = temp.Next
newNode.Prev = temp
if temp.Next != nil
temp.Next.Prev = newNode
temp.Next = newNode
else
fmt.Println("链表已存在该节点")
return
// 删除
func Delete(head *HeroNode, id int)
temp := head
for
if temp.Next.Id == id
break
temp = temp.Next
temp.Next = temp.Next.Next
// 当要删除的的节点不是最后一个时进行下面的处理
if temp.Next != nil
temp.Next.Prev = temp
// 显示所有的节点
func List(head *HeroNode)
temp := head
for
if temp.Next == nil
break
temp = temp.Next
fmt.Printf("[%d, %s, %s]\\n", temp.Id, temp.Name, temp.NickName)
Josephus问题解决
问题描述:设标号为1,2…, n的n个小孩围坐在一圈,约定编号为k(1<=k<=n)的小孩从1开始报数,数到m的小孩出列,它的下一位又开始从1开始报数,数到m的小孩又出列,以此类推,直到所有小孩出列为止,求出由此产生一个出队编号的序列。
// 小孩的结构体
type Boy struct
No int
next *Boy
// num:表示小孩的个数
// *Boy:返回该环形链表的第一个小孩的指针
func AddBoy(num int) *Boy
first := &Boy
currentBoy := &Boy // 辅助指针
if num < 1
fmt.Println("number is uncorrect")
return first
// 循环构建链表
for i := 1; i <= num; i++
boy := &Boy
No: i,
// 第一个小孩
if i == 1
// 第一个小孩的头指针不能动,否则后面的节点就无法找到第一个小孩的位置
first = boy
currentBoy = boy
currentBoy.next = first // 形成循环
else
currentBoy.next = boy
currentBoy = boy
currentBoy.next = first
return first
// 显示单向的环形链表
func List(first *Boy)
// 处理空的环形链表
if first.next == nil
fmt.Println("链表为空")
return
currentBoy := first
for
fmt.Printf("小孩编号:%d\\n", currentBoy.No)
// 退出条件
if currentBoy.next == first
break
currentBoy = currentBoy.next
// 按照要求,在环形链表中留下最后一个人
func palyGame(first *Boy, startNo int, countNum int)
// 空链表处理
if first.next == nil
fmt.Println("null")
return
// 辅助指针,帮助删除小孩
tail := first
// 让tail指向环形链表的最后一个节点`
for
// 到了最后一个小孩了
if tail.next == first
break
tail = tail.next
// 让first移动到startNo
for i := 0; i < startNo-1; i++
first = first.next
tail = tail.next
// 开始数countNum下,然后删除first指向的小孩
for
// 开始数countNUm-1下,自己也要数
for i := 0; i < countNum-1; i++
first = first.next
tail = tail.next
fmt.Println("出圈小孩为:", first.No)
// 删除first指向的节点
first = first.next
tail.next = first
// 剩下最后一个小孩不删除
if tail == first
break
fmt.Printf("%d小孩编号\\n", first.No)
// 测试
func main()
first := AddBoy(5)
palyGame(first, 2, 3)
最后
完整代码在这里:https://github.com/bigzoro/go_algorithm/tree/main/linkedList
以上是关于Go数据结构与算法—链表的主要内容,如果未能解决你的问题,请参考以下文章