Go语言高级 -- 高级数据结构
稀疏数组(sparsearray)
问题分析

基本介绍
当一个数组中大部分元素为0 或者其他值,可以使用稀疏数组来保存该数组
稀疏数组的处理方法是: 压缩算法
- 记录数组一共有几行几列,有多少个不同的值
- 把具有不同值得元素的寒烈及值记录在一个小规模的数组中,从而缩小程序的规模

发散思维
比如,对于一个字符串,如何进行压缩后发送?
将字符串中的各个元素出现次数,出现位置记录下来,然后发送后解压
应用案例
- 使用稀疏数组,来保留类似前面的二维数组(棋盘,地图等等)
- 把稀疏数组存盘,并且可以从新恢复成原来的二维数组
代码实现
package main
import "fmt"
// 稀疏数组的数据结构[结构体,结构体,结构体] 结构体中存放3个描述字段
type ValCode struct {
row int
col int
val int
}
func main() {
// 创建一个11 * 11 的二维数组数据
var chans [11][11]int
chans[2][5] = 1 // 1表示黑子
chans[3][6] = 2 // 2表示白子,0表示无子
for _,v := range(chans){
for _,val := range(v){
fmt.Printf("%d\t",val)
}
fmt.Println()
}
// 将11 * 11 的二维数组转换成一个稀疏数组
// 行 列 值
// row col val
// 11 11 0 二维数组是一个11 * 11的二维数组,默认值是0
// 2 5 1 3行 6 列有一个值为1
valCode := ValCode{
row : 11,
col : 11,
val : 0,
}
// 创建一个稀疏数组,用于存放各个参数,
var sarray []ValCode
sarray = append(sarray,valCode)
// 扫描整个二维数组,将二维数组转成稀疏数组
for i,v := range(chans){
for j,val := range(v){
if val != 0{
var valCode ValCode
valCode.row = i
valCode.col = j
valCode.val = val
sarray = append(sarray,valCode)
}
}
}
// 打印出稀疏数组中的所有值
for _,val := range(sarray){
fmt.Printf("%d\t%d\t%d\n",val.row,val.col,val.val)
}
}
效果展示

将稀疏数组恢复成原始的二维数组
// 将稀疏数组恢复成二维数组,因为Golang在定义数组的时候必须定义长度
// 可以用切片做
var erArray [11][11]int // 默认值为0
for i,val := range(sarray){
if i == 0{
continue
}
erArray[val.row][val.col] = val.val
}
// 查看二维数组
for _,v := range(erArray){
for _,val := range(v){
fmt.Printf("%d\t",val)
}
fmt.Println()
}
数组实现队列
队列是一个有序列表,可以用数组或是链表来实现
遵循先入先出的原则.即:先存入队列的数据要先取出,后存入的要后取出

单向队列(不会复用队列中的空闲空间)
- 队列本身是有序列表,若使用数组的结构来存储队列的数据,则队列数组的声明如下:其中,maxSize是该队列的最大容量
- 因为队列的输出,输入是分别从前后端来处理,因此需要两个变量front及rear分别记录队列前后端的下标,front会随着数据
输出而改变,而rear则是随着数据的输入而改变
实现思路
当我们将数据存入队列时称为"addqueue",addqueue 的处理需要有两个步骤:
- 当尾指针往后移:rear + 1,front == real
- 若尾指针 rear 小于等于队列的最大下标 MaxSize - 1,则将数据存入rear所指的数组元素中,否则无法存入数据
代码实现
package main
import (
"errors"
"fmt"
)
// 创建一个结构体用于管理队列
type Queue struct {
MaxSize int
Array [5]int
Begin int
last int
}
func (q *Queue) AddQueue(val int) (err error) {
// 判断队列是不是已经满了
if q.last == q.MaxSize-1 {
return errors.New("队列满了")
}
// 向数组的尾部添加元素
q.last++
q.Array[q.last] = val
return
}
func (q *Queue) ShowQueue() {
for i, v := range q.Array {
fmt.Printf("下标为%d的元素,值为%d \n", i, v)
}
}
func (q *Queue) GetArray()(val int,err error) {
// 判断队列是否为空
if q.Begin == q.last{
return -1,errors.New("队列为空")
}
q.Begin++
return q.Array[q.Begin],nil
}
func main() {
queue := &Queue{
MaxSize: 5,
Begin: -1,
last: -1,
}
var key int
var val int
for{
fmt.Println("1,添加,2,展示,3,获取一个")
fmt.Scanln(&key)
switch key {
case 1:
fmt.Println("输入想添加的值")
fmt.Scanln(&val)
err := queue.AddQueue(val)
if err != nil{
fmt.Println(err.Error())
}
case 2:
queue.ShowQueue()
case 3:
val,err := queue.GetArray()
if err != nil{
fmt.Println(err.Error())
}
fmt.Println(val)
}
}
}
上面代码基本上实现了基本队列结构,但是没有有效的利用数组的空间
环形队列
对前面数组的优化,充分利用数组的空间(通过取模的方式来实现)
- 尾索引的下一个为头索引时表示队列满,即将队列容量空出一个作为约定,这个在做判断队列满的时候需要注意
[tail + 1] % MaxSize == head [队列已满]
- tail == head [队列为空]
- 统计该队列有多少个元素:(tail + MaxSize - head) % MaxSize
代码实现
链表
链表是有序的列表,但是它在内存中的存储如下

单链表
一般为了比较好的对单链表进行CRUD 一般会设置一个头结点,用来标识链表头,本身不存放数据

单链表的应用实例
使用head头的单向链表实现 水浒传英雄排行榜管理,完成对英雄人物的增删改查,
不排序,尾部追加
package main
import "fmt"
// 创建一个结构体用于管理队列
type Heros struct {
id int
name string
next *Heros
}
func append(head *Heros,newHero *Heros){
temp := head
// 目的是找到最后一个元素
for {
if temp.next == nil{
break
}
temp = temp.next // 让temp 不断的指向下一个节点
}
// 此时的temp就是最后的那个链表元素
// 将新的节点添加到最后一个节点后面
temp.next = newHero
}
func list(head *Heros){
temp := head
// 先判断该链表是否是一个空链表
if temp.next == nil{
fmt.Println("空链表")
return
}
// 不是空的链表则要循环这个链表
for {
// 最后一个节点了
fmt.Printf("%d %s -->%v\n",temp.next.id,temp.next.name,temp.next.next)
// 打印完毕后,将临时变量指向下一个节点,
// 如果下一个节点的指针为空则跳出循环
// 否则继续循环打印
temp = temp.next
if temp.next == nil{
break
}
}
}
func main() {
// 创建一个头部节点
head := &Heros{}
// 创建一个新节点
newHero := &Heros{
id: 1,
name: "宋江",
}
newHero1 := &Heros{
id: 2,
name: "李逵",
}
append(head,newHero)
append(head,newHero1)
list(head)
}
排序,尾部追加 通过reverse变量控制根据id的编号从小到大或者从大到小的顺序添加
package main
import "fmt"
// 创建一个结构体用于管理队列
type Heros struct {
id int
name string
next *Heros
}
func append(head *Heros,newHero *Heros,reverse bool){
temp := head
// 目的是找到最后一个元素
//flag := true
for {
if temp.next == nil {
break
}
if reverse && temp.next.id >= newHero.id{
break
}else if !reverse && temp.next.id < newHero.id{
break
}
// 让temp 不断的指向下一个节点
temp = temp.next
}
//if !flag{ // 表示要插入了,必须先将新节点的下一个指向后一个节点,否则会丢失数据
newHero.next = temp.next
temp.next = newHero
//}else{
// return
//}
// 此时的temp就是最后的那个链表元素
// 将新的节点添加到最后一个节点后面
temp.next = newHero
}
func list(head *Heros){
temp := head
// 先判断该链表是否是一个空链表
if temp.next == nil{
fmt.Println("空链表")
return
}
// 不是空的链表则要循环这个链表
for {
// 最后一个节点了
fmt.Printf("%d %s -->%v\n",temp.next.id,temp.next.name,temp.next.next)
// 打印完毕后,将临时变量指向下一个节点,
// 如果下一个节点的指针为空则跳出循环
// 否则继续循环打印
temp = temp.next
if temp.next == nil{
break
}
}
}
func main() {
// 创建一个头部节点
head := &Heros{}
// 创建一个新节点
newHero := &Heros{
id: 3,
name: "宋江",
}
newHero1 := &Heros{
id: 1,
name: "李逵",
}
append(head,newHero,true)
append(head,newHero1,true)
list(head)
}
删除节点
package main
import "fmt"
// 创建一个结构体用于管理队列
type Heros struct {
id int
name string
next *Heros
}
func append(head *Heros,newHero *Heros,reverse bool){
temp := head
// 目的是找到最后一个元素
//flag := true
for {
if temp.next == nil {
break
}
if reverse && temp.next.id >= newHero.id{
break
}else if !reverse && temp.next.id < newHero.id{
break
}
// 让temp 不断的指向下一个节点
temp = temp.next
}
//if !flag{ // 表示要插入了,必须先将新节点的下一个指向后一个节点,否则会丢失数据
newHero.next = temp.next
temp.next = newHero
//}else{
// return
//}
// 此时的temp就是最后的那个链表元素
// 将新的节点添加到最后一个节点后面
temp.next = newHero
}
func list(head *Heros){
temp := head
// 先判断该链表是否是一个空链表
if temp.next == nil{
fmt.Println("空链表")
return
}
// 不是空的链表则要循环这个链表
for {
// 最后一个节点了
fmt.Printf("%d %s -->%v\n",temp.next.id,temp.next.name,temp.next.next)
// 打印完毕后,将临时变量指向下一个节点,
// 如果下一个节点的指针为空则跳出循环
// 否则继续循环打印
temp = temp.next
if temp.next == nil{
break
}
}
}
func delete(head *Heros,id int){
temp := head
for {
if temp.next == nil{
return
}else if temp.next.id == id {
break
}
temp = temp.next
}
// 将当前节点的next 指向被删除节点的下一个节点
temp.next = temp.next.next
}
func main() {
// 创建一个头部节点
head := &Heros{}
// 创建一个新节点
newHero := &Heros{
id: 3,
name: "宋江",
}
newHero1 := &Heros{
id: 1,
name: "李逵",
}
newHero2 := &Heros{
id: 2,
name: "林冲",
}
append(head,newHero,true)
append(head,newHero1,true)
append(head,newHero2,true)
list(head)
delete(head,2)
fmt.Println()
list(head)
}
双向链表
单向链表的缺点:
- 单向链表查找的方向只能是一个方向,而双向链表可以从前或者从后查找,效率会高一些
- 单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除
package main
import "fmt"
// 创建一个结构体用于管理队列
type Heros struct {
id int
name string
prev *Heros
next *Heros
}
func append(head *Heros,newHero *Heros,reverse bool){
temp := head
// 目的是找到最后一个元素
//flag := true
for {
if temp.next == nil {
break
}
if reverse && temp.next.id >= newHero.id{
break
}else if !reverse && temp.next.id < newHero.id{
break
}
// 让temp 不断的指向下一个节点
temp = temp.next
}
// 先将新节点的下一个节点指向当前节点的下一个节点
newHero.next = temp.next
newHero.prev = temp
if temp.next != nil{
temp.next.prev = newHero
}
temp.next = newHero
//newHero.next = temp.next
//temp.next = newHero
}
func list(head *Heros){
temp := head
// 先判断该链表是否是一个空链表
if temp.next == nil{
fmt.Println("空链表")
return
}
// 不是空的链表则要循环这个链表
for {
// 最后一个节点了
fmt.Printf("%d %s -->%v\n",temp.next.id,temp.next.name,temp.next.next)
// 打印完毕后,将临时变量指向下一个节点,
// 如果下一个节点的指针为空则跳出循环
// 否则继续循环打印
temp = temp.next
if temp.next == nil{
break
}
}
}
// 逆向打印
func reverselist(head *Heros){
temp := head
if temp.next == nil{
return
}
// 将temp指向到最后一个
for {
temp = temp.next
if temp.next == nil{
break
}
}
for {
fmt.Printf("%d %s -->%v\n",temp.id,temp.name,temp.next)
temp = temp.prev
if temp.prev == nil{
break
}
}
}
func delete(head *Heros,id int){
temp := head
for {
if temp.next == nil{
return
}else if temp.next.id == id {
break
}
temp = temp.next
}
// 将当前节点的next 指向被删除节点的下一个节点
temp.next = temp.next.next
// 将下一个节点的prev 指向当前节点
if temp.next != nil{
temp.next.prev = temp
}
}
func main() {
// 创建一个头部节点
head := &Heros{}
// 创建一个新节点
newHero := &Heros{
id: 3,
name: "宋江",
}
newHero1 := &Heros{
id: 1,
name: "李逵",
}
newHero2 := &Heros{
id: 2,
name: "林冲",
}
// 正序添加
append(head,newHero,true)
append(head,newHero1,true)
append(head,newHero2,true)
// 逆序添加
//append(head,newHero,false)
//append(head,newHero1,false)
//append(head,newHero2,false)
// 正序打印
list(head)
// 逆序打印
fmt.Println("删除前")
reverselist(head)
delete(head,2)
fmt.Println("删除后")
list(head)
}
单向环形链表

思路分析
需要删除的节点有可能是头结点,因为头结点也是有值得,所以头节点必须参与比较
- 先让temp指向head
- 定义一个helper,让helper指向环形链表的最后
- 让temp和要删除的节点的id进行比较,如果相同,则和helper完成删除,但是要考虑删除的节点是否是头结点
python防脱发技巧

浙公网安备 33010602011771号