线性结构
字符串
线性表
数组
数组是在内存中顺序存储的,一种连续存储的线性结构,元素类型相同,如果是多维的,元素大小相同,尺寸固定,比如
func TestDemoTwo4(t *testing.T) {
//代表长度是3 元素类型是int
arr:= [3]int{1,2,3}
//二维数组 代表外层数组长度是2 ,存储的元素是int类型的数组,长度为2
arr3:=[3][2]int{{1,2},{1,2},{1,2}}
}
数组拥有非常高效的随机访问能力,只要给出下标,就可以用常量时间找到对应元素。但是由于数组元素连续紧密地存储在内存中,插入、删除元 素都会导致大量元素被迫移动,影响效率。
链表
链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干 节点(node)所组成。如果说数组在内存中的存储方式是顺序存储,那么链表在内存中的存储 方式则是随机存储 。而链表则采用了见缝插针的方式,链表的每一个节点分布 在内存的不同位置,依靠next指针关联起来。这样可以灵活有效地利用零散的碎片空间。数组,就像在一列军队中,每个士兵有自己的编号(也就是数组的下标),而且每个士兵都可以快速的找到排在自己前面的人和排在自己后面的人,因为他们都是连续存在的。但是作为特务机构就不一样了,一群特务聚集在一起,那不就被一网打击了吗。特务的特点就是分散,见缝插针,这一个,那一个,他们保持单线联系,即他知道他在哪,知道他的下级在哪,但是不知道下级的下级在哪,这里面的每个特务,都是链表中的一个数据节点
单向链表
单向 链表的每一个节点又包含两部分,一部分是存放数据的变量data, 另一部分是指向下一个节点的指针next。
链表的第1个节点被称为头节点,最后1个节点被称为尾节点,尾节点的 next指针指向空。对于链表的其中一个节点A,我 们只能根据节点A的next指针来找到该节点的下一个节点B,再根据节点 B的next指针找到下一个节点C。。。这样的单线传递
package main
import "fmt"
//节点
type Node struct {
Data interface{} //当前节点的数据
Next *Node //下一个节点的位置
}
//链表结构
type LList struct {
Header *Node //链表的头部
Length int //链表的长度
}
//创建一个节点 返回节点的地址
func CreateNode(v interface{}) *Node {
return &Node{
Data: v,
Next: nil,
}
}
//初始化一个链表
func CreateList(data interface{}) *LList {
header := CreateNode(data) //创建头部节点
return &LList{
Header: header,
Length: 1,
}
}
//链表头部添加一个元素
func (l *LList) Add(data interface{}) {
node := CreateNode(data)
defer func() {
l.Length++
}()
node.Next = l.Header
l.Header = node
}
//尾部追加
//链表头部添加一个元素
func (l *LList) Append(data interface{}) {
node := CreateNode(data)
defer func() {
l.Length++
}()
//根据头部节点,找到尾部节点
header := l.Header
for header.Next != nil {
header = header.Next
}
header.Next = node
}
//后插法,在第n个元素后插入数据data
func (l *LList) Insert(n int, data interface{}) {
//先创建一个新的节点
node := CreateNode(data)
i := 1 //循环初始变量
length := l.Length
if n > length-1 || n < i {
return
}
defer func() {
l.Length++
}()
header := l.Header
for header.Next != nil && i < n {
header = header.Next
i++
}
//到达第n个
node.Next = header.Next
header.Next = node
}
//删除第n个元素
func (l *LList) Delete(n int) {
defer func() {
l.Length--
}()
//如果删除的是头部节点
if n == 1 {
l.Header = l.Header.Next
return
}
i := 1 //循环初始变量
header := l.Header
for header.Next != nil && i < n-1 {
header = header.Next
i++
}
//删除尾部
if n == l.Length {
header.Next = nil
return
}
//到达第n个
header.Next = header.Next.Next
}
//循环遍历 显示
func (l *LList) Scan() {
header := l.Header
for header.Next != nil {
fmt.Println(header)
header = header.Next
}
fmt.Println(header)
}
/**
* @Description
* @Author 老a技术联盟
* @Date 2022/4/14 17:12
**/
func main() {
l := CreateList(1) //初始化一个链表
for i := 2; i < 6; i++ { //增加了2 3 4 5
l.Append(i)
}
//l.Append(6)
l.Insert(2, 2.5)
//fmt.Println(l.Header.Next.Next)
//l.Scan()
l.Delete(3)
l.Scan()
}
双向链表
单向链表我们可以找到自己的下级,但是怎么找到自己的上级呢?我们可以使用双向链表来解决。双向链表的每一个节点除了拥有data和 next指针,还拥有指向前置节点的prev 指针。
- 相关代码
package main
import "fmt"
//节点
type Node struct {
Data interface{} //当前节点的数据
Next *Node //下一个节点的位置
Prev *Node //上一个节点的位置
}
//链表结构
type LList struct {
Header *Node //链表的头部
Length int //链表的长度
}
//创建一个节点 返回节点的地址
func CreateNode(v interface{}) *Node {
return &Node{
Data: v,
Next: nil,
Prev: nil,
}
}
//初始化一个链表
func CreateList(data interface{}) *LList {
header := CreateNode(data) //创建头部节点
return &LList{
Header: header,
Length: 1,
}
}
//链表头部添加一个元素
func (l *LList) Add(data interface{}) {
node := CreateNode(data)
defer func() {
l.Length++
}()
headerNode := l.Header
l.Header = node
node.Next = headerNode
headerNode.Prev = node
}
//尾部追加
//链表头部添加一个元素
func (l *LList) Append(data interface{}) {
node := CreateNode(data)
defer func() {
l.Length++
}()
//根据头部节点,找到尾部节点
header := l.Header
for header.Next != nil {
header = header.Next
}
header.Next = node
node.Prev = header
}
//后插法,在第n个元素后插入数据data
func (l *LList) Insert(n int, data interface{}) {
//先创建一个新的节点
node := CreateNode(data)
i := 1 //循环初始变量
length := l.Length
if n > length-1 || n < i {
return
}
defer func() {
l.Length++
}()
header := l.Header
for header.Next != nil && i < n {
header = header.Next
i++
}
//到达第n个
node.Next = header.Next
node.Prev = header
header.Next.Prev = node
header.Next = node
}
//删除第n个元素
func (l *LList) Delete(n int) {
defer func() {
l.Length--
}()
//如果删除的是头部节点
if n == 1 {
l.Header = l.Header.Next
l.Header.Prev = nil
return
}
i := 1 //循环初始变量
header := l.Header
for header.Next != nil && i < n-1 {
header = header.Next
i++
}
//删除尾部
if n == l.Length {
header.Next.Prev = nil
header.Next = nil
return
}
//到达第n个
header.Next.Next.Prev = header
header.Next = header.Next.Next
}
//循环遍历 显示
func (l *LList) Scan() {
header := l.Header
for header.Next != nil {
fmt.Println(header)
header = header.Next
}
fmt.Println(header)
}
/**
* @Description
* @Author 老a技术联盟
* @Date 2022/4/14 17:12
**/
func main() {
l := CreateList(1) //初始化一个链表
for i := 2; i < 6; i++ { //增加了2 3 4 5
l.Append(i)
}
//l.Add(2)
l.Insert(2, 2.5)
//fmt.Println(l.Header.Next.Next)
//l.Scan()
l.Delete(3)
l.Scan()
fmt.Println(&l.Header)
}
链表和数组的区别
从图中可以看出,写的多用链表,读的多用数组
hash表
todo
栈
- 很多数据结构都是基于数组和链表加工出来的。包括栈和队列
package main
import "fmt"
type Stack struct{
data []int
count int //栈的大小
top int //栈顶指针
}
//初始化一个栈
func NewStack(cap int)*Stack{
return &Stack{
data:make([]int,cap,cap),
count: cap,
top :-1,
}
}
//数据进栈
func (s *Stack)Push(i int)bool{
if s.top>=s.count-1{
return false
}
//指针上移
s.top++
s.data[s.top]=i
return true
}
func (s *Stack)IsEmpty()bool{
if s.top==-1 {
return true
}
return false
}
func (s *Stack)Pop()(int){
if s.IsEmpty() {
return -1
}
res:= s.data[s.top]
s.top--
return res
}
/**
返回栈顶元素
*/
func (s *Stack)Top()(int){
if s.IsEmpty() {
return -1
}
res:= s.data[s.top]
//s.top--
return res
}
//遍历栈元素
func (s *Stack)Scan(){
if s.IsEmpty() {
fmt.Println("empty stack")
}
for i :=s.top;i>=0;i--{
fmt.Println(s.data[i])
}
}
func (s *Stack)Flush(){
s.top=-1
}
//是否空栈
/**
基于数组实现栈
*/
func main() {
s:=NewStack(10)
for i:=0;i<10;i++ {
s.Push(i)
}
//s.Scan()
s.Flush()
s.Scan()
}
队列
队列(queue)是一种线性数据结构,它的特征和行驶车辆的单行隧道 很相似。不同于栈的先入后出,队列中的元素只能先入先出 (First In First Out,简称FIFO )。队列的出口端叫作队头 (front),队列的入 口端叫作队尾 (rear)。即从哪出 叫对头,从哪入叫队尾。
- 看一下循环队列
- 相关代码
package main
import "fmt"
/**
循环队列的实现
*/
type Queue struct {
data []interface{} //存放数据
Head int //对头
Tail int //队尾
Cap int //容量
Size int//队列的实际长度
}
//初始化一个队列
func NewQueue(i int)*Queue{
return &Queue{
data :make([]interface{}, i),
Head: 0,
Tail:0,
Cap: i,
Size:0,
}
}
//是否对满
func(q *Queue)IsFull()bool{
if (q.Tail+1)%q.Cap==q.Head {
fmt.Println("队列已满")
return true
}
return false
}
//是否对空
func(q *Queue)IsEmpty()bool{
if q.Tail==q.Head {//对头和队尾一样
return true
}
return false
}
//加入队列
func(q *Queue)Push(v interface{})bool{
if q.IsFull()==true {
return false
}
q.data[q.Tail]=v
q.Tail=(q.Tail+1)%q.Cap
q.Size++
return false
}
//从队列弹出
func(q *Queue)Pop()interface{}{
if q.IsEmpty()==true {
return false
}
val:= q.data[q.Head]
q.Head=(q.Head+1)%q.Cap
q.Size--
return val
}
/**
* @Description
* @Author 老a技术联盟
* @Date 2022/4/15 15:39
**/
func main() {
q:=NewQueue(10)
for i := 0;i<10;i++{
q.Push(i)//第10个会打印 队列已满
}
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.Pop())
}
bitmap
逻辑结构
树
二叉树
二叉树(binary tree)是树的一种特殊形式。二叉,顾名思义,这种树 的每个节点最多有2个孩子节点 。注意,这里是最多有2个,也可能只 有1个,或者没有孩子节点。
类型
斜树
所有节点都只有左子树的二叉树叫做左斜树,所有节点都只有右子树的二叉树叫做右斜树。本质就是链表
满二叉树
一个二叉树的所有非叶子节点都存在左右孩子,并且所有叶子节点都在 同一层级上,那么这个树就是满二叉树。
完全二叉树
对一个有n个节点的二叉树,按层级顺序编号,则所有节点的编号为从1 到n。如果这个树所有节点和同样深度的满二叉树的编号为从1到n的节 点位置相同,则这个二叉树为完全二叉树。完全二叉树的条件没有满二叉树那么苛刻:满二叉树要求所有分支都是 满的;而完全二叉树只需保证最后一个节点之前的节点都齐全即可。
遍历
数据结构可以划分 为物理结构和逻辑结构。二叉树属于逻辑结构,它可以通过多种物 理结构来表达。物理结构就比如链式存储和数组。一个节点最多可以指向左右两个孩子节点,所 以二叉树的每一个节点包含3部分。 存储数据的data变量 指向左孩子的left指针 指向右孩子的right指针。
使用数组存储时,会按照层级顺序把二叉树的节点放到数组中对应的位 置上。如果某一个节点的左孩子或右孩子空缺,则数组的相应位置也空 出来。 为什么这样设计呢?因为这样可以更方便地在数组中定位二叉树的孩子 节点和父节点。
假设一个父节点的下标是parent,那么它的左孩子节点下标就 是2×parent + 1 ;右孩子节点下标就是2×parent + 2 。
反过来,假设一个左孩子节点的下标是leftChild,那么它的父节点下标 就是(leftChild-1)/ 2 。
- 相关代码
package main
import "fmt"
//每个节点都有一个左子树和右子树
type Tree struct {
Left *Tree
Data int
Right *Tree
}
// 返回一个空树
func NewTree(rootValue int) *Tree {
return &Tree{
Left: nil,
Data: rootValue,
Right: nil,
}
}
//给树增加节点
func (t *Tree) Insert(n int) *Tree {
if t == nil {
NewNode := NewTree(n)
return NewNode
}
if n < t.Data {
t.Left = t.Left.Insert(n)
} else {
t.Right = t.Right.Insert(n)
}
return t
}
//是否包含某个元素
func (t *Tree) Contains(n int) bool {
if t == nil {
return false
}
if n > t.Data {
return t.Right.Contains(n)
} else if n < t.Data {
return t.Left.Contains(n)
} else {
return true
}
}
//移出元素
//1.先找到对应的值
//2.替换为树的值(右子树 和左子树 是否全)
func (t *Tree) Remove(n int) *Tree {
if t == nil {
return t
}
if n > t.Data {
t.Right.Remove(n)
} else if n < t.Data {
t.Left.Remove(n)
} else {
//这里就是找到了,左右都无子树
if t.Left == nil && t.Right == nil {
return nil
}
//只有右子树
if t.Right != nil && t.Left == nil {
return t.Right
}
//只有左子树
if t.Left != nil && t.Right == nil {
return t.Left
}
//既有左子树,又有右子树,选择右子树的最下值作为当前的值,并把最小值 置为nil
if t.Right != nil && t.Left != nil {
t.Data = t.Right.FindMin()
t.Right.Remove(t.Data)
}
}
return t
}
//查找最大值
func (t *Tree) FindMax() int {
if t == nil {
return -1
}
if t.Right == nil {
return t.Data
} else {
return t.Right.FindMax()
}
}
//查找最大的节点
func (t *Tree) FindMaxNode() *Tree {
if t == nil {
return t
}
for t.Right != nil {
t = t.Right
}
return t
}
//查找最小值
func (t *Tree) FindMin() int {
if t == nil {
return -1
}
if t.Left == nil {
return t.Data
} else {
return t.Left.FindMax()
}
}
//查找最小的节点
func (t *Tree) FindMinNode() *Tree {
if t == nil {
return t
}
for t.Right != nil {
t = t.Right
}
return t
}
//前序遍历的顺序是 根 -----> 左子树 -----> 右子树
func (t *Tree) PreOrder() {
if t != nil {
println(t.Data)
t.Left.PreOrder()
t.Right.PreOrder()
}
}
//中序遍历的顺序是 左子树 -----> 根 ------> 右子树
func (t *Tree) MidOrder() {
if t != nil {
t.Left.MidOrder()
println(t.Data)
t.Right.MidOrder()
}
}
//后序遍历的顺序是 左子树 -----> 右子树 -----> 根
func (t *Tree) PostOrder() {
if t != nil {
t.Left.PostOrder()
t.Right.PostOrder()
println(t.Data)
}
}
/**
层级遍历
*/
func (tree *Tree) LevelOrder() {
if tree == nil {
return
}
// 采用队列实现
queue := make([]*Tree, 0)
queue = append(queue, tree) // queue push
for len(queue) > 0 {
//fmt.Printf("%#v\n", queue[0])
tree = queue[0]
fmt.Printf(" %d -> ", tree.Data)
queue = queue[1:] // queue pop
if tree.Left != nil {
queue = append(queue, tree.Left)
}
if tree.Right != nil {
queue = append(queue, tree.Right)
}
}
}
/**
* @Description 二分查找树的构建
* @Author 老a技术联盟
* @Date 2022/4/15 17:26
**/
func main() {
t := NewTree(100)
t.Insert(200)
t.Insert(50)
t.Insert(120)
t.Insert(380)
t.Insert(220)
t.Insert(320)
t.Insert(20)
//fmt.Println(t.FindMaxNode())
t.LevelOrder()
}
动态查找树
二叉查找树bst
二叉查找树(Binary Search Tree)是指一棵空树或者具有下列性质的二叉树: 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值; 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值; 任意节点的左、右子树也分别为二叉查找树; 没有键值相等的节点。
平衡二叉树avl
平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。
含有相同节点的二叉查找树可以有不同的形态,而二叉查找树的平均查找长度与树的深度有关,所以需要找出一个查找平均长度最小的一棵,那就是平衡二叉树,具有以下性质: 要么是棵空树,要么其根节点左右子树的深度之差的绝对值不超过1; 其左右子树也都是平衡二叉树; 二叉树节点的平衡因子定义为该节点的左子树的深度减去右子树的深度。则平衡二叉树的所有节点的平衡因子只可能是-1,0,1。
- 平衡因子和树的高度
- 相关代码实现
package avl
import (
"errors"
"fmt"
)
/**
* @Description
* @Author 老a技术联盟
* @Date 2022/4/18 19:30
**/
var (
errNotExist = errors.New("index is not existed")
errTreeNil = errors.New("tree is null")
errTreeIndexExist = errors.New("tree index is existed")
)
type Node struct {
lchild *Node
rchild *Node
height int
index int
data int
}
func max(data1 int ,data2 int)int{
if data1>data2 {
return data1
}
return data2
}
func getHeight(node *Node) int {
if node == nil {
return 0
}
return node.height
}
//左旋转
// node BF = 2
// \
// prchild -----> prchild BF = 1
// / \ / \
// pplchild pprchild node pprchild
func llRotation(node *Node)*Node{
prchild:=node.rchild
node.rchild=prchild.lchild
prchild.lchild=node
node.height=max(getHeight(node.lchild),getHeight(node.rchild))+1
prchild.height = max(getHeight(prchild.lchild), getHeight(prchild.rchild)) + 1
return prchild
}
// 右旋转
// node BF = -2
// /
// plchild -----> plchild BF = 1
// / / \
// pplchild lchild node
func rrRotation(node *Node)*Node{
plchild:=node.lchild
node.lchild=plchild.rchild
plchild.rchild=node
node.height=max(getHeight(node.lchild),getHeight(plchild.rchild))+1
plchild.height = max(getHeight(plchild.lchild), getHeight(plchild.rchild)) + 1
return plchild
}
// 先左转再右转
// node node
// / 左 / 右
// node1 ----> node2 ---> node2
// \ / / \
// node2s node1 node1 node
func lrRotation(node *Node)*Node{
plchild:=llRotation(node.lchild)
node.lchild=plchild
return rrRotation(node)
}
// 先右转再左转
// node node
// \ 右 \ 左
// node1 ----> node2 ---> node2
// / \ / \
// node2 node1 node node1
func rlRotation(node *Node)*Node{
prchild:=llRotation(node.rchild)
node.rchild=prchild
return rrRotation(node)
}
//处理节点高度 二叉树的节点的高度:叶子节点的高度是1,左右不同的话 就是左右子树最高的高度+1(其实就是 最大子树高度+节点自身1)
//平衡因子 就是 左右子树的高度差 空树的高度是0 注意:是左右子树
func handleBF(node *Node)*Node{
if getHeight(node.lchild)-getHeight(node.rchild)==2 {
//右旋
if getHeight(node.lchild.lchild)-getHeight(node.lchild.rchild)>0{
node=rrRotation(node)
}
}else{
node=lrRotation(node)
}
if getHeight(node.lchild)-getHeight(node.rchild)==-2 {
//右旋
if getHeight(node.lchild.lchild)-getHeight(node.lchild.rchild)<0{
node=llRotation(node)
}
}else{
node=rlRotation(node)
}
return node
}
//插入节点 ---> 依次向上递归,调整树平衡
func Insert(node *Node, index int, data int) (*Node, error) {
if node == nil {
return &Node{lchild: nil, rchild: nil, index: index, data: data, height: 1}, nil
}
if node.index > index {
node.lchild, _ = Insert(node.lchild, index, data)
node = handleBF(node)
} else if node.index < index {
node.rchild, _ = Insert(node.rchild, index, data)
node = handleBF(node)
} else {
return nil, errTreeIndexExist
}
node.height = max(getHeight(node.lchild), getHeight(node.rchild)) + 1
return node, nil
}
//中序遍历树,并根据钩子函数处理数据
func Midtraverse(node *Node, handle func(interface{}) error) error {
if node == nil {
return nil
} else {
if err := handle(node); err != nil {
return err
}
if err := Midtraverse(node.lchild, handle); err != nil { //处理左子树
return err
}
if err := Midtraverse(node.rchild, handle); err != nil { //处理右子树
return err
}
}
return nil
}
//查找并返回节点
func Search(node *Node, index int) (*Node, error) {
for {
if node == nil {
return nil, errNotExist
}
if index == node.index { //查找到index节点
return node, nil
} else if index > node.index {
node = node.rchild
} else {
node = node.lchild
}
}
}
//删除指定index节点
//查找节点 ---> 删除节点 ----> 调整树结构
//删除节点时既要遵循二叉搜索树的定义又要符合二叉平衡树的要求 ---> 重点处理删除节点的拥有左右子树的情况
func Delete(node *Node, index int) (*Node, error) {
if node == nil {
return nil, errNotExist
}
if node.index == index { //找到对应节点
//如果没有左子树或者右子树 --->直接返回nil
if node.lchild == nil && node.rchild == nil {
return nil, nil
} else if node.lchild == nil || node.rchild == nil { //若只存在左子树或者右子树
if node.lchild != nil {
return node.lchild, nil
} else {
return node.rchild, nil
}
} else { //左右子树都存在
//查找前驱,替换当前节点,然后再进行依次删除 ---> 节点删除后,前驱替换当前节点 ---> 需遍历到最后,调整平衡度
var n *Node
//前驱
n = node.lchild
for {
if n.rchild == nil {
break
}
n = n.rchild
}
//
n.data, node.data = node.data, n.data
n.index, node.index = node.index, n.index
node.lchild, _ = Delete(node.lchild, n.index)
}
} else if node.index > index {
node.lchild, _ = Delete(node.lchild, index)
} else { //node.index < index
node.rchild, _ = Delete(node.rchild, index)
}
//删除节点后节点高度
node.height = max(getHeight(node.lchild), getHeight(node.rchild)) + 1
//调整树的平衡度
node = handleBF(node)
return node, nil
}
//test
func main() {
//打印匿名函数
f := func(node interface{}) error {
fmt.Println(node)
//fmt.Printf("this node is tree root node. node.index:%d node.data:%d\n", node.(*Node).index, node.(*Node).data)
return nil
}
// 插入测试数据
tree, _ := Insert(nil, 3, 6)
tree, _ = Insert(tree, 4, 7)
tree, _ = Insert(tree, 5, 8)
tree, _ = Insert(tree, 7, 10)
tree, _ = Insert(tree, 6, 11)
tree, _ = Insert(tree, 15, 12)
fmt.Println("Midtravese\n")
if err := Midtraverse(tree, f); err != nil {
fmt.Printf("Midtraverse failed err:%v\n", err)
}
// 搜索index=4的节点
fmt.Println("\ntest Search in the tree")
node, err := Search(tree, 4)
if err != nil {
fmt.Printf("err = %s\n", err)
} else {
fmt.Printf("in the tree ,found the node:%v\n", node)
}
//测试删除节点
fmt.Println("\ntest Delete the node in the tree")
fmt.Println("before delete node index:4\n")
Midtraverse(tree, f)
tree, _ = Delete(tree, 4)
//Delete(tree, 3)
fmt.Println("after delete node index:4\n")
Midtraverse(tree, f)
}