Go语言-数据结构与算法

go语言之专业数据结构与算法

3.golang实现数组结构

code\ArrayList\ArrayList.go
package ArrayList

import (
	"errors"
	"fmt"
)

// 接口
type List interface {
	Size() int                                  // 数组大小
	Get(index int) (interface{}, error)         // 抓取第几个元素
	Set(index int, newval interface{}) error    // 修改数据
	Insert(index int, newval interface{}) error // 插入数据
	Append(newval interface{})                  // 追加
	Clear()                                     // 清空
	Delete(index int) error                     // 删除
	String() string                             // 返回字符串
}

// 数据结构,字符串,整数,实数
type ArrayList struct {
	dataStore []interface{} // 数组存储
	TheSize   int           // 数组的大小
}

func NewArrayList() *ArrayList {
	list := new(ArrayList)                      // 初始化结构体
	list.dataStore = make([]interface{}, 0, 10) // 开辟空间10个
	list.TheSize = 0
	return list
}

func (list *ArrayList) Size() int {
	return list.TheSize // 返回数据大小
}

func (list *ArrayList) Append(newval interface{}) {
	list.dataStore = append(list.dataStore, newval) // 叠加数据
	list.TheSize++
}

func (list *ArrayList) Get(index int) (interface{}, error) { // 抓取第几个元素
	if index < 0 || index > list.TheSize {
		return nil, errors.New("索引越界")
	}
	return list.dataStore[index], nil
}

func (list *ArrayList) String() string {
	return fmt.Sprint(list.dataStore)
}

调用ArrayList.go

code\main.go

package main

import (
	"fmt"

	"./ArrayList"
)

func main() {
	list := ArrayList.NewArrayList()
	list.Append(1)
	list.Append(2)
	list.Append(3)
	fmt.Println(list)
}
package main

import (
	"fmt"

	"./ArrayList"
)

func main1() {
	list := ArrayList.NewArrayList()
	list.Append(1)
	list.Append(2)
	list.Append(3)
	fmt.Println(list)
}

func main2() {
	list := ArrayList.NewArrayList()
	list.Append("a1")
	list.Append("b2")
	list.Append("c3")
	fmt.Println(list.TheSize) //TheSize大写包外可见
}

func main() {
	var list ArrayList.List = ArrayList.NewArrayList()
	list.Append("a1")
	list.Append("b2")
	list.Append("c3")
	fmt.Println(list.Size) //Size大写包外可见
}

    var list ArrayList.List = ArrayList.NewArrayList() 无法编译,由于接口中的方法没有都实现。

package ArrayList

import (
	"errors"
	"fmt"
)

// 接口
type List interface {
	Size() int                                  // 数组大小
	Get(index int) (interface{}, error)         // 抓取第几个元素
	Set(index int, newval interface{}) error    // 修改数据
	Insert(index int, newval interface{}) error // 插入数据
	Append(newval interface{})                  // 追加
	Clear()                                     // 清空
	Delete(index int) error                     // 删除
	String() string                             // 返回字符串
}

// 数据结构,字符串,整数,实数
type ArrayList struct {
	dataStore []interface{} // 数组存储
	TheSize   int           // 数组的大小
}

func NewArrayList() *ArrayList {
	list := new(ArrayList)                      // 初始化结构体
	list.dataStore = make([]interface{}, 0, 10) // 开辟空间10个
	list.TheSize = 0
	return list
}

// 数组大小
func (list *ArrayList) Size() int {
	return list.TheSize // 返回数据大小
}

// 追加
func (list *ArrayList) Append(newval interface{}) {
	list.dataStore = append(list.dataStore, newval) // 叠加数据
	list.TheSize++
}

// 抓取第几个元素
func (list *ArrayList) Get(index int) (interface{}, error) {
	if index < 0 || index > list.TheSize {
		return nil, errors.New("索引越界")
	}
	return list.dataStore[index], nil
}

// 返回字符串
func (list *ArrayList) String() string {
	return fmt.Sprint(list.dataStore)
}

// 修改数据
func (list *ArrayList) Set(index int, newval interface{}) error {
	return nil
}

// 插入数据
func (list *ArrayList) Insert(index int, newval interface{}) error {
	return nil
}

// 清空
func (list *ArrayList) Clear() {

}

// 删除
func (list *ArrayList) Delete(index int) error {
	return nil
}

现在编译没有问题

func main() {
	// 定义接口对象,赋值的对象必须实现接口的所有方法
	var list ArrayList.List = ArrayList.NewArrayList()
	list.Append("a1")
	list.Append("b2")
	list.Append("c3")
	fmt.Println(list.Size) //TheSize大写包外可见
}
package ArrayList

import (
	"errors"
	"fmt"
)

// 接口
type List interface {
	Size() int                                  // 数组大小
	Get(index int) (interface{}, error)         // 抓取第几个元素
	Set(index int, newval interface{}) error    // 修改数据
	Insert(index int, newval interface{}) error // 插入数据
	Append(newval interface{})                  // 追加
	Clear()                                     // 清空
	Delete(index int) error                     // 删除
	String() string                             // 返回字符串
}

// 数据结构,字符串,整数,实数
type ArrayList struct {
	dataStore []interface{} // 数组存储
	TheSize   int           // 数组的大小
}

func NewArrayList() *ArrayList {
	list := new(ArrayList)                      // 初始化结构体
	list.dataStore = make([]interface{}, 0, 10) // 开辟空间10个
	list.TheSize = 0
	return list
}

func (list *ArrayList) checkisFull() {
	if list.TheSize == cap(list.dataStore) { // 判断内存使用
		newdataStore := make([]interface{}, 0, 2*list.TheSize) // 开辟双倍内存
		copy(newdataStore, list.dataStore)                     // 拷贝
		list.dataStore = newdataStore                          // 赋值
	}
}

// 数组大小
func (list *ArrayList) Size() int {
	return list.TheSize // 返回数据大小
}

// 追加
func (list *ArrayList) Append(newval interface{}) {
	list.dataStore = append(list.dataStore, newval) // 叠加数据
	list.TheSize++
}

// 抓取第几个元素
func (list *ArrayList) Get(index int) (interface{}, error) {
	if index < 0 || index > list.TheSize {
		return nil, errors.New("索引越界")
	}
	return list.dataStore[index], nil
}

// 返回字符串
func (list *ArrayList) String() string {
	return fmt.Sprint(list.dataStore)
}

// 修改数据
func (list *ArrayList) Set(index int, newval interface{}) error {
	if index < 0 || index > list.TheSize {
		return errors.New("索引越界")
	}
	list.dataStore[index] = newval // 数据设置
	return nil
}

// 插入数据
func (list *ArrayList) Insert(index int, newval interface{}) error {
	if index < 0 || index > list.TheSize {
		return errors.New("索引越界")
	}
	list.checkisFull()                               // 检查内存,如果满了,自动追加
	list.dataStore = list.dataStore[:list.TheSize+1] // 插入数据,内存移动一位
	for i := list.TheSize; i > index; i-- {          // 从后往前移动
		list.dataStore[i] = list.dataStore[i-1]
	}
	list.dataStore[index] = newval // 插入数据
	list.TheSize++                 // 索引追加
	return nil
}

// 清空
func (list *ArrayList) Clear() {
	// 把原先的内存废弃,重写开辟内存
	list.dataStore = make([]interface{}, 0, 10) // 开辟空间10个
	list.TheSize = 0
}

// 删除
func (list *ArrayList) Delete(index int) error {
	// 重写叠加,跳过index
	list.dataStore = append(list.dataStore[:index], list.dataStore[index+1:]...)
	list.TheSize--
	return nil
}

测试

func main() {
	// 定义接口对象,赋值的对象必须实现接口的所有方法
	var list ArrayList.List = ArrayList.NewArrayList()
	list.Append("a1")
	list.Append("b2")
	list.Append("c3")
	for i := 0; i < 10; i++ {
		list.Insert(1, "x5")
		fmt.Println(list) 
	}
	
	fmt.Println(list) 
}

内存失效

func main() {
	// 定义接口对象,赋值的对象必须实现接口的所有方法
	var list ArrayList.List = ArrayList.NewArrayList()
	list.Append("a1")
	list.Append("b2")
	list.Append("c3")
	for i := 0; i < 4; i++ {
		list.Insert(1, "x5")
		fmt.Println(list)
	}
	fmt.Println("delete")
	list.Delete(5)
	fmt.Println(list)
}

 

func main() {
	// 定义接口对象,赋值的对象必须实现接口的所有方法
	var list ArrayList.List = ArrayList.NewArrayList()
	list.Append("a1")
	list.Append("b2")
	list.Append("c3")
	for i := 0; i < 15; i++ {
		list.Insert(1, "x5")
		fmt.Println(list)
	}

	// fmt.Println(list)
}

优化接口的插入方法,checkisFull方法

func (list *ArrayList) checkisFull() {
	if list.TheSize == cap(list.dataStore) { // 判断内存使用
		fmt.Println("A", list.dataStore)

		newdataStore := make([]interface{}, 0, 2*list.TheSize) // 开辟双倍内存
		copy(newdataStore, list.dataStore)                     // 拷贝
		list.dataStore = newdataStore                          // 赋值
		fmt.Println("A", list.dataStore)
		fmt.Println("A", newdataStore)

	}
}

 

func (list *ArrayList) checkisFull() {
	if list.TheSize == cap(list.dataStore) { // 判断内存使用
		fmt.Println("A", list.dataStore)

		newdataStore := make([]interface{}, 0, 2*list.TheSize) // 开辟双倍内存
		// copy(newdataStore, list.dataStore)                     // 拷贝
		// 使用for循环赋值
		for i := 0; i < len(list.dataStore); i++ {
			newdataStore[i] = list.dataStore[i]
		}
		list.dataStore = newdataStore // 赋值
		fmt.Println("A", list.dataStore)
		fmt.Println("A", newdataStore)

	}
}

newdataStore := make([]interface{}, 0, 2*list.TheSize)

0导致并没有开辟内存,

newdataStore := make([]interface{}, 2*list.TheSize, 2*list.TheSize)

func (list *ArrayList) checkisFull() {
	if list.TheSize == cap(list.dataStore) { // 判断内存使用
		fmt.Println("A", list.dataStore)
        // make 中间的参数,0,没有开辟内存
		newdataStore := make([]interface{}, 2*list.TheSize, 2*list.TheSize) // 开辟双倍内存
		copy(newdataStore, list.dataStore)                     // 拷贝
		// 使用for循环赋值
		// for i := 0; i < len(list.dataStore); i++ {
		//	 newdataStore[i] = list.dataStore[i]
		// }
		list.dataStore = newdataStore // 赋值
		fmt.Println("A", list.dataStore)
		fmt.Println("A", newdataStore)

	}
}

 

4.golang实现数组迭代器

code\ArrayList\ArrayListIterator.go

package ArrayList

import "errors"

type Iterator interface {
	HasNext() bool              // 是否有下一个
	Next() (interface{}, error) // 下一个
	Remove()                    // 删除
	GetIndex() int              // 得到索引
}

type Iterable interface {
	Iterator() Iterator // 构造初始化接口
}

// 构造指针访问数组
type ArraylistIterator struct {
	list         *ArrayList // 数组指针
	currentindex int        // 当前索引
}

func (list *ArrayList) Iterator() Iterator {
	it := new(ArraylistIterator)
	it.currentindex = 0
	it.list = list
	return it
}

func (it *ArraylistIterator) HasNext() bool {
	return it.currentindex < it.list.TheSize // 是否有下一个
}

func (it *ArraylistIterator) Next() (interface{}, error) {
	if !it.HasNext() {
		return nil, errors.New("没有下一个")
	}
	value, err := it.list.Get(it.currentindex) // 抓取当前数据
	it.currentindex++
	return value, err
}

func (it *ArraylistIterator) Remove() {
	it.currentindex--
	it.list.Delete(it.currentindex) // 删除一个元素
}

func (it *ArraylistIterator) GetIndex() int {
	return it.currentindex
}

D:\Workspace\Go\src\projects\code\ArrayList\ArrayList.go

package ArrayList

import (
	"errors"
	"fmt"
)

// 接口
type List interface {
	Size() int                                  // 数组大小
	Get(index int) (interface{}, error)         // 抓取第几个元素
	Set(index int, newval interface{}) error    // 修改数据
	Insert(index int, newval interface{}) error // 插入数据
	Append(newval interface{})                  // 追加
	Clear()                                     // 清空
	Delete(index int) error                     // 删除
	String() string                             // 返回字符串
	Iterator() Iterator
}

// 数据结构,字符串,整数,实数
type ArrayList struct {
	dataStore []interface{} // 数组存储
	TheSize   int           // 数组的大小
}

func NewArrayList() *ArrayList {
	list := new(ArrayList)                      // 初始化结构体
	list.dataStore = make([]interface{}, 0, 10) // 开辟空间10个
	list.TheSize = 0
	return list
}

func (list *ArrayList) checkisFull() {
	if list.TheSize == cap(list.dataStore) { // 判断内存使用
		fmt.Println("A", list.dataStore)
		// make 中间的参数,0,没有开辟内存
		newdataStore := make([]interface{}, 2*list.TheSize, 2*list.TheSize) // 开辟双倍内存
		copy(newdataStore, list.dataStore)                                  // 拷贝
		// 使用for循环赋值
		// for i := 0; i < len(list.dataStore); i++ {
		// 	newdataStore[i] = list.dataStore[i]
		// }
		list.dataStore = newdataStore // 赋值
		fmt.Println("A", list.dataStore)
		fmt.Println("A", newdataStore)

	}
}

// 数组大小
func (list *ArrayList) Size() int {
	return list.TheSize // 返回数据大小
}

// 追加
func (list *ArrayList) Append(newval interface{}) {
	list.dataStore = append(list.dataStore, newval) // 叠加数据
	list.TheSize++
}

// 抓取第几个元素
func (list *ArrayList) Get(index int) (interface{}, error) {
	if index < 0 || index > list.TheSize {
		return nil, errors.New("索引越界")
	}
	return list.dataStore[index], nil
}

// 返回字符串
func (list *ArrayList) String() string {
	return fmt.Sprint(list.dataStore)
}

// 修改数据
func (list *ArrayList) Set(index int, newval interface{}) error {
	if index < 0 || index > list.TheSize {
		return errors.New("索引越界")
	}
	list.dataStore[index] = newval // 数据设置
	return nil
}

// 插入数据
func (list *ArrayList) Insert(index int, newval interface{}) error {
	if index < 0 || index > list.TheSize {
		return errors.New("索引越界")
	}
	list.checkisFull()                               // 检查内存,如果满了,自动追加
	list.dataStore = list.dataStore[:list.TheSize+1] // 插入数据,内存移动一位
	for i := list.TheSize; i > index; i-- {          // 从后往前移动
		list.dataStore[i] = list.dataStore[i-1]
	}
	list.dataStore[index] = newval // 插入数据
	list.TheSize++                 // 索引追加
	return nil
}

// 清空
func (list *ArrayList) Clear() {
	// 把原先的内存废弃,重写开辟内存
	list.dataStore = make([]interface{}, 0, 10) // 开辟空间10个
	list.TheSize = 0
}

// 删除
func (list *ArrayList) Delete(index int) error {
	// 重写叠加,跳过index
	list.dataStore = append(list.dataStore[:index], list.dataStore[index+1:]...)
	list.TheSize--
	return nil
}

D:\Workspace\Go\src\projects\code\main.go

package main

import (
	"fmt"
	"projects/code/ArrayList"
)

func main() {
	// 定义接口对象,赋值的对象必须实现接口的所有方法
	var list ArrayList.List = ArrayList.NewArrayList()
	list.Append("a1")
	list.Append("b2")
	list.Append("c3")
	list.Append("d3")
	list.Append("f3")
	for it := list.Iterator(); it.HasNext(); {
		item, _ := it.Next()
		fmt.Println(item)
	}
}
PS D:\Workspace\Go\src\projects\code> go run main.go
a1
b2
c3
d3
f3

D:\Workspace\Go\src\projects\code\main.go

func main() {
	// 定义接口对象,赋值的对象必须实现接口的所有方法
	var list ArrayList.List = ArrayList.NewArrayList()
	list.Append("a1")
	list.Append("b2")
	list.Append("c3")
	list.Append("d3")
	list.Append("f3")
	for it := list.Iterator(); it.HasNext(); {
		item, _ := it.Next()
		if item == "d3" {
			it.Remove()
		}
		fmt.Println(item)
	}
	fmt.Println(list)
}
PS D:\Workspace\Go\src\projects\code> go run main.go
a1
b2
c3
d3
f3
[a1 b2 c3 f3]

5.数组栈的高级实现

D:\Workspace\Go\src\projects\code\StackArray\StackArray.go

package StackArray

type StackArray interface {
	Clear()                // 清空
	Size() int             // 大小
	Pop() interface{}      // 弹出
	Push(data interface{}) // 压入
	IsFull() bool          // 是否满了
	IsEmpty() bool         // 是否为空
}

type Stack struct {
	dataSource  []interface{}
	capsize     int // 最大范围
	currentsize int // 实际使用大小
}

func NewStack() *Stack {
	mystack := new(Stack)
	mystack.dataSource = make([]interface{}, 0, 10) // 数组
	mystack.capsize = 10                            // 空间
	mystack.currentsize = 0
	return mystack
}

func (mystack *Stack) Clear() {
	mystack.dataSource = make([]interface{}, 0, 10) // 数组
	mystack.currentsize = 0
	mystack.capsize = 10 // 空间

}
func (mystack *Stack) Size() int {
	return mystack.currentsize
}
func (mystack *Stack) Pop() interface{} {
	if !mystack.IsEmpty() {
		last := mystack.dataSource[mystack.currentsize-1]               // 最后一个数据
		mystack.dataSource = mystack.dataSource[:mystack.currentsize-1] // 删除最后一个数据
		mystack.currentsize--                                           // 删除
		return last
	}
	return nil
}
func (mystack *Stack) Push(data interface{}) {
	if !mystack.IsFull() {
		mystack.dataSource = append(mystack.dataSource, data) // 叠加数据,压入
		mystack.currentsize++
	}
}
func (mystack *Stack) IsFull() bool { // 判断满了
	if mystack.currentsize >= mystack.capsize {
		return true
	} else {
		return false
	}
}
func (mystack *Stack) IsEmpty() bool {
	if mystack.currentsize == 0 {
		return true
	} else {
		return false
	}
}

D:\Workspace\Go\src\projects\code\main.go

func main() {
	mystack := StackArray.NewStack()
	mystack.Push(1)
	mystack.Push(2)
	mystack.Push(3)
	mystack.Push(4)
	fmt.Println(mystack.Pop())
	fmt.Println(mystack.Pop())
	fmt.Println(mystack.Pop())
	fmt.Println(mystack.Pop())
}
PS D:\Workspace\Go\src\projects\code> go run main.go
4
3
2
1

D:\Workspace\Go\src\projects\code\ArrayList\ArrayListStack.go

package ArrayList

type StackArray interface {
	Clear()                // 清空
	Size() int             // 大小
	Pop() interface{}      // 弹出
	Push(data interface{}) // 压入
	IsFull() bool          // 是否满了
	IsEmpty() bool         // 是否为空
}

type Stack struct {
	myarray *ArrayList
	capsize int // 最大范围
}

func NewArrayListStack() *Stack {
	mystack := new(Stack)
	mystack.myarray = NewArrayList() // 数组
	mystack.capsize = 10             // 空间
	return mystack
}

func (mystack *Stack) Clear() {
	mystack.myarray.Clear()
	mystack.capsize = 10 // 空间

}
func (mystack *Stack) Size() int {
	return mystack.myarray.TheSize
}
func (mystack *Stack) Pop() interface{} {
	if !mystack.IsEmpty() {
		last := mystack.myarray.dataStore[mystack.myarray.TheSize-1] // 删除
		mystack.myarray.Delete(mystack.myarray.TheSize - 1)
		return last
	}
	return nil
}
func (mystack *Stack) Push(data interface{}) {
	if !mystack.IsFull() {
		mystack.myarray.Append(data)
	}
}
func (mystack *Stack) IsFull() bool { // 判断满了
	if mystack.myarray.TheSize >= mystack.capsize {
		return true
	} else {
		return false
	}
}
func (mystack *Stack) IsEmpty() bool {
	if mystack.myarray.TheSize == 0 {
		return true
	} else {
		return false
	}
}

D:\Workspace\Go\src\projects\code\main.go

func main() {
	mystack := ArrayList.NewArrayListStack()
	mystack.Push(1)
	mystack.Push(2)
	mystack.Push(3)
	mystack.Push(4)
	fmt.Println(mystack.Pop())
	fmt.Println(mystack.Pop())
	fmt.Println(mystack.Pop())
	fmt.Println(mystack.Pop())
}

D:\Workspace\Go\src\projects\code\ArrayList\ArrayListIteratorStack.go

package ArrayList

type StackArrayX interface {
	Clear()                // 清空
	Size() int             // 大小
	Pop() interface{}      // 弹出
	Push(data interface{}) // 压入
	IsFull() bool          // 是否满了
	IsEmpty() bool         // 是否为空
}

type StackX struct {
	myarray *ArrayList
	Myit    Iterator
}

func NewArrayListStackX() *StackX {
	mystack := new(StackX)
	mystack.myarray = NewArrayList()          // 数组
	mystack.Myit = mystack.myarray.Iterator() // 迭代
	return mystack
}

func (mystack *StackX) Clear() {
	mystack.myarray.Clear()
	mystack.myarray.TheSize = 0

}
func (mystack *StackX) Size() int {
	return mystack.myarray.TheSize
}
func (mystack *StackX) Pop() interface{} {
	if !mystack.IsEmpty() {
		last := mystack.myarray.dataStore[mystack.myarray.TheSize-1] // 删除
		mystack.myarray.Delete(mystack.myarray.TheSize - 1)
		return last
	}
	return nil
}
func (mystack *StackX) Push(data interface{}) {
	if !mystack.IsFull() {
		mystack.myarray.Append(data)
	}
}
func (mystack *StackX) IsFull() bool { // 判断满了
	if mystack.myarray.TheSize >= 10 {
		return true
	} else {
		return false
	}
}
func (mystack *StackX) IsEmpty() bool {
	if mystack.myarray.TheSize == 0 {
		return true
	} else {
		return false
	}
}

D:\Workspace\Go\src\projects\code\main.go

func main() {
	mystack := ArrayList.NewArrayListStackX()
	mystack.Push(1)
	mystack.Push(2)
	mystack.Push(3)
	mystack.Push(4)
	// fmt.Println(mystack.Pop())
	// fmt.Println(mystack.Pop())
	// fmt.Println(mystack.Pop())
	// fmt.Println(mystack.Pop())
	for it := mystack.Myit; it.HasNext(); {
		item, _ := it.Next()
		fmt.Println(item)
	}
}

6.栈模拟低级递归

1.斐波那契数列栈模拟递归

2.递归实现文件夹遍历

3.栈模拟文件递归

4.层级展示文件夹

5.数组队列的实现

6.队列实现遍历文件夹

7.循环队列

8.链式栈

9.链式队列

10.作业

1.排序与查找

2.选择排序

3.字符串比较大小

4.字符串选择排序

5.插入排序

6.冒泡排序

7.堆排序

8.快速排序

9.奇偶排序

1.归并排序

2.希尔排序

3.基数排序

4.统计次数排序

5.锦标赛排序

6.鸡尾酒

7.数据提取

8.数据排序时间

1.数据硬盘搜索

2.数据的内存搜索

3.数据的结构体数组内存模型查找

4.数据的map内存模型

5.快速排序编程实现

6.二分查找法

7.7二分查找与快速排序用于数据搜索

1.内容说明

2.二分查找法

3.顺序搜索数据

4.快速排序与二分查找在数据搜索实战

5.QQ数据的快速排序与二分查找

6.改良版快速排序

7.QQ的数据20秒排序完一个亿

8.性能调优中值搜索

9.斐波那契查找

10.二分查找变形写法

1.查询第N大的QQ

2.gnomesort

3.休眠排序

4.希尔排序改良梳子排序

5.木桶排序

6.三分查找

7.哈希表原理

8.set结构

9.作业

Go语言 数据结构与算法

20.4 稀疏 sparsearray 数组

20.4.1 先看一个实际的需求

编写的五子棋程序中,有存盘退出和续上盘的功能

稀疏数组的处理方法是 :
1) 记录数组一共有几行几列,有多少个不同的值
2) 思想:把具有不同值的元素的行列及值记录在一个小规模的数组中,从而 缩小程序 的规模

20.4.4 应用实例

1) 使用稀疏数组,来保留类似前面的二维数组 ( 棋盘、地图等等 )
2) 把稀疏数组存盘,并且可以从新恢复原来的二维数组数
3) 整体思路分析

4) 代码实现

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()
	}
}
PS D:\Workspace\Go\src\projects\gin-demo> go run test.go
0       0       0       0       0       0       0       0       0       0       0
0       0       1       0       0       0       0       0       0       0       0
0       0       0       2       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
当前的稀疏数组是:::::
0:11 11 0 %!d(MISSING)
1:1 2 1 %!d(MISSING)
2:2 3 2 %!d(MISSING)
恢复后的原始数据......
0       0       0       0       0       0       0       0       0       0       0
0       0       1       0       0       0       0       0       0       0       0
0       0       0       2       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
0       0       0       0       0       0       0       0       0       0       0
对老师的稀疏数组的改进
1) 将构建的稀疏数组,存盘 chessmap.data
2) 在恢复原始二维数组,要求从文件 chessmap.data 读取。

20.5 队列(queue)

20.5.1 队列的应用场景

20.5.3 数组模拟队列

先完成一个非环形的队列 ( 数组来实现 )

思路分析

 代码实现

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.rear == this.front { //队空
		return -1, errors.New("queue empty")
	}
	this.front++
	val = this.array[this.front]
	return val, err
}

//显示队列, 找到队首,然后到遍历到队尾
func (this *Queue) ShowQueue() {
	fmt.Println("队列当前的情况是:")
	//this.front 不包含队首的元素
	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)
		}
	}
}
PS D:\Workspace\Go\src\projects\gin-demo> go run test.go
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出程序
1
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出程序
add
输入你要入队列数
1
加入队列ok
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出程序
get
从队列中取出了一个数= 1
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
4. 输入 exit 表示退出程序
show
队列当前的情况是:

1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出程序
队列当前的情况是:
exit status 0xc000013a
PS D:\Workspace\Go\src\projects\gin-demo> go run test.go
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出程序
add
输入你要入队列数
1
加入队列ok
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出程序
get
从队列中取出了一个数= 1
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出程序
show
队列当前的情况是:

1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出程序
exit
PS D:\Workspace\Go\src\projects\gin-demo>
对上面代码的小结和说明
1 ) 上面代码实现了基本队列结构,但是 没有有效的利用数组 空间
2 ) 请思考,如何使用数组 实现一个环形的队列

20.5.4 数组模拟环形队列

分析思路 :
1) 什么时候表示队列满 (tail + 1) % maxSize hea d
2) tail == head 表示空
3) 初始化时, tail = 0 head = 0
4) 怎么统计该队列有多少个元素 (tail + maxSize - head ) % maxSize
代码实现
package main

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

//使用一个结构体管理环形队列
type CircleQueue struct {
	maxSize int    // 4
	array   [5]int // 数组
	head    int    // 指向队列队首 0
	tail    int    // 指向队尾
}

//如队列 AddQueue(push) GetQueue(pop)
//入队列
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
}

//出队列
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)
		}
	}
}
PS D:\Workspace\Go\src\projects\gin-demo> go run test.go
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出
add
输入你要入队列数
1
加入队列ok
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出
add
输入你要入队列数
4
加入队列ok
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出
add
输入你要入队列数
7
加入队列ok
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出
show
环形队列情况如下:
arr[0]=1        arr[1]=4        arr[2]=7
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出
get
从队列中取出了一个数= 1
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出
get
从队列中取出了一个数= 4
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出
get
从队列中取出了一个数= 7
1. 输入 add 表示添加数据到队列
2. 输入 get 表示从队列获取数据
3. 输入 show 表示显示队列
4. 输入 exit 表示退出
exit

20.6 链表

20.6.3 单链表的应用实例

案例的说明:
使用带 head 头的单向链表实现 –水浒英雄排行榜管理
完成对英雄人物的增删改查操作, 注 : 删除和修改 , 查找可以考虑学员独立完成
第一种方法在添加英雄时,直接添加到链表的尾部
代码实现:
package main

import "fmt"

//定义一个 HeroNode
type HeroNode struct {
	no       int
	name     string
	nickname string
	next     *HeroNode //这个表示指向下一个结点
}

//给链表插入一个结点
//编写第一种插入方法,在单链表的最后加入.[简单]
func InsertHreoNode(head *HeroNode, newHeroNode *HeroNode) {
	//思路
	//1. 先找到该链表的最后这个结点
	//2. 创建一个辅助结点[跑龙套, 帮忙]
	temp := head
	for {
		if temp.next == nil { //表示找到最后
			break
		}
		temp = temp.next // 让 temp 不断的指向下一个结点
	}

	//3. 将 newHeroNode 加入到链表的最后
	temp.next = newHeroNode
}

//给链表插入一个结点
//编写第 2 种插入方法,根据 no 的编号从小到大插入..【实用】
func InsertHreoNode2(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 DelHeroNode(head *HeroNode, id int) {
	temp := head
	flag := false
	// 找到要删除结点的no,和temp的下一个结点的no比较
	for {
		if temp.next == nil { // 说明到链表的最后
			break
		} else if temp.next.no == id {
			// 说明我们找到了
			flag = true
			break
		}
		temp = temp.next
	}
	if flag { // 找到,删除
		temp.next = temp.next.next
	} else {
		fmt.Println("sorry, 要删除的id不存在")
	}
}

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. 加入
	InsertHreoNode2(head, hero3)
	InsertHreoNode2(head, hero1)
	InsertHreoNode2(head, hero2)
	InsertHreoNode2(head, hero4)

	// 4. 显示
	ListHeroNode(head)

}
PS D:\Workspace\Go\src\projects\gin-demo> go run test.go
[1, 宋江, 及时雨]==>[2, 卢俊义, 玉麒麟]==>[3, 吴用, 智多星]==>[3, 林冲, 豹子头]==>

20.6.4 双向链表的应用实例

 示意图

代码实现

package main

import "fmt"

//定义一个 HeroNode
type HeroNode struct {
	no       int
	name     string
	nickname string
	pre      *HeroNode // 这个表示指向前一个结点
	next     *HeroNode // 这个表示指向下一个结点
}

//给双向链表插入一个结点
//编写第一种插入方法,在单链表的最后加入.[简单]
func InsertHreoNode(head *HeroNode, newHeroNode *HeroNode) {
	//思路
	//1. 先找到该链表的最后这个结点
	//2. 创建一个辅助结点[跑龙套, 帮忙]
	temp := head
	for {
		if temp.next == nil { //表示找到最后
			break
		}
		temp = temp.next // 让 temp 不断的指向下一个结点
	}

	//3. 将 newHeroNode 加入到链表的最后
	temp.next = newHeroNode
	newHeroNode.pre = temp
}

//给双向链表插入一个结点
//编写第 2 种插入方法,根据 no 的编号从小到大插入..【实用】
func InsertHreoNode2(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 // ok
		newHeroNode.pre = temp       // ok
		if temp.next != nil {
			temp.next.pre = newHeroNode // ok
		}
		temp.next = newHeroNode // ok
	}
}

//显示链表的所有结点信息
//这里仍然使用单向的链表显示方式
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 DelHeroNode(head *HeroNode, id int) {
	temp := head
	flag := false
	// 找到要删除结点的no,和temp的下一个结点的no比较
	for {
		if temp.next == nil { // 说明到链表的最后
			break
		} else if temp.next.no == id {
			// 说明我们找到了
			flag = true
			break
		}
		temp = temp.next
	}
	if flag { // 找到,删除
		temp.next = temp.next.next
		if temp.next != nil {
			temp.next.pre = temp
		}
	} else {
		fmt.Println("sorry, 要删除的id不存在")
	}
}

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. 加入
	InsertHreoNode2(head, hero1)
	InsertHreoNode2(head, hero2)
	InsertHreoNode2(head, hero3)

	// 4. 显示
	ListHeroNode(head)
	fmt.Println()
	fmt.Println("逆序打印")
	ListHeroNode2(head)

}
PS D:\Workspace\Go\src\projects\gin-demo> go run test.go
[1, 宋江, 及时雨]==>[2, 卢俊义, 玉麒麟]==>[3, 林冲, 豹子头]==>
逆序打印
[3 , 林冲 , 豹子头]==>[2 , 卢俊义 , 玉麒麟]==>[1 , 宋江 , 及时雨]==>

20.6.5 单向环形链表的应用场景

20.6.6 环形单向链表介绍

20.6.7 环形的单向链表的案例

完成对单向环形链表的 添加结点 删除结点 显示
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 { //只有一个结点
		if temp.no == id {
			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()
	fmt.Println()

	ListCircleLink(head)
}
PS D:\Workspace\Go\src\projects\gin-demo> go run test.go
&{1 tom <nil>} 加入到环形的链表
环形链表的情况如下:
猫的信息为为=[id=1 name=tom] ->
猫的信息为为=[id=2 name=tom2] ->
猫的信息为为=[id=3 name=tom3] ->
对不起,没有 no=30



环形链表的情况如下:
猫的信息为为=[id=1 name=tom] ->
猫的信息为为=[id=2 name=tom2] ->
猫的信息为为=[id=3 name=tom3] ->

20.6.8 环形单向链表的应用实例

Josephu 问题
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)
		// 退出的条件?curBoy.Next == first
		if curBoy.Next == first {
			break
		}
		//curBoy 移动到下一个
		curBoy = curBoy.Next
	}
}

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

//分析思路
//1. 编写一个函数,PlayGame(first *Boy, startNo int, countNum int)
//2. 最后我们使用一个算法,按照要求,在环形链表中留下最后一个人
func PlayGame(first *Boy, startNo int, countNum int) {
	// 空的链表我们单独的处理
	if first.Next == nil {
		fmt.Println("空的链表,没有小孩")
		return
	}
	//留一个,判断 startNO <= 小孩的总数
	//2. 需要定义辅助指针,帮助我们删除小孩
	tail := first
	//3. 让 tail 执行环形链表的最后一个小孩,这个非常的重要
	//因为 tail 在删除小孩时需要使用到
	for {
		if tail.Next == first { //说明 tail 到了最后的小孩
			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 = first.Next
		tail = tail.Next
		//判断如果 tail == first, 圈子中只有一个小孩.
		if tail == first {
			break
		}
	}
	fmt.Printf("小孩小孩编号为%d 出圈 \n", first.No)
}

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

20.7 排序算法

20.7.2 冒泡排序

20.7.3 选择排序基本介绍

选择排序(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 次,得到一个按排序码从小到大排列的有序序列

代码实现

func SelectSort(arr *[6]int) {
	// 标准的访问方式
	// (*arr)[1] = 600 等价于 arr[1] = 900
	// arr[1] = 900
	// 1.先完成将第一个最大值和arr[0]=>先易后难

	// 1 假设 arr[0]最大值
	for j := 0; j < len(arr)-1; j++ {
		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)
	}
}

20.7.7 插入排序法介绍:

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

20.7.11 插入排序的代码实现

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 {
			// 插入到insertIndex + 1这个位置
			arr[insertIndex+1] = insertVal
		}
		fmt.Printf("第%d次插入后 %v\n", i, *arr)
	}
}

func main() {
	array1 := [7]int{23, 0, 12, 56, 34}
	InsertSort(&array1)
}
PS D:\Workspace\Go\src\projects\testorm> go run main.go
第1次插入后 [23 0 12 56 34]
第2次插入后 [23 12 0 56 34]
第3次插入后 [56 23 12 0 34]
第4次插入后 [56 34 23 12 0]

20.7.12 快速排序法介绍

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

20.7.13 快速排序法示意图

 20.7.14 快速排序法应用实例

20.7.15 快速排序法的代码实现

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--
		}
		// 1 >= r 表明本次分解任务完成, break
		if l >= r {
			break
		}
		//交换
		temp = array[l]
		array[l] = array[r]
		array[r] = temp
		//优化
		if array[l] == pivot {
			r--
		}
		if array[r] == pivot {
			l++
		}
	}
	// 如果 1== 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("初始", arr)
	//调用快速排序
	QuickSort(0, len(arr)-1, &arr)
	fmt.Println("main..")
	fmt.Println(arr)
}
PS D:\Workspace\Go\src\projects\testorm> go run main.go
初始 [-9 78 0 23 -567 70 123 90 -23]
main..
[-567 -23 -9 0 23 70 78 90 123]

20.8

20.8.1 看一个实际需求

 20.8.3 栈的入栈和出栈的示意图

 20.8.4 栈的应用场景

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

20.8.5 栈的案例

 代码实现

package main

import (
	"errors"
	"fmt"
)

// 使用数组来模拟一个栈的使用
type Stack struct {
	MaxTop int    // 表示我们栈最大可以存放数个数
	Top    int    // 表示栈顶,因为栈顶固定,因此我们直接使用Top
	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")
	}

	// 先取值,再this.Top--
	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])
	}
}

20.8.6 栈实现综合计算器

分析了实现的思路

 代码实现

package main

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

// 使用数组来模拟一个栈的使用
type Stack struct {
	MaxTop int    // 表示我们栈最大可以存放数个数
	Top    int    // 表示栈顶,因为栈顶固定,因此我们直接使用Top
	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")
	}

	// 先取值,再this.Top--
	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] // 字符串
		//ch ==>"+" ===> 43
		temp := int([]byte(ch)[0])  // 就是字符对应的 ASCiI 码
		if operStack.IsOper(temp) { // 说明是符号
			//如果 operStack 是一个空栈, 直接入栈
			if operStack.Top == -1 { //空栈
				operStack.Push(temp)
			} else {
				//如果发现 opertStack 栈顶的运算符的优先级大于等于当前准备入栈的运算符的优先级
				//,就从符号栈 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 后面测试看看是不是运算符 [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", exp, res)
}
PS D:\Workspace\Go\src\projects\testorm> go run main.go
表达式30+30*6-4-6 = 200

20.9 递归

20.9.1 递归的一个应用场景[迷宫问题]

 20.9.2 递归的概念

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

20.9.3 递归快速入门

1) 打印问题
2) 阶乘问题
3) 快速入门的示意图

20.9.4 递归用于解决什么样的问题

1 ) 各种数学问题如 : 8 皇后问题 , 汉诺塔 , 阶乘问题 , 迷宫问题 , 球和篮子的问题 (google 编程大
)
2 ) 将用栈解决的问题 --> 第归代码比较简洁

20.9.5 递归需要遵守的重要原则

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

20.9.6 举一个比较综合的案例,迷宫问题

 代码

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 { // 说明这个点不能探测,为 1,是墙
			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()
	}
}
PS D:\Workspace\Go\src\projects\testorm> go run main.go
1111111
1010001
1010001
1110001
1000001
1000001
1000001
1111111
探测完毕....
1111111
1310001
1310001
1110001
1000001
1000001
1000001
1111111
课后思考题:
思考 : 如何求出最短路径 ?

20.10 哈希表(散列)

20.10.1 实际的需求

google 公司的一个上机题 :
有一个公司 , 当有新的员工来报道时 , 要求将该员工的信息加入 (id, 性别 , 年龄 , 住址 ..), 当输入该员工
id , 要求查找到该员工的 所有信息 .
要求 : 不使用数据库 , 尽量节省内存 , 速度越快越好 => 哈希表 ( 散列 )

20.10.2 哈希表的基本介绍

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

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

应用实例 google 公司的一个上机题 :
有一个公司 , 当有新的员工来报道时 , 要求将该员工的信息加入 (id, 性别 , 年龄 , 住址 ..), 当输入该员工
id , 要求查找到该员工的 所有信息 .
要求 :
1) 不使用数据库 , 尽量节省内存 , 速度越快越好 => 哈希表 ( 散列 )
2) 添加时,保证按照雇员的 id 从低到高插入
思路分析
1) 使用链表来实现哈希表 , 链表不带 表头
[ : 链表的第一个结点就存放雇员信息 ]
2) 思路分析并画出示意图

3) 代码实现[删改(显示所有员工id 查询)]

package main

import "fmt"

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

//定义 EmpLink
//我们这里的 EmpLink 不带表头,即第一个结点就存放雇员
type EmpLink struct {
	Head *Emp
}

//1. 添加员工的方法, 保证添加时,编号从小到大
func (this *EmpLink) Insert(emp *Emp) {
	cur := this.Head   // 这是辅助指针
	var pre *Emp = nil // 这是一个辅助指针 p
	//如果当前的 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() //换行处理
}

//定义 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 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 "exit":
            os.Exit(-1)
		default:
			fmt.Println("输入错误")
		}
	}
}
PS D:\Workspace\Go\src\projects\testorm> go run main.go
===============雇员系统菜单============
input 表示添加雇员
show 表示显示雇员
find 表示查找雇员
exit 表示退出系统
请输入你的选择
input
输入雇员 id
1
输入雇员 name
wu
===============雇员系统菜单============
input 表示添加雇员
show 表示显示雇员
find 表示查找雇员
exit 表示退出系统
请输入你的选择
input
输入雇员 id
2
输入雇员 name
ku
===============雇员系统菜单============
input 表示添加雇员
show 表示显示雇员
find 表示查找雇员
exit 表示退出系统
请输入你的选择
input
输入雇员 id
3
输入雇员 name
lu
===============雇员系统菜单============
input 表示添加雇员
show 表示显示雇员
find 表示查找雇员
exit 表示退出系统
请输入你的选择
show
链表0 为空
链表1 雇员 id=1 名字=wu ->
链表2 雇员 id=2 名字=ku ->
链表3 雇员 id=3 名字=lu ->
链表4 为空
链表5 为空
链表6 为空

数据结构和算法(Golang实现)

基础知识

一、什么是算法

汉诺塔问题可以描述为:
有三根杆 ( 编号 A B C ) ,在 A 杆自下而上、由大到小按顺序放置 64 个金盘 ( 如下
) 。游戏的目标:把 A 杆上的金盘全部移到 C 杆上,并仍保持原有顺序叠好。
操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作
过程中盘子可以置于 A B C 任一杆上。
我们很自然想到一个算法:
1. 我们必须先借助 C 杆,将 A 杆前面 N-1 个盘子,移动到 B 杆后,将 A 杆剩下
的一个盘子,直接移动到 C 杆,这时候 A 空了。
2. 然后借助 A 杆,将 B 杆的 N-1 个盘子,移动到 C 杆,任务就完成了。
十分朴素的思路,我们用编程语言来实现:
package main

import "fmt"

var total = 0

// 汉诺塔
// 一开始A杆上有N个盘子,B和C杆都没有盘子。
func main() {
	n := 4   // 64 个盘子
	a := "a" // 杆子A
	b := "b" // 杆子B
	c := "c" // 杆子C
	tower(n, a, b, c)

	// 当 n=1 时,移动次数为 1
	// 当 n=2 时,移动次数为 3
	// 当 n=3 时,移动次数为 7
	// 当 n=4 时,移动次数为 15
	fmt.Println(total)
}

// 表示将N个盘子,从 a 杆,借助 b 杆移到 c 杆
func tower(n int, a, b, c string) {
	if n == 1 {
		total = total + 1
		fmt.Println(a, "->", c)
		return
	}

	tower(n-1, a, c, b)
	total = total + 1
	fmt.Println(a, "->", c)
	tower(n-1, b, a, c)
}
PS D:\Workspace\Go\src\projects\demo> go run main.go
a -> b
a -> c
b -> c
a -> b
c -> a
c -> b
a -> b
a -> c
b -> c
b -> a
c -> a
b -> c
a -> b
a -> c
b -> c
15
通过归纳,我们可以知道移动次数 Total(N) 的关系是 Total(N)=2*Total(N-1)+1 ,每多一
个盘子,移动次数就会翻倍加一,我们通过相关的数列数学方法可以知道 Total(N)=2^N-1 ,也就
是移动次数是一个指数方程 : 2 N 次方 , 指数等于盘子的数量。
我们计算出 2^64-1=18446744073709551615 ,可以知道一个人日夜不停,一秒移动一
次: 18446744073709551615/3600/24/365/100000000=5849 ,要 5849 亿年时间才可以完成这件
事,那时候世界确实可能已经毁灭。
在计算机科学中,因为所有的算法都是人定义的规则,规则是死的,所以不要担心学不会。当你学会了
这些算法,你将会觉得,哇,一切都那么简单。

二、什么是数据结构

数据结构,顾名思义就是存放数据的结构,也可以认为是存放数据的容器。比如,你要找出 1000 个数字 中的最大值,首先你要将1000 个数字记在某些卡片上,然后对卡片进行排序。
大多数算法都需要组织数据,所以产生了数据结构。数据结构在计算机中,主要是用来实现各种算法的 基础,当然数据结构本身也是算法的一部分。
基本的数据结构有:链表,栈和队列,树和图。
链表,就是把数据链接起来,关联起来,一个数据节点指向另外一个数据节点,像自然界的一条条铁链,大部分
数据结构,都是由链表的若干变种来表示。
在每种编程语言中,数组作为基本数据类型提供,数组是连续的内存存储空间,通过下标 0 1 2 可以迅速获取
到数组指定位置的数据。链表也可以用数组来实现,但一般情况下,因为数组是连续的,在链表增加和删除节点
时容易造成冗余,效果不佳。所以链表在不同编程语言实现是这样的: C C++ 是用指针来实现
的, Java 是用类来实现的,而 Golang 是用结构体引用来实现。
栈和队列,主要用来存储多个数据,只不过一个是先进后出,一个先进先出。比如下压栈,先入栈的数据是最后
才能出来,而我们熟知的队列,先排队的人肯定先获得服务。
其次是树和图,树就是有一个树根节点,存放着数据,下面有很多子节点,也存放着数据,类比自然界的树。图
则可以类比自然界的地图,多个点指向多个点,点和点之间有一条或多条边,而这些点存放着数据,边也可以存
放着数据,比如距离等。
围绕这几种数据结构,有若干延伸,加上一些排序,查找逻辑,就形成了更高层次的高级数据结构。
数据结构是算法实现的辅助,是为了更高效组织数据的结构,所以数据结构和算法其实密切联系,并不需要分得太清,大家可以把数据结构等同于算法。

三、什么叫好的数据结构和好的算法

学习算法的原因,是好的算法可以节约资源,但是选择合适的算法很难。我们要进行复杂的数学分析才能知道,什么叫做好的,在计算机里,我们把这种数学分析这叫做算法分析。
什么是好的数据结构和好的算法?
1. 计算机资源是有限 的,所以占用计算机资源越少的数据结构和算法越好。
2. 人的生命是有限的 的,等待时间是有忍耐度的,所以能辅助程序越快完成工作的数据结构和算法越好。
所以出了个理论:时间和空间算法复杂度理论。
程序执行过程中,要么空间换时间,要么时间换空间,空间可以认为是一种计算机资源如内存使用情 况,而时间是人类感知的第四个维度,就是慢还是快,两者一般不能兼得,如果发现居然兼得了,那就 是发明了一种更好的算法。
在计算机科学发展的四五十年,这种既省资源又省时间的发明还是比较少的,比如数据压缩算法,因为发明了超高效的无损数据压缩算法,我们网上看视频的时候,既不失真,也不卡顿,又快又好,这种就叫好算法。
目前有一种新型的计算方式正在研究中,叫量子计算,可以在非常小的空间,使用非常少的资源,短时间内计算超级大量的数据,让我们期待能成功量产的那天,到了那时候,人类生产力将极大被解放。

四、总结

程序设计在一般程度上,很多人都认为 = 数据结构 + 算法。
我们学习数据结构和算法,是为了更高效率写出更快,更好的代码。
因为学习过,所以我们不需要从零开始设计,工作效率就提高了。
因为知道每种数据结构和算法的复杂度和适用场景,自由选择组合,我们写出的代码计算速度变快了,占用的资源更少了。
所以我们要好好学习和理解常见的数据结构和算法。

分治法和递归

在计算机科学中,分治法是一种很重要的算法。
字面上的解释是 分而治之 ,就是把一个复杂的问题分成两个或更多的相同或相似的子问题。
直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
分治法一般使用递归来求问题的解。

一、递归

递归就是不断地调用函数本身。
比如我们求阶乘 1 * 2 * 3 * 4 * 5 *...* N
package main

import "fmt"

func Rescuvie(n int) int {
	if n == 0 {
		return 1
	}
	return n * Rescuvie(n-1)
}
func main() {
	fmt.Println(Rescuvie(5))
}
会反复进入一个函数,它的过程如下 :
Rescuvie(5)
{5 * Rescuvie(4)}
{5 * {4 * Rescuvie(3)}}
{5 * {4 * {3 * Rescuvie(2)}}}
{5 * {4 * {3 * {2 * Rescuvie(1)}}}}
{5 * {4 * {3 * {2 * 1}}}}
{5 * {4 * {3 * 2}}}
{5 * {4 * 6}}
{5 * 24}
120

函数不断地调用本身,并且还乘以一个变量:n * Rescuvie(n-1),这是一个递归的过程。

很容易看出, 因为递归式使用了运算符,每次重复的调用都使得运算的链条不断加长,系统不得不使用栈进行数据保存和恢复。

如果每次递归都要对越来越长的链进行运算,那速度极慢,并且可能栈溢出,导致程序奔溃。

所以有另外一种写法,叫尾递归:

package main

import "fmt"

func RescuvieTail(n int, a int) int {
	if n == 1 {
		return a
	}
	return RescuvieTail(n-1, a*n)
}
func main() {
	fmt.Println(RescuvieTail(5, 1))
}
他的递归过程如下 :
RescuvieTail(5, 1)
RescuvieTail(4, 1*5)=RescuvieTail(4, 5)
RescuvieTail(3, 5*4)=RescuvieTail(3, 20)
RescuvieTail(2, 20*3)=RescuvieTail(2, 60)
RescuvieTail(1, 60*2)=RescuvieTail(1, 120)
120
尾部递归是指递归函数在调用自身后直接传回其值,而不对其再加运算,效率将会极大的提高。
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。
尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用 这种特点自动生成优化的代码。— 来自百度百科。
尾递归函数,部分高级语言编译器会进行优化,减少不必要的堆栈生成,使得程序栈维持固定的层数,不会出现栈溢出的情况。
我们将会举多个例子说明。

二、例子:斐波那契数列

斐波那契数列是指,后一个数是前两个数的和的一种数列。如下:

1 1 2 3 5 8 13 21 ... N-1 N 2N-1

尾递归的求解为:

package main

import "fmt"

func F(n int, a1, a2 int) int {
	if n == 0 {
		return a1
	}
	return F(n-1, a2, a1+a2)
}
func main() {
	fmt.Println(F(1, 1, 1))
	fmt.Println(F(2, 1, 1))
	fmt.Println(F(3, 1, 1))
	fmt.Println(F(4, 1, 1))
	fmt.Println(F(5, 1, 1))
}

输出:

1
2
3
5
8

当 n=5 的递归过程如下:

F(5,1,1)
F(4,1,1+1)=F(4,1,2)
F(3,2,1+2)=F(3,2,3)
F(2,3,2+3)=F(2,3,5)
F(1,5,3+5)=F(1,5,8)
F(0,8,5+8)=F(0,8,13)
8

三、例子:二分查找

在一个已经排好序的数列,找出某个数,如:

1 5 9 15 81 89 123 189 333

从上面排好序的数列中找出数字 189

二分查找的思路是,先拿排好序数列的中位数与目标数字 189 对比,如果刚好匹配目标,结束。

如果中位数比目标数字大,因为已经排好序,所以中位数右边的数字绝对都比目标数字大,那么从中位数的左边找。

如果中位数比目标数字小,因为已经排好序,所以中位数左边的数字绝对都比目标数字小,那么从中位数的右边找。

这种分而治之,一分为二的查找叫做二分查找算法。

递归解法:

package main

import "fmt"

// 二分查找递归解法
func BinarySearch(array []int, target int, l, r int) int {
	if l > r {
		// 出界了,找不到
		return -1
	}
	//
	mid := (l + r) / 2
	middleNum := array[mid]

	if middleNum == target {
		return mid // 找到了
	} else if middleNum > target {
		// 中间的数比目标还大,从左边找
		return BinarySearch(array, target, 0, mid-1)
	} else {
		// 中间的数比目标还小,从右边找
		return BinarySearch(array, target, mid+1, r)
	}
}
func main() {
	array := []int{1, 5, 9, 15, 81, 89, 123, 189, 333}
	target := 500
	result := BinarySearch(array, target, 0, len(array)-1)
	fmt.Println(target, result)

	target = 189
	result = BinarySearch(array, target, 0, len(array)-1)
	fmt.Println(target, result)
}

输出:

PS D:\Workspace\Go\src\projects\demo> go run main.go
500 -1
189 7

可以看到,189 这个数字在数列的下标 7 处,而 500 这个数找不到。

当然,递归解法都可以转化为非递归,如:

package main

import "fmt"

// 二分查找非递归解法
func BinarySearch2(array []int, target int, l, r int) int {
	ltemp := l
	rtemp := r

	for {
		if ltemp > rtemp {
			// 出界了,找不到
			return -1
		}
		// 从中间开始找
		mid := (ltemp + rtemp) / 2
		middleNum := array[mid]

		if middleNum == target {
			return mid // 找到了
		} else if middleNum > target {
			// 中间的数比目标还大,从左边找
			rtemp = mid - 1
		} else {
			// 中间的数比目标还小,从右边找
			ltemp = mid + 1
		}
	}
}
func main() {
	array := []int{1, 5, 9, 15, 81, 89, 123, 189, 333}
	target := 500
	result := BinarySearch2(array, target, 0, len(array)-1)
	fmt.Println(target, result)

	target = 189
	result = BinarySearch2(array, target, 0, len(array)-1)
	fmt.Println(target, result)
}

很多计算机问题都可以用递归来简化求解,理论上,所有的递归方式都可以转化为非递归的方式,只不过使用递归,代码的可读性更高。

算法复杂度及渐进符号

一、算法复杂度

首先每个程序运行过程中,都要占用一定的计算机资源,比如内存,磁盘等,这些是空间,计算过程中需要判断,循环执行某些逻辑,周而反复,这些是时间。

那么一个算法有多好,多快,怎么衡量一个算法的好坏?所以,计算机科学在算法分析过程中,提出了算法复杂度理论,这套理论可以量化算法的效率,以此作为标准,方便我们能衡量到底选择哪一种算法。

复杂度有两个维度:时间和空间。

我们说,一个实现了某算法的程序:

  1. 如果计算的速度越快,那么这个算法时间复杂度越低。
  2. 如果占用的计算资源越少,那么空间复杂度越低。

我们要选择复杂度低的算法,衡量好空间和时间的消耗,选出适合特定场景的算法。

这两个复杂度维度的量化过程都是一样的,所以我们这里主要介绍时间复杂度。

二、算法规模

我们要计算公式 1 + 2 + 3 + ... + 100,那么按照最直观的算法来写:

package main

import "fmt"

func sum(n int) int {
	total := 0
	// 从1加到N, 1+2+3+4+5+..+N
	for i := 1; i <= n; i++ {
		total = total + i
	}
	return total
}

func main() {
	fmt.Println(sum(100))
}

当 n = 10 时就等于我们要计算的公式。这个算法要循环 n-1 次,当 n 很小时,计算很快,但当 n 无限大的时候,计算很慢。

所以,算法衡量要衡量的是在不同 问题规模 n 下,算法的速度。

在这里,因为要循环计算 n-1 次,而当 n 无限大时,常数项基本忽略不计,所以这个算法的时间复杂度,我们用 O(n) 来表示。

我们有另外一种计算方式:

func sum2(n int) int {
	total := ((1 + n) * n) / 2
	return total
}

这次算法只需执行 1 次,所以这个算法的时间复杂度是 O(1)。可以看出,时间复杂度为 O(1) 的算法优于复杂度为 O(n) 的算法。

当然,还有指数级别的比如之前的汉诺塔算法,对数级别的,阶乘级别的复杂度,如 O(2^n)O(n!)O(logn) 等。

算法的优先级排列如下,一般排在上面的要优于排在下面的:

  1. 常数复杂度:O(1)
  2. 对数复杂度:O(logn)
  3. 一次方复杂度:O(n)
  4. 一次方乘对数复杂度:O(nlogn)
  5. 乘方复杂度:O(n^2)O(n^3)
  6. 指数复杂度:O(2^n)
  7. 阶乘复杂度:O(n!)
  8. 无限大指数复杂度:O(n^n)

三、渐进符号

如何量化一个复杂度,到底有多复杂,计算机科学抽象出了几个复杂度渐进符号。

渐进符号如下:

OοΘΩω

分别读作:Omicron(大欧),omicron(小欧),Theta(西塔),Omega(大欧米伽),omega(小欧米伽)。

总结

记号 含义 通俗理解
Θ 紧确界 相当于"="
O 上界 相当于"<="
ο 非紧的上界 相当于"<"
Ω 下界 相当于">="
ω 非紧的下界 相当于">"

我们一般用 O 渐进上界来评估一个算法的时间复杂度,表示逼近的最坏情况。其他渐进符合基本不怎么使用。

算法复杂度主方法

有时候,我们要评估一个算法的复杂度,但是算法被分散为几个递归的子问题,这样评估起来很难,有一个数学公式可以很快地评估出来。

一、复杂度主方法

主方法,也可以叫主定理。对于那些用分治法,有递推关系式的算法,可以很快求出其复杂度。

定义如下:

如果对证明感兴趣的可以翻阅书籍:《算法导论》。如果觉得太难思考,可以跳过该节。

由于主定理的公式十分复杂,所以这里有一种比较简化的版本来计算:

二、举例

  1. 二分搜索,每次问题规模减半,只查一个数,递推过程之外的查找复杂度为 O(1),递推运算时间公式为:T(n) = T(n/2) + O(1)
  2. 快速排序,每次随机选一个数字作为划分进行排序,每次问题规模减半,递推过程之外的排序复杂度为 O(n),递推运算时间递推公式为:T(n) = 2T(n/2) + O(n)

按照简化版的主定理,可以知道:

二分查找:a = 1,b = 2,d = 0,可以知道 a = b^d,所以二分查找的时间复杂度为:O(logn)

快速排序:a = 2,b = 2,d = 1,可以知道 a = b^d,所以快速排序的时间复杂度为:O(nlogn)

强调:并非所有递推关系式都可应用主定理,但是大部分情况下都可以。

因为需要较多的数学知识,所以我们只简单介绍到这里。

常见数据结构及算法

数据结构主要用来组织数据,也作为数据的容器,载体。

各种各样的算法,都需要使用一定的数据结构来组织数据。

常见的典型数据结构有:

  1. 链表
  2. 栈和队列

上述可以延伸出各种各样的术语和结构,如列表,集合,哈希表,堆,优先队列,二叉树,红黑树,B+树以及各种变种等。

我们区别开数据结构和算法,是因为算法是更高层次的一种智慧结晶,目的就是为了解决问题,基本的算法分类有:

  1. 排序算法
  2. 查找算法
  3. 图相关的算法
  4. 其他的算法

计算机科学作为数学的一个分支,大部分的数学知识都是离散数学。我们学习微积分,都是连续的量,可是计算机处理的都是离散的量,数据不存在渐变,都是一个个离散数据。

所以针对离散的计算机科学来说,很多算法都是很简单,也是富含哲学的。 也就是说,现在已知的所有算法,都是严格定义的,是死的,是千篇一律的。作为解决日常生活的一种思路,不需要纠结算法是什么分类,只要知道有这种方法,在什么时候需要使用它就行了。

一般在日常工程开发中,也就是做软件,做网站,基本只使用到排序和查找算法,甚至有些情况下不需要使用。100%的日常开发场景是,我拿到一个数据存在数据库,你需要这个数据,我再帮你找出来。

我们会在后面的篇章介绍这些数据结构和算法。

链表

讲数据结构就离不开讲链表。因为数据结构是用来组织数据的,如何将一个数据关联到另外一个数据呢?链表可以将数据和数据之间关联起来,从一个数据指向另外一个数据。

一、链表

定义:

链表由一个个数据节点组成的,它是一个递归结构,要么它是空的,要么它存在一个指向另外一个数据节点的引用。

链表,可以说是最基础的数据结构。

最简单的链表如下:

package main

import "fmt"

type LinkNode struct {
	Data     int64
	NextNode *LinkNode
}

func main() {
	// 新的节点
	node := new(LinkNode)
	node.Data = 2

	// 新的节点
	node1 := new(LinkNode)
	node1.Data = 3
	node.NextNode = node1 // node1 链接到 node 节点上

	// 新的节点
	node2 := new(LinkNode)
	node2.Data = 4
	node1.NextNode = node2 // node2 链接到 node1 节点上
	// 按顺序打印数据
	nowNode := node
	for {
		if nowNode != nil {
			// 打印节点值
			fmt.Println(nowNode.Data)
			// 获取下一个节点
			nowNode = nowNode.NextNode
			continue
		}
		// 如果下一个节点为空,表示链表结束了
		break
	}

}

打印出:

PS D:\Workspace\Go\src\projects\demo> go run main.go
2
3
4

结构体 LinkNode 有两个字段,一个字段存放数据 Data,另一个字典指向下一个节点 NextNode 。这种从一个数据节点指向下一个数据节点的结构,都可以叫做链表。

有些书籍,把链表做了很细的划分,比如单链表,双链表,循环单链表,循环双链表,其实没有必要强行分类,链表就是从一个数据指向另外一个数据,一种将数据和数据关联起来的结构而已。

好吧,我们还是要知道是什么。

  1. 单链表,就是链表是单向的,像我们上面这个结构一样,可以一直往下找到下一个数据节点,它只有一个方向,它不能往回找。
  2. 双链表,每个节点既可以找到它之前的节点,也可以找到之后的节点,是双向的。
  3. 循环链表,就是它一直往下找数据节点,最后回到了自己那个节点,形成了一个回路。循环单链表和循环双链表的区别就是,一个只能一个方向走,一个两个方向都可以走。

我们来实现一个循环链表 Ring(集链表大成者),参考 Golang 标准库 container/ring::

// 循环链表
type Ring struct {
	next, prev *Ring       // 前驱和后驱节点
	Value      interface{} // 数据
}

该循环链表有一个三个字段,next 表示后驱节点,prev 表示前驱节点,Value 表示值。

我们来分析该结构各操作的时间复杂度。

1.1.初始化循环链表

初始化一个空的循环链表:

package main

// 循环链表
type Ring struct {
	next, prev *Ring       // 前驱和后驱节点
	Value      interface{} // 数据
}

// 初始化空的循环链表,前驱和后驱都指向自己,因为是循环的
func (r *Ring) init() *Ring {
	r.next = r
	r.prev = r
	return r
}
func main() {
	r := new(Ring)
	r.init()
}

因为绑定前驱和后驱节点为自己,没有循环,时间复杂度为:O(1)

创建一个指定大小 N 的循环链表,值全为空:

// 创建N个节点的循环链表
func New(n int) *Ring {
	if n <= 0 {
		return nil
	}
	r := new(Ring)
	p := r
	for i := 1; i < n; i++ {
		p.next = &Ring{prev: p}
		p = p.next
	}
	p.next = r
	r.prev = p
	return r
}

会连续绑定前驱和后驱节点,时间复杂度为:O(n)

1.2.获取上一个或下一个节点

// 获取下一个节点
func (r *Ring) Next() *Ring {
	if r.next == nil {
		return r.init()
	}
	return r.next
}
// 获取上一个节点
func (r *Ring) Prev() *Ring {
	if r.next == nil {
		return r.init()
	}
	return r.prev
}

获取前驱或后驱节点,时间复杂度为:O(1)

1.2.获取第 n 个节点

因为链表是循环的,当 n 为负数,表示从前面往前遍历,否则往后面遍历:

func (r *Ring) Move(n int) *Ring {
	if r.next == nil {
		return r.init()
	}
	switch {
	case n < 0:
		for ; n < 0; n++ {
			r = r.prev
		}
	case n > 0:
		for ; n > 0; n-- {
			r = r.next
		}
	}
	return r
}

因为需要遍历 n 次,所以时间复杂度为:O(n)

1.3.添加节点

// 往节点A,链接一个节点,并且返回之前节点A的后驱节点
func (r *Ring) Link(s *Ring) *Ring {
	n := r.Next()
	if s != nil {
		p := s.Prev()
		r.next = s
		s.prev = r
		n.prev = p
		p.next = n
	}
	return n
}

添加节点的操作比较复杂,如果节点 s 是一个新的节点。

那么也就是在 r 节点后插入一个新节点 s,而 r 节点之前的后驱节点,将会链接到新节点后面,并返回 r 节点之前的第一个后驱节点 n,图如下:

可以看到插入新节点,会重新形成一个环,新节点 s 被插入了中间。

执行以下程序:

func linkNewTest() {
	// 第一个节点
	r := &Ring{Value: -1}
	// 链接新的五个节点
	r.Link(&Ring{Value: 1})
	r.Link(&Ring{Value: 2})
	r.Link(&Ring{Value: 3})
	r.Link(&Ring{Value: 4})

	node := r
	for {
		// 打印节点值
		fmt.Println(node.Value)

		// 移到下一个节点
		node = node.Next()
		//  如果节点回到了起点,结束
		if node == r {
			return
		}
	}
}

func main() {
	linkNewTest()
}
PS D:\Workspace\Go\src\projects\demo> go run main.go
-1
4
3
2
1

每次链接的是一个新节点,那么链会越来越长,仍然是一个环。因为只是更改链接位置,时间复杂度为:O(1)

1.4.删除节点

// 删除节点后面的 n 个节点
func (r *Ring) Unlink(n int) *Ring {
	if n < 0 {
		return nil
	}
	return r.Link(r.Move(n + 1))
}

将循环链表的后面几个节点删除。

执行:

func deleteTest() {
	// 第一个节点
	r := &Ring{Value: -1}

	// 链接新的五个节点
	r.Link(&Ring{Value: 1})
	r.Link(&Ring{Value: 2})
	r.Link(&Ring{Value: 3})
	r.Link(&Ring{Value: 4})

	temp := r.Unlink(3) // 解除了后面两个节点
	// 打印原来的节点
	node := r
	for {
		// 打印节点值
		fmt.Println(node.Value)
		// 移到下一个节点
		node = node.Next()
		//  如果节点回到了起点,结束
		if node == r {
			break
		}
	}
	fmt.Println("------")
	// 打印被切断的节点
	node = temp
	for {
		// 打印节点值
		fmt.Println(node.Value)
		// 移到下一个节点
		node = node.Next()

		//  如果节点回到了起点,结束
		if node == temp {
			break
		}
	}
}
func main() {
	deleteTest()
}

输出:

PS D:\Workspace\Go\src\projects\demo> go run main.go
-1
1
------
4
3
2

删除循环链表后面的三个节点:r.Unlink(3)

可以看到节点 r 后面的两个节点被切断了,然后分成了两个循环链表,r 所在的链表变成了 -1,1

而切除的那部分形成一个新循环链表是 4 3 2,并且返回给了用户。

因为只要定位要删除的节点位置,然后进行链接:r.Link(r.Move(n + 1)),所以时间复杂度为:O(n)+O(1)=O(n)

1.5.获取链表长度

// 查看循环链表长度
func (r *Ring) Len() int {
	n := 0
	if r != nil {
		n = 1
		for p := r.Next(); p != r; p = p.next {
			n++
		}
	}
	return n
}

通过循环,当引用回到自己,那么计数完毕,时间复杂度:O(n)

因为循环链表还不够强壮,不知道起始节点是哪个,计数链表长度还要遍历,所以用循环链表实现的双端队列就出现了,一般具体编程都使用更高层次的数据结构。

详细可查看栈和队列章节。

二、数组和链表

数组是编程语言作为一种基本类型提供出来的,相同数据类型的元素按一定顺序排列的集合。

它的作用只有一种:存放数据,让你很快能找到存的数据。如果你不去额外改进它,它就只是存放数据而已,它不会将一个数据节点和另外一个数据节点关联起来。比如建立一个大小为5的数组 array:

package main

import "fmt"

//  打印出:
//  [0 0 0 0 0]
//  [8 9 7 0 0]
//  7
func main() {
	array := [5]int64{}
	fmt.Println(array)
	array[0] = 8
	array[1] = 9
	array[2] = 7
	fmt.Println(array)
	fmt.Println(array[2])
}

我们可以通过下标 0,1,2 来获取到数组中的数据,下标 0,1,2 就表示数据的位置,排第一位,排第二位,我们也可以把指定位置的数据替换成另外一个数据。

数组这一数据类型,是被编程语言高度抽象封装的结构,下标 会转换成 虚拟内存地址,然后操作系统会自动帮我们进行寻址,这个寻址过程是特别快的,所以往数组的某个下标取一个值和放一个值,时间复杂度都为 O(1)

它是一种将 虚拟内存地址 和 数据元素 映射起来的内置语法结构,数据和数据之间是挨着,存放在一个连续的内存区域,每一个固定大小(8字节)的内存片段都有一个虚拟的地址编号。当然这个虚拟内存不是真正的内存,每个程序启动都会有一个虚拟内存空间来映射真正的内存,这是计算机组成的内容,和数据结构也有点关系,我们会在另外的高级专题讲,这里就不展开了。

用数组也可以实现链表,比如定义一个数组 [5]Value,值类型为一个结构体 Value :

package main

import "fmt"

func ArrayLink() {
	type Value struct {
		Data      string
		NextIndex int64
	}

	var array [5]Value          // 五个节点的数组
	array[0] = Value{"I", 3}    // 下一个节点的下标为3
	array[1] = Value{"Army", 4} // 下一个节点的下标为4
	array[2] = Value{"You", 1}  // 下一个节点的下标为1
	array[3] = Value{"Love", 2} // 下一个节点的下标为2
	array[4] = Value{"!", -1}   // -1表示没有下一个节点
	node := array[0]
	for {
		fmt.Println(node.Data)
		if node.NextIndex == -1 {
			break
		}
		node = array[node.NextIndex]
	}
}

func main() {
	ArrayLink()
}

打印出:

PS D:\Workspace\Go\src\projects\demo> go run main.go
I
Love
You
Army
!

获取某个 下标 的数据,通过该数据可以知道 下一个数据的下标 是什么,然后拿出该下标的数据,继续往下做。问题是,有时候需要做删除,移动等各种操作,而数组的大小是固定的,需要大量空间移动,所以某些情况下,数组的效率很低。

数组和链表是两个不同的概念。一个是编程语言提供的基本数据类型,表示一个连续的内存空间,可通过一个索引访问数据。另一个是我们定义的数据结构,通过一个数据节点,可以定位到另一个数据节点,不要求连续的内存空间。

数组的优点是占用空间小,查询快,直接使用索引就可以获取数据元素,缺点是移动和删除数据元素要大量移动空间。

链表的优点是移动和删除数据元素速度快,只要把相关的数据元素重新链接起来,但缺点是占用空间大,查找需要遍历。

很多其他的数据结构都由数组和链表配合实现的。

三、总结

链表 和 数组 可以用来辅助构建各种基本数据结构。

数据结构名字特别多,在以后的计算机生涯中,有些自己造的数据结构,或者不常见的别人造的数据结构,不知道叫什么名字是很正常的。我们只需知道常见的数据结构即可,方便与其他程序员交流。

可变长数组

因为数组大小是固定的,当数据元素特别多时,固定的数组无法储存这么多的值,所以可变长数组出现了,这也是一种数据结构。在 Golang 语言中,可变长数组被内置在语言里面:切片 slice

slice 是对底层数组的抽象和控制。它是一个结构体:

type slice struct {
	array unsafe.Pointer
	len int
	cap int
}
  1. 指向底层数组的指针。( Golang 语言是没有操作原始内存的指针的,所以 unsafe 包提供相关的对内存指针的操作,一般情况下非专业人员勿用)
  2. 切片的真正长度,也就是实际元素占用的大小。
  3. 切片的容量,底层固定数组的长度。

每次可以初始化一个固定容量的切片,切片内部维护一个固定大小的数组。当 append 新元素时,固定大小的数组不够时会自动扩容,如:

package main

import (
	"fmt"
	"unsafe"
)

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

func main() {
	// 创建一个容量为2的切片
	array := make([]int, 0, 2)
	fmt.Println("cap", cap(array), "len", len(array), "array:", array)
	// 虽然 append 但是没有赋予原来的变量 array
	_ = append(array, 1)
	fmt.Println("cap", cap(array), "len", len(array), "array:", array)
	_ = append(array, 1)
	fmt.Println("cap", cap(array), "len", len(array), "array:", array)
	_ = append(array, 1)
	fmt.Println("cap", cap(array), "len", len(array), "array:", array)

	fmt.Println("-------")
	// 赋予回原来的变量
	array = append(array, 1)
	fmt.Println("cap", cap(array), "len", len(array), "array:", array)
	array = append(array, 1)
	fmt.Println("cap", cap(array), "len", len(array), "array:", array)
	array = append(array, 1)
	fmt.Println("cap", cap(array), "len", len(array), "array:", array)
	array = append(array, 1, 1, 1, 1)
	fmt.Println("cap", cap(array), "len", len(array), "array:", array)
	array = append(array, 1, 1, 1, 1, 1, 1, 1, 1, 1)
	fmt.Println("cap", cap(array), "len", len(array), "array:", array)
}

输出:

PS D:\Workspace\Go\src\projects\demo> go run main.go
cap 2 len 0 array: []
cap 2 len 0 array: []
cap 2 len 0 array: []
cap 2 len 0 array: []
-------
cap 2 len 1 array: [1]
cap 2 len 2 array: [1 1]
cap 4 len 3 array: [1 1 1]
cap 8 len 7 array: [1 1 1 1 1 1 1]
cap 16 len 16 array: [1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]

我们可以看到 Golang 的切片无法原地 append,每次添加元素时返回新的引用地址,必须把该引用重新赋予之前的切片变量。并且,当容量不够时,会自动倍数递增扩容。事实上,Golang 在切片长度大于 1024 后,会以接近于 1.25 倍进行容量扩容。

具体可参考标准库 runtime 下的 slice.go 文件。

一、实现可变长数组

我们来实现一个简单的,存放整数的,可变长的数组版本。

因为 Golang 的限制,不允许使用 [n]int 来创建一个固定大小为 n 的整数数组,只允许使用常量来创建大小。

所以我们这里会使用切片的部分功能来代替数组,虽然切片本身是可变长数组,但是我们不会用到它的 append 功能,只把它当数组用。

import "sync"

// Array 可变长数组
type Array struct {
	array []int      // 固定大小的数组,用满容量和满大小的切片来代替
	len   int        // 真正长度
	cap   int        // 容量
	lock  sync.Mutex // 为了并发安全使用的锁
}

1.1. 初始化数组

创建一个 len 个元素,容量为 cap 的可变长数组:

// Make 新建一个可变长数组
func Make(len, cap int) *Array {
	s := new(Array)
	if len > cap {
		panic("len large than cap")
	}
	// 把切片当数组用
	array := make([]int, cap, cap)

	// 元数据
	s.array = array
	s.cap = cap
	s.len = 0
	return s
}

主要利用满容量和满大小的切片来充当固定数组,结构体 Array 里面的字段 len 和 cap 来控制值的存取。不允许设置 len > cap 的可变长数组。

时间复杂度为:O(1),因为分配内存空间和设置几个值是常数时间。

1.2. 添加元素

// Append 增加一个元素
func (a *Array) Append(element int) {
	// 并发锁
	a.lock.Lock()
	defer a.lock.Lock()

	// 大小等于容量,表示没多余位置了
	if a.len == a.cap {
		// 没容量,数组要扩容,扩容到两倍
		newCap := 2 * a.len

		// 如果之前的容量为0,那么新容量为1
		if a.cap == 0 {
			newCap = 1
		}
		newArray := make([]int, newCap, newCap)

		// 把老数组的数据移动到新数组
		for k, v := range a.array {
			newArray[k] = v
		}

		// 替换数组
		a.array = newArray
		a.cap = newCap
	}
	// 把元素放在数组里
	a.array[a.len] = element
	// 真实长度+1
	a.len = a.len + 1
}

首先添加一个元素到可变长数组里,会加锁,这样会保证并发安全。然后将值放在数组里:a.array[a.len] = element,然后 len + 1,表示真实大小又多了一个。

当真实大小 len = cap 时,表明位置都用完了,没有多余的空间放新值,那么会创建一个固定大小 2*len 的新数组来替换老数组:a.array = newArray,当然容量也会变大:a.cap = newCap。如果一开始设置的容量 cap = 0,那么新的容量会是从 1 开始。

添加元素中,耗时主要在老数组中的数据移动到新数组,时间复杂度为:O(n)。当然,如果容量够的情况下,时间复杂度会变为:O(1)

如何添加多个元素:

// AppendMany 增加多个元素
func (a *Array) AppendMany(element ...int) {
	for _, v := range element {
		a.Append(v)
	}
}

只是简单遍历一下,调用 Append 函数。其中 ...int 是 Golang 的语言特征,表示多个函数变量。

1.3. 获取指定下标元素

// Get 获取某个下标的元素
func (a *Array) Get(index int) int {
	// 越界了
	if a.len == 0 || index >= a.len {
		panic("index over len")
	}
	return a.array[index]
}

当可变长数组的真实大小为0,或者下标 index 超出了真实长度 len ,将会 panic 越界。

因为只获取下标的值,所以时间复杂度为 O(1)

1.4. 获取真实长度和容量

// Len 返回真实长度
func (a *Array) Len() int {
	return a.len
}

// Cap 返回容量
func (a *Array) Cap() int {
	return a.cap
}

时间复杂度为 O(1)

1.5. 示例

现在我们来运行完整的可变长数组的例子:

package main

import (
	"fmt"
	"sync"
)

// Array 可变长数组
type Array struct {
	array []int      // 固定大小的数组,用满容量和满大小的切片来代替
	len   int        // 真正长度
	cap   int        // 容量
	lock  sync.Mutex // 为了并发安全使用的锁
}

// Make 新建一个可变长数组
func Make(len, cap int) *Array {
	s := new(Array)
	if len > cap {
		panic("len large than cap")
	}
	// 把切片当数组用
	array := make([]int, cap, cap)

	// 元数据
	s.array = array
	s.cap = cap
	s.len = 0
	return s
}

// Append 增加一个元素
func (a *Array) Append(element int) {
	// 并发锁
	a.lock.Lock()
	defer a.lock.Unlock()

	// 大小等于容量,表示没多余位置了
	if a.len == a.cap {
		// 没容量,数组要扩容,扩容到两倍
		newCap := 2 * a.len

		// 如果之前的容量为0,那么新容量为1
		if a.cap == 0 {
			newCap = 1
		}
		newArray := make([]int, newCap, newCap)

		// 把老数组的数据移动到新数组
		for k, v := range a.array {
			newArray[k] = v
		}

		// 替换数组
		a.array = newArray
		a.cap = newCap
	}
	// 把元素放在数组里
	a.array[a.len] = element
	// 真实长度+1
	a.len = a.len + 1
}

// AppendMany 增加多个元素
func (a *Array) AppendMany(element ...int) {
	for _, v := range element {
		a.Append(v)
	}
}

// Get 获取某个下标的元素
func (a *Array) Get(index int) int {
	// 越界了
	if a.len == 0 || index >= a.len {
		panic("index over len")
	}
	return a.array[index]
}

// Len 返回真实长度
func (a *Array) Len() int {
	return a.len
}

// Cap 返回容量
func (a *Array) Cap() int {
	return a.cap
}

// Print 辅助打印
func Print(array *Array) (result string) {
	result = "["
	for i := 0; i < array.Len(); i++ {
		// 第一个元素
		if i == 0 {
			result = fmt.Sprintf("%s%d", result, array.Get(i))
			continue
		}
		result = fmt.Sprintf("%s %d", result, array.Get(i))
	}
	result = result + "]"
	return
}
func main() {
	// 创建一个容量为3的动态数组
	a := Make(0, 3)
	fmt.Println("cap", a.Cap(), "len", a.Len(), "array:", Print(a))
	// 增加一个元素
	a.Append(10)
	fmt.Println("cap", a.Cap(), "len", a.Len(), "array:", Print(a))

	// 增加一个元素
	a.Append(9)
	fmt.Println("cap", a.Cap(), "len", a.Len(), "array:", Print(a))

	// 增加多个元素
	a.AppendMany(8, 7)
	fmt.Println("cap", a.Cap(), "len", a.Len(), "array:", Print(a))
}

将打印出:

PS D:\Workspace\Go\src\projects\demo> go run main.go
cap 3 len 0 array: []
cap 3 len 1 array: [10]      
cap 3 len 2 array: [10 9]    
cap 6 len 4 array: [10 9 8 7]

可以看到,容量会自动翻倍。

二、总结

可变长数组在实际开发上,经常会使用到,其在固定大小数组的基础上,会自动进行容量扩展。

因为这一数据结构的使用频率太高了,所以,Golang 自动提供了这一数据类型:切片(可变长数组)。大家一般开发过程中,直接使用这一类型即可。

猜你喜欢

转载自blog.csdn.net/niwoxiangyu/article/details/130466596