数据结构

数据结构介绍

  • 数据结构是一门研究算法的学科,自从有了编程语言就有了数据结构,学好数据结构可以编写出更加漂亮,更加有效率的代码
  • 要学习好数据结构就要多多考虑如何将生活中遇到的问题,用程序去解决
  • 程序 = 数据结构 + 算法

数据结构与算法的关系

  • 算法是建立在数据结构基础之上的
  • 算法是程序的灵魂

稀疏sparsearray数组

当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组

稀疏数组的处理方法:

  1. 记录数组一共有几行几列,有多少个不同的值
  2. 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模

在这里插入图片描述

应用实例

  1. 使用稀疏数组,来保留类似前面的二维数组(棋盘,地图等等)
  2. 把稀疏数组存盘,并且可以重新恢复原来的二维数组的数

在这里插入图片描述

package main

import "fmt"

type ValNode struct {
	row int
	col int
	val int
}
func main()  {
	//1.先创建一个原始数组
	var chessMap [11][11]int
	chessMap[1][2] = 1  //黑子
	chessMap[2][3] = 2 //蓝子
	//2. 输出原始数组
	for _, v := range chessMap {
		for _, v2 := range v {
			fmt.Printf("%d\t", v2)
		}
		fmt.Println()
	}
	//3.转成稀疏数组
	//1. 遍历chessMap,如果有一个元素的值不为0,创建一个node结构体
	//2.将其放入对应的切片中
	var sparseArr []ValNode
	//标准的一个稀疏数组还有一个记录原始二维数组的规模(行和列,默认值)
	//创建一个valNode
	valNode := ValNode{
		row: 11,
		col: 11,
		val: 0,
	}
	sparseArr = append(sparseArr, valNode)

	for i, v := range chessMap {
		for j, v2 := range v {
			if v2 != 0 {
				//创建一个valNode
				valNode := ValNode{
					row: i,
					col: j,
					val: v2,
				}
				sparseArr = append(sparseArr, valNode)
			}
		}
	}
	//输出稀疏数组
	fmt.Println("当前的稀疏数组是:")
	for i, valNode := range sparseArr {
		fmt.Printf("%d: %d %d %d\n", i, valNode.row, valNode.col, valNode.val)
	}
	//将这个稀疏数组,存盘 d:/chessmap.data
	//恢复原始数组
	//1. 打开这个d:/chessmap.data => 恢复原始数组
	//2. 这里使用稀疏数组恢复
	//先创建原始数组
	var chessMap2 [11][11]int
	//遍历sparseArr数组
	for i, valNode := range sparseArr {
		if i != 0 { //跳过第一行记录的值
			chessMap2[valNode.row][valNode.col] = valNode.val
		}
	}
	//chessMap2是不是恢复
	fmt.Println("恢复后的原始数据...")
	for _, v := range chessMap2 {
		for _, v2 := range v {
			fmt.Printf("%d\t", v2)
		}
		fmt.Println()
	}
}

稀疏数组改进

  1. 稀疏数组存盘 chessmap.data
  2. 从文件chessmap.data中恢复原始二维数组

队列(QUEUE)

队列介绍

  • 队列是一个有序列表,可以用数组或链表来实现
  • 遵循先入先出的原则

在这里插入图片描述

数组模拟队列

  • 队列本身是有序列表,若使用数组的结构来存储队列的数据,maxSize是该队列的最大容量
  • 因为队列的输出、输入是分别从前后端来处理,因此需要两个变量 frontrear 分别记录队列前后端的下标。front会随着数据输出而改变,而rear则是随着数据输入而改变

在这里插入图片描述

非环形队列(数组实现)
数据存入队列是称为 “addqueue”, addqueue的处理需要两步

  1. 将尾指针往后移: rear+1, front == rear [空]
  2. 若尾指针 rear 小于等于队列的最大下标MaxSize-1, 则将数据存入rear所指的数组元素中,否则无法存入数据。 rear == MaxSize - 1 [队列满]

在这里插入图片描述

package main

import (
	"errors"
	"fmt"
	"os"
)

//使用结构体管理队列
type Queue struct {
	maxSize int
	array [5]int //数组=》模拟队列
	front int //指向队列首部
	rear int //指向队列尾部
}
//添加数据到队列
func (this *Queue) AddQueue(val int) (err error) {
	//先判断队列是否已满
	if this.rear == this.maxSize - 1 {//rear是队列尾部(含最后元素)
		return errors.New("queue full")
	}
	this.rear++ //rear 后移
	this.array[this.rear] = val
	return
}

//从队列中取出数据
func (this * Queue) GetQueue() (val int, err error) {
	//先判断队列是否为空
	if this.front == this.rear {//队列空
		return -1, errors.New("queue empty")
	}
	this.front++
	val = this.array[this.front]
	return val, err
}

//显示队列,找到队首,然后遍历到队尾
func (this *Queue) ShowQueue() {
	fmt.Println("队列当前的情况是:")
	for i := this.front + 1; i <= this.rear; i++ {
		fmt.Printf("array[%d]=%d\t", i, this.array[i])
	}
	fmt.Println()
}
func main() {
	queue := &Queue{
		maxSize: 5,
		front: -1,
		rear: -1,
	}
	var key string
	var val int
	for  {
		fmt.Println("1. 输入add表示添加数据到队列")
		fmt.Println("2. 输入get表示从队列获取数据")
		fmt.Println("3. 输入show表示显示队列")
		fmt.Println("4. 输入exit表示退出队列")
		fmt.Scanln(&key)
		switch key {
		case "add":
			fmt.Println("请输入要入队列数")
			fmt.Scanln(&val)
			err := queue.AddQueue(val)
			if err != nil {
				fmt.Println(err.Error())
			} else {
				fmt.Println("加入队列ok")
			}
		case "get":
			val, err := queue.GetQueue()
			if err != nil {
				fmt.Println(err.Error())
			} else {
				fmt.Println("从队列中取出了一个数=", val)
			}
		case "show":
			queue.ShowQueue()
		case "exit":
			os.Exit(0)
		}
	}
}

说明:
实现了基本队列结构,但没有有效利用数组空间

数组模拟环形队列

通过取模实现环形
队列容量空出一个作为约定
(tail + 1) % maxSize == head [满]
tail == head [空]
初始化时,tail =0 head = 0
队列一共有多少个元素 (tail + maxSize - head) % maxSize

package main

import (
	"errors"
	"fmt"
	"os"
)

//使用结构体管理环形队列
type CircleQueue struct {
	maxSize int
	array [5]int
	head int //指向队列首部
	tail int //指向队尾
}
//入队列 AddQueue(push)
func (this *CircleQueue) Push(val int) (err error) {
	if this.IsFull() {
		return errors.New("queue full")
	}
	//this.tail在队列尾部,但是不包含最后元素
	this.array[this.tail] = val //把值给尾部
	this.tail = (this.tail + 1) % this.maxSize
	return
}
//出队列 GetQueue(pop)
func (this *CircleQueue) Pop() (val int, err error) {
	if this.IsEmpty() {
		return 0, errors.New("queue empty")
	}
	//取出, head指向队首,并且包含队首元素
	val = this.array[this.head]
	this.head = (this.head + 1) % this.maxSize
	return
}
//显示队列
func (this *CircleQueue) ListQueue() {
	fmt.Println("环形队列情况如下:")
	//取出当前队列有多少个元素
	size := this.Size()
	if size == 0 {
		fmt.Println("队列为空")
	}
	//设计一个辅助变量,指向head
	tempHead := this.head
	for i := 0; i < size; i++ {
		fmt.Printf("arr[%d]=%d\t", tempHead, this.array[tempHead])
		tempHead = (tempHead + 1) % this.maxSize
	}
	fmt.Println()
}

//判断环形队列为满
func (this *CircleQueue) IsFull() bool {
	return (this.tail + 1) % this.maxSize == this.head
}
//判断环形队列是空
func (this *CircleQueue) IsEmpty() bool {
	return this.tail == this.head
}
//取出环形队列有多少个元素
func (this *CircleQueue) Size() int {
	return (this.tail + this.maxSize - this.head) % this.maxSize
}

func main()  {
	//初始化
	queue := &CircleQueue{
		maxSize: 5,
		head: 0,
		tail: 0,
	}
	var key string
	var val int
	for  {
		fmt.Println("1. 输入add表示添加数据到队列")
		fmt.Println("2. 输入get表示从队列获取数据")
		fmt.Println("3. 输入show表示显示队列")
		fmt.Println("4. 输入exit表示退出队列")
		fmt.Scanln(&key)
		switch key {
		case "add":
			fmt.Println("请输入要入队列数")
			fmt.Scanln(&val)
			err := queue.Push(val)
			if err != nil {
				fmt.Println(err.Error())
			} else {
				fmt.Println("加入队列ok")
			}
		case "get":
			val, err := queue.Pop()
			if err != nil {
				fmt.Println(err.Error())
			} else {
				fmt.Println("从队列中取出了一个数=", val)
			}
		case "show":
			queue.ListQueue()
		case "exit":
			os.Exit(0)
		}
	}
}

链表

链表介绍

  • 链表是有序的列表

在这里插入图片描述

单链表介绍

单链表(带头结点)逻辑结构示意图
在这里插入图片描述

在这里插入图片描述

说明: 为了比较好的对单链表进行增删改查的操作,给它设置一个头结点,头结点的作用主要是用来标识链表头,本身这个结点不存放数据

注: 用链表可以做一个属于自己的内存数据库(数据管理机制)

单链表应用实例

使用带head头的单向链表实现 水浒英雄排行榜管理
完成对英雄人物的增删改查操作

第一种插入方式: 添加英雄时,直接添加到链表的尾部
第二种插入方式: 根据no编号从小到大插入

package main

import "fmt"

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
	}
	//3.将newHeroNode加入到链表的最后
	temp.next = newHeroNode
}

//给链表插入一个结点
//第二种插入方式,根据no编号从小到大插入
func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode)  {
	//1.找到适当的结点
	//2.创建一个辅助结点[跑龙套,帮忙]
	temp := head
	flag := true
	//让插入的结点的no, 和temp的下一个结点的no比较
	for  {
		if temp.next == nil { //说明到链表的最后
			break
		} else if temp.next.no > newHeroNode.no {
			//说明newHeroNode 就应该插入到temp后面
			break
		} else if temp.next.no == newHeroNode.no {
			//说明链表中已经有这个no,不能插入
			flag = false
			break
		}
		temp = temp.next
	}
	if !flag {
		fmt.Println("已经存在no= ", newHeroNode.no)
		return
	} else {
		newHeroNode.next = temp.next
		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 main() {
	//1. 先创建一个头结点
	head := &HeroNode{}

	//2.创建一个新的HeroNode
	hero1 := &HeroNode{
		no: 1,
		name: "宋江",
		nickname: "及时雨",
	}

	hero2 := &HeroNode{
		no: 2,
		name: "卢俊义",
		nickname: "玉麒麟",
	}

	hero3 := &HeroNode{
		no: 3,
		name: "林冲",
		nickname: "豹子头",
	}

	hero4 := &HeroNode{
		no: 3,
		name: "吴用",
		nickname: "智多星",
	}

	//3.加入
	InsertHeroNode2(head, hero3)
	InsertHeroNode2(head, hero1)
	InsertHeroNode2(head, hero2)
	InsertHeroNode2(head, hero4)

	//4.显示
	ListHeroNode(head)
}

删除结点:

package main

import "fmt"

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
	}
	//3.将newHeroNode加入到链表的最后
	temp.next = newHeroNode
}

//给链表插入一个结点
//第二种插入方式,根据no编号从小到大插入
func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode)  {
	//1.找到适当的结点
	//2.创建一个辅助结点[跑龙套,帮忙]
	temp := head
	flag := true
	//让插入的结点的no, 和temp的下一个结点的no比较
	for  {
		if temp.next == nil { //说明到链表的最后
			break
		} else if temp.next.no > newHeroNode.no {
			//说明newHeroNode 就应该插入到temp后面
			break
		} else if temp.next.no == newHeroNode.no {
			//说明链表中已经有这个no,不能插入
			flag = false
			break
		}
		temp = temp.next
	}
	if !flag {
		fmt.Println("已经存在no= ", newHeroNode.no)
		return
	} else {
		newHeroNode.next = temp.next
		temp.next = newHeroNode
	}
}

//删除一个结点
func DelHerNode(head *HeroNode, id int)  {
	temp := head
	flag := false
	//找到要删除结点的no,和temp的下一个结点的no比较
	for  {
		if temp.next == nil { //说明到链表的最后
			break
		} else if temp.next.no == id {
			//说明链表中已经有这个no,不能插入
			flag = true
			break
		}
		temp = temp.next
	}
	if flag {
		temp.next = temp.next.next
	} else {
		fmt.Println("要删除的id不存在")
	}
}
//显示链表所有结点信息
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 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.加入
	InsertHeroNode2(head, hero3)
	InsertHeroNode2(head, hero1)
	InsertHeroNode2(head, hero2)

	//4.显示
	ListHeroNode(head)
	//5.删除
	fmt.Println()
	DelHerNode(head, 1)
	DelHerNode(head, 3)
	ListHeroNode(head)
}

双向链表

单向链表的缺点

  1. 单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找
  2. 单向链表不能自我删除,需要靠辅助结点,而双向链表,则可以自我删除,所以单向链表删除结点时,总是找到temp的下一个结点来删除

在这里插入图片描述

package main

import "fmt"

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
	}
	//3.将newHeroNode加入到链表的最后
	temp.next = newHeroNode
	newHeroNode.pre = temp
}

//给双向链表插入一个结点
//第二种插入方式,根据no编号从小到大插入
func InsertHeroNode2(head *HeroNode, newHeroNode *HeroNode)  {
	//1.找到适当的结点
	//2.创建一个辅助结点[跑龙套,帮忙]
	temp := head
	flag := true
	//让插入的结点的no, 和temp的下一个结点的no比较
	for  {
		if temp.next == nil { //说明到链表的最后
			break
		} else if temp.next.no > newHeroNode.no {
			//说明newHeroNode 就应该插入到temp后面
			break
		} else if temp.next.no == newHeroNode.no {
			//说明链表中已经有这个no,不能插入
			flag = false
			break
		}
		temp = temp.next
	}
	if !flag {
		fmt.Println("已经存在no= ", newHeroNode.no)
		return
	} else {
		newHeroNode.next = temp.next
		newHeroNode.pre = temp
		if temp.next != nil {
			temp.next.pre = newHeroNode
		}
		temp.next = newHeroNode
	}
}

//删除一个结点[双向链表删除一个结点]
func DelHerNode(head *HeroNode, id int)  {
	temp := head
	flag := false
	//找到要删除结点的no,和temp的下一个结点的no比较
	for  {
		if temp.next == nil { //说明到链表的最后
			break
		} else if temp.next.no == id {
			//说明链表中已经有这个no,不能插入
			flag = true
			break
		}
		temp = temp.next
	}
	if flag {
		temp.next = temp.next.next
		if temp.next != nil {
			temp.next.pre = temp
		}
	} else {
		fmt.Println("要删除的id不存在")
	}
}
//显示链表所有结点信息
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 ListHeroNode2(head *HeroNode)  {
	//1.创建一个辅助结点[跑龙套,帮忙]
	temp := head

	//先判断该链表是不是一个空链表
	if temp.next == nil {
		fmt.Println("空空")
		return
	}

	//2.让temp定位到双向链表的最后结点
	for  {
		if temp.next == nil {
			break
		}
		temp = temp.next
	}

	//2.遍历这个链表
	for  {
		fmt.Printf("[%d, %s, %s]==>", temp.no,
			temp.name, temp.nickname)
		//判断是否链表头
		temp = temp.pre
		if temp.pre == nil {
			break
		}
	}
}

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("逆序打印")
	ListHeroNode2(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 //构成一个环形
		fmt.Println(newCatNode, "加入到环形链表")
		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("空的环形链表...")
		return
	}
	for  {
		fmt.Printf("猫的信息为=[id=%d name=%s] ->\n", temp.no, temp.name)
		if temp.next == head {
			break
		}
		temp = temp.next
	}
}
//删除一只猫
func DelCatNode(head *CatNode, id int) *CatNode {
	temp := head
	helper := head
	//空链表
	if temp.next == nil {
		fmt.Println("这是一个空的环形链表,不能删除")
		return head
	}
	//如果只有一个结点
	if temp.next == head { //只有一个结点
		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.no == id {
			if temp == head { //说明删除的是头结点
				head = head.next
			}
			//找到,直接删除
			helper.next = temp.next
			fmt.Printf("猫=%d\n", id)
			flag = false
			break
		}
		temp = temp.next //移动【比较】
		helper = helper.next //移动 【一旦找到要删除的结点, 使用helper】
	}
	//最后还要比较一次
	if flag { //如果flag为真,则上面没有删除
		if temp.no == id {
			helper.next = temp.next
			fmt.Printf("猫=%d\n", id)
		} else {
			fmt.Printf("没有no=%d\n", id)
		}
	}
	return head
}
func main() {
	//初始化一个环形链表的头结点
	head := &CatNode{}
	//创建一只猫
	cat1 := &CatNode{
		no: 1,
		name: "tom",
	}
	cat2 := &CatNode{
		no: 2,
		name: "tom2",
	}
	cat3 := &CatNode{
		no: 3,
		name: "tom3",
	}
	InsertCatNode(head, cat1)
	InsertCatNode(head, cat2)
	InsertCatNode(head, cat3)
	ListCircleLink(head)
	head = DelCatNode(head, 30)
	fmt.Println()
	fmt.Println()
	ListCircleLink(head)
}

删除一个环形单向链表的思路:

  1. 先让temp指向head
  2. 让helper指向环形链表最后
  3. 让temp和要删除的id进行比较,如果相同,则通过helper完成删除【这里必须考虑如果删除就是头结点】

环形单向链表应用实例

Josephu问题: 设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列

提示:用一个不带头结点的循环链表来处理Josephu问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计数到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除,算法结束

在这里插入图片描述

package main

import "fmt"

type Boy struct {
	No int
	Next *Boy //指向下一个小孩的指针[默认值是nil]
}
//编写一个函数,构成单向环形链表
//num: 表示小孩的个数
//*Boy: 返回该环形链表的第一个小孩的指针
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
	}
}

//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
	}
	//startNo <= 小孩总数
	//2.需要定义辅助指针,帮助删除小孩
	tail := first
	//3. 让tail指向链表最后一个小孩,删除小孩时使用
	for  {
		if tail.Next == first {
			break
		}
		tail = tail.Next
	}
	//4. 让first移动到startNo【删除小孩,就以first为准】
	for i := 1; i <= startNo - 1; i++ {
		first = first.Next
		tail = tail.Next
	}
	fmt.Println()
	//5.开始数countNum,然后就删除first指向的小孩
	for  {
		//开始数countNum-1次
		for i := 1; i <= countNum - 1; i++ {
			first = first.Next
			tail = tail.Next
		}
		fmt.Printf("小孩编号为%d 出圈\n", first.No)
		//删除
		first = first.Next
		tail.Next = first
		//如果tail == first, 圈中只有一个小孩
		if tail == first {
			break
		}
	}
	fmt.Printf("小孩编号为%d 出圈\n", first.No)
}

func main() {
	first := AddBoy(500)
	//显示
	ShowBoy(first)
	PlayGame(first, 20, 31)
}

排序

排序介绍

排序是将一组数据,依指定的顺序进行排列的过程,常见排序:

  1. 冒泡排序
  2. 选择排序
  3. 插入排序
  4. 快速排序

冒泡排序

冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从后向前(从下标较大的元素开始),依次比较相邻元素的排序码,若发现逆序则交换,使排序码较小的元素逐渐从后部移向前部(从下标较大的单元移向下标较小的单元),就像水底下的气泡一样逐渐向上冒

因为排序的过程中,各元素不断接近自己的位置,如果一趟比较下来没有进行过交换,就说明序列有序,因此要在排序过程中设置一个标志flag判断元素是否进行过交换,从而减少不必要的比较

选择排序

选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,经过和其它元素重整,再依原则交换位置后达到排序的目的

选择排序思想

选择排序(select sorting)也是一种简单地排序方法。基本思想是:

  • 第一次从R[0]~R[n-1]中选取最小值,与R[0]交换,
  • 第二次从R[1]~R[n-1]中选取最小值,与R[1]交换,
  • 第三次从R[2]~R[n-1]中选取最小值,与R[2]交换,
  • …,
  • 第i次从R[i-1]~R[n-1]中选取最小值, 与R[i-1]交换,
  • …,
  • 第n-1次从R[n-2]~R[n-1]中选取最小值,与R[n-2]交换,
  • 总共通过n-1次,得到一个按排序码从小到大排列的有序序列

示意图

在这里插入图片描述

package main

import "fmt"

func SelectSort(arr *[6]int)  {
	for j := 0; j < len(arr) - 1; j++ {
		//1.先完成将第一个最大值和arr[0]交换
		//1 假设arr[0]是最大值
		max := arr[j]
		maxIndex := j
		//2 遍历后面 1---【len(arr) - 1】比较
		for i := j + 1; i < len(arr); i++ {
			if max < arr[i] {
				max = arr[i]
				maxIndex = i
			}
		}
		//交换
		if maxIndex != j {
			arr[j], arr[maxIndex] = arr[maxIndex], arr[j]
		}
		fmt.Printf("第%d次 %v\n", j + 1, *arr)
	}
}
func main() {
	//定义一个数组
	arr := [6]int{10, 34, 19, 100, 80, 789}
	fmt.Println("原始数据", arr)
	SelectSort(&arr)
	fmt.Println("main()")
	fmt.Println(arr)
}

插入排序法

插入排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序目的

插入排序法思想

插入排序(Insertion Sorting)的基本思想: 把n个待排序的元素看成为一个有序表和一个无序表,开始时有序表中只包含一个元素,无序表中包含有n-1个元素,排序过程中每次从无序表中取出第一个元素,把它的排序码依次与有序表元素的排序码进行比较,将它插入到有序表中的适当位置,使之成为新的有序表

示意图

在这里插入图片描述

package main

import "fmt"

func InsertSort(arr *[7]int)  {
	for i := 1; i < len(arr); i++ {
		//完成第一次,给第二个元素找到合适的位置并插入
		insertVal := arr[i]
		insertIndex := i - 1 //下标

		for insertIndex >= 0 && arr[insertIndex] < insertVal {
			arr[insertIndex + 1] = arr[insertIndex] //数据后移
			insertIndex--
		}
		//插入
		if insertIndex + 1 != i {
			arr[insertIndex + 1] = insertVal
		}
		fmt.Printf("第%d次插入后 %v\n", i, *arr)
	}
}
func main() {
	arr := [7]int{23, 0, 12, 56, 34, -1, 55}
	fmt.Println("原始数组=", arr)
	InsertSort(&arr)
	fmt.Println("main()")
	fmt.Println(arr)
}

快速排序法

快速排序(Quicksort)是对冒泡排序的一种改进。基本思想: 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

示意图

在这里插入图片描述

快速排序应用实例

package main

import "fmt"

//快速排序
//1.left 表示数组左边的下标
//2. right 表示数组右边的下标
//3. array 表示要排序的数组
func QuickSort(left int, right int, array *[9]int)  {
	l := left
	r := right
	//pivot 是中轴,支点
	pivot := array[(left + right) / 2]
	temp := 0
	//for循环的目标是将比pivot小的数放到左边
	// 比pivot大的数放到右边
	for ; l < r; {
		//从 pivot 的左边找到大于等于pivot的值
		for ; array[l] < pivot; {
			l++
		}
		//从 pivot 的右边找到小于等于pivot的值
		for ; array[r] > pivot; {
			r--
		}
		// l >= r 表明本次分解任务完成
		if l >= r {
			break
		}
		temp = array[l]
		array[l] = array[r]
		array[r] = temp
		if array[l] == pivot {
			r--
		}
		if array[r] == pivot {
			l++
		}
	}
	//如果 l==r , 再移动一下
	if l == r {
		l++
		r--
	}
	//向左递归
	if left < r {
		QuickSort(left, r, array)
	}
	//向右递归
	if right > l {
		QuickSort(l, right, array)
	}
}

func main() {
	arr := [9]int{-9, 78, 0, 23, -567, 70, 123, 90, -23}
	fmt.Println("原始数据")
	fmt.Println(arr)
	QuickSort(0, len(arr) - 1, &arr)
	fmt.Println("main()")
	fmt.Println(arr)
}

对计算机而言,它接收到的就是一个字符串

栈的介绍

有些程序员也把栈称为堆栈,即栈和堆栈是同一个概念

  1. 栈的英文(stack)
  2. 是一个先入后出(FILO-First In Last Out)的有序列表
  3. 栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)
  4. 根据堆栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除

栈的入栈和出栈示意图

  • 入栈

在这里插入图片描述

  • 出栈

在这里插入图片描述

栈的应用场景

  1. 子程序的调用: 在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中
  2. 处理递归调用: 和子程序的调用类似,只是除了存储下一个指令的地址外,也将参数、区域变量等数据存入堆栈中
  3. 表达式的转换与求值
  4. 二叉树的遍历
  5. 图形的深度优先(depth—first)搜索法

栈的案例

用数组模拟栈的使用,堆栈是一种有序列表,可以使用数组的结构来存储栈的数据内容

package main

import (
	"errors"
	"fmt"
)

//使用数组模拟一个栈的使用
type Stack struct {
	MaxTop int //表示栈最大可以存放数的个数
	Top int //表示栈顶
	arr [5]int //数组模拟栈
}
//入栈
func (this *Stack) Push(val int) (err error) {
	//先判断栈是否满
	if this.Top == this.MaxTop - 1 {
		fmt.Println("stack full")
		return errors.New("stack full")
	}
	this.Top++
	//放入数据
	this.arr[this.Top] = val
	return
}
//出栈
func (this *Stack) Pop() (val int, err error) {
	//判断栈是否空
	if this.Top == -1 {
		fmt.Println("stack empty")
		return 0, errors.New("stack empty")
	}
	//先取值,再--
	val = this.arr[this.Top]
	this.Top--
	return val, nil
}
//遍历栈, 需要从栈顶开始遍历
func (this *Stack) List() {
	//先判断栈是否为空
	if this.Top == -1 {
		fmt.Println("stack empty")
		return
	}
	fmt.Println("栈的情况:")
	for i := this.Top; i >= 0; i-- {
		fmt.Printf("arr[%d]=%d\n", i, this.arr[i])
	}
}

func main() {
	stack := &Stack{
		MaxTop: 5, //最多存放5个数
		Top: -1, //当栈顶为-1,表示栈为空
	}
	//入栈
	stack.Push(1)
	stack.Push(2)
	stack.Push(3)
	stack.Push(4)
	stack.Push(5)

	//显示
	stack.List()

	val, _ := stack.Pop()
	fmt.Println("出栈val=", val)
	stack.List()
	fmt.Println()
	val, _ = stack.Pop()
	val, _ = stack.Pop()
	val, _ = stack.Pop()
	val, _ = stack.Pop()
	val, _ = stack.Pop()
	fmt.Println("出栈val=", val)
	stack.List()
}

栈实现综合计算器

3+2*6-2

算法:

  1. 创建两个栈,numStack operStack
  2. numStack存放数, operStack操作符
  3. Index := 0,
  4. exp 计算表达式, 是一个字符串
  5. 如果扫描发现是一个数字,则直接入numStack
  6. 如果发现是一个运算符
    (1) 如果 operStack 是一个空栈,直接入栈
    (2) 如果 operStack 不是一个空栈
    2.1
    如果发现operStack栈顶的运算符的优先级大于等于当前准备入栈的运算符的优先级,就从符号栈pop出,并从数栈也pop两个数,进行运算,运算后的结果再重新入栈到数栈, 符号再入符号栈
    2.2
    否则,运算符就直接入栈
  7. 如果扫描表达式完毕,依次从符号栈取出符号,然后从数栈取出两个数, 运算后的结果,入数栈,直到符号栈为空

在这里插入图片描述

简单版

package main

import (
	"errors"
	"fmt"
	"strconv"
)

//使用数组模拟一个栈的使用
type Stack struct {
	MaxTop int //表示栈最大可以存放数的个数
	Top int //表示栈顶
	arr [20]int //数组模拟栈
}
//入栈
func (this *Stack) Push(val int) (err error) {
	//先判断栈是否满
	if this.Top == this.MaxTop - 1 {
		fmt.Println("stack full")
		return errors.New("stack full")
	}
	this.Top++
	//放入数据
	this.arr[this.Top] = val
	return
}
//出栈
func (this *Stack) Pop() (val int, err error) {
	//判断栈是否空
	if this.Top == -1 {
		fmt.Println("stack empty")
		return 0, errors.New("stack empty")
	}
	//先取值,再--
	val = this.arr[this.Top]
	this.Top--
	return val, nil
}
//遍历栈, 需要从栈顶开始遍历
func (this *Stack) List() {
	//先判断栈是否为空
	if this.Top == -1 {
		fmt.Println("stack empty")
		return
	}
	fmt.Println("栈的情况:")
	for i := this.Top; i >= 0; i-- {
		fmt.Printf("arr[%d]=%d\n", i, this.arr[i])
	}
}

//判断一个字符是不是一个运算符[+,-,*,/]
func (this *Stack) IsOper(val int) bool {
	if val == 42 || val == 43 || val == 45 || val == 47 {
		return true
	} else {
		return false
	}
}
//运算的方法
func (this *Stack) Cal(num1 int, num2 int, oper int) int {
	res := 0
	switch oper {
	case 42:
		res = num2 * num1
	case 43:
		res = num2 + num1
	case 45:
		res = num2 - num1
	case 47:
		res = num2 / num1
	default:
		fmt.Println("运算符错误...")
	}
	return res
}
//编写一个方法,返回某个运算符的优先级【程序员定义】
//【 * / =>1  + - => 0】
func (this *Stack) Priority(oper int) int {
	res := 0
	if oper == 42 || oper == 47 {
		res = 1
	} else if oper == 43 || oper == 45 {
		res = 0
	}
	return res
}

func main() {
	//数栈
	numStack := &Stack{
		MaxTop: 20,
		Top: -1,
	}
	//符号栈
	operStack := &Stack{
		MaxTop: 20,
		Top: -1,
	}
	exp := "3+3*6-4"
	//定义一个index,帮助扫描exp
	index := 0
	//为了配合运算,定义需要的变量
	num1 := 0
	num2 := 0
	oper := 0
	result := 0

	for  {
		ch := exp[index:index+1] //字符串
		temp := int([]byte(ch)[0]) //就是字符对应的ASCII码
		if operStack.IsOper(temp) { //说明是符号
			// 如果 operStack 是一个空栈,直接入栈
			if operStack.Top == -1 {
				operStack.Push(temp)
			} else {
				// 如果发现operStack栈顶的运算符的优先级大于等于当前准备入栈的运算符的优先级
				// ,就从符号栈pop出,并从数栈也pop两个数,进行运算,
				// 运算后的结果再重新入栈到数栈, 符号再入符号栈
				if operStack.Priority(operStack.arr[operStack.Top]) >=
					operStack.Priority(temp) {
					num1, _ = numStack.Pop()
					num2, _ = numStack.Pop()
					oper, _ = operStack.Pop()
					result = operStack.Cal(num1, num2, oper)
					//将计算结果重新入数栈
					numStack.Push(result)
					//当前符号压入符号栈
					operStack.Push(temp)
				} else {
					operStack.Push(temp)
				}
			}
		} else { //说明是数
			val, _ := strconv.ParseInt(ch, 10, 64)
			numStack.Push(int(val)) // 3 ==> 51 (错)
		}
		
		//继续扫描
		//先判断index是否已经扫描到计算表达式的最后
		if index + 1 == len(exp) {
			break
		}
		index++
	}

	// 如果扫描表达式完毕,依次从符号栈取出符号,然后从数栈取出两个数,
	// 运算后的结果,入数栈,直到符号栈为空
	for  {
		if operStack.Top == -1 {
			break //退出条件
		}
		num1, _ = numStack.Pop()
		num2, _ = numStack.Pop()
		oper, _ = operStack.Pop()
		result = operStack.Cal(num1, num2, oper)
		//将计算结果重新入数栈
		numStack.Push(result)
	}
	//如果没有问题,表达式也是正确,则结果就是numStack最后的数
	res, _ := numStack.Pop()
	fmt.Printf("表达式%s=%v\n", exp, res)
}

复杂版

package main

import (
	"errors"
	"fmt"
	"strconv"
)

//使用数组模拟一个栈的使用
type Stack struct {
	MaxTop int //表示栈最大可以存放数的个数
	Top int //表示栈顶
	arr [20]int //数组模拟栈
}
//入栈
func (this *Stack) Push(val int) (err error) {
	//先判断栈是否满
	if this.Top == this.MaxTop - 1 {
		fmt.Println("stack full")
		return errors.New("stack full")
	}
	this.Top++
	//放入数据
	this.arr[this.Top] = val
	return
}
//出栈
func (this *Stack) Pop() (val int, err error) {
	//判断栈是否空
	if this.Top == -1 {
		fmt.Println("stack empty")
		return 0, errors.New("stack empty")
	}
	//先取值,再--
	val = this.arr[this.Top]
	this.Top--
	return val, nil
}
//遍历栈, 需要从栈顶开始遍历
func (this *Stack) List() {
	//先判断栈是否为空
	if this.Top == -1 {
		fmt.Println("stack empty")
		return
	}
	fmt.Println("栈的情况:")
	for i := this.Top; i >= 0; i-- {
		fmt.Printf("arr[%d]=%d\n", i, this.arr[i])
	}
}

//判断一个字符是不是一个运算符[+,-,*,/]
func (this *Stack) IsOper(val int) bool {
	if val == 42 || val == 43 || val == 45 || val == 47 {
		return true
	} else {
		return false
	}
}
//运算的方法
func (this *Stack) Cal(num1 int, num2 int, oper int) int {
	res := 0
	switch oper {
	case 42:
		res = num2 * num1
	case 43:
		res = num2 + num1
	case 45:
		res = num2 - num1
	case 47:
		res = num2 / num1
	default:
		fmt.Println("运算符错误...")
	}
	return res
}
//编写一个方法,返回某个运算符的优先级【程序员定义】
//【 * / =>1  + - => 0】
func (this *Stack) Priority(oper int) int {
	res := 0
	if oper == 42 || oper == 47 {
		res = 1
	} else if oper == 43 || oper == 45 {
		res = 0
	}
	return res
}

func main() {
	//数栈
	numStack := &Stack{
		MaxTop: 20,
		Top: -1,
	}
	//符号栈
	operStack := &Stack{
		MaxTop: 20,
		Top: -1,
	}
	exp := "30+30*6-4-6"
	//定义一个index,帮助扫描exp
	index := 0
	//为了配合运算,定义需要的变量
	num1 := 0
	num2 := 0
	oper := 0
	result := 0
	keepNum := ""

	for  {
		//增加一个逻辑,处理多位数问题
		ch := exp[index:index+1] //字符串
		temp := int([]byte(ch)[0]) //就是字符对应的ASCII码
		if operStack.IsOper(temp) { //说明是符号
			// 如果 operStack 是一个空栈,直接入栈
			if operStack.Top == -1 {
				operStack.Push(temp)
			} else {
				// 如果发现operStack栈顶的运算符的优先级大于等于当前准备入栈的运算符的优先级
				// ,就从符号栈pop出,并从数栈也pop两个数,进行运算,
				// 运算后的结果再重新入栈到数栈, 符号再入符号栈
				if operStack.Priority(operStack.arr[operStack.Top]) >=
					operStack.Priority(temp) {
					num1, _ = numStack.Pop()
					num2, _ = numStack.Pop()
					oper, _ = operStack.Pop()
					result = operStack.Cal(num1, num2, oper)
					//将计算结果重新入数栈
					numStack.Push(result)
					//当前符号压入符号栈
					operStack.Push(temp)
				} else {
					operStack.Push(temp)
				}
			}
		} else { //说明是数
			// 处理多位数
			//1. 定义一个变量,keepNum string, 做拼接
			keepNum += ch
			//2. 每次要向index的后面字符测试一下,看看是不是运算符,然后处理
			//如果已经到表达式最后,直接将keepNum拼接
			if index == len(exp) - 1 {
				val, _ := strconv.ParseInt(keepNum, 10, 64)
				numStack.Push(int(val))
			} else {
				//向index后面测试,看看是不是运算符
				if operStack.IsOper(int([]byte(exp[index+1:index+2])[0])) {
					val, _ := strconv.ParseInt(keepNum, 10, 64)
					numStack.Push(int(val))
					keepNum = ""
				}
			}
		}
		
		//继续扫描
		//先判断index是否已经扫描到计算表达式的最后
		if index + 1 == len(exp) {
			break
		}
		index++
	}

	// 如果扫描表达式完毕,依次从符号栈取出符号,然后从数栈取出两个数,
	// 运算后的结果,入数栈,直到符号栈为空
	for  {
		if operStack.Top == -1 {
			break //退出条件
		}
		num1, _ = numStack.Pop()
		num2, _ = numStack.Pop()
		oper, _ = operStack.Pop()
		result = operStack.Cal(num1, num2, oper)
		//将计算结果重新入数栈
		numStack.Push(result)
	}
	//如果没有问题,表达式也是正确,则结果就是numStack最后的数
	res, _ := numStack.Pop()
	fmt.Printf("表达式%s=%v\n", exp, res)
}

递归

递归的概念

简单地说,递归就是函数/方法自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让代码变得简洁

递归用于解决什么问题

  • 各种数学问题: 8皇后问题,汉诺塔,阶乘问题,迷宫问题, 球和篮子的问题
  • 用栈解决的问题 ----> 递归代码比较简洁

递归需要遵守的原则

  1. 执行一个函数时,就创建一个新的受保护的独立空间新函数栈
  2. 函数的局部变量是独立的,不会相互影响,如果希望各个函数栈使用同一个数据,使用引用传递
  3. 递归必须向退出递归的条件逼近【程序员自己必须分析】,否则就是无限递归
  4. 当一个函数执行完毕,或者遇到 return,就会返回,遵守谁调用,就将结果返回给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁

迷宫问题(回溯)

在这里插入图片描述

说明:

  1. 小球得到的路径,和程序设置的找路策略有关,找路的上下左右的顺序相关
  2. 再得到小球路径时,可以先使用(下右上左),再改成(上右下左),看看路径是不是有变化
package main

import "fmt"

//编写一个函数,完成老鼠找路
//myMap *[8][7]int:地图,保证是同一个地图,因此使用引用
//i,j 表示对地图的哪个点进行测试
func SetWay(myMap *[8][7]int, i int, j int) bool {
	//分析出什么情况下,找到出路
	//myMap[6][5] == 2
	if myMap[6][5] == 2 {
		return true
	} else {
		//说明要继续找
		if myMap[i][j] == 0 {//如果这个点是可以探测
			//假设这个点是可以通,但是需要探测
			//换一个策略 下右上左
			myMap[i][j] = 2
			if SetWay(myMap, i + 1, j) {//下
				return true
			} else if SetWay(myMap, i, j + 1) {//右
				return true
			} else if SetWay(myMap, i - 1, j) {//上
				return true
			} else if SetWay(myMap, i, j - 1) {//左
				return true
			} else { //死路
				myMap[i][j] = 3
				return false
			}
			
		} else {//说明这个点不能探测,上下左右
			return false
		}
	}
}
func main() {
	//先创建一个二维数组,模拟迷宫
	//规则
	//1.如果元素值为1, 就是墙
	//2. 如果元素值为0, 是没有走过的点
	//3. 如果元素值为2,是一个通路
	//4. 如果元素值为3, 是走过的点,但是走不通
	var myMap [8][7]int

	//先把地图的最上和最下设置为1
	for i := 0; i < 7; i++ {
		myMap[0][i] = 1
		myMap[7][i] = 1
	}
	//先把地图的最左和最右设置为1
	for i := 0; i < 8; i++ {
		myMap[i][0] = 1
		myMap[i][6] = 1
	}
	myMap[3][1] = 1
	myMap[3][2] = 1

	//设置把路堵死
	myMap[1][2] = 1
	myMap[2][2] = 1


	//输出地图
	for i := 0; i < 8; i++ {
		for j := 0; j < 7; j++ {
			fmt.Print(myMap[i][j], " ")
		}
		fmt.Println()
	}
	//使用测试
	SetWay(&myMap, 1, 1)
	fmt.Println("探测完毕...")
	//输出地图
	for i := 0; i < 8; i++ {
		for j := 0; j < 7; j++ {
			fmt.Print(myMap[i][j], " ")
		}
		fmt.Println()
	}
}

哈希表(散列)

哈希表介绍

散列表(Hash table, 也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表

在这里插入图片描述

使用hashtable来实现一个雇员的管理系统[增删改查]

有一个公司,当有新的员工来报道时,要求将该员工的信息加入(id,性别,年龄,住址…),当输入该员工的id时,要求查找到该员工的所有信息

要求:

  1. 不使用数据库,尽量节省内存,速度越快越好 -->哈希表(散列)
  2. 添加时,保证按照雇员的id从低到高插入

思路:
使用链表来实现哈希表,该链表不带表头[即: 链表的第一个结点就存放雇员信息]

示意图

在这里插入图片描述

package main

import (
	"fmt"
	"os"
)

//定义emp
type Emp struct {
	Id int
	Name string
	Next *Emp
}

func (this *Emp) ShowMe() {
	fmt.Printf("链表%d 找到该雇员 %d\n", this.Id % 7, this.Id)
}

//定义EmpLink
//这里的EmpLink 不带表头,即第一个结点就存放雇员
type EmpLink struct {
	Head *Emp
}
//1.添加员工的方法, 保证添加时,编号从小到大
func (this *EmpLink) Insert(emp *Emp) {
	cur := this.Head //这是辅助指针
	var pre *Emp = nil //这是一个辅助指针 pre在cur前面
	//如果当前的EmpLink就是一个空链表
	if cur == nil {
		this.Head = emp //完成
		return
	}
	//如果不是一个空链表, 给emp找到对应的位置并插入
	//思路 让cur 和 emp 比较,然后让 pre 保持在 cur前面
	for  {
		if cur != nil {
			//比较
			if cur.Id > emp.Id {
				//找到位置
				break
			}
			pre = cur //保证同步
			cur = cur.Next

		} else {
			break
		}
	}
	//退出时,看下是否将emp添加到链表最后
	pre.Next = emp
	emp.Next = cur

}
//显示链表的信息
func (this *EmpLink) ShowLink(no int) {
	if this.Head == nil {
		fmt.Printf("链表%d 为空\n", no)
		return
	}
	//遍历当前链表,并显示数据
	cur := this.Head //辅助指针
	for  {
		if cur != nil {
			fmt.Printf("链表%d 雇员id=%d 名字=%s ->", no, cur.Id, cur.Name)
			cur = cur.Next
		} else {
			break
		}
	}
	fmt.Println() //换行处理
}

//根据id查找对应的雇员, 如果没有就返回nil
func (this *EmpLink) FindById(id int) *Emp {
	cur := this.Head
	for  {
		if cur != nil && cur.Id == id {
			return cur
		} else if cur == nil {
			break
		}
		cur = cur.Next
	}
	return nil
}

//定义hashtable, 含有一个链表数组
type HashTable struct {
	LinkArr [7]EmpLink
}

//给HashTable 编写 Insert 雇员方法
func (this *HashTable) Insert(emp *Emp) {
	//使用散列函数,确定将该雇员添加到哪个链表
	linkNo := this.HashFun(emp.Id)
	//使用对应的链表添加
	this.LinkArr[linkNo].Insert(emp)
}
//显示hashtable的所有雇员
func (this *HashTable) ShowAll() {
	for i := 0; i < len(this.LinkArr); i++ {
		this.LinkArr[i].ShowLink(i)
	}
}

//编写一个散列方法
func (this *HashTable) HashFun(id int) int {
	return id % 7 //得到一个值,就是对应的链表的下标
}
//完成查找
func (this *HashTable) FindById(id int) *Emp {
	//使用散列函数,确定该雇员应该在哪个链表
	linkNo := this.HashFun(id)
	return this.LinkArr[linkNo].FindById(id)
}

func main() {
	key := ""
	id := 0
	name := ""
	var hashtable HashTable
	for  {
		fmt.Println("==========雇员系统菜单===========")
		fmt.Println("input 表示添加雇员")
		fmt.Println("show 表示显示雇员")
		fmt.Println("find 表示查找雇员")
		fmt.Println("exit 表示退出系统")
		fmt.Println("请输入你的选择")
		fmt.Scanln(&key)
		switch key {
		case "input":
			fmt.Println("输入雇员id")
			fmt.Scanln(&id)
			fmt.Println("输入雇员name")
			fmt.Scanln(&name)
			emp := &Emp{
				Id: id,
				Name: name,
			}
			hashtable.Insert(emp)
		case "show":
			hashtable.ShowAll()
		case "find":
			fmt.Println("请输入id号:")
			fmt.Scanln(&id)
			emp := hashtable.FindById(id)
			if emp == nil {
				fmt.Printf("id=%d 的雇员不存在\n", id)
			} else {
				//编写一个方法,显示雇员信息
				emp.ShowMe()
			}
		case "exit":
			os.Exit(0)
		default:
			fmt.Println("输入错误")
		}
	}
}

二叉树

在这里插入图片描述

package main

import "fmt"

type Hero struct {
	No int
	Name string
	Left *Hero
	Right *Hero
}

//前序遍历[先输出root结点,然后再输出左子树,然后再输出右子树]
func PreOrder(node *Hero)  {
	if node != nil {
		fmt.Printf("no=%d name=%s\n", node.No, node.Name)
		PreOrder(node.Left)
		PreOrder(node.Right)
	}
}

//中序遍历[先输出root的左子树,再输出root结点,最后输出root的右子树]
func InfixOrder(node *Hero)  {
	if node != nil {
		InfixOrder(node.Left)
		fmt.Printf("no=%d name=%s\n", node.No, node.Name)
		InfixOrder(node.Right)
	}
}

//后序遍历[先输出root的左子树,再输出root的右子树,最后输出root结点]
func PostOrder(node *Hero)  {
	if node != nil {
		PostOrder(node.Left)
		PostOrder(node.Right)
		fmt.Printf("no=%d name=%s\n", node.No, node.Name)
	}
}

func main() {
	//构建一个二叉树
	root := &Hero{
		No: 1,
		Name: "宋江",
	}

	left1 := &Hero{
		No: 2,
		Name: "吴用",
	}

	node10 := &Hero{
		No: 10,
		Name: "tom",
	}

	node12 := &Hero{
		No: 12,
		Name: "jack",
	}
	left1.Left = node10
	left1.Right = node12

	right1 := &Hero{
		No: 3,
		Name: "卢俊义",
	}
	
	root.Left = left1
	root.Right = right1

	right2 := &Hero{
		No: 4,
		Name: "林冲",
	}
	right1.Right = right2

	//PreOrder(root)
	//InfixOrder(root)
	PostOrder(root)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuxingge

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值