Go の 数据结构



一.链表

1.1双向链表的基本操作

package main

import "fmt"

type Node struct {
	Data     int
	PrePoint *Node
	NextPont *Node
}

type LinkList struct {
	head    *Node
	current *Node
	tail    *Node
}

func main() {
	datas := []int{1, 21, 31, 51, 62, 2, 3, 42, 33, 12, 12}
	linklists := new(LinkList)
	for _, v := range datas {
		node := new(Node)
		node.Data = v
		InsertLinkList(node, linklists)
	}
	node := linklists.head
	ShowLinkList(node)
}
func InsertLinkList(node *Node, link *LinkList) {
	if link.head == nil {
		link.head = node
		link.current = node
		link.tail = node
	} else {
		link.tail.NextPont = node
		node.PrePoint = link.tail
		link.tail = node // 只移动尾,头指针一直不动,中间的指针也一直不动
	}
}

func ShowLinkList(node *Node) {
	for node != nil {
		fmt.Println(node.Data)
		node = node.NextPont
	}
}


1.2 将两个递增的链表合成一个



二.串

简单的模式匹配算法

时间复杂度 O(mn)

package main

import "fmt"

func Index(a, a1 string) int {
	i, j, k := 0, 0, 0
	s := []byte(a)
	p := []byte(a1)
	for i < len(s) && j < len(p) {
		if s[i] == p[j] {
			i++
			j++
		} else {
			j = 0 // 如果没有找到的话,i接着走,k+1
			k++
			i = k // 匹配不上的话,才会走下边这条路,所以k能记录一开始匹配的位置
		}
	}
	if j >= len(p) {
		return k
	} else {
		return 0
	}
}

func main() {
	index := Index("absegabciegeggegeabcacbab", "ciegegge")
	fmt.Println(index)
}
KMP

查考文献https://www.zhihu.com/question/21923021

package main

import "fmt"

func KMP(t1, p1 string) int {
	t := []byte(t1)
	p := []byte(p1)
	next := getNext(p1)
	i, j := 0, 0
	for i < len(t) && j < len(p) {
		if j == -1 || t[i] == p[j] { //j=-1 是因为next的数组的第一个是-1
			i++
			j++
		} else {
			// 只要匹配了,i和 j 就一起往前走,如果不匹配,那么就让 j 回朔到 next[j] 的位置
			j = next[j]
		}
	}
	if j == len(p) {
		return i - j
	} else {
		return -1
	}
}
func getNext(p1 string) []int {
	next := make([]int, 20)
	next[0] = -1
	p := []byte(p1)
   // 看一下那个图,上边的模板字符串和下边的模板字符串是错开的,上边的1,对应下边的0,所以上边的0,应该对应下边的-1 
   // j=-1是为了好算,不然模板字符串自己和自己匹配,第一个是个特殊情况。
	i, j := 0, -1
	for i < len(p) {
		if j == -1 || p[i] == p[j] {
			i++
			j++
			next[i] = j
		} else {
		// 根据已经建好的next数组回朔
		// 和上面kmp一样
			j = next[j]
		}
	}
	return next
}

func main() {
	a := "ababaeabacaaaaaddfdfdfdfdf"
	b := "aca"
	rel := KMP(a, b)
	fmt.Println(rel)
}
// 输出8



三.树

树的定义

由唯一的根和若干互不相交的子树,每一颗子树又是一棵树。


相关概念
  • 结点的度:拥有子树的个数
  • 树的度:树中各节点度的最大值
  • 双亲节点:
  • 祖先节点:他上边所有的节点都是祖先节点
  • 森林:把根去掉,剩下的树就构成了森林

树的存储结构

顺序存储:一般使用称双亲存储,一组数组就可以搞定

如知道了节点 i,那么 tree[i] 就是 i 的双亲节点
在这里插入图片描述

链式存储包括:

  • 孩子存储结构.
  • 孩子兄弟存储结构。


二叉树

在普通树上再加两个条件,就构成了完全二叉树。

  • 每个节点最多有两个子树
  • 子树有左右之分,不能颠倒

二叉树又分为满二叉树,完全二叉树,完全二叉树是由满二叉树由右到左,从下到上排着删得到的。不能跳着删除


二叉树主要性质
  • 非空二叉树的叶子结点数,等于双分支结点数+1;
  • 在二叉树的第 i 层上,最多有 2i-1个结点。

对于完全二叉树的第 i 结点来说:

  • i 的双亲节点为 【i/2】向下取整
  • 如果 n>=2i 那么 i 的左孩子的编号为 2i ,如果 n<2i 则无左结点
  • 如果n>=2i+1,则右节点为 2i+1,如果 n<2i+1 则无右节点

在这里插入图片描述



二叉树的遍历
  • 先序遍历
type treeNode struct {
	data   int
	lchild *treeNode
	rchild *treeNode
}

// 先序遍历
func preorder(treenode *treeNode) {
	if treenode != nil {
		Visit(treenode)
		preorder(treenode.lchild)
		preorder(treenode.rchild)
	}
}
  • 总序遍历
  • 后序遍历
  • 层次遍历
二叉树的层次遍历
func Level(node *BTNode) {
	que := make([]*BTNode, 20) //20长的循环队列
	front, rear := 0, 0
	if node != nil {
		rear = (rear + 1) % 20
		que[rear] = node    // 根节点入队
		for front != rear { // 如果不是空队
			front = (front + 1) % 20
			q := que[front] // 跟节点出队
			Visit(q)
			if q.lchilid != nil { // 如果有左节点就是入队
				rear = (rear + 1) % 20 
				que[rear] = q.lchilid 
			}
			if q.rchilid != nil { // 如果有右节点就入队
				rear = (rear + 1) % 20
				que[rear] = q
			}
		}
	}
}


森林还有树

森林还有树之间的转换,孩子兄弟链表的存储方式,具体还是看书吧。




赫夫曼树 (最小代价树)

赫夫曼树又叫最优二叉树,它的特点是带权路径最短。

在这里插入图片描述



赫夫曼树的构造过程:

在这里插入图片描述

  • 先从所有的节点中,找出两个权值最小的节点
  • 将这两个节点构成一个新的树,然后,然后根节点权值就是左右之和
  • 把这个节点放到之前的节点中去
  • 以此类推着写

赫夫曼树的特点:

  • 权值越大,和根节点的距离越近
  • 树中没有度为 1 的节点,这类树叫做严格二叉树
  • 树的带权路径长度最短



四.图

为了将图和树做区分。往往将图中的节点,叫做顶点。两个定点中间存在边,则为相邻关系。

  • 注意有向图的边称为 ,分为弧头还有 弧尾
  • 有向图还分 如度出度
深度优先算法
package main

import "fmt"

type ArcNode struct { //邻接表
	data     int      // 要指向的节点
	nextNode *ArcNode // 下一个节点
}
type VNode struct {
	data         int      //顶点
	firstArcNode *ArcNode // 指向第一个节点
}

type AGraph struct {
	vnodes    []*VNode // 邻接表
	vnumber   int      // 定点个数
	arcNumber int      //边的个数
}

var (
	visit [20]int // 标记访问过的点
)

func NewGraph(v, arc int) AGraph { //创建一个新的表
	agraph := new(AGraph)
	agraph.vnumber = v     // 节点的数目
	agraph.arcNumber = arc // 边的数目
	return *agraph
}

func DFS(aGraph *AGraph, i int) {
	p := aGraph.vnodes[i].firstArcNode // 第一个定点
	visit[i] = 1
	fmt.Println("节点数", i) // 打印节点
	p = p.nextNode        // p指向下一个节点
	for p != nil {
		if visit[p.data] == 0 {
			DFS(aGraph, p.data)
		} else {
			p = p.nextNode
		}
	}
}

func main() {
}


深度优先和广度优先算法
package main

import "fmt"

type ArcNode struct { // 根据图记忆
	i    int
	next *ArcNode
}

type VNode struct {
	i        int
	firstArc *ArcNode
}

type AGraph struct {
	n, e    int // 表点
	adjList []VNode
}

func newAGraph(n int) *AGraph {
	adjList := make([]VNode, n)
	for i := 0; i < len(adjList); i++ {
		adjList[i].i = i
	}
	return &AGraph{
		n:       n,
		e:       0,
		adjList: adjList,
	}
}

func addArc(G *AGraph, from, to int) {
	newarcNode := &ArcNode{
		i: to,
	}
	if G.adjList[from].firstArc == nil {
		G.adjList[from].firstArc = newarcNode
	} else {
		p := G.adjList[from].firstArc
		for p.next != nil {
			p = p.next
		}
		p.next = newarcNode
	}
}

var visitedDFS [10]int

func DFS(G *AGraph, v int) { // 深度优先遍历

	visitedDFS[v] = 1 //标记为访问过了
	fmt.Println("深度优先遍历:", v)
	p := G.adjList[v].firstArc
	for p != nil {
		if visitedDFS[p.i] == 0 {
			DFS(G, p.i)
		}
		p = p.next
	}
}

// 广度优先和树的层次遍历,都是不需要递归的
func BFS(G *AGraph) { // 广度优先遍历
	var visited [20]int
	var queue [20]int
	front, rear := 0, 0
	rear++
	for front != rear {
		front = (front + 1) % 20
		j := queue[front]          // 先出队
		p := G.adjList[j].firstArc // 循环的关注点,是定点的子节点,而不是节点本身
		for p != nil {
			if visited[p.i] == 0 {
				fmt.Println("中序遍历这个节点", p.i)
				visited[p.i] = 1
				rear = (rear + 1) % 20
				queue[rear] = p.i
			}
			p = p.next
		}
	}
}

func main() {
	G := newAGraph(5)
	addArc(G, 0, 1)
	addArc(G, 0, 3)
	addArc(G, 0, 4)
	addArc(G, 1, 2)
	addArc(G, 1, 4)
	addArc(G, 2, 0)
	addArc(G, 3, 2)
	DFS(G, 0)
	BFS(G)
}


五.排序

在这里插入图片描述

在这里插入图片描述

插入排序

就想是在军训排队的时候,已经排好队了,这时又有人临时加如这个队中来,于是教官大喊:“新来大,迅速找到你的位置,并插入进去”。 这种排序方式,包括 直接插入排序折半插入排序希尔排序


直接插入排序
package main

import (
	"fmt"
	"math/rand"
	"time"
)

// 插入排序就相当于用 R[i] 做分界线,他前面的是有序的,后面的是无序的

func InsertSort(R []int) {
	for i := 1; i < len(R); i++ { //第一个肯定是有序的
		temp := R[i]
		j := i - 1
		for j >= 0 && temp < R[j] { //这个地方必须要有 j==0
			R[j+1] = R[j]
			j--
		}
		R[j+1] = temp
	}
}

func main() {
	rand.Seed(time.Now().UnixNano()) // 随机数种子
	a := make([]int, 15)
	for i := 0; i < 15; i++ {
		a[i] = rand.Intn(100) // 插入100以内的随机数
	}
	fmt.Println(a)
	InsertSort(a)
	fmt.Println(a)
}

## 输出结果
[35 36 7 75 28 36 90 58 59 35 61 32 64 79 17]
[7 17 28 32 35 35 36 36 58 59 61 64 75 79 90]

复杂度分析
时间复杂度:用 R[j+1]=R[j] 这一句作为基本操作,那么时间复杂度,最大是(n2)
空间复杂度: 辅助存储空间不随排序规模的扩大而扩大,因此是个常量,空间复杂度为 O(1)



折半插入排序

交换排序

冒泡排序
func Sort(R []int) {
	for i := 0; i < len(R); i++ {
		for j := i + 1; j < len(R); j++ {
			if R[i] > R[j] {
			// 冒泡排序就是相当于把每一次都把 i 后面的最小的一个给选出来。
				R[i], R[j] = R[j], R[i] 
			}
		}
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())
	r := make([]int, 15)
	for i := 0; i < 15; i++ {
		r[i] = rand.Intn(100)
	}
	fmt.Println(r)
	Sort(r)
	fmt.Println(r)
}

## 时间复杂度
时间复杂度 O(n2)
空间复杂度 O(1
快速排序
func Sort(R []int) []int {
	if len(R) <= 1 {
		return R
	}
	splitdata := R[0]
	low := make([]int, 0)
	high := make([]int, 0)
	mid := make([]int, 0)
	for _, v := range R {
		if v > splitdata {
			high = append(high, v)
		} else if v < splitdata {
			low = append(low, v)
		} else {
			mid = append(mid, v)
		}
	}
	high, low = Sort(high), Sort(low) // 把分好组的数穿进去再排
	// mid = Sort(mid) 中间的部分不用快排了
	rel := append(low, append(mid, high...)...)
	return rel
}

func main() {
	rand.Seed(time.Now().UnixNano())
	r := make([]int, 15)
	for i := 0; i < 15; i++ {
		r[i] = rand.Intn(100)
	}
	fmt.Println(r)
	r = Sort(r)
	fmt.Println(r)
}

复杂度分析:
最好的情况下:时间复杂度为 O(nlog2n),待排序列越接近于无序,本算法的效率就越高,最快情况下为 O(n2)。平均复杂度为 O(nlog2n)



选择类排序

选择排序的最主要动作就是选择。

简单选择排序

选择排序就是选择最小的。

func Sort(R []int) {
	for i := 0; i < len(R); i++ {
		k := i
		for j := i + 1; j < len(R); j++ {
			if R[j] < R[k] {
				k = j // 这个地方记录的是k,而不是直接交换的数组,这是和冒泡最大的区别
			}
		}
		R[k], R[i] = R[i], R[k]
	}
}

时间和空间复杂度和冒泡都一样,时间复杂度都是 n2 ,空间复杂度是 O(1)



堆排序
// 从 R[low]到R[high]的范围内对位置在low上的节点进行调整
// 没执行一直这个函数,就相当于位于 low 的这个点彻底调完,hight就是后来帮忙的。
func Sift(R []int, low, high int) {
	i, j := low, 2*low // 树的节点,默认是从1开始的。
	temp := R[i]
	for j <= high {
		if R[j] < R[j+1] && j < high { // 从左右节点中挑出来一个最大的
			j++ // 变成右节点
		}
		if temp < R[j] {
			R[i] = R[j] // 把j调整到双亲节点上
			i = j       // i是要放的节点 继续往下调整
			j = i * 2
		} else {
			break
		}
	}
	R[i] = temp // 把调整后的节点放在最终位置
}

func heapSort(R []int) {
	// 调整顺序,先下后上,先右后左
	n := len(R) - 1               // 因为序号要比次数-1,比如第5个节点,应该是 R[4]
	for i := n / 2; i >= 1; i-- { // 叶子节点肯定都是堆,所以从 n/2 开始,应是从下到上,从右往左切换
		Sift(R, i, n) // 循环调整完以后,最大的肯定在最上面。
	}
	for i := n; i >= 2; i-- {
		R[1],R[i]=R[i],R[1]
		Sift(R, 1, i-1) //把最大的和最后面的进行替换调整
	}
}

func main() {
	rand.Seed(time.Now().UnixNano())
	r := make([]int, 20)
	r[0] = 0
	for i := 1; i < 20; i++ {
		r[i] = rand.Intn(100)
	}
	fmt.Println(r)
	heapSort(r)
	fmt.Println(r)
}



查找

折半查找

func Bsearch(R []int, k int) int {
	low := 0
	high := len(R)
	for low <= high {
		mid := (low + high) / 2
		if R[mid] == k {
			return mid
		} else if R[mid] > k {
			high = mid - 1
		} else {
			low = mid + 1
		}
	}
	return -1
}

func Sort(R []int) {
	for i := 0; i < len(R); i++ {
		for j := i + 1; j < len(R); j++ {
			if R[j] < R[i] {
				R[j], R[i] = R[i], R[j]
			}
		}
	}
}

func main() {
	rand.Seed(time.Now().Unix())
	r := make([]int, 20)
	for i := 0; i < len(r); i++ {
		r[i] = rand.Intn(100)
	}
	fmt.Println(r)
	Sort(r)
	fmt.Println(r)
	rel := Bsearch(r, r[4])
	fmt.Println(rel)
}

二叉排序树

  • 若它的左子树不为空,则左子树的所有关键点的值均不大于根关键点的值
  • 若它的右子树不为空,则右子树的所有关键点的值均不大于根关键点的值
  • 左右子树又各是一棵二叉排序树

type BTNode struct {
	data   int
	lchild *BTNode
	rchild *BTNode
}

func BTsearch(btnode *BTNode, k int) *BTNode {  //注意返回类型
	if btnode == nil {
		return nil
	} else {
		if btnode.data == k {
			return btnode
		} else if k < btnode.data {
			return BTsearch(btnode.lchild, k)
		} else {
			return BTsearch(btnode.rchild, k)
		}
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值