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数据结构与算法—链表的主要内容,如果未能解决你的问题,请参考以下文章