【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树 的相关文章

  • EF Core Group By 翻译支持条件总和

    听说 EF Core 2 1 将支持翻译小组 我感到非常兴奋 我下载了预览版并开始测试它 但发现我在很多地方仍然没有得到翻译分组 在下面的代码片段中 对 TotalFlagCases 的查询将阻止翻译分组工作 无论如何 我可以重写这个以便我
  • 如何使用 C# 中的参数将用户重定向到 paypal

    如果我有像下面这样的简单表格 我可以用它来将用户重定向到 PayPal 以完成付款
  • 以文化中立的方式将字符串拆分为单词

    我提出了下面的方法 旨在将可变长度的文本拆分为单词数组 以进行进一步的全文索引处理 删除停止词 然后进行词干分析 结果似乎不错 但我想听听关于这种实现对于不同语言的文本的可靠性的意见 您会建议使用正则表达式来代替吗 请注意 我选择不使用 S
  • 按成员序列化

    我已经实现了template
  • HTTPWebResponse 响应字符串被截断

    应用程序正在与 REST 服务通信 Fiddler 显示作为 Apps 响应传入的完整良好 XML 响应 该应用程序位于法属波利尼西亚 在新西兰也有一个相同的副本 因此主要嫌疑人似乎在编码 但我们已经检查过 但空手而归 查看流读取器的输出字
  • 不同枚举类型的范围和可转换性

    在什么条件下可以从一种枚举类型转换为另一种枚举类型 让我们考虑以下代码 include
  • 堆栈溢出:堆栈空间中重复的临时分配?

    struct MemBlock char mem 1024 MemBlock operator const MemBlock b const return MemBlock global void foo int step 0 if ste
  • 使用 WebClient 时出现 System.Net.WebException:无法创建 SSL/TLS 安全通道

    当我执行以下代码时 System Net ServicePointManager ServerCertificateValidationCallback sender certificate chain errors gt return t
  • C#中如何移动PictureBox?

    我已经使用此代码来移动图片框pictureBox MouseMove event pictureBox Location new System Drawing Point e Location 但是当我尝试执行时 图片框闪烁并且无法识别确切
  • 重载<<的返回值

    include
  • 如何设计以 char* 指针作为类成员变量的类?

    首先我想介绍一下我的情况 我写了一些类 将 char 指针作为私有类成员 而且这个项目有 GUI 所以当单击按钮时 某些函数可能会执行多次 这些类是设计的单班在项目中 但是其中的某些函数可以执行多次 然后我发现我的项目存在内存泄漏 所以我想
  • 什么时候虚拟继承是一个好的设计? [复制]

    这个问题在这里已经有答案了 EDIT3 请务必在回答之前清楚地了解我要问的内容 有 EDIT2 和很多评论 有 或曾经 有很多答案清楚地表明了对问题的误解 我知道这也是我的错 对此感到抱歉 嗨 我查看了有关虚拟继承的问题 class B p
  • 链接器错误:已定义

    我尝试在 Microsoft Visual Studio 2012 中编译我的 Visual C 项目 使用 MFC 但出现以下错误 error LNK2005 void cdecl operator new unsigned int 2
  • 向现有 TCP 和 UDP 代码添加 SSL 支持?

    这是我的问题 现在我有一个 Linux 服务器应用程序 使用 C gcc 编写 它与 Windows C 客户端应用程序 Visual Studio 9 Qt 4 5 进行通信 是什么very在不完全破坏现有协议的情况下向双方添加 SSL
  • 为什么编译时浮点计算可能不会得到与运行时计算相同的结果?

    In the speaker mentioned Compile time floating point calculations might not have the same results as runtime calculation
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • C# 模拟VolumeMute按下

    我得到以下代码来模拟音量静音按键 DllImport coredll dll SetLastError true static extern void keybd event byte bVk byte bScan int dwFlags
  • Windows 和 Linux 上的线程

    我在互联网上看到过在 Windows 上使用 C 制作多线程应用程序的教程 以及在 Linux 上执行相同操作的其他教程 但不能同时用于两者 是否存在即使在 Linux 或 Windows 上编译也能工作的函数 您需要使用一个包含两者的实现
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • 对来自流读取器的过滤数据执行小计

    编辑问题未得到解答 我有一个基于 1 个标准的过滤输出 前 3 个数字是 110 210 或 310 给出 3 个不同的组 从流阅读器控制台 问题已编辑 因为第一个答案是我给出的具体示例的字面解决方案 我使用的实际字符串长度为 450 个

随机推荐

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

    北冥神功与六脉神剑 言念及此 登时心下坦然 默默祷祝 神仙姊姊 你吩咐下来的事 段誉当然一定遵行不误 但愿你法力无边 逍遥派弟子早已个个无疾而终 战战兢兢的打开绸包 里面是个卷成一卷的帛卷 展将开来 第一行写着 北冥神功 字迹娟秀而有力 便
  • 如何解决: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 在较高的左