数据结构专题和名词解释(二)

二三树
  • 满足二分搜索树的基本性质(有序)
  • 节点可以存放一个元素或者两个元素,一个节点可以有两个孩子或者三个孩子,二节点和三节点
  • 二三树是一颗绝对平衡的树(根节点到任意叶子节点 的高度是一致的)
    在这里插入图片描述
  • 插入 如果子树为空,那么新数据会插入到叶子节点中去,产生节点融合。
  • 形成4节点之后,开始分裂为子树,即二分搜索树
  • 当第二个四节点形成之后,由于需要绝对平衡,所以需要新的节点融合进上一级的节点
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
红黑树
  • 红黑树本质上是二叉查找树,但是又增加了额外的要求
    1.节点是红色或者黑色
    2.根节点是黑色
    3.每个叶 子点都是黑色(即null节点)
    4.每个红色节点的两个子节点都是黑色(不能有连续的红色节点)
    5.从任一节点到其叶子节点的路径都包含相同数据的黑色节点

它是一种弱平衡二叉树(由于是弱平衡,可以推出,相同的节点情况下,AVL树的高度低于红黑树),相对于要求严格的AVL树来说,它的旋转次数变少,所以对于搜索、插入、删除操作多的情况下,我们就用红黑树

其实红黑树是对二叉树和平衡二叉树的一种综合权衡的产物,因为二叉树可能会退化成链表,所以出现了平衡二叉树,每次的增删可能都需要频繁的旋转来达到平衡,但是这样其实影响了写的效率。

那么此时,红黑树应运而生,他不要求严格的平衡,所以写的话他的旋转次数变少,更利于写多的情况

  • 讲解:
    在这里插入图片描述
  • 用红色的变表示并列关系,我们怎么用红色表示红色的边呢? b节点用红色标识,标识b节点和父节点是并列关系,红色节点都是向左倾斜的
    在这里插入图片描述

在这里插入图片描述

跳表
  • 相关参考
  • 我们知道,数组在内存中是一块连续的内存空间,所以数组是可以根据下标随机访问的,一个有序的数组,是可以实现logn的查找的,但是链表却不行,链表中查找元素必须从表头开始遍历,那么怎么让链表也实现logn的查找呢?那么我们通过以空间还时间的思想,提出了跳表。比如下面这个图。我们基于链表提取出来一个索引链表。然后再基于索引链表提取一个索引链表。
    在这里插入图片描述
    原来的链表写成了三个链表,记从下到上的编号为0、1、2,可以发现0号链表就是原始链表,1号链表是原始链表四等分点,2号链表是原始链表的二等分点。

我们再来查找7,初始搜索范围为(H, T):
在2号链表中与4比较,7>4,更新搜索范围为(4, T)
在1号链表中与6比较,7>6,更新搜索范围为(6, T)
在0号链表中与7比较,7=7,查找成功。

形象化地说,SkipList就是额外保存了二分查找的中间信息。不过SkipList中含有随机化,生成的结构不会像上面那样完美,来看实际生成的一个SkipList,是一个金字塔形状的。旁边有记录的level

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 跳表相比红黑树来说,更好维护,写操作只需要找到位置就可以插入,不需要维护树的平衡,跳表是对索引的索引进行提取,高度更灵活
  • 代码实现
package main

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

type SkipNode struct { //节点结构
	score int         //权重值
	val   interface{} //存放的值
	right *SkipNode   //右侧节点指针
	down  *SkipNode   // 向下指针
}
type SkipList struct { //跳表的结构
	headNode *SkipNode //头结点
	level    int       //当前层级
	maxLevel int       //最大的层
}

/**
构造一个跳表结构
*/
func NewSkipList(maxLevel int) *SkipList {
	return &SkipList{
		headNode: &SkipNode{
			score: math.MinInt64,
		},
		maxLevel: maxLevel,
	}
}

/**
查询操作

(1) 从team节点出发,如果当前节点的key与查询的key相等,那么返回当前节点(如果是修改操作那么一直向下进行修改值即可)(2) 如果key不相等,且右侧为null,那么证明只能向下(结果可能出现在下右方向),此时team=team.down
(3) 如果key不相等,且右侧不为null,且右侧节点key小于待查询的key。那么说明同级还可向右,此时team=team.right
(4)(否则的情况)如果key不相等,且右侧不为null,且右侧节点key大于待查询的key 。那么说明如果有结果的话就在这个索引和下个索引之间,此时team=team.down。
最终将按照这个步骤返回正确的节点或者null(说明没查到)。
*/

func (sl *SkipList) Search(key int) *SkipNode {
	node := sl.headNode
	for node != nil {
		//fmt.Printf("%+v \n", node)
		//return node
		if node.score == key { //已经找到了0层并且分值相等
			//这里要看是不是已经到达原始链表了
			if node.down == nil {
				return node
			} else { //如果下面还有,则下降
				node = node.down
			}

		} else if node.right == nil { //如果右侧没有了,那就去下一级去找
			node = node.down

		} else if node.right.score > key { //右侧节点大于查询值,则下降
			node = node.down
		} else { //查询节点比当前节点的右侧节点还要大
			//右侧还有节点,那么移动到右侧节点
			node = node.right
		}

	}
	return nil
}

/**

(1)如果team右侧为null,那么team=team.down(之所以敢直接这么判断是因为左侧有头结点在左侧,不用担心特殊情况)
(2)如果team右侧不 为null,并且右侧的key等于待删除的key,那么先删除节点,再team向下team=team.down为了删除下层节点。
(3)如果team右侧不 为null,并且右侧key小于待删除的key,那么team向右team=team.right。
(4)如果team右侧不 为null,并且右侧key大于待删除的key,那么team向下team=team.down,在下层继续查找删除节点。

*/
func (sl *SkipList) Delete(key int) {
	node := sl.headNode
	for node != nil {
		//和当前节点的右侧的值相等
		if node.right.score == key {
			node.right = node.right.right
			node = node.down //继续下一层查找
		} else if node.right == nil { //右侧没了,下降去找
			node = node.down
		} else if node.right.score > key { //比右侧节点小,下沉
			node = node.down
		} else { //比右侧节点大 继续往右查
			node = node.right
		}
	}
}

/**
插入
随机化的方法去判断是否向上层插入索引。即产生一个[0-1]的随机数如果小于0.5就向上插入索引,插入完毕后再次使用随机数判断是否向上插入索引。运气好这个值可能是多层索引,运气不好只插入最底层(这是100%插入的)。但是索引也不能不限制高度,我们一般会设置索引最高值如果大于这个值就不往上继续添加索引了。
*/

func (sl *SkipList) Add(NewNode SkipNode) {
	//先看看这个数据是否已经存在,存在则更新
	findNode := sl.Search(NewNode.score)
	if findNode != nil {
		findNode.val = NewNode.val
		return
	}
	//使用切片作为栈存储,此处解释一下为什么要用栈,因为查找的过程我们是从level高往低去查找位置的,但是插入数据的时候我们是从低往高采用随机算法插入的,就需要后进先出
	stack := make([]*SkipNode, 0)
	node := sl.headNode
	for node != nil {
		if node.right == nil {
			stack = append(stack, node)
			node = node.down

		} else if node.right.score > NewNode.score {
			stack = append(stack, node)
			node = node.down
		} else {
			node = node.right
		}
	}
	//开始添加到原始链表
	level := 1
	//初始化一个节点
	downNode := &SkipNode{}



	len := len(stack)
	for i := len - 1; i >= 0; i-- {
		nodeLeft := stack[i] //待插入节点的左侧
		nodeNew := &SkipNode{ //构建一个全新的节点
			val:   NewNode.val,
			score: NewNode.score,
		}
		//开始构建竖方向的链表
		nodeNew.down = downNode
		downNode = nodeNew //这个值用来连接下一次循环
		//fmt.Printf("%+v \n",nodeLeft)
		if nodeLeft.right == nil { //右侧没值了直接插入
			nodeLeft.right = nodeNew
		} else { //右侧有值插入中间
			nodeNew.right = nodeLeft.right
			nodeLeft.right = nodeNew
		}

		//超出了最大层级
		if level > sl.maxLevel {
			break //结束循环体 ,执行循环体后面的代码
			//continue//本次循环结束 开始继续下一次循环
			//return 结束当前函数,不在往下执行
		}
		rand.Seed(time.Now().UnixNano())
		randNum := rand.Float64()
		if randNum > 0.5 { //
			break
		}

		level++               //添加层级+1
		if level > sl.level { //假设很幸运,超出了最上层索引,这个时候需要给跳表层级+1
			headNode := &SkipNode{
				score: math.MinInt64,
			}
			headNode.down = sl.headNode //改变head
			//	必须要把元素插入0的位置
			stack = append([]*SkipNode{headNode}, stack...)
			i = 0 //此处为什么+1,因为何种情况出现在所有的栈元素已经遍历完毕,即i=-1了,但是我们又往里面插入了一个元素,如果要再次执行,必须将i置为0
		}
	}
}


func (sl *SkipList)PrintList(){
	node:=sl.headNode
	lastNode:=sl.headNode
	for  {
		if node!=nil {
			fmt.Println(node)
			node=node.right
		}else{
			if lastNode.down != nil {
				node=lastNode.down
				lastNode=lastNode.down
			}else{
				fmt.Println("打印完毕")
				break
			}
		}

	}

}

func main() {
	sl := NewSkipList(20)
	for i := 0; i < 20; i++ {
		sl.Add(SkipNode{score: i, val: "hahha" + strconv.Itoa(i)})
	}

	sl.PrintList()
}


哈夫曼树

多路查找树

b树

B-树就是B树,不是叫B减树(一开始我也经常叫错),是普遍运用于文件系统和数据库的一种多叉(即,每个非叶子结点可以有多个孩子)平衡查找树。每次深度加1就会进行一次磁盘IO的查询,将当前高度的数据加到内存中,再进行数值比较。从中可以看出相比大部分的查询时间是花费在磁盘IO的速度上,所以要想提高性能就是将树的高度足够低,IO次数足够少。总的来说,为了让性能更高,就把"瘦高"的树变的"矮胖" B树的应用:主要用于文件系统以及部分数据库索引(MongoDB) 而Mysql是用B+树的。

b+树

B+树是应文件系统所需而产生的一种B树的变形树(文件的目录一级一级索引,只有最底层的叶子节点(文件)保存数据)非叶子节点只保存索引,不保存实际的数据,数据都保存在叶子节点中。

B+树的好处主要体现在查询性能上:
单行查询:由于B+树中间节点没有数据,所以同样大小的磁盘页可以容纳更多的节点元素,在数据量相同的情况下,B+树结构比B树更加"矮胖",查询IO次数更少。而且B+树每次查询都必须查到叶子节点,比B树更稳定。
范围查询 自顶向下 方便范围查找

综合起来,B+比B树优势有三个:1、IO次数更少 2、查询性能稳定 3、范围查询简便 应用:B和B+树主要用在文件系统以及数据库做索引,比如MySQL

b*树
r树

二叉堆

二叉堆本质上是一种完全二叉树,即最多只缺失右节点

在这里插入图片描述

  • 相关索引的计算
    在这里插入图片描述

  • 最大堆
    最大堆的任何一个父节点的值,都大于或等于 它 左、右孩子节点的值(左右子树无相对比较)。
    在这里插入图片描述

  • 最小堆
    最小堆的任何一个父节点的值,都小于或等于它左、 右孩子节点的值。
    在这里插入图片描述
    二叉堆的根节点叫作堆顶 。最大堆和最小堆的特点决定了:最大堆的堆顶是整个堆中的最大元素 ;最小堆的堆顶是整个堆中的最小元素 。

  • 堆的构造和实现

package main

import (
	"fmt"
)

type MinHeap struct {
	Element []int
	Size    int
}

//定义一个构造方法
func NewMinHeap(i int) *MinHeap {
	h := &MinHeap{
		Element: []int{i},
		Size:    1,
	}
	return h
}

//插入元素
func (h *MinHeap) Insert(v int) {
	h.Element = append(h.Element, v)
	h.Size++
	//获取当前元素的索引下标
	i := h.Size - 1
	pIndex := (i - 1) / 2 //父节点索引

	for i > 0 && v < h.Element[pIndex] { //通过层层比较,最终得到合适的位置
		h.Element[i] = h.Element[pIndex]
		i = pIndex
		pIndex = (i - 1) / 2
	}
	h.Element[i] = v
}

func (h *MinHeap) DeleteMin() (int, error) {
	if len(h.Element) == 0 {
		return 0, fmt.Errorf("empty")
	}
	top := h.Element[0]
	h.Element[0] = h.Element[h.Size-1]
	h.Size--

	//开始节点下沉操作
	h.HeapUpDown()
	return top, nil
}

/**
下沉,其实就是求出最大的值的索引值是多少
值用来做比较,不赋值
*/
func (h *MinHeap) HeapUpDown() {

	for i := 0; i < h.Size/2; {
		mindId := i
		lId := i*2 + 1
		rId := i*2 + 2
		//fmt.Println(i)

		if h.Element[mindId] > h.Element[lId] {
			mindId = lId
		}


		if h.Element[lId] > h.Element[rId] {
			mindId = rId
		}

		if mindId==i {
			break
		}

		h.Element[i],h.Element[mindId]=h.Element[mindId],h.Element[i]

		i=mindId
	}

}

/**
 * @Description
 * @Author 老a技术联盟
 * @Date 2022/4/20 10:38
 **/
func main() {
	h := NewMinHeap(10)
	h.Insert(30)
	h.Insert(2)
	h.Insert(20)
	h.Insert(13)
	h.Insert(100)
	h.Insert(50)
	h.Insert(25)
	fmt.Println(h.DeleteMin())
	fmt.Println(h.DeleteMin())
	fmt.Println(h.DeleteMin())
	fmt.Println(h.DeleteMin())
	fmt.Println(h.DeleteMin())


}


左倾堆

斜堆

二项堆

斐波那契堆

类型

邻接矩阵无向图
邻接表无向图
邻接矩阵有向图
邻接表有向图

最小生成树

kruskai算法
prim算法

最短路径

关键路径

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

老A技术联盟

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

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

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

打赏作者

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

抵扣说明:

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

余额充值