数据结构
1. 数据结构(算法)介绍
数据结构是一门研究算法的学科,好的数据结构可以编写出更加漂亮、更加有效率的代码。算法是程序的灵魂。数据结构来源于现实问题。
2. 稀疏数组sparse array
问题引出:
编写五子棋程序中,存在存盘退出和续上盘的功能。
按照原始的二维数组方式记录,很多重复数据(0)没有意义,浪费空间和操作时间
基本介绍:
当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。
稀疏数组的处理方法是:
1.记录原数组几行几列及共性值,与有多少个不同的值
2. 把具有不同值得元素行列及值记录在一个小规模的数组中,从而压缩数据,缩小程序的规模
应用实例
1.使用稀疏数组,来保留类似前面二维数组(棋盘、地图等等)
2.把稀疏数组存盘,并可以重新恢复原来的二维数组
3.整体思路分析
4.代码实现
package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
)
type ValNode struct {
row int
col int
val interface{}
}
func main() {
// 1.先创建一个原始数组
var chessMap [11][11]int
chessMap[1][2] = 1 //黑子
chessMap[2][3] = 2 //白子
// 2.输出原始的数组
for _, v := range chessMap {
for _, v1 := range v {
fmt.Printf("%d\t", v1)
}
fmt.Println()
}
// 3.转成稀疏数组(在golang中,使用切片形式+结构体实现)
//思路
// 1.由于Golang中数组是明确固定大小的的,为达到稀疏数组的效果需使用动态数组切片
// 2.遍历原数组,当发现有元素的值不为零时,创建一个值节点记录行列、值信息
// 3.再将值节点放入切片中
var sparseArray []ValNode
// 标准的稀疏数组,首行需记录员对应二维数组的规模(行列数、以及默认值)
valNode := ValNode{
row: len(chessMap),
col: len(chessMap[0]),
val: 0,
}
sparseArray = append(sparseArray, valNode)
for i, v := range chessMap {
for j, v1 := range v {
if v1 != 0 {
valNode := ValNode{
row: i,
col: j,
val: v1,
}
sparseArray = append(sparseArray, valNode)
}
}
}
//输出稀疏数组
for i, valNode := range sparseArray {
fmt.Printf("index %d:%d %d %d\n", i, valNode.row, valNode.col, valNode.val.(int))
}
//4.将这个稀疏数组,存盘
// 将数据写入文件中 src\go_code\chapter20\sparsearray\chessMap.data
filepath := "E:\\goproject\\src\\go_code\\chapter20\\sparsearray\\chessMap.data"
file, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
log.Fatal(err)
}
// 要及时关闭file句柄
defer file.Close()
writer := bufio.NewWriter(file)
for _, valNode := range sparseArray {
str := fmt.Sprintf("%d %d %d", valNode.row, valNode.col, valNode.val.(int))
writer.WriteString(str + "\r\n")
// writer.WriteString(str)
}
// 因为writer是带缓存的,WriteString方法时
// 写入的数据存放在writer(*bufio.Writer)的缓存切片中
// 因此还需调用Flush方法,将缓冲中的数据写入下层的io.Writer接口
// 实现写的操作
writer.Flush() //如没有此操作,文件将不会更新写入内容
// 查看文件内容
content, err := ioutil.ReadFile(filepath)
if err != nil {
log.Fatal(err)
}
fmt.Printf("文件chessMap.data内容:\n%s", content)
// 5.恢复原数组
// 5.1从文件中取出数据 =>恢复原始数组
file, err = os.Open("E:\\goproject\\src\\go_code\\chapter20\\sparsearray\\chessMap.data")
if err != nil {
fmt.Println("open file err :", err)
}
// 开一个文件,一般后直接使用defer方法,当函数退出后及时关闭文件
defer func() {
err = file.Close()
if err != nil {
fmt.Println("close file err :", err)
} else {
fmt.Println("file closes successfully")
}
}() //使用defer+匿名函数方法及时关闭file句柄,否则会有内存泄漏
var newChessMap [][]int
reader1 := bufio.NewReader(file)
for {
str, err := reader1.ReadString('\n') //读取到换行符时结束
if err == io.EOF {
fmt.Println("`````") //io.EOF表示文件的末尾
break
}
str = strings.Trim(str, "\r\n")
strslice := strings.Split(str, " ")
row1, _ := strconv.ParseInt(strslice[0], 10, 64)
col1, _ := strconv.ParseInt(strslice[1], 10, 64)
val1, _ := strconv.ParseInt(strslice[2], 10, 64)
if val1 == 0 {
for i := 0; i < int(row1); i++ {
var rowslice []int
for j := 0; j < int(col1); j++ {
rowslice = append(rowslice, 0)
}
newChessMap = append(newChessMap, rowslice)
}
continue
}
newChessMap[int(row1)][int(col1)] = int(val1)
}
for _, v := range newChessMap {
for _, v1 := range v {
fmt.Printf("%d\t", v1)
}
fmt.Println()
}
}
3. 队列 queue
场景引出:
银行排队系统
队列介绍:
- 队列是一个有序列表,可以用环形数组或是链表来实现
- 遵循先入先出的原则
3.1 数组模拟队列思路
- 队列本身是有序列表,若使用数组的结构来储存队列的数据,则队列数组的声明如下,其中
MaxSize
是该队列的最大容量。 - 由于队列的输出和输入分别是从前后端来处理,因此需要两个变量
front
和rear
分别记录队列的前后端的下标,front会随着数据的输出而改变,rear则是随着输入而改变。当front == rear
时表示该队列为空。(front指向头部但不包含头部数据)
先完成一个非环形的队列: - 当数据存入队列时被称为
addqueue
,addqueue
的处理需要两个步骤:
1) 将尾指针往后移:rear+1,
2) 若尾指针rear小于或等于队列的最大下标MaxSize-1
,则表示数据存放成功,且存入在rear所指的数组元素中,否责无法存入数据。当rear==MaxSize-1
时,表示队列满。
思路分析:
代码实现:
package main
import (
"errors"
"fmt"
"os"
)
// 使用一个结构体管理队列
type Queue struct {
maxSize int //队列最大容量
array [5]int //数组=> 模拟队列
front int //表示指向队首 初始为-1 指向队首但不包含队首的元素
rear int //表示指向队尾 初始为-1 是指向队尾含有最后一个元素
}
//为Queue结构体绑定方法实现,队列功能
//1.添加数据
func (queue *Queue) AddQueue(val int) error {
// 先判断队列是否已经满了
if queue.rear == queue.maxSize-1 { //重要提示,rear是指向队尾(并且含有最后一个元素)
return errors.New("queue full")
}
// 能添加数据,则尾部指针rear往后移一位
queue.rear++
queue.array[queue.rear] = val
return nil
}
//1.环形数组 添加数据
func (queue *Queue) NewAddQueue(val int) error {
// 先判断队列是否已经满了
//队列容量是确定的,即尾部指针和首部指针的差为该队列当前含有的元素个数
if queue.rear-queue.front == queue.maxSize {
return errors.New("queue full")
}
// 能添加数据,则尾部指针rear往后移一位
queue.rear++
index := queue.rear % queue.maxSize //循环使用数组
queue.array[index] = val
return nil
}
// 2. 显示队列,找到队首,然后遍历到队尾
func (queue *Queue) ShowQueue() {
// front 在此设计中指向队首但不包含队首的元素
fmt.Println("队列当前情况是:")
for i := queue.front + 1; i <= queue.rear; i++ {
fmt.Printf("array[%d]=%d\n", i, queue.array[i])
}
}
// 2.循环数组 显示队列,找到队首,然后遍历到队尾
func (queue *Queue) NewShowQueue() {
// front 在此设计中指向队首但不包含队首的元素
fmt.Println("队列当前情况是:")
for i := queue.front + 1; i <= queue.rear; i++ {
fmt.Printf("array[%d]=%d\n", i%queue.maxSize, queue.array[i%queue.maxSize])
}
}
// 3.从队列取出数据
func (queue *Queue) GetQueue() (int, error) {
// 先判断队列是否为空
if queue.front == queue.rear {
return -1, errors.New("queue empty")
}
queue.front++
val := queue.array[queue.front]
// fmt.Println("推出数据val=", val)
return val, nil
}
// 3.循环数组从队列取出数据
func (queue *Queue) NewGetQueue() (int, error) {
// 先判断队列是否为空
if queue.front == queue.rear {
return -1, errors.New("queue empty")
}
queue.front++
index := queue.front % queue.maxSize
val := queue.array[index]
// fmt.Println("推出数据val=", val)
return val, nil
}
func main() {
queue := &Queue{
maxSize: 5,
front: -1,
rear: -1,
}
var (
key string
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.NewAddQueue(val)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println("加入队列成功")
}
case "get":
val, err := queue.NewGetQueue()
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println("推出数据val=", val)
}
case "show":
queue.NewShowQueue()
case "exit":
os.Exit(0)
}
}
}
环形结构:
package main
import (
"errors"
"fmt"
"os"
)
// 使用一个结构体管理环形队列
type CircleQueue struct {
maxSize int
arrary [5]int //数组
head int //指向队列首 初始值为0 指向并包含队首元素
tail int //指向队尾 初始值为0 指向但不包含队尾巴元素
}
// 入队列
func (cq *CircleQueue) Push(val int) error {
if cq.IsFull() {
return errors.New("queue full")
}
cq.arrary[cq.tail] = val
cq.tail = (cq.tail + 1) % cq.maxSize //tail 在队尾但不包含最后的元素
return nil
}
// 出队列
func (cq *CircleQueue) Pop() (int, error) {
if cq.IsEmpty() {
return -1, errors.New("queue EMPTY")
}
val := cq.arrary[cq.head]
cq.head = (cq.head + 1) % cq.maxSize
return val, nil
}
// 判断环形队列为满
func (cq *CircleQueue) IsFull() bool {
return (cq.tail+1)%cq.maxSize == cq.head
}
// 判断环形队列空
func (cq *CircleQueue) IsEmpty() bool {
return cq.tail == cq.head
}
// 取出环形列表有多少个元素
func (cq *CircleQueue) Size() int {
// 关键点
return (cq.tail + cq.maxSize - cq.head) % cq.maxSize
}
// 显示队列信息
func (cq *CircleQueue) Show() {
// 取出当前队列有多少个元素
size := cq.Size()
if size == 0 {
fmt.Println("队列为空")
}
fmt.Println("队列当前信息:")
for i := cq.head; i < size; i++ {
index := i % cq.maxSize
fmt.Printf("arr[%d]=%d\n", index, cq.arrary[index])
}
}
func main() {
queue := &CircleQueue{
maxSize: 5,
head: 0,
tail: 0,
}
var (
key string
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("加入队列成功")
}
case "get":
val, err := queue.Pop()
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println("推出数据val=", val)
}
case "show":
queue.Show()
case "exit":
os.Exit(0)
}
}
}
4. 链表
链表的基本介绍
链表是有序的列表,其存储的元素的地址可以是无序的,因为其明确的下一个元素的地址指向。
相较而言,数组的存储的元素,地址是连续的,这是由于数组的数据查询,基于头地址(第一个元素的地址)根据数据类型占字节大小,递增寻找下一个元元素的地址。
链表在内存中的存储如下:
1. 单向链表的介绍
单链表示意图
在Go中,结构体是值类型,故链表元素间的传递,中间会先指向“地址”,再指向“地址”所指向的值空间。
一般情况下,为了较好的对单链表进行增删改查的操作,都会设计头节点。
头节点的主用:用来表示链表的头,本身该节点不存放数据。
代码实现:
package main
import "fmt"
// 创建一个结构体管理链表
type HeroNode struct {
no int //第几个节点
name string //节点内容
nicname string //节点内容
next *HeroNode //指向下一个节点
}
// 给链表插入一个节点
// 编写第一种插入方法,在单链表的最后插入
func InsertHeroNode(head, newHeroNode *HeroNode) {
// 思路
// 1,先找到该链表的最后节点
// 2.由于头节点十分重要,轻易不要动用,故创建一个辅助节点【跑龙套,帮忙】
temp := head
for {
if temp.next == nil { //链表到最后节点了
// 将新的节点加入到链表的最后
temp.next = newHeroNode
break
}
temp = temp.next
}
}
// 编写第二种插入方法,根据节点的no字段大小顺序插入
func InsertHeroNodeByno(head, newHeroNode *HeroNode) {
// 创建辅助节点
temp := head
for {
if temp.next == nil { //节点到最后了
break
} else if temp.next.no <= newHeroNode.no {
//节点按照no降序排序,
// 相同大小的no节点后插入的,排在前面
//从顺序排序 则temp.next.no >= newHeroNode.no,
break
}
temp = temp.next
}
newHeroNode.next = temp.next
temp.next = newHeroNode
}
//删除某个节点
func deleteHeroNode(head *HeroNode, no int) {
temp := head
for {
if temp.next == nil {
fmt.Println("该节点不存在")
return
}
if temp.next.no == no {
break
}
temp = temp.next
}
temp.next = temp.next.next
}
// 从链表中获取信息
func getHeroNode(head *HeroNode) {
temp := head
heroNode := HeroNode{}
if temp.next == nil {
fmt.Println("链表为空")
return
}
heroNode = *temp.next
//并删除链表中的该节点
deleteHeroNode(head, heroNode.no)
}
func ListHeroNode(head *HeroNode) {
// 1.仍然创建一个辅助节点
temp := head
// 先判断链表是否为空
if temp.next == nil {
fmt.Println("链表为空")
return
}
for {
temp = temp.next
fmt.Printf("[%d,%s,%s] ==>", temp.no, temp.name, temp.nicname)
if temp.next == nil {
break
}
}
}
func main() {
// 1.先创建一个头节点
head := HeroNode{}
head1 := HeroNode{}
// 2.链表中创建新的节点
Hero1 := HeroNode{
no: 1,
name: "宋江",
nicname: "及时雨",
}
Hero2 := HeroNode{
no: 2,
name: "吴庸",
nicname: "智多星",
}
Hero3 := HeroNode{
no: 3,
name: "林聪",
nicname: "豹子头",
}
Hero4 := HeroNode{
no: 3,
name: "卢俊义",
nicname: "九纹龙",
}
//3.添加
InsertHeroNode(&head, &Hero1)
InsertHeroNode(&head, &Hero2)
InsertHeroNode(&head, &Hero3)
InsertHeroNodeByno(&head1, &Hero2)
InsertHeroNodeByno(&head1, &Hero3)
InsertHeroNodeByno(&head1, &Hero1)
InsertHeroNodeByno(&head1, &Hero4)
// 4.显示
ListHeroNode(&head)
fmt.Println()
ListHeroNode(&head1)
fmt.Println()
// [1,宋江,及时雨] ==>
// [3,卢俊义,九纹龙] ==>[3,林聪,豹子头] ==>[2,吴庸,智多星] ==>[1,宋江,及时雨] ==>
// 由于节点含有的next为引用类型,且HeroNode绑定的方法全是指针类型,
// 故对链表内容(元素next字段)的修改会直接修改到链表中元素的实际内容
// 5.删除
deleteHeroNode(&head1, 1)
deleteHeroNode(&head1, 3)
ListHeroNode(&head1)
// [3,林聪,豹子头] ==>[2,吴庸,智多星] ==>
}
2. 链表构成队列结构:
使用上述构建的getHeroNode()
和InsertHeroNode()
方法即可以实现队列结构的功能。
3. 双向链表介绍
1)单向链表,查找的方向只能是从单向的(从头到尾),双向链表则是可以从前或者从后查找
2)单向链表不能自我删除,需要靠辅助节点找到相应的待删除的节点;双向链表可以实现自我删除。
代码实现:
package main
import "fmt"
type HeroNode struct {
no int //第几个节点
name string //节点内容
nicname string //节点内容
next *HeroNode //指向下一个节点
pre *HeroNode //指向前一个节点
}
//给双向链表插入一个节点
//编写第一种插入方法,在双向链表尾巴加入
func InsertHeroNode(head, newHeroNode *HeroNode) {
temp := head
// 找到最后的节点
for {
if temp.next == nil {
temp.next = newHeroNode
newHeroNode.pre = temp
break
}
temp = temp.next
}
}
// 编写第二种插入方法,根据节点的no字段大小顺序插入
func InsertHeroNodeByno(head, newHeroNode *HeroNode) {
// 创建辅助节点
temp := head
for {
if temp.next == nil { //节点到最后了
break
} else if temp.next.no <= newHeroNode.no {
//节点按照no降序排序,
// 相同大小的no节点后插入的,排在前面
//从顺序排序 则temp.next.no >= newHeroNode.no,
break
}
temp = temp.next
}
newHeroNode.next = temp.next
newHeroNode.pre = temp
temp.next = newHeroNode
if temp.next != nil { //在最后增加
temp.next.pre = newHeroNode
}
}
//删除某个节点
func deleteHeroNode(head *HeroNode, no int) {
temp := head
for {
if temp.next == nil {
fmt.Println("该节点不存在")
return
}
if temp.no == no {
break
}
temp = temp.next
}
temp.pre.next = temp.next
if temp.next != nil { //删除最后一个节点
temp.next.pre = temp.pre
}
}
//从前往后显示
func ListHeroNode(head *HeroNode) {
// 1.仍然创建一个辅助节点
temp := head
// 先判断链表是否为空
if temp.next == nil {
fmt.Println("链表为空")
return
}
for {
temp = temp.next
fmt.Printf("[%d,%s,%s] ==>", temp.no, temp.name, temp.nicname)
if temp.next == nil {
break
}
}
}
//从后往前显示
func ListHeroNode1(head *HeroNode) {
// 1.仍然创建一个辅助节点
temp := head
// 先判断链表是否为空
if temp.next == nil {
fmt.Println("链表为空")
return
}
for {
temp = temp.next
if temp.next == nil {
break
}
}
for {
fmt.Printf("[%d,%s,%s] ==>", temp.no, temp.name, temp.nicname)
temp = temp.pre
if temp.pre == nil {
break
}
}
}
func main() {
head := &HeroNode{}
Hero1 := &HeroNode{
no: 1,
name: "宋江",
nicname: "及时雨",
}
Hero2 := &HeroNode{
no: 2,
name: "吴庸",
nicname: "智多星",
}
Hero3 := &HeroNode{
no: 3,
name: "林聪",
nicname: "豹子头",
}
Hero4 := &HeroNode{
no: 3,
name: "卢俊义",
nicname: "九纹龙",
}
InsertHeroNode(head, Hero1)
InsertHeroNode(head, Hero2)
InsertHeroNode(head, Hero3)
InsertHeroNode(head, Hero4)
// 从后往前显示
ListHeroNode1(head)
fmt.Println()
deleteHeroNode(head, 3)
ListHeroNode(head)
}
4. 环形链表
创建单方向环形链表时,为了便于管理,仍采用head
头部的概念,但该head
本身也存储数据。
由于环形列表实际上没有头部的概念,为了便于管理增加head
指代。与单链表的head
不包含内容不会被删除不同,循环链表的任何节点都是可以被删除的。因此,在实现删除环形链表中某个值节点时,需特别注意该节点是否为指定的head
,若是需要返回新的head
指代节点,负责main
栈中的head
还是指向被删除的值节点,往后不能再通过main
栈中的head
对循环链表实现CRUD
操作了。
示意图
package main
import (
"errors"
"fmt"
)
type catNode struct {
no int
name string
next *catNode
}
func InsertcatNode(head, newcatNode *catNode) {
//与单链表带头方式不同处:
// 单链表的头部不存放数据,只是next 指向下一个 值节点的地址空间
// 循环列表实则上没有头部的概念,因为处处都可以为头,但为了初始化(创建和管理的需求)
// 使用头节点的思想,但在循环列表中头节点也等价于值节点,即头节点也存放数据
//先判断该循环链表是否为空
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("该循环列表为空")
return
}
for {
fmt.Printf("cat的信息为=[no:%d name:%s] ->", temp.no, temp.name)
if temp.next == head {
fmt.Printf("cat的信息为=[no:%d name:%s]\n", head.no, head.name)
break
}
temp = temp.next
}
}
//删除
func DelcatNode(head *catNode, id int) (*catNode, error) {
//创建两个辅助节点
temp := head
helper := head
// helper指向最后的节点
for {
if helper.next == head {
break
}
helper = helper.next
}
// helper指向最后节点 和 temp指向头节点 便于单循环连边的删除操作
// 判断是否为空链表
if temp.next == nil {
return head, errors.New("head 没指向一个链表")
}
//如果只有一个节点
if temp.next == head {
if temp.no == id {
temp.next = nil
}
return head, nil
}
//如果含有多个节点
for {
if temp.no == id {
if temp == head {
head = temp.next
}
helper.next = temp.next
return head, nil
}
if temp.next == head { //达到最后节点
return head, errors.New("del failed ")
}
temp = temp.next
helper = helper.next
}
}
func main() {
head := &catNode{}
catNode1 := &catNode{
no: 1,
name: "tom",
}
catNode2 := &catNode{
no: 2,
name: "jarry",
}
catNode3 := &catNode{
no: 3,
name: "curry",
}
InsertcatNode(head, catNode1)
InsertcatNode(head, catNode2)
InsertcatNode(head, catNode3)
ListCircleLink(head)
// ListCircleLink(catNode2)
// head, err := DelcatNode(head, 1)
// head, err := DelcatNode(head, 2)
head, err := DelcatNode(head, 10)
ListCircleLink(head)
fmt.Println("err=", err)
}
Josephu问题:
设编号为1,2,… n的n个人围坐一圈,约定编号为k (1<=k<=n)的人从1开始报数,数到m的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。
提示:
用一个不带头结点的循环链表来处理Josephu问题,先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。
package main
import "fmt"
//值节点结构体
type ValNode struct {
no int //编号
next *ValNode //指向下一个值节点的指针
}
// 构建单循环环形链表
func constructorCricleLink(n int) *ValNode { //链表的值节点个数,返回循环链表的一个指代头节点
head := &ValNode{}
for i := 1; i <= n; i++ {
newValNote := &ValNode{
no: i,
}
addVlaNote(head, newValNote)
}
return head
}
// 添加环形链表的值节点
func addVlaNote(head, newValNote *ValNode) {
if head.next == nil {
head.no = newValNote.no
head.next = head //构建循环
return
}
temp := head
for {
if temp.next == head {
break
}
temp = temp.next
}
temp.next = newValNote
newValNote.next = head
}
//删除环形链表中的值
func pushNote(head *ValNode, n, m int) []int {
intSort := make([]int, 0)
temp := head
helper := head
for {
if helper.next == head {
break
}
helper = helper.next
}
//n==0
if temp.next == nil {
fmt.Println("链表为空")
return intSort
}
//n==1
if temp.next == head {
temp.next = nil
intSort = append(intSort, temp.no)
return intSort
}
// n >=2
for {
if temp.next == temp {
intSort = append(intSort, temp.no)
temp.next = nil
break
}
// if i%n != 0 {
// continue
// }
for j := 1; j < m; j++ {
temp = temp.next
helper = helper.next
}
intSort = append(intSort, temp.no)
helper.next = temp.next
temp = temp.next
}
return intSort
}
//暴力法
func JosePhu(m, n int) []int {
numSort := make([]int, 0) //m =5 n=2
for i := n; i <= m*n; { //
if i%n == 0 {
index := (i - 1) % m
numSort = append(numSort, index+1)
// head = delValNote(head, index)
}
i += n
}
return numSort
}
func ListCircleLink(head *ValNode) {
fmt.Println("循环列表情况如下:")
temp := head
if temp.next == nil {
fmt.Println("该循环列表为空")
return
}
for {
fmt.Printf("cat的信息为=[no:%d] ->", temp.no)
if temp.next == head {
fmt.Printf("cat的信息为=[no:%d]\n", head.no)
break
}
temp = temp.next
}
}
func main() {
// 输入n=5 间隔m=2
head := constructorCricleLink(5) //输入n=5
ListCircleLink(head)
s := pushNote(head, 5, 2)
fmt.Println(s)
// numSort := make([]int, 5)
// for i := 1; i <= 10; i++ {
// if i%2 == 0 {
// index := i % 5
// numSort = append(numSort, index)
// head = delValNote(head, index)
// }
// }
}
排序
排序介绍
排序是将一组数据,依指定的顺序进行排列的过程。
排序的分类:
1)内部排序:
将所需要处理的所有数据都加载到内部存储器中进行排序。包括:交换式排序法、选择式排序法和插入式排序法
2)外部排序:
数据量过大时,无法全部加载到内存中,需要借助外部存储器进行排序。有:合并排序法 和 直接合并排序法
交换式排序法
运用数据比较后,以判断规则对数据位置进行交换,以达到排序的目的。
交换式排序法又可分为:
1)冒泡排序法(Bubble sort)
2)快速排序法(Quick sort)
排序速度:冒泡<选择<插入<快速
- 1.冒泡排序
- 2.选择排序
- 3.插入排序
- 4.快速排序
1.冒泡排序
//冒泡法:分为轮次和每轮的交换次数,两者的关系为 第几轮+该轮交换次数 = 数组长度
//从后往前
func bubbleSort(slice []int) {
for i := 1; i < len(slice); i++ { //总轮数= 数组长度-1
temp := 0
flag := 0
for j := len(slice) - 1; j >= i; j-- { //表示第i论时的j次交换 i + j = 5
if slice[j] < slice[j-1] {
temp = slice[j]
slice[j] = slice[j-1]
slice[j-1] = temp
flag++
}
}
if flag == 0 {
switch i {
case 1:
fmt.Println("本身有序")
default:
fmt.Printf("第%v轮完成排序\n", i-1)
}
break
}
}
fmt.Println("排序后:", slice)
}
//从前往后
func bubbleSort1(slice []int) {
for i := 1; i < len(slice); i++ { //总轮数= 数组长度-1
temp := 0
flag := 0
for j := 0; j < len(slice)-i; j++ { //表示第i论时的j次交换 i + j = 5
if slice[j] > slice[j+1] {
temp = slice[j]
slice[j] = slice[j+1]
slice[j+1] = temp
flag++
}
}
if flag == 0 {
switch i {
case 1:
fmt.Println("本身有序")
default:
fmt.Printf("第%v轮完成排序\n", i-1)
}
break
}
}
fmt.Println("从前向后~排序后:", slice)
}
func main() {
intArr := [5]int{1, 15, 29, 8, 16}
slice := intArr[:]
fmt.Println("slice排序前:", slice) //slice排序前: [1 15 29 8 16]
bubbleSort(slice) //排序后: [1 8 15 16 29]
fmt.Println("-------------")
bubbleSort1(slice) //本身有序 从前向后~排序后: [1 8 15 16 29]
fmt.Println("intArr排序后:", intArr) //intArr排序后: [1 8 15 16 29]
}
2.选择排序
package main
import "fmt"
// selectsort
//从小到大
func SelectSort(arr []int) {
for i := 0; i < len(arr)-1; i++ { //交换轮次 len(arr)-1 每轮换一次
regular := arr[i]
regularIndex := i
for j := i + 1; j < len(arr); j++ {
if regular > arr[j] {
regularIndex = j
regular = arr[j]
}
}
if regularIndex == i { //比较比交换操作时间更短
continue //表示当前值即为此轮的最小值
}
arr[regularIndex] = arr[i]
arr[i] = regular
}
}
func main() {
arr := []int{10, 21, 13, 4, 55}
SelectSort(arr)
fmt.Println(arr)
}
3.插入排序
package main
import "fmt"
func insertSort(intslice []int) {
for i := 1; i < len(intslice); i++ { // 第二个元素开始比较,一共len(n-1)轮
insertVal := intslice[i]
insertIndex := i - 1 //首先要比较的是要插入的前一个元素
for insertIndex >= 0 && intslice[insertIndex] > insertVal { //从小到大排序
intslice[insertIndex+1] = intslice[insertIndex] //数据往右(后)移
insertIndex--
}
// 插入
if insertIndex+1 != i { //加快效率,应对插入值本身是在该index+1处的情况
intslice[insertIndex+1] = insertVal
}
fmt.Printf("第%d次插入后%v\n", i, intslice)
}
}
func main() {
intslice := []int{50, 12, 64, 1, 34, 27}
insertSort(intslice)
fmt.Println("main():")
fmt.Println("intslice=", intslice)
}
4.快速排序
package main
import "fmt"
func QuickSort(left, right int, intslice []int) {
l := left
r := right
// pivot 是中轴,支点
pivot := intslice[(l+r)/2]
temp := 0
// for循环的目的是
// 将比pivot小的数放到左边
// 将比pivot大的数放到右边
for l < r { //二分法的应用
//从 pivot的左边找到一个比它大的数
for intslice[l] < pivot {
l++
}
// 再从pivot的右边找到一个比它小的数
for intslice[r] > pivot {
r--
}
//交换
// 如果此时切片中刚好pivot值的前面元素都小于它自己
// pivot值的前面元素都大于它自己,则不需交换
if l >= r {
break
}
temp = intslice[l]
intslice[l] = intslice[r]
intslice[r] = temp
if intslice[l] == pivot {
r--
}
if intslice[r] == pivot {
l++
}
fmt.Println(intslice)
}
if l == r {
l++
r--
}
if left < r {
QuickSort(left, r, intslice)
}
if right > l {
QuickSort(l, right, intslice)
}
}
func main() {
intslice := []int{0, 14, 2, 13, 15, 3, 1}
QuickSort(0, len(intslice)-1, intslice)
}