【UE】TMap&TSet&Map实现原理 红黑树&稀松数组&哈希表

注:本文有一些基础内容没有讲解,可以自行百度, 看到底层实现默认有基础

Map和TMap比较

STL容器Map底层是用红黑树实现的
UE的TMap底层是用哈希表实现的

链表 内存是不连续的,所以查询效率低,插入/删除效率高
数组 内存是连续的,所以下标查询效率高,插入/删除效率低

红黑树是基于链表的数据结构,查询效率是O(logN)
哈希表是基于数组的数据结构,查询效率是O(1)

Map(红黑树)

Map的底层是红黑树,红黑树是一种特殊的二叉树,二叉树是为了加快搜索效率而出现的
在这里插入图片描述
但是最坏的情况下二叉搜索树会退化为链表结构, 影响查询效率
在这里插入图片描述
这时就出现了二叉搜索树的变种, 红黑树、完全平衡二叉树等等,通过限制最短分支和最长分支的差值,从而避免出现二叉搜索树退化成链表的问题
完全平衡二叉树要求最长的分支和最短的分支之间节点个数相差为1,红黑树的话只要满足红黑树五大特性就可以
本质区别:就是插入效率和搜索效率的转换,搜索效率都是logN,完全平衡二叉树最差搜索情况也是logN, 红黑树最差情况是2 * logN(最长分支是最短分支的两倍), 完全平衡二叉树几乎每次插入数据时都要重新平衡二叉树,红黑树有时候插入的时候只要改改颜色
个人认为完全平衡二叉树插入的效率降低和搜索的效率提升不成正比,这也是为什么红黑树用的比完全平衡二叉树广泛的原因】

红黑树的五大特性

1.每个节点只能是红色或者是黑色
2.根节点只能是黑色,且黑色根节点不存储数据
3.任何相邻的节点都不能同时为红色
4.红色的节点,它的子节点只能是黑色
5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点

节点设计

// 红黑树颜色枚举
enum RBColor
{
	Red,
	Black
}
// 节点
struct  Node
{
	int Data;                  //值
	RBColor Color;           //节点颜色
	Node* Parent;            //父节点
	Node* LeftChild;         //左孩子节点
	Node* RightChild;        //右孩子节点
}

class RBTree
{
public:
	//根节点
	Node<T>* Root;
}

当插入或者删除数据后导致红黑树不满足五大特性时,就需要重新调整红黑树,用到的就是左旋、右旋等

左右旋(以左旋代码示范,右旋同理,这里不涉及到变色)

【图片来源于网络】
在这里插入图片描述

所有的操作都是双向的, 修改一个节点的父节点后, 要将 修改后的父节点 的子节点也对应修改

// 下方统一用 当前节点 代替 node
void LeftRotate(Node* node) {
	// 先将右孩子节点取出来  
	Node Child = node->RightChild
	// 先调整父节点
	// 有父节点时
	if(node->Parent) {
		//右孩子的父节点改为当前节点的父节点
		Child->Parent = node->Parent;
		//同理,当前节点的父节点的子节点 也要修改为 右孩子节点
		//当前节点有可能是父节点的左孩子节点, 也有可能是右孩子节点
		if(node->Parent->LeftChild == node) {
			node->Parent->LeftChild = Child;
		} else {
			node->Parent->RightChild = Child;
		} 
	} else {
		//没有父节点时, 当前节点就是根节点, 修改根节点为右孩子节点就行
		root = Child;
	}
	//右孩子的左孩子节点改为当前节点的右孩子节点
	node->RightChild = Child->LeftChild;
	//同理 右孩子节点的左孩子节点的父节点也设置为当前节点
	if (Child->LeftChild) {
		Child->LeftChild->Parent = node;
	}
	//当前节点的父节点设置为右孩子节点,
	node->Parent = Child;
	//同理 右孩子节点的左子节点设置为当前节点
	Child->Left = node;
}

插入

void Insert(Node* node) {
	Node* ParentNode = Root;// 初始指向Root节点,作用是记录node的父节点
	Node* CurrentNode = Root;// 初始指向Root节点,作用是记录当前遍历到哪个节点了
	while (CurrentNode != nullptr) {
		ParentNode = CurrentNode;// y保存更新前的x
		if (node->Data < CurrentNode->Data) {// node 比遍历到的节点的key小 去左子树中查找
			CurrentNode = CurrentNode->LeftChild;
		} else {// 去右子树中查找
			CurrentNode = CurrentNode->RightChild;
		}
	}
	node->Parent = ParentNode;  //设置父节点
	if (ParentNode == nullptr) {// 如果父节点为空, 那就说明没有走上面的循环,当前树为空 插入的是第一个节点
		Root = node;// 
	} else if (ParentNode->Data < ParentNode->Data) {
		ParentNode->LeftChild = node;// node的值比父节点值小 作为左子节点
	} else {
		ParentNode->RightChild = node;// node的值比父节点值大 作为右子节点
	}
	node->LeftChild = nullptr;
	node->RightChild = nullptr;// z是叶子节点
	node->Color = RED;// 叶子节点红色
	AdjustColor(node);// 插入之后需要调整树的结构, 因为有可能不满足红黑树的规律了
}

插入要考虑的情况就比较多, 如果破坏了红黑树的特性, 就涉及到旋转操作了,大体分为以下几种情况

1、红黑树为空,那么就把插入节点的颜色改为黑就可(变色)
2、如果新插入节点的父节点是黑色, 此时就不涉及任何调整(无调整)
3、插入节点的父节点为红色,这种情况又包含了几种特殊情况,挨个分析(变色 + 旋转)

这里要说两句提醒一下
(1)红黑树插入的节点要是红色, 如果是黑色的话每次插入都破坏了红黑树的规则
(2)红黑树新插入的节点一定是叶子节点,只有父节点(看上面的插入代码,只有比较到nullptr的节点才会终止比较)

插入节点的父节点为红色

【图片来源于网络】
参考文章:https://www.jianshu.com/p/e136ec79235c

1、插入节点的叔叔节点也为红色,如下图 I 是插入节点, P是父节点, S是叔节点(也是红色)

在这里插入图片描述
把P和S都设置为黑色节点,设置之后,此时包含PP的路径上的黑色节点就一定比不包含PP节点路径的黑色节点多1,所以还需要将PP节点设置为红色,但PP节点也有可能有父节点, 因为PP节点之前是黑色的, 所以PP节点的父节点是红色和黑色都有可能, 此时就将PP节点作为我们新插入的节点, 继续去递归分析

2、叔叔结点不存在或为黑色,并且插入结点的父亲结点是祖父结点的左子节点

(1)插入节点(I)是左子节点
在这里插入图片描述
对PP节点进行右旋, 将P设置为黑色, 将PP设置为红色, 然后如果P节点有右子树, 还要将P节点的右子树设置为PP节点的左子树
(2)插入节点(I)是右子节点
在这里插入图片描述
先将P左旋,然后再对I右旋

3、叔叔结点不存在或为黑色,并且插入结点的父亲结点是祖父结点的右子节点

和上面的一样,就是右旋变成左旋
(1)插入节点I是右子节点
在这里插入图片描述
(2)插入节点I是左子节点
在这里插入图片描述

哈希表

这篇文章讲的很好,可以直接去看:https://blog.csdn.net/weixin_61508423/article/details/128043581

TMap

参考文章:https://zhuanlan.zhihu.com/p/670028363
UE的TMap是基于TSet实现的, 而TSet又是基于两个数组实现的,Hash数组(Hash存储区)和元素数组(稀疏数组:数据存储区)

TMap只有一个Pairs成员类型是TSet
传入Key,Value最终存放在TSet中
在这里插入图片描述

TSet

在这里插入图片描述
在这里插入图片描述

Hash存储区

连续的内存
存储数据存储区中元素的索引

数据存储区

数据存储区就是TSparseArray(稀疏数组),内存连续
TMap/TSet存储的数据,都存在TSparseArray中

TSparseArray

在这里插入图片描述
在这里插入图片描述
TsparseArrayElementOrFreeListLink内部结构
在这里插入图片描述
TsparseArrayElementOrFreeListLink是个共用体,有数据的时候装载的就是数据,没数据的时候就是个结构体, 存储上一个空闲位置的索引和下一个空闲位置的索引,所以删除元素时可以不用把后续的元素都往前移,只需要将标记为置为false,然后空位数+1,并将该位置修改成结构体并记录对应的索引即可

插入查找元素的过程

在这里插入图片描述
在这里插入图片描述

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值