【C++/STL】手撕AVL树

2023-11-18


在这里插入图片描述

1.map中的问题

  1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元
    素。

  2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:

    typedef pair<const Key,T> value_type
    

3.在内部,map中的元素总是按照键值key进行比较排序的。

4.map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。

5.map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。

6.map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))

1.1map的insert()函数剖析

函数原型

image-20220802222402680

返回值

image-20220802223022808

insert()如果成功,那么返回值pair<iterator,bool>中的bool为true,表示插入成功;迭代器iterator指向插入的位置。

insert()如果插入失败,那么返回值pair<iterator,bool>中的bool值为false,表示插入失败,说明map中已经有val->key关键字相同的数据,迭代器iterator指向map中与关键字val->key相等的位置。

比如

map<string, int>mp;
mp.insert(make_pair("桃子", 2));
mp.insert(make_pair("梨", 3));
auto it=mp.insert(make_pair("苹果", 4));
//此时it指向的是苹果的结点

当我们再插入一个梨的数据时

pair<map<string, int>::iterator, bool> it = mp.insert(make_pair("桃子", 6));

image-20220802224526619

此时迭代器指向的是(“桃子”,2)的位置

1.2map对[ ]的重载

函数原型

image-20220802224705493

实现方式

image-20220802224745375

(*((this->insert(make_pair(k,mapped_type()))).first)).second;
//上面的表达式过于的臃肿,我们进行简化
auto it=this->insert(make_pair(k,mapped_type());
                //这一步会创建一个键值对<K,V>
                     
//上面的表达式可以表示为
((*it).first)->second;
//相当于使用insert返回值pair<iterator,bool>中,迭代器中的value值

所以我们在向map中添加数据时,有下面的写法:

void test_map2()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	map<string, int> countMap;
	for (auto& str : arr)
	{
		countMap[str]++;
	}
}
/*
会出现两种情况:
情况一:
	当coutMap中没有对应关键字的数据,那么会先创建一个键值对<K,V>,在通过=赋值给对应的变量的value。
	
	
情况二:
	当coutMap中有对应的关键字是,那么会指向有相同关键字的键值对<K-V>的value
*/

在这里插入图片描述

2.AVL树的模拟实现

2.1AVL树的概念

​ 二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。

image-20220802230043540

为了解决二叉搜索树的退化问题:

​ 两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法:**当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。 **

一棵AVL树有下面的性质

  • 它的左右子树都是AVL树
  • 左右子树的高度差的绝对值不超过2(高度差为-1 1 0 )

image-20220802230330357

我们规定平衡因子bf为:右子树的高度-左子树的高度

template <class K,class V>
class AVLTree
{
public:
	typedef AVLTreeNode<K, V> Node;

	//构造函数
	AVLTree()
		:_root(nullptr)
	{}
    /*
    	内部函数实现
    	....................
    	....................
    	....................
    	....................
    */
private:
    Node* _root;
}
2.2AVL树节点的定义
template <class K,class V>
struct  AVLTreeNode
{
	AVLTreeNode<K,V>* _left;  //左子树
	AVLTreeNode<K,V>* _right; //右子树
	AVLTreeNode<K,V>* _parent; //父亲节点
	pair<K, V>_kv;	//存放的K-V键值对
	//左右子树的高度差
	int _bf; //平衡因子
	//构造函数
	AVLTreeNode(const pair<K,V>& kv)
		:_left(nullptr),_right(nullptr),_parent(nullptr),_bf(0),_kv(kv)
	{}
};

在这里插入图片描述

2.3AVL树的插入

​ AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:

  • 按照二叉搜索树的方式插入新节点
  • 调整节点的平衡因子

插入新节点

1.第一步插入数据和一般的二叉搜索树一样

2.更新平衡因子

cur插入后,parent的平衡因子一定需要调整,在插入之前,parent
的平衡因子分为三种情况:-1,0, 1, 分以下两种情况:

  1. 如果cur插入到parent的左侧,只需给parent的平衡因子-1即可
  2. 如果cur插入到parent的右侧,只需给parent的平衡因子+1即可
    此时:pParent的平衡因子可能有三种情况:0,正负1, 正负2
    1. )如果parent的平衡因子为0,说明插入之前pParent的平衡因子为正负1,插入后被调整成0,此时满足AVL树的性质,插入成功。不需要再继续向上调整。
    2. ) 如果parent的平衡因子为正负1,说明插入前parent的平衡因子一定为0,插入后被更新成正负1,此时以parent为根的树的高度增加,需要继续向上更新
    3. ) 如果parent的平衡因子为正负2,则parent的平衡因子违反AVL树的性质,需要对其进行旋转处理
bool insert(const pair<K, V>& kv)
	{
		//按照普通的搜索二叉树的规则插入
		if (_root == nullptr)
		{
			_root = new Node(kv);
			_root->_bf = 0;
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		//比较K
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(kv);
		cur->_parent = parent;
		if (kv.first > parent->_kv.first)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		//更新_bf平衡因子
		while (parent)
		{
			if (parent->_left == cur)
			{
				parent->_bf--;
			}
			else if (parent->_right == cur)
			{
				parent->_bf++;
			}

			if (parent->_bf == 0)
			{
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1)
			{
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == -2 || parent->_bf == 2)
            {
    		 /*
     			旋转:
     			一共右四种情况,分别会对应
     			左单旋			右单旋
             	左右单旋		右左单旋
     .......................................
     .......................................
     .......................................
     		*/
            }
}
1.)在较高的右子树右侧插入数据(左单旋)

image-20220802231946682

if (parent->_bf == 2 && cur->_bf == 1)
{
	//左单旋
	RouteL(parent);
}

//左单旋的实现
void RouteL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* pparent = parent->_parent;
	parent->_right = subRL;
	if (subRL)
	{
		subRL->_parent = parent;
	}
	subR->_left = parent;
	parent->_parent = subR;
	//如果root为根
	if (parent == _root)
	{
		_root = subR;
		subR->_parent = nullptr;
	}
	else
	{
		subR->_parent = pparent;
		if (parent == pparent->_left)
		{
			pparent->_left = subR;
		}
		else
		{
			pparent->_right = subR;
		}
	}
	//更新平衡因子
	parent->_bf = 0;
	subR->_bf = 0;
}
2.)在较高的左子树左侧插入数据(右单旋)

image-20220802232631975

else if (parent->_bf == -2 && cur->_bf == -1){
	RouteR(parent);
}

//右单旋
void RouteR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	parent->_left = subLR;
	if (subLR)
	{
		subLR->_parent = parent;
	}
	subL->_right = parent;
	Node* pparent = parent->_parent;
	parent->_parent = subL;
	if (parent == _root)
	{
		_root = subL;
		subL->_parent = nullptr;
	}
	else
	{
		if (pparent->_left == parent)
		{
			pparent->_left = subL;
		}
		else
		{
			pparent->_right = subL;
		}
		subL->_parent = pparent;
	}
	//平衡因子调整
	parent->_bf = 0;
	subL->_bf = 0;
}
3.)在较高的左子树的右侧插入数据(左右双旋)

image-20220802233041707

还有一种h==0的特殊情况

image-20220802233129408

else if (parent->_bf == -2 && cur->_bf == 1)
{
	RouteLR(parent);
}

void RouteLR(Node* parent)
{
	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;
	RouteL(subL);
	RouteR(parent);
	if (bf == -1)
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 1;
	}
	else if (bf == 1)
	{
		subLR->_bf = 0;
		subL->_bf = -1;
		parent->_bf = 0;
	}
	else if (bf == 0)
	{
		subLR->_bf = 0;
		subL->_bf = 0;
		parent->_bf = 0;
	}
	else
	{
		assert(false);
	}
}
4.)在较高的右子树的左侧插入数据(右左双旋)

image-20220802233421331

else if (parent->_bf == 2 && cur->_bf == -1)
{
	RouteRL(parent);
}

void RouteRL(Node* parent)
{
	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;
	RouteR(subR);
	RouteL(parent);
	if (bf == 0)
	{
		subR->_bf = 0;
		subRL->_bf = 0;
		parent->_bf = 0;
	}
	else if (bf == -1)
	{
		subRL->_bf = 0;
		parent->_bf = 0;
		subR->_bf = 1;
	}
	else if (bf == 1)
	{
		subRL->_bf = 0;
		parent->_bf = -1;
		subR->_bf = 0;
	}
	else
	{
		assert(false);
	}
}
2.4验证是否为AVL树

我们只需要验证每棵子树平衡因子的绝对值是否小于2,平衡因子与高度差是否相等即可。

int _Height(Node* root)
{
	if (root == nullptr)
	{
		return 0;
	}
	int left = _Height(root->_left);
	int right = _Height(root->_right);
	return max(left, right) + 1;
}

//判断是否为平衡二叉树
bool _isAVLTree(Node* root)
{
	if (root == nullptr)
	{
		return true;
	}
	int height_left = _Height(root->_left);
	int height_right = _Height(root->_right);
	int bf = height_right - height_left;
	if (abs(bf) >= 2)
	{
		cout << "平衡因子异常,不是平衡二叉树" << endl;
		return false;
	}
	if (bf != root->_bf)
	{
		cout << "平衡因子计算错误" << endl;
		return false;
	}
	return _isAVLTree(root->_left) && _isAVLTree(root->_right);
}

bool isAVLTree()
{
	return _isAVLTree(_root);
}
2.5层序遍历

和一般的多叉树层序遍历一样,借助一个queue<Node*>

void _levelOrderBottom(Node* root) {
	if (root == nullptr)
	{
		return;
	}
	queue<Node*>q;
	q.push(root);
	while (!q.empty())
	{
		int size = q.size();
		for (int i = 0;i < size;i++)
		{
			Node* front = q.front();
			cout << front->_kv.second << " ";
			if (front->_left)
			{
				q.push(front->_left);
			}
			if (front->_right)
			{
				q.push(front->_right);
			}
			q.pop();
		}
		cout << endl;
	}
}
//层序遍历
void levelOrderBottom()
{
	_levelOrderBottom(_root);
}
2.6中序遍历
//中序遍历
void _inorder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}
	_inorder(root->_left);
	cout << root->_kv.first << " ";
	_inorder(root->_right);
}

void inorder()
{
	_inorder(_root);
}

在这里插入图片描述

3.实现结果

我们可以向AVLTree中添加随机数,再调用isAVLTree()和中序遍历验证。如果isAVLTree()返回1,并且中序遍历的结果是升序,那么就是一棵AVLTree。

int main()
{
	srand(time(0));
	AVLTree<int, int>avl;
	int N = 100;
	for (int i = 0;i < N;i++)
	{
		int m = rand();
		avl.insert(make_pair(m, m));
	}
	cout << avl.isAVLTree() << endl;
	avl.inorder();
	//avl.levelOrderBottom();
}

image-20220802235121869

感谢阅读!
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++/STL】手撕AVL树 的相关文章

  • Grpc - 将消息从一个客户端发送到连接到同一服务器的另一个客户端

    是否可以将消息从一个客户端发送到连接到同一服务器的另一个客户端 我想将数据从一个客户端发送到服务器然后发送到特定客户端 我想我需要获取客户端 ID 但我不知道如何获取此 ID 以及如何从服务器将此消息发送到该客户端 我这里有一个样本 这是一
  • 转换 const void*

    我有一个函数返回一个const void 我想用它的信息作为char 我可以将它投射为 C 风格的罚款 char variable但是当我尝试使用reinterpret cast like reinterpret cast
  • MVC3中设置下拉列表中的所选项目

    我必须为视图中的下拉列表设置所选项目 但它不起作用 View div class editor label Html LabelFor model gt model Gender div div class editor field Htm
  • 如何将 SOLID 原则应用到现有项目中

    我对这个问题的主观性表示歉意 但我有点卡住了 我希望之前处理过这个问题的人能够提供一些指导和建议 我有 现在已经成为 一个用 C 2 0 编写的非常大的 RESTful API 项目 并且我的一些类已经变得巨大 我的主要 API 类就是一个
  • 如何在 C# Designer.cs 代码中使用常量字符串?

    如何在 designer cs 文件中引用常量字符串 一个直接的答案是在我的 cs 文件中创建一个私有字符串变量 然后编辑 Designer cs 文件以使用此变量 而不是对字符串进行硬编码 但设计者不喜欢这样抛出错误 我明白为什么这行不通
  • 即使没有异步,CallContext.LogicalGetData 也会恢复。为什么?

    我注意到CallContext LogicalSetData LogicalGetData不按照我期望的方式工作 内部设置的值async方法得到恢复即使没有异步或任何类型的线程切换 无论如何 这是一个简单的例子 using System u
  • C++中判断unicode字符是全角还是半角

    我正在编写一个终端 控制台 应用程序 该应用程序应该包装任意 unicode 文本 终端通常使用等宽 固定宽度 字体 因此要换行文本 只需计算字符数并观察单词是否适合一行并采取相应的操作 问题是 Unicode 表中的全角字符在终端中占用了
  • 从网页运行 ClickOnce 应用程序,无需用户操作

    我们有一个基于 Java 的 Web 应用程序以及用 C 编写的相同应用程序 如果 java 检查器发现客户端计算机上没有安装 Java 则应该运行该应用程序 这个想法是运行 C 单击一次 http en wikipedia org wik
  • 如果输入被重定向则执行操作

    我想知道如果我的输入被重定向 我应该如何在 C 程序中执行操作 例如 假设我有已编译的程序 prog 并且我将输入 input txt 重定向到它 我这样做 prog lt input txt 我如何在代码中检测到这一点 一般来说 您无法判
  • 模板外部链接?谁能解释一下吗?

    模板名称具有链接 3 5 非成员函数模板可以有内部链接 任何其他模板名称应具有外部链接 从具有内部链接的模板生成的实体与在其他翻译单元中生成的所有实体不同 我知道使用关键字的外部链接 extern C EX extern C templat
  • 将 Word 转换为 PDF - 禁用“保存”对话框

    我有一个用 C 编写的 Word 到 PDF 转换器 除了一件事之外 它工作得很好 有时 在某些 Word 文件上 后台会出现一条消息保存源文件中的更改 gt 是 否 取消 但我没有对源文件进行任何更改 我只想从 Word 文件创建 PDF
  • 将函数参数类型提取为参数包

    这是一个后续问题 解包 元组以调用匹配的函数指针 https stackoverflow com questions 7858817 unpacking a tuple to call a matching function pointer
  • 如何最好地以编程方式将 `__attribute__ ((unused))` 应用于这些自动生成的对象?

    In my makefile我有以下目标 它将文本 HTML 资源 编译 为unsigned char数组使用xxd i http linuxcommand org man pages xxd1 html 我将结果包装在匿名命名空间和标头保
  • Oauth2中如何同时撤销RefreshToken和使AccessToken失效

    我正在使用 Owin Oauth2 授权和资源服务器相同 开发单页面应用程序 AngularJS Net MVC Json Rest API 的身份验证流程 我选择了 Bearer Token 路由而不是传统的 cookie session
  • 模板类的模板构造函数的 C++ 显式模板特化

    我有一个像这样的课程 template
  • C++ 对象用 new 创建,用 free() 销毁;这有多糟糕?

    我正在修改一个相对较大的 C 程序 不幸的是 并不总是清楚我之前的人使用的是 C 还是 C 语法 这是在一所大学的电气工程系 我们 EE 总是想用 C 来做所有事情 不幸的是 在这种情况下 人们实际上可以逃脱惩罚 但是 如果有人创建一个对象
  • C++:二叉树所有节点值的总和

    我正在准备面试 我被一个二叉树问题困住了 我们如何计算二叉树所有节点中存在的值的总和 优雅的递归解决方案 伪代码 def sum node if node NULL return 0 return node gt value sum nod
  • 为什么空循环使用如此多的处理器时间?

    如果我的代码中有一个空的 while 循环 例如 while true 它将把处理器的使用率提高到大约 25 但是 如果我执行以下操作 while true Sleep 1 它只会使用大约1 那么这是为什么呢 更新 感谢所有精彩的回复 但我
  • 我可以使用 lambda 函数或 std::function 对象来代替函数指针吗?

    我有一个需要使用的库 它定义了以下内容 typedef void CallbackFunction const int i 并且有一个注册回调的函数 如下所示 void registerCallback CallbackFunction p
  • 如何在 C 中将 char 连接到 char* ?

    我怎样才能前置char c to char myChar 我有c值为 A and myChar值为 LL 我怎样才能前置c to myChar使 ALL 这应该有效 include

随机推荐

  • 北冥神功与六脉神剑(一)

    北冥神功与六脉神剑 言念及此 登时心下坦然 默默祷祝 神仙姊姊 你吩咐下来的事 段誉当然一定遵行不误 但愿你法力无边 逍遥派弟子早已个个无疾而终 战战兢兢的打开绸包 里面是个卷成一卷的帛卷 展将开来 第一行写着 北冥神功 字迹娟秀而有力 便
  • 如何解决:OSError: Unable to create file (unable to open file: name = ‘. et_classification.h5‘, errno = 2

    报错 OSError Unable to create file unable to open file name et classification h5 errno 22 error message Invalid argument f
  • 【深度学习工作站】CUDA + cuDNN + Tensorflow-gpu

    安装有两种路径 1 Anaconda简便安装 不需要安装CUDA和cuDNN 即使装了 Conda环境还是会重装CUDA和cuDNN 在清华镜像下载Anaconda3 新建环境后conda install tensorflow gpu 1
  • [ECharts] There is a chart instance already initialized on the dom.问题原因

    在使用vue绘图的时候 我设置间隔时间进行绘制 控制台一直警告 ECharts There is a chart instance already initialized on the dom 查看代码是因为获取了两次dom进行了初始化 m
  • Mac下 cobra安装

    Mac下 cobra安装 1 配置 bash profile export GOPATH PWD go export GOBIN GOPATH bin export PATH PATH GOBIN 2 在 GOPATH src go get
  • 刚体动力学

    文章目录 刚体状态 将某个物体从局部坐标系变化到全局坐标系 对时间求导 对矩阵求导 惯性 刚体属性 1 质心 计算方法 体素法 直接计算法 四面体体积 四面体的中心 2 惯性张量 世界坐标系中的惯性变量 刚体运动 力矩 刚体的固定属性 当前
  • c语言停车场

    include
  • Google Test(GTEST)使用入门(2)- 原生例子分析

    目录 一 原生例子路径 二 待测代码 三 主程序入口 四 测试用例代码 五 总结 一 原生例子路径 上篇我们已经介绍原生的例子在如下路径 googletest release 1 8 1 googletest samples 测试用例和待测
  • spring boot一个奇怪的错误(There was an unexpected error (type=Internal Server Error, status=500). Exceptio)

    今天运行spring boot的时候爆了这个错 There was an unexpected error type Internal Server Error status 500 Exception parsing document t
  • numpy--argsort含义及连续两个argsort用法

    官方文档 https docs scipy org doc numpy 1 15 0 reference generated numpy argsort html numpy argsort argsort函数返回的是数组值从小到大的索引值
  • 三极管的知识

    三极管的知识 在实际的电路中 三极管可以应用到很多的场景中 三极管最常用的功能是开关的作用 要利用其开关的作用 那么必须了解三极管的特性 B为基极 E为发射极 C为集电极 根据箭头的方向来判定三极管是NPN还是PNP 1 截止状态 当加在三
  • 【SpringSecurity】使用注解方式实现匿名访问

    SpringSecurity实现匿名访问的方式如下 spring security配置 link EnableGlobalMethodSecurity 如果想要启用spring方法级安全时 使用这个注解 author ruoyi Enabl
  • css常用选择器

    一 常用的css基本选择器 4种 1 标签选择器 结构 标签名 css属性名 属性值 作用 通过标签名 找到页面中所有的这类标签 设置样式 注意 1 标签选择器选择的是一类标签 而不是单独的一个 2 标签选择器无论嵌套关系有多深 都能够找到
  • AC自动机 (多模式匹配)

    AC自动机 感谢博主 https blog csdn net bestsort article details 82947639 感谢博主 https fanfansann blog csdn net article details 106
  • C++socket编程(三):3.5 accept读取用户的连接信息

    读取用户的连接信息 顾名思义 就是在服务段中获取连接进来的客户端的ip地址 套接字编号 ip地址 端口号等 下面开看代码 获取用户客户端的socket号 int client accept sock 0 0 创建一个新的socket 用来与
  • 软件测试笔记(九)- 兼容性测试

    了解如何针对不同的软件应用程序和操作系统交互的问题进行测试 一 兼容性测试综述 随着用户对来自各个厂商的各种类型程序之间共商数据能力和充分利用空间同时执行多个程序能力的要求 测试程序支架能否写作变得越来越重要 软件兼容性测试 softwar
  • Qt使用msvc编译器情况下,如何进行内存泄漏检测

    背景 使用Qt5版本 编译器选择msvc2017 在测试基于tinyxml2的二次封装类接口是否存在内存泄漏问题时 寻找内存泄漏检测工具 问题 寻找适合Qt msvc编程的内存泄漏检测工具 尝试 VLD Visual Leak Detect
  • 【Linux】基础I/O

    在C语言中我们学习到了文件的I O 可以回顾一下 https blog csdn net mmwwxx123 article details 81516082 系统文件I O linux下所有设备都是以文件存在的 可以说是一切皆文件 所以当
  • dc综合报告wand,vc spyglass lint工具不报告W145多驱动。原因是什么?

    因为vc spyglass lint对unload的信号 会不报告W145多驱动 另外 dc工具 会在某层次 认为1 b0是n1487信号线 dc check design报告里 会提醒多驱动 连接到了n1487信号线上 解决方法 根据dc
  • 【C++/STL】手撕AVL树

    文章目录 1 map中的问题 1 1map的insert 函数剖析 1 2map对 的重载 2 AVL树的模拟实现 2 1AVL树的概念 2 2AVL树节点的定义 2 3AVL树的插入 1 在较高的右子树右侧插入数据 左单旋 2 在较高的左