go_链表
1、链表
本文讲解了单向链表、双向链表、环形链表以及约瑟夫案例
1.1 链表的介绍
链表是有序的链表,但是它在内存中的存储如下:
1.2 单链表的介绍
一般来说,为了更好的对单链表进行增删改查的操作,我们都会给他设置一个头结点,头结点的作用主要是用来标识链表头,本身这个节点不存放数据。
1.3 单链表的应用示例
-
案例说明
- 使用带head头的单向链表实现 ---- 水浒英雄排行版管理
- 完成对英雄人物的增删改查
-
第一种方法,添加英雄时,直接添加到链表尾部
package main import "fmt" type heroNode struct { no int name string next *heroNode } //增 //1、在链表末尾添加节点 func InsertHeroNode(head *heroNode, newHeroNode *heroNode) { //1、找到该链表的最后节点 //2、创建一个临时节点 temp := head for { if temp.next == nil { break } temp = temp.next } //3、将新节点加到链表的最后 temp.next = newHeroNode } //2、按顺序添加节点到链表 func InsertHeroNode2(head *heroNode, newHeroNode *heroNode) { //1、找到该链表的最后节点 //2、创建一个临时节点 temp := head for { if temp.next == nil { break } temp = temp.next } //3、将新节点加到链表的最后 temp.next = newHeroNode } //删 //改 //显示链表的所有信息 func listHeroNode(head *heroNode) { temp := head if temp.next == nil { fmt.Println("链表为空") return } for { fmt.Printf("[%d %s]====》", temp.next.no, temp.next.name) temp = temp.next //判断是否链表最后节点 if temp.next == nil { break } } } //实现一个单向链表 func main() { //头节点 head := &heroNode{} //新节点 heronode1 := &heroNode{ no: 1, name: "宋江", } heronode2 := &heroNode{ no: 2, name: "卢俊义", } heronode3 := &heroNode{ no: 2, name: "林冲", } //增加节点 InsertHeroNode(head, heronode1) InsertHeroNode(head, heronode2) InsertHeroNode(head, heronode3) //显示节点列表 listHeroNode(head) }
-
第二种根据英雄排名,排序遍历链表节点
//给链表插入一个节点 //2、编写第二种插入方法,根据no的编号从小到大插入【实用】 func InsertHeroNode2(head *heroNode, newHeroNode *heroNode) { //1、找到适当的节点 //2、创建一个临时节点 temp := head flag := true //让插入的节点的no和temp的下一个节点的no比较 for { if temp.next == nil { break } else if newHeroNode.no < temp.next.no { //newHeroNode.no <= temp.next.no (表示no可以重复) //说明neHeroNode就应该插入到temp后面 break } else if temp.next.no == newHeroNode.no { //说明链表中已经有这个no,不允许插入 flag = false break } temp = temp.next } if !flag { fmt.Printf("hero no 已经存在,no=[%d]\n", newHeroNode.no) return } else { newHeroNode.next = temp.next temp.next = newHeroNode } }
4、单链表删除节点
//删出一个节点
func delNode(head *heroNode, no int) {
temp := head
flag := false
//让要删除的节点的no和temp的下一个节点的no比较
for {
if temp.next == nil { //说明找到了链表最后
break
} else if temp.next.no == no {
//说明找到了要删除的节点
flag = true
break
}
temp = temp.next
}
if flag { //找到,删除
temp.next = temp.next.next
} else {
fmt.Println("要删除的id 不存在")
}
}
1.4 双向链表
单向链表的缺点分析:
1、单向链表查找的方向只能是一个方向,而双向链表可以向前或者向后查找
2、单向链表不能自我删除,需要靠辅助节点,而双向链表可以自我删除,所以前面我们单链表删除时节点总是找到temp的下一个节点来删除的
package main
import "fmt"
type HeroNode struct {
no int
name string
pre *HeroNode
next *HeroNode
}
//在双向链表末尾追加一个节点
func insertNode(head *HeroNode, newHeroNOde *HeroNode) {
//1、判断链表是否为空
//创建一个辅助变量
temp := head
for {
if temp.next == nil {
break
}
temp = temp.next //节点后移
}
temp.next = newHeroNOde
newHeroNOde.pre = temp
}
//双向链表,添加的节点,按照no从小到大排序
func insetNode1(head *HeroNode, newHeroNode *HeroNode) {
temp := head
flag := true
for {
if temp.next == nil {
break
} else if temp.next.no > newHeroNode.no {
break
} else if temp.next.no == newHeroNode.no {
flag = false
break
}
temp = temp.next
}
if !flag {
fmt.Println("不允许出现重复的no排名")
return
} else {
newHeroNode.next = temp.next
newHeroNode.pre = temp
if temp.next != nil {
temp.next.pre = newHeroNode
}
temp.next = newHeroNode
}
}
//显示链表的节点信息
func listNode(head *HeroNode) {
temp := head
if temp.next == nil {
fmt.Println("链表为空")
return
}
for {
fmt.Printf("[%d %s]==>", temp.next.no, temp.next.name)
temp = temp.next
if temp.next == nil {
break
}
}
fmt.Println()
}
//验证双向链表,逆序打印
func listNode2(head *HeroNode) {
temp := head
//判断链表是否为空
if temp.next == nil {
fmt.Println("链表为空")
return
}
//让temp定位到双向链表的最后节点
for {
if temp.next == nil {
break
}
temp = temp.next
}
//遍历链表
for {
fmt.Printf("[%d %s]==>", temp.no, temp.name)
temp = temp.pre
//判断是否链表头
if temp.pre == nil {
break
}
}
fmt.Println()
}
//删除节点
func delNode(head *HeroNode, val int) {
temp := head
flag := false
for {
if temp.next == nil {
break
} else if temp.next.no == val {
flag = true
break
}
temp = temp.next
}
if !flag {
fmt.Println("删除的节点不存在")
return
} else {
temp.next = temp.next.next
if temp.next != nil {
temp.next.pre = temp
}
}
}
func main() {
//1、头结点
head := &HeroNode{}
//2、新节点
Node1 := &HeroNode{
no: 1,
name: "宋江",
}
Node2 := &HeroNode{
no: 2,
name: "卢俊义",
}
Node3 := &HeroNode{
no: 3,
name: "林冲",
}
//3、
insertNode(head, Node1)
insertNode(head, Node2)
insertNode(head, Node3)
listNode(head)
listNode2(head)
delNode(head, 2)
listNode(head)
}
1.5 环形单向链表介绍
1.6 环形单向链表案例
完成对单向链表的添加节点,删除节点和显示节点
删除一个环形单向链表的思路:
1、先让temp指向head
2、让helper指向环形链表最后
3、让temp和要删除的id进行比较,如果相同,则同helper完成删除操作【这里必须考虑如果删除的是头结点?】
package main
import "fmt"
type Node struct {
no int
name string
next *Node
}
//增加节点
func addNode(head *Node, newNode *Node) {
if head.next == nil {
head.no = newNode.no
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 listLink(head *Node) {
fmt.Println("环形链表的情况如下:......")
temp := head
if temp.next == nil {
fmt.Println("链表为空")
return
}
for {
fmt.Printf("节点信息:【no=%d,name=%s】", temp.no, temp.name)
if temp.next == head {
break
}
temp = temp.next
}
}
//删除节点
func deleNode(head *Node, id int) *Node {
temp := head
helper := head
if temp.next == nil {
fmt.Println("空链表,不能删除")
return head
}
//只有一个节点的环形链表
if temp.next == head {
head.next = nil
return head
}
//将helper定位到链表最后
for {
if helper.next == head {
break
}
helper = helper.next
}
//如果有两个或以上的节点,要先找到最后的节点
flag := true
for {
if temp.next == head { //到这里说明比较到了最后一个,但是最后一个还没比较
break
}
if temp.no == id {
if temp == head { //说明删除的是头节点
head = head.next
}
//找到要删除的节点
helper.next = temp.next
fmt.Println(id, "被删除")
flag = false
break
}
//temp和helper同步移动
temp = temp.next
helper = helper.next //找到要删除的节点,helper来删除
}
//出来还要判断最后一次
if flag { //如果flag为真,则我们上面没有删除
if temp.no == id { //最后一个节点比较
helper.next = temp.next
} else {
fmt.Println(id, "删除的节点不存在")
}
}
return head
}
func main() {
head := &Node{}
head1 := &Node{
no: 1,
name: "tom",
}
head2 := &Node{
no: 2,
name: "jack",
}
addNode(head, head1)
addNode(head, head2)
listLink(head)
fmt.Println()
deleNode(head, 2)
listLink(head)
}
1.7 约瑟夫小案例
约瑟夫问题是个有名的问题:N个人围成一圈,约定编号为k(1<=k<=N)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列
提示:
用一个不带头结点的循环链表来处理约瑟夫问题:
先构成一个有n个节点的单向循环链表
然后由k节点开始从1开始计数
记到m时,对应节点从链表中删除
然后再从被删除节点的下一个开始计数
直到最后一个节点从链表中删除
算法结束
代码实现:
package main
import "fmt"
type Boy struct {
num int
next *Boy
}
//构建一个单向循环链表
func AddBoy(num int) *Boy {
first := &Boy{}
curBoy := &Boy{}
//判断
if num < 1 {
fmt.Println("num 的值不对")
return first
}
//循环的构建这个环形链表
for i := 0; i <= num; i++ {
boy := &Boy{
num: i,
}
//分析,构成循环链表需要一个辅助指针
//1、因为第一个小孩比较特殊
if i == 1 {
first = boy //不能动
curBoy = boy
curBoy.next = first
} else {
curBoy.next = boy
curBoy = boy
curBoy.next = first //构成环形链表
}
}
return first
}
//显示单向的环形链表
func listBoy(first *Boy) {
if first.next == nil {
fmt.Println("链表为空")
return
}
//创建一个指针,帮助遍历
curBoy := first
for {
fmt.Printf("小孩编号=%d ->", curBoy.num)
//退出的条件 curBoy.next ==first
if curBoy.next == first {
break
}
//curBoy移动到下一个位置
curBoy = curBoy.next
}
}
/*N个人围成一圈,约定编号为k(1<=k<=N)的人从1开始报数,数到m的那个人出列,
它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列*/
//分析思路
//1、编写一个函数,playGame(first *Boy,startNo int,countNum int)
//2、最后我们使用一个算法,按照要求,在环形链表中留下最后一个
func playGame(first *Boy, startNo int, countNum int) {
if first.next == nil {
fmt.Println("链表为空,说明没人")
return
}
//2、定义个辅助指针,帮助删除节点
tail := first
//3、让tail执行环形链表到最后一个节点,这个非常重要
//tail在删除节点的时候会用到
for {
if tail.next == first { //说明tail到了最后的小孩
break
}
tail = tail.next
}
//4、让first移动到startNo【后面删除小孩就以first为准】
for i := 1; i <= startNo; i++ {
first = first.next
tail = tail.next
}
//5、开始数countNum,删除first指向的节点
for {
//开始数count-1次
for i := 1; i < countNum-1; i++ {
first = first.next
tail = tail.next
}
fmt.Printf("删除节点【%d】", first.num)
//删除first指向的节点
first = first.next
tail.next = first
//如果tail ==first,圈中只有一个节点
if tail == first {
break
}
}
fmt.Printf("最后留下的小孩节点为【%d】", first.num)
}
func main() {
first := AddBoy(5)
listBoy(first)
fmt.Println()
playGame(first, 2, 3)
}
参考网站:### 6、链表
6.1 链表的介绍
链表是有序的链表,但是它在内存中的存储如下:
6.2 单链表的介绍
一般来说,为了更好的对单链表进行增删改查的操作,我们都会给他设置一个头结点,头结点的作用主要是用来标识链表头,本身这个节点不存放数据。
6.3 单链表的应用示例
-
案例说明
- 使用带head头的单向链表实现 ---- 水浒英雄排行版管理
- 完成对英雄人物的增删改查
-
第一种方法,添加英雄时,直接添加到链表尾部
package main import "fmt" type heroNode struct { no int name string next *heroNode } //增 //1、在链表末尾添加节点 func InsertHeroNode(head *heroNode, newHeroNode *heroNode) { //1、找到该链表的最后节点 //2、创建一个临时节点 temp := head for { if temp.next == nil { break } temp = temp.next } //3、将新节点加到链表的最后 temp.next = newHeroNode } //2、按顺序添加节点到链表 func InsertHeroNode2(head *heroNode, newHeroNode *heroNode) { //1、找到该链表的最后节点 //2、创建一个临时节点 temp := head for { if temp.next == nil { break } temp = temp.next } //3、将新节点加到链表的最后 temp.next = newHeroNode } //删 //改 //显示链表的所有信息 func listHeroNode(head *heroNode) { temp := head if temp.next == nil { fmt.Println("链表为空") return } for { fmt.Printf("[%d %s]====》", temp.next.no, temp.next.name) temp = temp.next //判断是否链表最后节点 if temp.next == nil { break } } } //实现一个单向链表 func main() { //头节点 head := &heroNode{} //新节点 heronode1 := &heroNode{ no: 1, name: "宋江", } heronode2 := &heroNode{ no: 2, name: "卢俊义", } heronode3 := &heroNode{ no: 2, name: "林冲", } //增加节点 InsertHeroNode(head, heronode1) InsertHeroNode(head, heronode2) InsertHeroNode(head, heronode3) //显示节点列表 listHeroNode(head) }
-
第二种根据英雄排名,排序遍历链表节点
//给链表插入一个节点 //2、编写第二种插入方法,根据no的编号从小到大插入【实用】 func InsertHeroNode2(head *heroNode, newHeroNode *heroNode) { //1、找到适当的节点 //2、创建一个临时节点 temp := head flag := true //让插入的节点的no和temp的下一个节点的no比较 for { if temp.next == nil { break } else if newHeroNode.no < temp.next.no { //newHeroNode.no <= temp.next.no (表示no可以重复) //说明neHeroNode就应该插入到temp后面 break } else if temp.next.no == newHeroNode.no { //说明链表中已经有这个no,不允许插入 flag = false break } temp = temp.next } if !flag { fmt.Printf("hero no 已经存在,no=[%d]\n", newHeroNode.no) return } else { newHeroNode.next = temp.next temp.next = newHeroNode } }
4、单链表删除节点
//删出一个节点
func delNode(head *heroNode, no int) {
temp := head
flag := false
//让要删除的节点的no和temp的下一个节点的no比较
for {
if temp.next == nil { //说明找到了链表最后
break
} else if temp.next.no == no {
//说明找到了要删除的节点
flag = true
break
}
temp = temp.next
}
if flag { //找到,删除
temp.next = temp.next.next
} else {
fmt.Println("要删除的id 不存在")
}
}
6.4 双向链表
单向链表的缺点分析:
1、单向链表查找的方向只能是一个方向,而双向链表可以向前或者向后查找
2、单向链表不能自我删除,需要靠辅助节点,而双向链表可以自我删除,所以前面我们单链表删除时节点总是找到temp的下一个节点来删除的
package main
import "fmt"
type HeroNode struct {
no int
name string
pre *HeroNode
next *HeroNode
}
//在双向链表末尾追加一个节点
func insertNode(head *HeroNode, newHeroNOde *HeroNode) {
//1、判断链表是否为空
//创建一个辅助变量
temp := head
for {
if temp.next == nil {
break
}
temp = temp.next //节点后移
}
temp.next = newHeroNOde
newHeroNOde.pre = temp
}
//双向链表,添加的节点,按照no从小到大排序
func insetNode1(head *HeroNode, newHeroNode *HeroNode) {
temp := head
flag := true
for {
if temp.next == nil {
break
} else if temp.next.no > newHeroNode.no {
break
} else if temp.next.no == newHeroNode.no {
flag = false
break
}
temp = temp.next
}
if !flag {
fmt.Println("不允许出现重复的no排名")
return
} else {
newHeroNode.next = temp.next
newHeroNode.pre = temp
if temp.next != nil {
temp.next.pre = newHeroNode
}
temp.next = newHeroNode
}
}
//显示链表的节点信息
func listNode(head *HeroNode) {
temp := head
if temp.next == nil {
fmt.Println("链表为空")
return
}
for {
fmt.Printf("[%d %s]==>", temp.next.no, temp.next.name)
temp = temp.next
if temp.next == nil {
break
}
}
fmt.Println()
}
//验证双向链表,逆序打印
func listNode2(head *HeroNode) {
temp := head
//判断链表是否为空
if temp.next == nil {
fmt.Println("链表为空")
return
}
//让temp定位到双向链表的最后节点
for {
if temp.next == nil {
break
}
temp = temp.next
}
//遍历链表
for {
fmt.Printf("[%d %s]==>", temp.no, temp.name)
temp = temp.pre
//判断是否链表头
if temp.pre == nil {
break
}
}
fmt.Println()
}
//删除节点
func delNode(head *HeroNode, val int) {
temp := head
flag := false
for {
if temp.next == nil {
break
} else if temp.next.no == val {
flag = true
break
}
temp = temp.next
}
if !flag {
fmt.Println("删除的节点不存在")
return
} else {
temp.next = temp.next.next
if temp.next != nil {
temp.next.pre = temp
}
}
}
func main() {
//1、头结点
head := &HeroNode{}
//2、新节点
Node1 := &HeroNode{
no: 1,
name: "宋江",
}
Node2 := &HeroNode{
no: 2,
name: "卢俊义",
}
Node3 := &HeroNode{
no: 3,
name: "林冲",
}
//3、
insertNode(head, Node1)
insertNode(head, Node2)
insertNode(head, Node3)
listNode(head)
listNode2(head)
delNode(head, 2)
listNode(head)
}
6.5 环形单向链表介绍
6.6 环形单向链表案例
完成对单向链表的添加节点,删除节点和显示节点
删除一个环形单向链表的思路:
1、先让temp指向head
2、让helper指向环形链表最后
3、让temp和要删除的id进行比较,如果相同,则同helper完成删除操作【这里必须考虑如果删除的是头结点?】
package main
import "fmt"
type Node struct {
no int
name string
next *Node
}
//增加节点
func addNode(head *Node, newNode *Node) {
if head.next == nil {
head.no = newNode.no
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 listLink(head *Node) {
fmt.Println("环形链表的情况如下:......")
temp := head
if temp.next == nil {
fmt.Println("链表为空")
return
}
for {
fmt.Printf("节点信息:【no=%d,name=%s】", temp.no, temp.name)
if temp.next == head {
break
}
temp = temp.next
}
}
//删除节点
func deleNode(head *Node, id int) *Node {
temp := head
helper := head
if temp.next == nil {
fmt.Println("空链表,不能删除")
return head
}
//只有一个节点的环形链表
if temp.next == head {
head.next = nil
return head
}
//将helper定位到链表最后
for {
if helper.next == head {
break
}
helper = helper.next
}
//如果有两个或以上的节点,要先找到最后的节点
flag := true
for {
if temp.next == head { //到这里说明比较到了最后一个,但是最后一个还没比较
break
}
if temp.no == id {
if temp == head { //说明删除的是头节点
head = head.next
}
//找到要删除的节点
helper.next = temp.next
fmt.Println(id, "被删除")
flag = false
break
}
//temp和helper同步移动
temp = temp.next
helper = helper.next //找到要删除的节点,helper来删除
}
//出来还要判断最后一次
if flag { //如果flag为真,则我们上面没有删除
if temp.no == id { //最后一个节点比较
helper.next = temp.next
} else {
fmt.Println(id, "删除的节点不存在")
}
}
return head
}
func main() {
head := &Node{}
head1 := &Node{
no: 1,
name: "tom",
}
head2 := &Node{
no: 2,
name: "jack",
}
addNode(head, head1)
addNode(head, head2)
listLink(head)
fmt.Println()
deleNode(head, 2)
listLink(head)
}
6.7 约瑟夫小案例
约瑟夫问题是个有名的问题:N个人围成一圈,约定编号为k(1<=k<=N)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列
提示:
用一个不带头结点的循环链表来处理约瑟夫问题:
先构成一个有n个节点的单向循环链表
然后由k节点开始从1开始计数
记到m时,对应节点从链表中删除
然后再从被删除节点的下一个开始计数
直到最后一个节点从链表中删除
算法结束
代码实现:
package main
import "fmt"
type Boy struct {
num int
next *Boy
}
//构建一个单向循环链表
func AddBoy(num int) *Boy {
first := &Boy{}
curBoy := &Boy{}
//判断
if num < 1 {
fmt.Println("num 的值不对")
return first
}
//循环的构建这个环形链表
for i := 0; i <= num; i++ {
boy := &Boy{
num: i,
}
//分析,构成循环链表需要一个辅助指针
//1、因为第一个小孩比较特殊
if i == 1 {
first = boy //不能动
curBoy = boy
curBoy.next = first
} else {
curBoy.next = boy
curBoy = boy
curBoy.next = first //构成环形链表
}
}
return first
}
//显示单向的环形链表
func listBoy(first *Boy) {
if first.next == nil {
fmt.Println("链表为空")
return
}
//创建一个指针,帮助遍历
curBoy := first
for {
fmt.Printf("小孩编号=%d ->", curBoy.num)
//退出的条件 curBoy.next ==first
if curBoy.next == first {
break
}
//curBoy移动到下一个位置
curBoy = curBoy.next
}
}
/*N个人围成一圈,约定编号为k(1<=k<=N)的人从1开始报数,数到m的那个人出列,
它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列*/
//分析思路
//1、编写一个函数,playGame(first *Boy,startNo int,countNum int)
//2、最后我们使用一个算法,按照要求,在环形链表中留下最后一个
func playGame(first *Boy, startNo int, countNum int) {
if first.next == nil {
fmt.Println("链表为空,说明没人")
return
}
//2、定义个辅助指针,帮助删除节点
tail := first
//3、让tail执行环形链表到最后一个节点,这个非常重要
//tail在删除节点的时候会用到
for {
if tail.next == first { //说明tail到了最后的小孩
break
}
tail = tail.next
}
//4、让first移动到startNo【后面删除小孩就以first为准】
for i := 1; i <= startNo; i++ {
first = first.next
tail = tail.next
}
//5、开始数countNum,删除first指向的节点
for {
//开始数count-1次
for i := 1; i < countNum-1; i++ {
first = first.next
tail = tail.next
}
fmt.Printf("删除节点【%d】", first.num)
//删除first指向的节点
first = first.next
tail.next = first
//如果tail ==first,圈中只有一个节点
if tail == first {
break
}
}
fmt.Printf("最后留下的小孩节点为【%d】", first.num)
}
func main() {
first := AddBoy(5)
listBoy(first)
fmt.Println()
playGame(first, 2, 3)
}