【Golang】十三、golang数据结构

一、稀疏数组

1. 什么是稀疏数组?

>  如果一个二维数组非常大,并且大部分都是默认值的情况下,我们就认为很稀疏。
>  所以我们可以将二维数据中有数据的坐标和值记录下来,保存在数据结构里,这就称之为稀疏数组!

2. 案例演示

我们以一个6*5的二维数组为例子

  1. 结构体

    type spare struct {
    	row int  // 行
    	col int  // 列
    	val int  // 值
    }
    
  2. 数组(golang数组默认值都是0)

    arr := [6][5]int{}
    // 不同坐标设置默认值
    arr[4][2] = 2
    arr[4][4] = 3
    arr[2][2] = 1
    arr[5][2] = 2
    
  3. 存储数据

    func saveSpaceArr(arr []int) []spare {
    	spareArr := []spare{}
    	// 遍历数组中所有不为0的元素
    	for i := 0; i < len(arr); i++ {
    		for j := 0; j < len(arr[i]); j++ {
    			if arr[i][j] != 0 {
    				// 记录坐标和值
    				space := spare{
    					col: j,
    					row: i,
    					val: arr[i][j],
    				}
    				spareArr = append(spareArr, space)
    			}
    		}
    	}
    	return spareArr
    }
    
  4. 读取数据

    func readSpaceArr(spareArr []spare) []int{
    	arr = [6][5]int{}
    	for i := 0; i < len(spareArr); i++ {
    		// 读取行列值
    		row := spareArr[i].row
    		col := spareArr[i].col
    		val := spareArr[i].val
    		arr[row][col] = val
    	}
    	return arr
    }
    

二、队列

我们这里使用数组来模拟

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头

1. 单向队列

1、起始位置
指针出始位置
2、添加一个元素
在这里插入图片描述
3、添加满元素时
在这里插入图片描述
4、删除元素
在这里插入图片描述
5、队列满员
在这里插入图片描述

  1. 结构体
    type query struct {
    	arr [5]int
    	// 最大长度
    	maxSize int
    	// 首指针(不包含首位)
    	front int
    	// 尾指针
    	real int
    }
    
  2. 增加、删除、判断满或者空的方法
    // 是否为空?
    func (this *query) isNil() bool {
    	return (this.front == this.real)
    }
    // 是否为以满?
    func (this *query) isFull() bool {
    	return (this.maxSize - 1 == this.real)
    }
    // 插入数据
    func (this *query) push(val int) error {
    	if isFull(){
    		return errors.New("队列以满")
    	}
    	this.real++
    	this.arr[this.real] = val
    	return nil
    }
    // 弹出数据
    func (this *query) pop() (int, error) {
    	if isNil(){
    		return -9999, errors.New("队列以空")
    	}
    	popVal := this.arr[this.front]
    	this.front++
    	return popVal, nil
    }
    // 显示数据
    func (this *query) show(){
    	if isNil(){
    		fmt.Println([]int{})
    	}
    	// 注意此处this.front要加1,this.front指向的是弹出的值
    	for i:=this.front+1; i<=this.real;i++ {
    		fmt.Println(this.arr[i])
    	} 
    }
    
  3. 初始化队列
    func main(){
    	query := &query{
    		maxSize: 5,
    		front: -1,
    		real: -1,
    	}
    	query.push(11)
    	query.push(22)
    	query.push(33)
    	query.push(44)
    	// 先进先出
    	val, err := query.pop()
    	if err != nil {
    		fmt.Println(err.Error())
    	}
    	// 显示数据
    	query.show()
    }
    

2. 环形队列

环形队列,故名思议,是一个环状,相对于单向队列来说是一个升级(单向列表一次性添加数据添加满了再删除无法继续使用队列),环形队列可以循环利用队列,只要队列有空余的位置就可以添加新的元素。

1、满员队列
在这里插入图片描述
2、删除元素
在这里插入图片描述
3、环形增加节点
在这里插入图片描述
4、环形满员状态
在这里插入图片描述

  1. 结构体
    type circleQuery struct {
    	maxlen 		int
    	arr 		[5]int
    	// head包含队首元素,tail比队尾元素多1位
    	head, tail 	int
    }
    
    func (this circleQuery) String() string {
    	str, _ := json.Marshal(this)
    	return string(str)
    }
    
  2. 增、删、查看个数等操作
    // 是否为空
    func (this *circleQuery) isNil() bool {
    	return (this.head == this.tail)
    }
    // 是否以满
    func (this *circleQuery) isFull() bool {
    	return ( (this.tail + 1) % this.maxLen == this.head )
    }
    // 队列元素个数
    func (this *circleQuery) count() int {
    	return ( (this.maxlen + this.tail - this.head) % this.maxlen )
    }
    // 添加元素
    func (this *circleQuery) push(newVal int) {
    	if isFull() {
    		return
    	}
    	this.arr[this.tail] = newVal
    	this.tail = (this.tail + 1) % this.maxLen
    }
    func (this *circleQuery) pop() int {
    	if isNil() {
    		return
    	}
    	popVal := this.arr[this.head]
    	this.head = (this.head + 1) % this.maxLen
    	return popVal
    }
    
  3. 初始化环形队列
    func main(){
    	cq := circleQuery{
    	head: 0,
    	tail: 0,
    	maxlen: 5,
    	}
    	cq.push(11)
    	cq.push(22)
    	cq.push(33)
    	cq.push(44)
    	fmt.Println(cq.pop())
    	cq.push(55)
    	
    	cq.show()
    }
    

三、链表

1、单向链表

1、初始状态
在这里插入图片描述
2、添加元素的状态
在这里插入图片描述

  1. 准备结构体

    type linkListNode struct {
    	No 			int
    	Name 		string
    	Next 		*linkListNode // 指向下一个节点
    }
    // json序列化结构体
    func (l *linkListNode) String() string {
    	str, _ := json.Marshal(l)
    	return string(str)
    }
    
  2. 添加、删除、显示节点

    // 插入节点
    func insert(head *linkListNode, new *linkListNode){
    	temp := head
    	// 先找到最后一个节点,将新值插入到最后
    	for {
    		// 当节点Next为nil代表后续没有节点,直接插入即可
    		if temp.Next == nil {
    			temp.Next = new
    			break
    		}
    		temp = temp.Next
    	}
    }
    
    // 有序差入节点 (此方法按照No的大小排序)
    func orderInsert(head *linkListNode, new *linkListNode){
    	temp := head
    	for {
    		if temp.Next == nil {
    			temp.Next = new
    			break
    		} else if temp.Next.No > new.No {
    			new.Next = tmep.Next
    			tmep.Next = new
    			break
    		}
    		temp = temp.Next
    	}
    }
    
    // 删除节点
    func delete(head *linkListNode, no int) {
    	temp := head
    	for {
    		if temp.Next == nil {
    			fmt.Println("未找到该节点")
    			break
    		} else if temp.Next.No == no {
    			temp.Next = temp.Next.Next
    			break
    		} 
    		temp = temp.Next
    	}
    }
    
    // 显示节点
    func show(head *linkListNode){
    	temp := head
    	for {
    		if temp.Next != nil {
    			temp = temp.Next
    			fmt.Println(temp.String())
    		} else {
    			break
    		}
    	}
    }
    
  3. 初始化节点

    func main(){
    	// 头节点,默认为空
    	head := linkListNode{}
    	fmt.Println(1)
    
    	realHead1 := linkListNode{
    		No: 3,
    		Name: "张三",
    		Nickname: "傻逼",
    	}
    	//insert(&head, &realHead1)
    	sortInsert(&head, &realHead1)
    
    	fmt.Println(2)
    	realHead2 := linkListNode{
    		No: 4,
    		Name: "李四",
    		Nickname: "呆子",
    	}
    	//insert(&head, &realHead2)
    	sortInsert(&head, &realHead2)
    
    	realHead3 := linkListNode{
    		No: 1,
    		Name: "赵一",
    		Nickname: "傻逼",
    	}
    	//insert(&head, &realHead1)
    	sortInsert(&head, &realHead3)
    
    	delete(&head, 3)
    
    	show(&head)
    }
    

2、双向链表

双向链表相对于单链表多了一个前指针,指向了上一个元素
在这里插入图片描述

3、环型链表

在这里插入图片描述

// 环形列表
type circleLinkList struct {
	No int
	Name string
	next *circleLinkList
}

func (c *circleLinkList) String() string {
	str,_ := json.Marshal(c)
	return string(str)
}

func insertCircleLinkList(head *circleLinkList, new *circleLinkList)  {
	// 判断是否是第一支猫
	if head.next == nil {
		head.No = new.No
		head.Name = new.Name
		head.next = head
		return
	}
	// 定义一个临时变量,找到最后一个节点
	temp := head
	for true {
		if temp.next == head {
			temp.next = new
			new.next = head
			break
		}
		temp = temp.next
	}
}

func deleteCircleLinkList(head *circleLinkList, no int) *circleLinkList {
	if head.next == nil {
		fmt.Println("空链表")
		return head
	}
	temp := head

	// 环形链表只有一个节点的情况
	if temp.next == head {
		temp.next = nil
		return head
	}

	for true {
		if temp.next.No == no {
			if temp.next == head {
				head = temp.next.next
			}
			temp.next = temp.next.next
		}
		if temp.next == head {
			break
		}
		temp = temp.next
	}

	return head
}
func showCircleLinkList(head *circleLinkList) {
	if head.next == nil {
		fmt.Println("链表为空")
		return
	}
	temp := head
	for true {
		fmt.Println(temp)
		if temp.next == head {
			break
		}
		temp = temp.next
	}
}

func main()  {
	head := &circleLinkList{}

	new1 := &circleLinkList{
		No: 1,
		Name: "new1",
	}
	new2 := &circleLinkList{
		No: 2,
		Name: "new2",
	}
	new3 := &circleLinkList{
		No: 3,
		Name: "new3",
	}
	new4 := &circleLinkList{
		No: 4,
		Name: "new4",
	}

	insertCircleLinkList(head, new1)
	insertCircleLinkList(head, new2)
	insertCircleLinkList(head, new3)
	insertCircleLinkList(head, new4)


	head = deleteCircleLinkList(head, 1)

	showCircleLinkList(head)
}

4、环型链表问题:约瑟夫环

丢手帕
设编号为1,2,n个人围坐一圈,约定编号为K(1=<K<=n)的人从1开始报数,数到m的人出列,她的下一位又从1开始报数,数到m的人又出列,以此类推直到所有人都出列,由此产生一个出队序列。

type dsp struct {
	Id int
	Name string
	next *dsp
}

func (d *dsp) String() string {
	str, _ := json.Marshal(d)
	return string(str)
}

func insertDsp(head *dsp, new *dsp)  {
	if head.next == nil {
		head.Name = new.Name
		head.Id = new.Id
		head.next = head
		return
	}
	temp := head
	for true {
		if temp.next == head {
			temp.next = new
			new.next = head
			break
		}
		temp = temp.next
	}
}

func showDsp(head *dsp) {
	if head.next == nil {
		fmt.Println("空链表")
		return
	}
	temp := head
	for true {
		if temp.next == head {
			break
		}
		temp = temp.next
	}
}

func deleteDsp(head *dsp, k int, m int) (*dsp, int) {
	if head.next == nil {
		fmt.Println("空链表")
		return head, -1
	}
	if head.next == head {
		head.next = nil
		return head, head.Id
	}

	flagTemp := -1
	temp := head
	flag := 1
	flagK := 1
	for true {
		if flagK == k {
			flag++
		} else {
			flagK++
		}
		if flag == m {
			if temp.next == head {
				head = temp.next.next
			}
			flagTemp = temp.next.Id
			temp.next = temp.next.next
			break
		}
		temp = temp.next
	}
	return head, flagTemp
}

func main() {
	head := &dsp{}
	n1 := &dsp{
		Id: 1,
		Name: "100",
	}
	n2 := &dsp{
		Id: 2,
		Name: "102",
	}
	n3 := &dsp{
		Id: 3,
		Name: "103",
	}
	n4 := &dsp{
		Id: 4,
		Name: "104",
	}
	insertDsp(head, n1)
	insertDsp(head, n2)
	insertDsp(head, n3)
	insertDsp(head, n4)

	showDsp(head)

	query := []int{}
	index := -1
	k := 2
	m := 3
	for true {
		head, index = deleteDsp(head, k, m)
		if index != -1 {
			fmt.Println(index)
			query = append(query, index)
		}
		if head.next == nil {
			break
		}
	}
	fmt.Println(query)
}

5、链表问题:链表反转

链表反转的核心思想就是反转next的指针
使用两个指针作为临时指针
在这里插入图片描述
在这里插入图片描述

type link struct {
	id int
	next *link
}
func reversal(head *link) {
	// 当前节点
	cur := head
	// 上一个节点
	pre := nil
	for {
		if pre.next == nil {
			break
		}
		// 暂存cur的下一个节点
		temp := cur.next
		// 将当前指针指针指向上一个节点
		cur.next = pre
		// 后移cur和pre两个指针
		pre = cur
		cur = temp
	}
}

四、栈

1、初始化top指针下标为-1,同理top为-1表示栈为空
在这里插入图片描述
2、添加一个元素,top++
在这里插入图片描述
3、栈满时,top+1 = max
在这里插入图片描述

  1. 数据结构
type stack struct {
	max int // 最大存放个数
	top int // 栈顶
	arr [5]int
}
  1. 增删该查
// 入栈
func (s *stack) push(new int)  {
	if s.top == (s.max - 1) {
		fmt.Println("栈以满")
		return
	}
	s.top++
	s.arr[s.top] = new
}

// 出栈
func (s *stack) pop() (int, error) {
	if s.top == -1 {
		fmt.Println("栈为空")
		return -1, errors.New("空栈")
	}
	val := s.arr[s.top]
	s.top--
	return val, nil
}

// 显示栈内的数据
func (s *stack) showStack() {
	if s.top == -1 {
		return
	}
	for i := 0; i <= s.top; i++ {
		fmt.Println(s.arr[i])
	}
}
  1. 初始化
func ShowStack() {
	stack := stack{
		max: 5,
		top: -1,
		arr: [5]int{0, 0, 0, 0, 0},
	}
	// 先进后出
	stack.push(2)
	stack.push(1)
	stack.push(4)
	stack.push(9)
	stack.push(10)

	stack.pop() // 先出10
	stack.pop() // 先出9

	stack.showStack()
}

五、哈希散列表

所谓的散列表,就是将一组值,按照一定的规律,比如取模、取余等,分散在对应不同的链表中,查找只需要定位到对应的呢根链表中就可以加快查询速度。
比如,之前有100个数据,分为十组,每组就只有10个数据,这样,只需要从10个数据中查找,数据量越大,散列的优势就会大大加快了查询速度。
在这里插入图片描述

代码实现

  1. 结构体
// 存放结构体head的数组
type hashTable struct {
	Heads [7]*userLink
}

// 头节点
type userLink struct {
	Head *userState
}

// 节点
type userState struct {
	Id int
	State byte
	next *userState
}
  1. 添加节点
// 节点长度是7
// 依据 id 来判断,放入第几根根的链表中
func (this *hashTable) push(userState *userState)  {
	mod := userState.Id % 7
	this.Heads[mod].insert(userState)
}
// userState 插入的新节点
func (this *userLink) insert(userState *userState) {
	temp := this.Head
	if temp.next == nil {
		temp.next = userState
		return
	}
	for true {
		if temp.next == nil {
			temp.next = userState
			break
		} else if temp.next.Id == userState.Id {
			temp.next.State = userState.State
			break
		} else if temp.next.Id > userState.Id {
			userState.next = temp.next
			temp.next = userState
			break
		}
		temp = temp.next
	}
}
  1. 查找节点
func (this *hashTable) getState(id int)  {
	// 1. id 取模判断是那根节点
	mod := id % 7
	// 2. 获取对应节点的根节点
	temp := this.Heads[mod].Head
	// 3. 去对应的节点查找
	for true {
		if temp.next == nil {
			fmt.Println("没有找到")
			break
		}
		if temp.next.Id == id {
			fmt.Println("找到了:", temp.next)
			break
		}
		temp = temp.next
	}
}
  1. 输出节点
func (this *hashTable) show() {
	for i := 0; i < len(this.Heads); i++ {
		temp := this.Heads[i].Head
		fmt.Print("第",i,"个队列:")
		for true {
			if temp.next == nil {
				break
			}
			fmt.Print(temp.next, "  ")
			temp = temp.next
		}
		fmt.Println()
	}
}

六、二叉树

构建如下结构的二叉树
在这里插入图片描述


初始化:

type tree struct {
	id int
	left *tree
	right *tree
}
func ShowTree() {
	
	tree3 := &tree{
		id: 3,
	}

	tree4 := &tree{
		id: 4,
	}

	tree6 := &tree{
		id: 6,
	}

	tree5 := &tree{
		id: 5,
		left: tree6,
	}

	tree1 := &tree{
		id: 1,
		left: tree5,
	}

	tree2 := &tree{
		id: 2,
		left: tree3,
		right: tree4,
	}

	headTree := &tree{
		id: 0,
		left: tree1,
		right: tree2,
	}
	//  前序遍历
	headTree.Preface()
}

1、前序遍历

遍历顺序:先根节点,再左边所有节点,再右边所有节点

func (this *tree) Preface()  {
	if this == nil {
		return
	}
	fmt.Println("当前节点id:", this.id)
	this.left.Preface()
	this.right.Preface()
}

在这里插入图片描述

2、中序遍历

遍历顺序:先左边所有节点,再根节点,再右边所有节点

func (this *tree) MidOrder()  {
	if this == nil {
		return
	}
	this.left.MidOrder()
	fmt.Println("当前节点id:", this.id)
	this.right.MidOrder()
}

在这里插入图片描述

3、后序遍历

遍历顺序:先左边所有节点,再右边所有节点,再根节点

func (this *tree) Postscript()  {
	if this == nil {
		return
	}
	this.left.Postscript()
	this.right.Postscript()
	fmt.Println("当前节点id:", this.id)
}

在这里插入图片描述

七、排序

此处省略冒泡排序…

1. 选择排序

这个排序就是冒泡的升级版本,顺序依次遍历数组,没有都将数组中最小/最大的元素遍历出来放在标志位。
在这里插入图片描述

func SelectSort(flag int, arr []int) {
	if flag == len(arr) {
		return
	}
	// 标记最小位置下标
	tag := flag
	for i := flag; i < len(arr); i++ {
		if arr[i] < arr[tag] {
			tag = i
		}
	}
	arr[flag], arr[tag] = arr[tag], arr[flag]
	// 递归遍历+1位后的数组
	SelectSort(flag + 1, arr)
}

2. 插入排序

在这里插入图片描述

	func InsertSort(arr []int){
		for i:=1;i<len(arr);i++{
			// 记录当前排序的值、下标
			insertVal := arr[i]
			insertIndex := i - 1
			// 循环判断,如果满足条件就将insertIndex+1的值变为insertIndex的值
			for insertIndex >= 0 && arr[insertIndex] > insertVal {
				arr[insertIndex+1] = arr[insertIndex]
				insertIndex--
			}
			// 因为最后满足条件时insertIndex--
			// 若insertIndex + 1 == i则表示不满足条件无序置换
			// 故insertIndex+1的下标才为当前需要插入的值
			if insertIndex + 1 != i {
				arr[insertIndex+1] = insertVal
			}
		}
	}

3. 希尔排序

希尔排序其实也是一种插入排序,缩小增量的插入排序
希尔是将数组依次划分等间距的数组,对等间距数组进行排序

	func ShellSort(arr []int){
		// 1、划分为凉快数组
		for i := len(arr)/2; i > 0;i /= 2 {
			// 2、分别对两个数组进行插入排序
			for j := i; j < len(arr);j += i {
				insertVal := arr[j]
				insertIndex := j - i 
				for insertIndex >= 0 && arr[insertIndex] > insertVal {
					arr[insertIndex + i] = arr[insertIndex]
					insertIndex -= i
				}
				if insertIndex + i != j {
					arr[insertIndex + i] = insertVal
				}
			}
		}
	}

4. 快速排序

在我看来快速排序是效率最快的,也是最好理解的一种排序方式,写起来非常复合大脑的思考的结果,思路并不反人类
在这里插入图片描述

	func QuickSort(arr []int, left, right int) {
		if left > right {
			return
		}
		i := left
		j := right
		// 将最左元素作为基准数
		base := arr[left]
	
		for i != j {
			for arr[j] > base && i < j {
				j--
			}
			for arr[i] < base && i < j {
				i++
			}
			// 左右两边相交换
			arr[i], arr[j] = arr[j], arr[i]
		}
		
		// 此时i、j的位置相交,左边比基准大,右边比基准小
		// 第一轮循环结束,将基准与i、j相交的位置进行交换
		arr[left], arr[i] = arr[i], arr[left]

		// 先递归排序左边
		QuickSort(arr, left, i-1)
		// 再递归排序右边
		QuickSort(arr, i+1, right)
	}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小鱼小鱼啊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值