当前位置: 首页 > article >正文

C++二叉树进阶

1.二叉搜索树

1.1二叉搜索树概念

二叉搜索树又称二叉排序树,它或者是一颗空树,或者具有以下性质的二叉树

  • 若它的左子树不为空,则左子树上所有结点的值小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
    在这里插入图片描述

1.2二叉搜索树操作

在这里插入图片描述

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

1.二叉搜索树的查找

  • a. 从根开始比较,查找,比根大则往右边查找,比根小则往左边查找
  • b. 最多查找高度次,走到空,还没找到,这个值不存在

2.二叉搜索树的插入
插入的具体过程如下:

  • a. 树为空,则直接新增节点,赋值给root指针
  • b. 树不为空,按二叉搜索树性质查找插入位置,插入新节点
    在这里插入图片描述
    1.二叉搜索树的删除
    首先查找元素是否在二叉搜索树中,如果不存在,则返回,否则要删除的结点可能分下面四种情况
  • a. 要删除的结点无孩子结点
  • b. 要删除的结点只有左孩子结点
  • c. 要删除的结点只有右孩子结点
  • d. 要删除的结点有左,右孩子结点

看起来有待删除节点有四种情况,实际情况a可以与情况b或c合并起来,因此真正的删除情况如下

  • 情况b: 删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点-直接删除
  • 情况c: 删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点_直接删除
  • 情况d: 在它的右子树中寻找中序下的第一个节点(关键码最小),用它的值填补到被删除节点中,再来处理该节点的删除问题_替换法删除
    在这里插入图片描述

1.3二叉搜索树的实现

template<class T>
struct BSTNode
{
 BSTNode(const T& data = T())
   : _pLeft(nullptr) , _pRight(nullptr), _data(data)
 {}
 BSTNode<T>* _pLeft;
 BSTNode<T>* _pRight;
 T _data;
};
template<class T>
class BSTree
{
 typedef BSTNode<T> Node;
 typedef Node* PNode;
public:
 BSTree(): _pRoot(nullptr)
 {}
 ~BSTree();
 // 根据二叉搜索树的性质查找:找到值为data的节点在二叉搜索树中的位置
 PNode Find(const T& data);
 bool Insert(const T& data)
 {
 // 如果树为空,直接插入
 if (nullptr == _pRoot)
 {
	 _pRoot = new Node(data);
	 return true;
 }
 // 按照二叉搜索树的性质查找data在树中的插入位置
 PNode pCur = _pRoot;
 // 记录pCur的双亲,因为新元素最终插入在pCur双亲左右孩子的位置
 PNode pParent = nullptr;
 while (pCur)
 {
	 pParent = pCur;
	 if (data < pCur->_data)
	 pCur = pCur->_pLeft;
	 else if (data > pCur->_data)
	 pCur = pCur->_pRight;  // 元素已经在树中存在
	 else
	 return false;
 }
 // 插入元素
 pCur = new Node(data);
 if (data < pParent->_data)
 	pParent->_pLeft = pCur;
 else
 	pParent->_pRight = pCur;
 return true;
 }
 bool Erase(const T& data)
 {
 // 如果树为空,删除失败
	 if (nullptr == _pRoot)
	 return false;
	 // 查找在data在树中的位置
	 PNode pCur = _pRoot;
	 PNode pParent = nullptr;
	 while (pCur)
	 {
	 if (data == pCur->_data)
	 	break;
	 else if (data < pCur->_data)
	 {
		 pParent = pCur;
		 pCur = pCur->_pLeft;
	 }
	 else
	 {
		 pParent = pCur;
		 pCur = pCur->_pRight;
	 	}
	 }
	 // data不在二叉搜索树中,无法删除
	 if (nullptr == pCur)
	 	return false;
	 	// 分以下情况进行删除
	 if (nullptr == pCur->_pRight)
	 {
	 	// 当前节点只有左孩子或者左孩子为空---可直接删除
	 }
	 else if (nullptr == pCur->_pRight)
	 {
		 // 当前节点只有右孩子---可直接删除
	 }
	 else
	 {
		 // 当前节点左右孩子都存在,直接删除不好删除,可以在其子树中找一个替代结点,
		比如:
		 // 找其左子树中的最大节点,即左子树中最右侧的节点,或者在其右子树中最小的节
		//点,即右子树中最小的节点
		 // 替代节点找到后,将替代节点中的值交给待删除节点,转换成删除替代节点
		 }
		 return true;
	 }
 void InOrder();
private:
 	PNode _pRoot;
};

1.4二叉搜索树的应用

1.k模型:k模型即只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下

  • 以词库只不过所有单词集合中的每个单词作为key,构建一颗二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误

2.kv模型:每一个关键码key,都有与之对应的值value,即<key, value>的键值对,该种方式在现实生活中非常常见

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文的单词与其对应的中文<word, Chinese >就构成一种键值对
  • 再比如统计单词次数,统计成功后,给定单词就可以快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对
// 改造二叉搜索树为KV结构
template<class K, class V>
struct BSTNode
 {
 BSTNode(const K& key = K(), const V& value = V())
   : _pLeft(nullptr) , _pRight(nullptr), _key(key), _Value(value)
 {}
	 BSTNode<T>* _pLeft;
	 BSTNode<T>* _pRight;
	 K _key;
	 V _value;
 };
template<class K, class V>
class BSTree
 {
	 typedef BSTNode<K, V> Node;
	 typedef Node* PNode;
public:
	 BSTree(): _pRoot(nullptr){}
	 PNode Find(const K& key);
	 bool Insert(const K& key, const V& value)
	 bool Erase(const K& key)
private:
 	PNode _pRoot;
 };
void TestBSTree3()
{
	 // 输入单词,查找单词对应的中文翻译
	 BSTree<string, string> dict;
	 dict.Insert("string", "字符串");
	 dict.Insert("tree", "树");
	 dict.Insert("left", "左边、剩余");
	 dict.Insert("right", "右边");
	 dict.Insert("sort", "排序");
	 // 插入词库中所有单词
	 string str;
	 while (cin>>str)
	 {
		 BSTreeNode<string, string>* ret = dict.Find(str);
		 if (ret == nullptr)
		 {
		 	cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl;
		 }
		 else
		 {
		 	cout << str << "中文翻译:" << ret->_value << endl;
		 }
	 }
}
void TestBSTree4()
{
	 // 统计水果出现的次数
	 string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", 
	"苹果", "香蕉", "苹果", "香蕉" };
	 BSTree<string, int> countTree;
	 for (const auto& str : arr)
	 {
		 // 先查找水果在不在搜索树中
		 // 1、不在,说明水果第一次出现,则插入<水果, 1>
		 // 2、在,则查找到的节点中水果对应的次数++
		 //BSTreeNode<string, int>* ret = countTree.Find(str);
		 auto ret = countTree.Find(str);
		 if (ret == NULL)
		 {
		 	countTree.Insert(str, 1);
		 }
		 else
		 {
		 	ret->_value++;
		 }
	 }
	 countTree.InOrder();
}

2.5二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树
在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:O(logn)
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为O(n)

问题:如果退化成单支树,二叉搜索树的性能就失去了,那能否改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优
1. 平衡二叉搜索树(AVL树)

  • 基本思想:AVL树是一种自平衡的二叉搜索树,它在插入或删除节点时,会通过旋转操作来保持树的平衡。
    它定义了一个平衡因子(Balance Factor),对于树中的每个节点,平衡因子是其左子树高度与右子树高度之差。平衡因子的取值只能是 -1、0 或 1。如果某个节点的平衡因子绝对值大于1,就需要通过旋转操作来调整树的结构,使其重新达到平衡。

  • 性能分析:AVL树通过不断地调整树的结构来保持平衡,使得树的高度始终保持在 O(logn)​ 的范围内,其中 n​ 是树中节点的数量。因此,无论插入节点的顺序如何,AVL树的查找、插入和删除操作的时间复杂度都能保持在 O(logn)​,性能相对稳定。

2. 红黑树

  • 基本思想:红黑树是一种自平衡的二叉搜索树,它通过对节点颜色的限制和调整来保持树的平衡。
    红黑树中的每个节点都有一个颜色属性,要么是红色,要么是黑色。并且满足以下性质:

    • 每个节点要么是红色,要么是黑色。
    • 根节点是黑色。
    • 每个叶子节点(NIL节点,空节点)是黑色。
    • 如果一个节点是红色,则它的两个子节点都是黑色。
    • 从任意一个节点到其每个叶子节点的所有路径上包含相同数目的黑色节点。
  • 性能分析:红黑树通过对节点颜色的限制和调整,保证了树的高度始终保持在 O(logn)​ 的范围内。因此,无论插入节点的顺序如何,红黑树的查找、插入和删除操作的时间复杂度都能保持在 O(logn)​,性能相对稳定。而且,红黑树的调整操作相对AVL树来说更加灵活,在实际应用中可能具有更好的性能表现。

3. 伸展树(Splay Tree

  • 基本思想:伸展树是一种自适应的二叉搜索树,它的基本思想是在每次访问一个节点后,通过一系列的旋转操作将该节点移动到根节点的位置,使得最近被访问的节点更容易被再次访问。
    伸展操作(Splaying)是伸展树的核心操作,它通过一系列的旋转操作将指定节点移动到根节点。伸展操作有多种实现方式,常见的有自底向上的伸展和自顶向下的伸展。

  • 性能分析:虽然伸展树不能保证在任何情况下树的高度都保持在 O(logn)​,但从均摊分析的角度来看,伸展树的操作时间复杂度为 O(logn)​。也就是说,在一系列操作中,平均每次操作的时间复杂度是对数级别的。因此,在实际应用中,伸展树也能够提供较好的性能。

虽然这些改进的数据结构可以在很大程度上避免二叉搜索树退化为单支树,提高性能的稳定性,但在某些极端情况下,仍然可能存在性能波动。例如,在AVL树和红黑树中,频繁的插入和删除操作可能会导致频繁的调整操作,影响性能;在伸展树中,如果访问模式非常不规则,也可能导致树的结构变得不平衡。

3.二叉树进阶题

这些题目更适合使用C++完成,难度也更大一些
1. 二叉树创建字符串
2. 二叉树的分层遍历1
3. 二叉树的分层遍历2
4. 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先5. 二叉树搜索树转换成排序双向链表
6. 根据一棵树的前序遍历与中序遍历构造二叉树
7. 根据一棵树的中序遍历与后序遍历构造二叉树
8. 二叉树的前序遍历,非递归迭代实现
9. 二叉树中序遍历 ,非递归迭代实现
10. 二叉树的后序遍历 ,非递归迭代实现


http://www.kler.cn/a/524983.html

相关文章:

  • 深入理解文件描述符
  • deepseek-r1(Mac版 安装教程)
  • 汽车蓝牙钥匙定位仿真小程序
  • 【新春不断更】题海拾贝:P1878 舞蹈课
  • 【论文复现】基于维度狩猎学习的改进秃鹰搜索算法用于自动驾驶问题
  • 多模态论文笔记——NaViT
  • Android 自定义View时四个构造函数使用详解
  • C语言中的局部变量和全局变量有什么区别?
  • 谷氨酸:大脑功能的多面手
  • 大数据治理实战:架构、方法与最佳实践
  • 12JavaWeb——SpringBootWeb登录认证
  • 【某大厂一面】HashSet底层怎么实现的
  • NLP模型大对比:Transformer > RNN > n-gram
  • 接口技术-第5次作业
  • 视觉语言大模型VisualGLM-6B环境配置与模型部署
  • Jackson中@JsonTypeId的妙用与实例解析
  • 嵌入式经典面试题之操作系统(一)
  • 牛客周赛77:A:JAVA
  • 【ComfyUI专栏】通过软件获取PNG图片中的工作流信息
  • h5 网页测试摄像头