第二课 golang数据结构和算法-链表

第二课 golang数据结构和算法-链表

tags:

  • golang
  • 2019尚硅谷

categories:

  • golang
  • 链表
  • 约瑟夫问题

第一节 链表

1.1 链表介绍

  1. 链表是有序的列表,但是它在内存中是存储(不连续)如下:
    在这里插入图片描述

1.2 单向链表

  1. 单链表的示意图:
  2. 说明:一般来说,为了比较好的对单链表进行增删改查的操作,我们都会给他设置一个头结点,头结点的作用主要是用来标识链表头,本身这个结点不存放数据
    在这里插入图片描述

1.3 单向链表的应用实例

  1. 使用带head头的单向链表实现–水浒英雄排行榜管理 案例的说明:
    • 完成对英雄人物的增删改查操作,删除和修改,查找
    • 第一种方法在添加英雄时,直接添加到链表的尾部
    • 第二种方式在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败并给出提示。
  2. 第一种方法代码实现:
package main
import (
	"fmt"
)

// 定义一个HeroNode
type HeroNode struct{
	no int // 编号
	name string // 名称
	nickname string // 花名
	next *HeroNode  //这个表示指向下一个节点
}

// 给链表插入一个节点
// 编写第一种插入方法, 在单链表的最后加入
func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode){
	// 思路 1. 先找到该链表的最后这个节点
	// 2. 创建一个辅助节点 【用来帮忙的】
	temp := head
	for {
		if temp.next == nil{ //表示找到最后了
			break
		}
		temp = temp.next // 让temp不断的指向下一个节点
	}
	// 3. 加入到链表的最后 (现在temp已经位于链表末尾了)
	temp.next = newHeroNode
}
// 显示链表的所有节点信息
func ListHeroNode(head *HeroNode){
	// 1. 创建一个辅助节点 来帮忙 头节点不要动
	temp := head
	if temp.next == nil{
		fmt.Println("链表为空链表!")
		return 
	}

	// 2. 至少有一个节点 遍历这个链表
	for {
		fmt.Printf("[%d, %s, %s]==>", temp.next.no, 
		temp.next.name, temp.next.nickname)
		// 判读是否到链表尾部
		temp = temp.next
		if temp.next == nil{
			break
		}
	}
}
// 删除节点
func DelHeroNode(head *HeroNode, id int){
	temp := head
	flag := false
	for {
		if temp.next == nil{
			break
		}else if temp.next.no > id{
			break
		}else if temp.next.no == id{
			flag = true
			break
		}
		temp = temp.next
	}
	if !flag{
		fmt.Println("对不起,不存在no=", id)
		return
	}else{
		temp.next = temp.next.next
	}
}

func main(){
	// 1.先创建一个头节点
	head := &HeroNode{}
	// 2. 创建一个新的HeroNode
	hero1 := &HeroNode{
		no : 1,
		name : "宋江",
		nickname: "及时雨",
	}
	hero2 := &HeroNode{
		no : 2,
		name : "卢俊义",
		nickname: "玉麒麟",
	}
	hero3 := &HeroNode{
		no : 3,
		name : "林冲",
		nickname: "豹子头",
	}
	// 3. 加入一个节点
	InsertHeroNode(head, hero1)
	InsertHeroNode(head, hero2)
	InsertHeroNode(head, hero3)
	// 4. 显示
	ListHeroNode(head)
	fmt.Println()
	// 删除
	DelHeroNode(head, 2)
	ListHeroNode(head)

}
  1. 第二种方法实现:根据no 的编号从小到大插入…
// 给链表插入一个节点 拍好之后插入数据库 会节省很多order_by内存
// 编写第二种插入方法, 根据no 的编号从小到大插入..
func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode){
	// 1. 找到适当的节点
	// 2. 创建一个辅助节点 【用来帮忙的】
	temp := head
	flag := true
	for {
		// 让插入节点的no 和temp的下一个节点的no 进行比较
		if temp.next == nil{ // 到链表的最后了
			break
		}else if temp.next.no > newHeroNode.no{
			// 说明newHeroNode 就应该插入到temp后面
			break
		}else if temp.next.no == newHeroNode.no{
			// 链表中已经有这个排名 不让插入
			flag = false
			break
		}
		temp = temp.next
	}
	if !flag{
		fmt.Println("对不起,已经存在no=", newHeroNode.no)
		return
	}else{
		// 3. 插入节点
		newHeroNode.next = temp.next
		temp.next = newHeroNode
	}
}

第二节 双向链表

2.1 双向链表介绍

  1. 单向链表的缺点分析
    • 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找
    • 单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp的下一个节点来删除的。
  2. 双向链表示意图
    在这里插入图片描述

2.2 双向链表应用实例

package main
import (
	"fmt"
)

// 定义一个HeroNode
type HeroNode struct{
	no int // 编号
	name string // 名称
	nickname string // 花名
	pre *HeroNode	// 这个表示指向前面一个节点
	next *HeroNode  //这个表示指向下一个节点
}

// 给双向链表插入一个节点
// 编写第一种插入方法, 在单链表的最后加入
func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode){
	// 思路 1. 先找到该链表的最后这个节点
	// 2. 创建一个辅助节点 【用来帮忙的】
	temp := head
	for {
		if temp.next == nil{ //表示找到最后了
			break
		}
		temp = temp.next // 让temp不断的指向下一个节点
	}
	// 3. 加入到链表的最后 (现在temp已经位于链表末尾了)
	temp.next = newHeroNode
	newHeroNode.pre = temp
}
// 显示链表的所有节点信息
func ListHeroNode(head *HeroNode){
	// 1. 创建一个辅助节点 来帮忙 头节点不要动
	temp := head
	if temp.next == nil{
		fmt.Println("链表为空链表!")
		return 
	}

	// 2. 至少有一个节点 遍历这个链表
	for {
		fmt.Printf("[%d, %s, %s]==>", temp.next.no, 
		temp.next.name, temp.next.nickname)
		// 判读是否到链表尾部
		temp = temp.next
		if temp.next == nil{
			break
		}
	}
}
// 逆序打印链表的所有节点信息
func ListHeroNode1(head *HeroNode){
	temp := head
	if temp.next == nil{
		fmt.Println("链表为空链表!")
		return 
	}
	for {
		if temp.next == nil{
			break
		}
		temp = temp.next
	}

	for {
		fmt.Printf("[%d, %s, %s]==>", temp.no, 
		temp.name, temp.nickname)
		// 判读是否到链表尾部
		temp = temp.pre
		if temp.pre == nil{
			break
		}
	}
}

// 删除节点
func DelHeroNode(head *HeroNode, id int){
	temp := head
	flag := false
	for {
		if temp.next == nil{
			break
		}else if temp.next.no > id{
			break
		}else if temp.next.no == id{
			flag = true
			break
		}
		temp = temp.next
	}
	if !flag{
		fmt.Println("对不起,不存在no=", id)
		return
	}else{
		temp.next = temp.next.next
		if temp.next != nil{
			// 这里需要注意 上面除了连线 还移动了一下
			temp.next.pre = temp.next
		}
	}
}

func main(){
	// 1.先创建一个头节点
	head := &HeroNode{}
	// 2. 创建一个新的HeroNode
	hero1 := &HeroNode{
		no : 1,
		name : "宋江",
		nickname: "及时雨",
	}
	hero2 := &HeroNode{
		no : 2,
		name : "卢俊义",
		nickname: "玉麒麟",
	}
	hero3 := &HeroNode{
		no : 3,
		name : "林冲",
		nickname: "豹子头",
	}
	// 3. 加入一个节点
	InsertHeroNode(head, hero1)
	InsertHeroNode(head, hero2)
	InsertHeroNode(head, hero3)
	// 4. 显示
	ListHeroNode(head)
	fmt.Println()
	fmt.Println("逆序打印")
	ListHeroNode1(head)
	fmt.Println()
	// 删除
	DelHeroNode(head, 2)
	ListHeroNode(head)
}

2.3 双向链表添加优化

func InsertHeroNode(head *HeroNode, newHeroNode *HeroNode){
	// 1. 找到适当的节点
	// 2. 创建一个辅助节点 【用来帮忙的】
	temp := head
	flag := true
	for {
		// 让插入节点的no 和temp的下一个节点的no 进行比较
		if temp.next == nil{ // 到链表的最后了
			break
		}else if temp.next.no > newHeroNode.no{
			// 说明newHeroNode 就应该插入到temp后面
			break
		}else if temp.next.no == newHeroNode.no{
			// 链表中已经有这个排名 不让插入
			flag = false
			break
		}
		temp = temp.next
	}
	if !flag{
		fmt.Println("对不起,已经存在no=", newHeroNode.no)
		return
	}else{
		// 3. 插入节点
		newHeroNode.next = temp.next
		newHeroNode.pre = temp
		if temp.next != nil{
			temp.next.pre = newHeroNode
		}
		temp.next = newHeroNode
	}
}

第三节 单向环形链表

3.1 单向环形链表介绍

在这里插入图片描述

3.2 单向环形链表实现

  1. 删除一个环形单向链表的思路如下:
    • 先让temp指向head
    • 先让helper指向head遍历后,让helper指向环形链表最后.
    • 让temp和要删除的id进行比较,如果相同,则同helepr完成删除[这里必须考虑如果删除就是头结点]
    • 返回一个新的head, 防止head指向删除节点
      在这里插入图片描述
package main

import (
	"fmt"
)

// 定义猫的结构体
type CatNode struct{
	no int // 猫猫编号
	name string
	next *CatNode
}

// 单向链表和双向链表的头节点都是空的 但是环形链表的头节点要有数据
func InsertCatNode(head *CatNode, newCatNode *CatNode){
	// 判断是不是添加的第一只猫
	if head.next == nil{
		head.no = newCatNode.no
		head.name = newCatNode.name
		head.next = head // 形成环形
		return
	}
	// 定义一个临时变量,帮忙找到环形最后的节点
	temp := head
	for {
		if temp.next == head{
			break
		}
		temp = temp.next
	}
	// 加入到链表中
	temp.next = newCatNode
	newCatNode.next = head
}

// 输出这个环形的链表
func ListCircleLink(head *CatNode){
	fmt.Println("环形链表输出如下")
	temp := head
	if temp.next == nil{
		fmt.Println("空的环形列表")
	}
	for{
		fmt.Printf("猫的信息为=[id=%d name=%s]->\n", temp.no, temp.name)
		if temp.next == nil{
			break
		}
		if temp.next == head{
			break
		}
		temp = temp.next
	}
}

// 删除一个节点
func DeleteCatNode(head *CatNode, id int) *CatNode{
	temp := head
	helper := head
	// 空链表
	if temp.next == nil{
		fmt.Println("这是一个空的环形链表 删不了")
		return head
	}
	// 如果只有一个节点
	if temp.next == head{ 
		if temp.no == id{
			temp.next = nil
			fmt.Printf("删除了no=%d的猫猫", id)
			return head
		}
		fmt.Printf("没有no是%d猫", id)
		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.Printf("删除了no=%d的猫猫", id)
			flag = false
			break
		}
		temp = temp.next // 移动 用于比较
		helper = helper.next // 移动 找到helper能帮我们删除它
	}
	// 这里是上面的最后一个
	if flag { //上面没有删除过flag为真
		if temp.no == id{
			helper.next = temp.next
			fmt.Printf("删除了no=%d的猫猫", id)
		}else{
			fmt.Printf("没有no是%d猫", id)
		}
	}
	return head
}

func main(){
	// 这里我们初始化一个环形链表的头节点
	head := &CatNode{}
	// 创建一只猫
	cat1 := &CatNode{
		no : 1,
		name : "tom",
	}
	// cat2 := &CatNode{
	// 	no : 2,
	// 	name : "Som",
	// }
	// cat3 := &CatNode{
	// 	no : 3,
	// 	name : "pom",
	// }
	InsertCatNode(head, cat1)
	// InsertCatNode(head, cat2)
	// InsertCatNode(head, cat3)
	ListCircleLink(head)
	// 这里必须给head重新赋值
	head = DeleteCatNode(head, 1)
	ListCircleLink(head)
}

第四节 约瑟夫问题

4.1 约瑟夫问题介绍

  1. Josephu问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(l<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
  2. 方案:用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
    在这里插入图片描述

4.2 约瑟夫问题解决

package main

import(
	"fmt"
)

// 小孩的结构体
type Boy struct{
	No int // 编号
	Next *Boy // 指向下一个小孩的指针
}

// 编写一个函数,构成单向的环形链表
// num:表示小孩的个数
func AddBoy(num int) *Boy {
	first := &Boy{} //空节点
	curBoy := &Boy{} //空节点 辅助节点
	// 判断
	if num < 1{
		fmt.Println("num的值不对")
		return first
	}
	// 循环的构建这个环形链表
	for i := 1; i <= num; i++{
		boy := &Boy{
			No : i,
		}
		// 分析构成循环链表,需要一个辅助指针[帮忙的]
		//1. 第一个小孩比较特殊
		if i == 1{ //构建第一个小孩
			first = boy
			curBoy = boy
			curBoy.Next = first
		}else{
			curBoy.Next = boy
			curBoy = boy
			curBoy.Next = first //构造环形链表 
		}
	}
	return first
}

// 显示单向的环形链表[遍历]
func ShowBoy(first *Boy){
	// 处理一下 如果环形链表为空
	if first.Next == nil{
		fmt.Println("链表为空")
		return
	}
	
	//创建一个指针 帮助遍历
	curBoy := first
	for {
		fmt.Printf("小孩编号=%d ->", curBoy.No)
		// 退出
		if curBoy.Next == first{
			break
		}
		curBoy = curBoy.Next
	}
}

func GetNumBoy(first *Boy) int {
	curBoy := first
	sum := 0
	if first.Next == nil{
		fmt.Println("链表为空")
		return sum
	}
	for {
		sum++
		if curBoy.Next == first{
			break
		}
		curBoy = curBoy.Next
	}
	return sum
}
//分析思路
//1.编写一个函数,PlayGame(first*Boy,startNo int,countNum int) 头, 开始位置, 计数长度
//2.最后我们使用一个算法,按照要求,在环形链表中留下最后一个人
func PlayGame(first *Boy, startNo int, countNum int){
	//1. 空的链表我们单独处理
	if first.Next == nil{
		fmt.Println("空的链表, 没有小孩")
		return
	}
	fmt.Println(GetNumBoy(first))
	if startNo > GetNumBoy(first){
		fmt.Println("参数输入错误")
		return
	}
	// 2. 创建一个辅助节点, 帮我们删除小孩
	tail := first
	// 3. 让tail执行环形链表的最后一个小孩 tail在删除时需要用到
	for{
		if tail.Next == first{ //说明tail到了最后一个小孩
			break
		}
		tail = tail.Next
	}
	// 4. 让fisrt移动到startNo [后面我们删除小孩 就用first做标准]
	for i := 0; i < startNo -1; i++{
		first = first.Next
		tail = tail.Next
	}
	// 5. 开始数countNum 然后就删除first 指向的小孩
	for{
		// 开始数countNum-1次
		for i := 0; i < countNum - 1; i++{
			first = first.Next
			tail = tail.Next
		}
		fmt.Printf("小孩编号为%d出圈->", first.No)
		// 删除first 指向的小孩
		first = first.Next
		tail.Next = first
		// 如果tail==first,圈中只有一个小孩
		if tail == first{
			break
		}
	}
	fmt.Printf("小孩编号为%d出圈->", first.No)
}



func main(){
	first := AddBoy(5)
	// 显示
	ShowBoy(first)
	PlayGame(first, 2, 3)
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值