Go语言高级 -- 高级数据结构

稀疏数组(sparsearray)

问题分析

基本介绍

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

稀疏数组的处理方法是: 压缩算法

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

发散思维

比如,对于一个字符串,如何进行压缩后发送?
将字符串中的各个元素出现次数,出现位置记录下来,然后发送后解压

应用案例

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

代码实现

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()
	}

数组实现队列

队列是一个有序列表,可以用数组或是链表来实现
遵循先入先出的原则.即:先存入队列的数据要先取出,后存入的要后取出

单向队列(不会复用队列中的空闲空间)

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

实现思路

当我们将数据存入队列时称为"addqueue",addqueue 的处理需要有两个步骤:

  1. 当尾指针往后移:rear + 1,front == real
  2. 若尾指针 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)
		}
	}
}

上面代码基本上实现了基本队列结构,但是没有有效的利用数组的空间

环形队列

对前面数组的优化,充分利用数组的空间(通过取模的方式来实现)

  1. 尾索引的下一个为头索引时表示队列满,即将队列容量空出一个作为约定,这个在做判断队列满的时候需要注意
[tail + 1] % MaxSize == head [队列已满]
  1. tail == head [队列为空]
  2. 统计该队列有多少个元素:(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)
}

双向链表

单向链表的缺点:

  1. 单向链表查找的方向只能是一个方向,而双向链表可以从前或者从后查找,效率会高一些
  2. 单向链表不能自我删除,需要靠辅助节点,而双向链表,则可以自我删除
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)

}

单向环形链表

思路分析

需要删除的节点有可能是头结点,因为头结点也是有值得,所以头节点必须参与比较

  1. 先让temp指向head
  2. 定义一个helper,让helper指向环形链表的最后
  3. 让temp和要删除的节点的id进行比较,如果相同,则和helper完成删除,但是要考虑删除的节点是否是头结点
posted @ 2021-10-15 17:32  河图s  阅读(68)  评论(0)    收藏  举报