高级数据结构——二叉搜索树

这篇具有很好参考价值的文章主要介绍了高级数据结构——二叉搜索树。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1. 二叉搜索树的概念

2. 二叉搜索树的实现

结点类

二叉搜索树的类

2.1 默认成员函数

2.1.1 构造函数

2.1.2 拷贝构造函数

2.1.3 赋值运算符重载函数

2.1.4 析构函数

2.2 中序遍历

2.3 insert插入函数

2.3.1 非递归实现

2.3.2 递归实现

2.4 erase删除函数

2.4.1 非递归实现

2.4.2 递归版本

2.5 find查找函数

2.5.1 非递归实现

2.5.2 递归实现

3. 二叉搜搜数的应用

3.1 k模型

3.2 KV模型

4. 二叉搜索树性能分析

1. 二叉搜索树的概念

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

  1. 若它的左子树不为空,则左子树上所有节点的值小于根节点的值
  2. 若它的右子树不为空,则右子树上所有节点的值大于根节点的值
  3. 它的左右子树也分别为二叉搜索树。

总结:任意一颗子树都满足左子树的值 < 根 < 右子树的值。
二叉搜索树又称二叉排序树,且任何一颗子树都满足左子树的值 < 根 < 右子树的值,由此我们进行中序遍历(左子树 根 右子树)得到的就是一个升序序列

2. 二叉搜索树的实现

要实现一颗二叉搜索树,要实现两个类,一个是结点类,用于存放节点值、左指针、右指针。第二个类专门用于二叉搜索树的增删查改。

结点类

结点类主要包含如下内容:

  1. 成员变量:节点值、左指针、右指针。
  2. 只需要一个构造函数对成员变量的初始化即可。
template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left; //左指针
	BSTreeNode<K>* _right;//右指针
	K _key;//节点值
	BSTreeNode(const K& key)//构造函数
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}
};

二叉搜索树的类

此类主要用于增删查改。

  • 基本框架:
template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
    //……
private:
	Node* _root = nullptr;
};

2.1 默认成员函数

2.1.1 构造函数

这里的构造函数直接让编译器默认生成就可以,不需要自己实现,但是后面的拷贝构造函数写了之后编译器就不会默认生成了,但是我们可以强制让它默认生成构造函数,不过要利用C++11的特性,具体看代码:

//强制编译器自己生成构造函数,忽视拷贝构造带来的影响
BSTree() = default;//C++11才支持

2.1.2 拷贝构造函数

注意这里的拷贝构造完成的是深拷贝,这里我们直接用前序递归的方式创建一颗与原来一样的二叉树即可。而递归前序拷贝结点的方式这里我们专门封装一个Copy函数即可。

Node* CopyTree(Node* root)
{
	if (root == nullptr)
		return nullptr;
	Node* copyNode = new Node(root->_key);//拷贝根结点
	//递归创建拷贝一棵树
	copyNode->_left = CopyTree(root->_left);//递归拷贝左子树
	copyNode->_right = CopyTree(root->_right);//递归拷贝右子树
	return copyNode;
}
//拷贝构造函数--深拷贝
BSTree(const BSTree<K>& t)
{
	_root = CopyTree(t._root);
}

2.1.3 赋值运算符重载函数

这里直接给出现代写法:写法很巧妙,假设把t2赋值给t1,t2传参的时候直接利用传值传参调用拷贝构造生成t,t就是t2的拷贝,此时再调用swap函数交换t1和t 的_root根结点即可,而拷贝构造出来的t会在赋值运算符重载结束后自动调用自己的析构函数完成释放。

//赋值运算符重载函数 t1 = t2
BSTree<K>& operator=(BSTree<K> t)//t就是t2的拷贝
{
	//现代写法
	swap(_root, t._root);
	return *this;
}

2.1.4 析构函数

析构函数是为了释放二叉搜索树的所有结点,这里我们优先采用后序的递归释放,可以采用封装一个Destory函数来专门用于递归删除结点,如下:

void DestoryTree(Node* root)
{
	if (root == nullptr)
		return;
	//通过递归删除所有结点
	DestoryTree(root->_left);//递归释放左子树中的结点
	DestoryTree(root->_right);//递归释放右子树中的结点
	delete root;
}
//析构函数
~BSTree()	
{
	DestoryTree(_root);//复用此函数进行递归删除结点
	_root = nullptr;
}

2.2 中序遍历

中序遍历的核心宗旨是左子树 -> 根结点 -> 右子树,这里我们采用递归的方式去实现中序遍历。

  • 代码如下:
//中序遍历 -- 递归	
void InOrder()
{
	_InOrder(_root);
	cout << endl;
}
//中序遍历的子树
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left);//递归到左子树
	cout << root->_key << " ";//访问根结点
	_InOrder(root->_right);//递归到右子树
}

2.3 insert插入函数

2.3.1 非递归实现

结合二叉搜索树的性质,插入的实现非常简单(注意重复的值不允许再次插入,默认不允许冗余)。主要分为两类:

1、如果是空树,直接把插入的结点作为根结点即可。

2、如果不是空树,则按如下规则讨论:首先得找到待插入的值的合适位置,其次找到位置后,将插入的值与此树链接起来

  • 1、寻找待插入的值的合适位置

定义cur指针从根结点开始(cur指针用于找到待插入的合适位置),定义parent指针最开始为nullptr(parent指针用于找到位置后的链接操作),把待插入的结点值定位key。遍历cur指针

  1. 若key > cur指向的结点值,让parent走到cur的位置,让cur指针走到右子树,指向_right的位置,继续遍历。
  2. 若key < cur指向的结点值,让parent走到cur的位置,让cur指针走到左子树,指向_left的位置,继续遍历。
  3. 若key = cur指向的结点值,说明待插入的结点值与此树当前结点值重合,插入结点失败。返回false。

高级数据结构——二叉搜索树

 遍历结束后,说明已经找到要插入的合适的位置(某一颗子树的尾部),接着指向第二步:

  • 2、将插入的值与父亲链接起来

链接的步骤很简单,确保链接位置即可:

  1. 若插入的值比父亲的值大,链接在父亲的右边。
  2. 若插入的值比父亲的值小,链接在父亲的左边。

高级数据结构——二叉搜索树

  • 代码如下:
//Insert非递归
bool Insert(const K& key)
{
	if (_root == nullptr)//若一开始树为空
	{
		_root = new Node(key);//直接申请值为key的结点作为二叉搜索树的根结点
		return true;
	}
	Node* parent = nullptr;
	Node* cur = _root;
	//1、找寻插入的合适位置
	while (cur)
	{
		if (cur->_key < key)//若key大于当前结点值
		{
			parent = cur;
			cur = cur->_right;//让cur走向右子树
		}
		else if (cur->_key > key)//若key小于当前结点值
		{
			parent = cur;
			cur = cur->_left;//让cur走向左子树
		}
		else
		{
			return false;//若key等于当前结点值,说明插入的值不合法,返回false
		}
	}
	//2、进行与父亲的链接
	cur = new Node(key);
	if (parent->_key < key)
	{
		parent->_right = cur;//比父亲的值大连接在右子树
	}
	else
	{
		parent->_left = cur;//比父亲的值小链接在左子树
	}
	return true;
}
  • 补充:搜索二叉树以相对有序的方式插入会比较坑,因为高度太高。

2.3.2 递归实现

 依旧是分为两大步骤走,1、先递归到合适位置,确定插入的值链接在何处,2、找到位置后链接即可。

  • 1、递归找到插入的正确位置

这里虽是递归,不过走的形式和非递归的找到正确位置整体思路大差不差:

  1. 若key > root指向的结点值,让root递归到右子树继续遍历。
  2. 若key < root指向的结点值,让root递归到左子树继续遍历。
  3. 若key = root指向的结点值,说明待插入的结点值与此树当前结点值重合,插入结点失败。返回false。
    高级数据结构——二叉搜索树

 当root结点递归到nullptr时,即可进行下一步:链接。

  • 2、找到位置后,进行链接插入的结点

先前非递归版本的链接过程中为了要找到新插入结点和父亲的链接关系,我们特地创建了parent指针,让cur结点在不断的遍历中更新parent的指向以此时刻保持parent为cur的父亲,这样链接关系就确认好了,不过这里的递归实现我们并不给与一个parent指针,而是采用一个巧妙的方法:参数为指针的引用!
高级数据结构——二叉搜索树

 通过这里可以看出传指针的引用已然达到没有父指针,胜似父指针的效果!!! 

//递归版插入

//插入的子树
bool _InsertR(Node*& root, const K& key)//Node*&为指针的引用
{
	if (root == nullptr)
	{
		root = new Node(key);//当root为空,把自己创建成新结点
		return true;
	}
	if (root->_key < key)
		return _InsertR(root->_right, key);//如果比key小,转换到右子树去插入
	else if (root->_key > key)
		return _InsertR(root->_left, key);//如果比key大,转换到左子树去插入
	else
		return false;//如果相等,就返回false
}

2.4 erase删除函数

2.4.1 非递归实现

二叉搜索树的删除函数最为复杂,这里我们主要通过两大步骤进行删除的操作:

  1. 遍历找到待删值的位置
  2. 删除找到的位置并链接父亲与剩下的结点

接下来针对这两大步骤展开讨论:

  • 一、先找到要删除的结点

首先定义cur指针指向根结点(cur指针用于找到待删除结点的位置),定义parent指针指向nullptr(parent指针用于删除后的链接操作),定义key为删除结点的值,按如下规则进行遍历:

  1. 若key > cur指向结点的值,让parent走到cur的位置,让cur走到右子树进行遍历
  2. 若key < cur指向结点的值,让parent走到cur的位置,让cur走到左子树进行遍历
  3. 若key = cur指向结点的值,接下来进行删除结点和链接的操作。

高级数据结构——二叉搜索树

 此时可以指向第二部,删除找到的位置并链接父亲与剩下的结点。

  • 二、删除结点并链接父亲与剩下的结点

当我删去结点后,一个最值得考虑的问题是,如果待删值还有孩子怎么办呢,因此还要考虑到链接父亲与孩子的问题,并且又要进行如下分类:

  1. 待删值只有一个孩子 -- 左为空 or 右为空 or 左右均为空
  2. 待删值两个孩子都在 -- 替换法删除

接下来同样是进行展开讨论:

1、待删值只有一个孩子 -- 左为空 or 右为空 or 左右均为空

我们按如下四步走:

  1. 如果左孩子为空且删除的值为根结点,直接更新根结点为右孩子(右孩子为空,就相反操作)。
  2. 如果父亲的左孩子为待删值,将父亲的左孩子指向待删值指向的右孩子。
  3. 如果父亲的左孩子不是待删值,将父亲的右孩子指向待删值指向的右孩子。
  4. 删除待删的结点。

高级数据结构——二叉搜索树

2、待删值两个孩子都在 -- 替换法删除

替换法删除的目的在于我删除目标结点后,让左子树或右子树其中一个叶结点到删除的位置上来,又要保持其删除后依旧是一个二叉搜索树的特性(左子树 < 根 < 右子树),这就要用到替换法。

准备工作如下:

  1. 定义myParent指针为cur指针的位置(myParent指针用于链接要删除结点的孩子)。
  2. 定义minRight指针为cur的右孩子结点指针的位置(minRight用于找到右子树的最小值)。

具体替换法的操作如下:

  1. 遍历minRight找到待删结点右子树的最小值(或左子树的最大值结点),中途不断更新myParent。
  2. 找到后,利用swap函数交换此最小值结点的值(minRight->_key)和待删结点的值(cur->_key)。
  3. 交换后,链接父亲myParent指针与minRight结点的孩子。
  4. 最后记得delete删除minRight结点。

高级数据结构——二叉搜索树

注意:若整个操作两大步骤遍历一遍找不到要删除的值,直接返回false。

//Erase删除
bool Erase(const K& key)
{
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		//1、先找到要删除的结点
		if (cur->_key < key)
		{
			parent = cur;
			//让parent始终为cur的父亲
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			//让parent始终为cur的父亲
			cur = cur->_left;
		}
		else
		{
			//找到了,分两类情况讨论:
			//1、待删值只有一个孩子 -- 左为空 or 右为空
			//2、待删值两个孩子都在 -- 替换法删除
			if (cur->_left == nullptr)
			{
				if (cur == _root)
				{
					//如果左孩子为空且删除的值为根结点,直接更新根结点为右孩子
					_root = cur->_right;
				}
				else
				{
					//左孩子为空
					if (cur == parent->_left)
					{
						//如果父亲的左孩子为待删值,将父亲的左孩子指向待删值指向的右孩子
						parent->_left = cur->_right;
					}
					else
					{
						//如果父亲的左孩子不是待删值,将父亲的右孩子指向待删值指向的右孩子
						parent->_right = cur->_right;
					}
				}
				//删除待删的结点
				delete cur;
			}
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
				{
					//如果右孩子为空且删除的值为根结点,直接更新根结点为左孩子
					_root = cur->_left;
				}
				else
				{
					//右孩子为空
					if (cur == parent->_left)
					{
						//如果父亲的左孩子为待删值,将父亲的左孩子指向待删值指向的左孩子
						parent->_left = cur->_left;
					}
					else
					{
						//如果父亲的左孩子不是待删值,将父亲的右孩子指向待删值指向的左孩子
						parent->_right = cur->_left;
					}
				}
				//删除待删的结点
				delete cur;
			}
			else
			{
				//待删值的两个孩子都在,替换法删除。
				//找右子树的最小值或找左子树的最大值,下面为找右子树最小值
				Node* minParent = cur;//右子树的根可能就是minRight,所以这里minParent不能为nullptr,
				//因为此时不会进入while循环导致minParent就一直为nullptr,最后删除的时候堆野指针的非法访问
				Node* minRight = cur->_right;
				while (minRight->_left)
				{
					minParent = minRight;
					//让minParent始终为minRight的父亲
					minRight = minRight->_left;
				}
				swap(minRight->_key, cur->_key);//或者cur->_key = minRight->_key;
				//链接父亲minParent和要删除的结点的孩子
				if (minParent->_left == minRight)
				{
					//如果minParent的左孩子为待删值,让minParent的左孩子指向minRight的右
					minParent->_left = minRight->_right;
				}
				else
				{
					//如果minParent的右孩子为待删值,让minParent的右孩子指向minRight的右
					minParent->_right = minRight->_right;
				}
				//删除要删的结点
				delete minRight;
			}
			return true;
		}
	}
	//遍历一遍找不到要删除的值,直接返回false
	return false;
}

2.4.2 递归版本

这里和非递归的主要实现思路大差不差,也是分为先找到删除的合适结点位置,找到后将其删除并确保链接关系正确这两大步骤。接下来,详细讨论下:

  • 一、先找到要删除的结点:

找到要删除的结点很简单,非递归是通过遍历的方式,只不过这里利用了递归来解决:

  1. 若当前结点root为空,说明此删除的结点不存在,返回false
  2. 若key > root指向的结点值,让root递归到右子树继续遍历。
  3. 若key < root指向的结点值,让root递归到左子树继续遍历。

高级数据结构——二叉搜索树

 

  • 二、删除此结点 + 链接父子关系:

当删去结点后,面临和非递归的删除同样一个问题:如果待删值还有孩子怎么办呢,因此还要考虑到链接父亲与孩子的问题,并且又要进行如下分类:

  1. 待删值只有一个孩子 -- 左为空 or 右为空 or 左右均为空
  2. 待删值两个孩子都在 -- 替换法删除

这里的核心写法和插入的递归实现一样,传参要传指针的引用,接下来,这两种删除情况我都会详细讲解下如何利用好传参要传指针的引用

  • 1、待删值只有一个孩子 -- 左为空 or 右为空 or 左右均为空

我们按如下三步走:

  1. 先把要删除的结点指针root保存为del;
  2. 如果root的左孩子为空,执行root = root->_right;                                                           此时的root为指针的引用,即父结点的左指针或右指针,假设root为父结点的右指针。执行此段代码的意思是让父结点的右孩子指针(root)链接到root的右孩子,即可天然借助指针的引用建立了父子的链接关系。
  3. 如果root的右孩子为空,执行root = root->_left;

这种情况和上面无任何区别,只是链接方向变了,思路均一样。下面给出图示说明:
高级数据结构——二叉搜索树

 

  • 2、待删值两个孩子都在 -- 替换法删除

准备工作如下:

  1. 先把要删除的结点指针root保存为del
  2. 定义minRight指针为root的右孩子结点指针的位置(minRight用于找到右子树的最小值)

具体替换法的操作如下:

  1. 遍历minRight找到待删结点右子树的最小值(或左子树的最大值结点)
  2. 找到后,利用swap函数交换此最小值结点的值(minRight->_key)和待删结点的值(root->_key)
  3. 交换后,到子树复用递归删除:return _EraseR(root->_right, key);意思是利用递归删除

图示说明:
高级数据结构——二叉搜索树

 

//递归版删除
bool EraseR(const K& key)
{
	return _EraseR(_root, key);
}
//删除的子树
bool _EraseR(Node*& root, const K& key)
{
	//1、递归查找删除的位置
	if (root == nullptr)
	{
		//如果是空就返回false
		return false;
	}
	if (root->_key < key)
	{
		return _EraseR(root->_right, key);//如果比key小,转换到右子树去插入
	}
	else if (root->_key > key)
	{
		return _EraseR(root->_left, key);//如果比key大,转换到左子树去插入
	}
	//2、确认链接关系
	else
	{
		Node* del = root;//提前保存root结点的位置
		//开始删除
		if (root->_left == nullptr)
		{
			//如果左为空
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
			//如果右为空
			root = root->_left;
		}
		else
		{
			Node* minRight = root->_right;//minRight用于找到右子树的最小值
			while (minRight->_left)
			{
				minRight = minRight->_left;
			}
			swap(root->_key, minRight->_key);
			return _EraseR(root->_right, key);
		}
		delete del;
		return true;
	}
}

2.5 find查找函数

2.5.1 非递归实现

Find查找函数的思路很简单,定义cur指针从根部开始按如下规则遍历:

  1. 若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。
  2. 若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。
  3. 若key值等于当前结点的值,则查找成功,返回true。
  4. 若遍历一圈cur走到nullptr了说明没有此结点,返回false
//Find非递归
bool Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			cur = cur->_right;//若key值大于当前结点的值,则应该在该结点的右子树当中进行查找。
		}
		else if (cur->_key > key)
		{
			cur = cur->_left;//若key值小于当前结点的值,则应该在该结点的左子树当中进行查找。
		}
		else
		{
			return true;//若key值等于当前结点的值,则查找成功,返回true。
		}
	}
	return false;//遍历一圈没找到返回false
}

2.5.2 递归实现

递归的实现主要是转换成子问题来解决。针对于Find的递归实现,只需遵循如下规则即可:

  1. 若树为空树,则查找失败,返回nullptr。
  2. 若key值小于当前结点的值,则递归到该结点的左子树当中进行查找。
  3. 若key值大于当前结点的值,则递归到该结点的右子树当中进行查找。
  4. 若key值等于当前结点的值,则查找成功,返回对应结点的地址。
//递归版查找
bool FindR(const K& key)
{
	return _FindR(_root, key);
}
//查找的子树
bool _FindR(Node* root, const K& key)
{
	if (root == nullptr)
		return false;
	if (root->_key < key)
	{
		//如果比key小,转换到右子树去找
		return _FindR(root->_right, key);
	}
	else if (root->_key > key)
	{
		//如果比key大,转换到左子树去找
		return _FindR(root->_left, key);
	}
	else
	{
		//找到了
		return true;
	}
}

3. 二叉搜搜数的应用

3.1 k模型

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
其实我前面模拟实现的二叉搜索树就是一个K模型。

3.2 KV模型

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

比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
我们可以针对K模型,在其内部实现进行稍稍修改即可达到KV模型的实现。

#pragma once
#include<iostream>
#include<string>
using namespace std;
namespace key_value
{
	template<class K, class V>
	struct BSTreeNode
	{
		BSTreeNode<K, V>* _left; //左指针
		BSTreeNode<K, V>* _right;//右指针
		const K _key;//节点值,假设const修饰防止后续修改key导致破坏二叉搜索树的结构
		V _value;
		BSTreeNode(const K& key, const V& value)//构造函数
			:_left(nullptr)
			, _right(nullptr)
			, _key(key)
			, _value(value)
		{}
	};

	template<class K, class V>
	class BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:
		//中序遍历 -- 递归
		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

		///递归版的插入、删除、查找/
			//查找
		Node* FindR(const K& key)
		{
			return _FindR(_root, key);
		}
		//插入
		bool InsertR(const K& key, const V& value)
		{
			return _InsertR(_root, key, value);
		}
		//删除
		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}

	private:
		//查找的子树
		Node* _FindR(Node* root, const K& key)//查找的时候要返回结点的指针,为了方便后续达到可以修改value而不能修改key的效果
		{
			if (root == nullptr)
				return nullptr;
			if (root->_key < key)
			{
				//如果比key小,转换到右子树去找
				return _FindR(root->_right, key);
			}
			else if (root->_key > key)
			{
				//如果比key大,转换到左子树去找
				return _FindR(root->_left, key);
			}
			else
			{
				//找到了
				return root;
			}
		}
		//插入的子树
		bool _InsertR(Node*& root, const K& key, const V& value)//Node*&为指针的引用
		{
			if (root == nullptr)
			{
				root = new Node(key, value);//当root为空,把自己创建成新结点
				return true;
			}
			if (root->_key < key)
				return _InsertR(root->_right, key, value);//如果比key小,转换到右子树去插入
			else if (root->_key > key)
				return _InsertR(root->_left, key, value);//如果比key大,转换到左子树去插入
			else
				return false;//如果相等,就返回false
		}
		//删除的子树
		bool _EraseR(Node*& root, const K& key)
		{
			//1、递归查找删除的位置
			if (root == nullptr)
			{
				//如果是空就返回false
				return false;
			}
			if (root->_key < key)
			{
				return _EraseR(root->_right, key);//如果比key小,转换到右子树去插入
			}
			else if (root->_key > key)
			{
				return _EraseR(root->_left, key);//如果比key大,转换到左子树去插入
			}
			//2、确认链接关系
			else
			{
				Node* del = root;//提前保存root结点的位置
				//开始删除
				if (root->_left == nullptr)
				{
					//如果左为空
					root = root->_right;
				}
				else if (root->_right == nullptr)
				{
					//如果右为空
					root = root->_left;
				}
				else
				{
					Node* minRight = root->_right;//minRight用于找到右子树的最小值
					while (minRight->_left)
					{
						minRight = minRight->_left;
					}
					swap(root->_key, minRight->_key);
					return _EraseR(root->_right, key);
				}
				delete del;
				return true;
			}
		}

		//中序遍历的子树
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);//递归到左子树
			cout << root->_key << ":" << root->_value << endl;;//访问根结点
			_InOrder(root->_right);//递归到右子树
		}
	private:
		Node* _root = nullptr;
	};
}

4. 二叉搜索树性能分析

 插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树。

  • 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:logN。
  • 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N / 2。
  • 综上时间复杂度为O(N)。

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?AVL树和红黑树就可以优化二叉树的性能。
 文章来源地址https://www.toymoban.com/news/detail-495610.html

到了这里,关于高级数据结构——二叉搜索树的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】二叉搜索树

    二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 它的左右子树也分别为二叉搜索树 两种情况: 树为空,则

    2024年01月19日
    浏览(37)
  • 数据结构:搜索二叉树 | 平衡二叉树

    博客写的代码都放在这里:gitee仓库链接 1.二叉搜索树 1.1.基本概念 二叉搜索树又称二叉排序树, 可以为空,如果不为空具有以下性质的二叉树 : 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的

    2024年01月23日
    浏览(55)
  • 算法与数据结构--二叉搜索树与自平衡二叉搜索树

    注:字典的 \\\"member运算\\\" 指的是检查字典中是否存在某个特定的键的操作,即查询操作。 如果我们使用数组来实现字典/map,虽然使用二分法查询也可以达到logn,但是的话插入和删除太慢了。使用链表实现的话虽然插入和删除是O(1),但是查询的话达到了O(n),也不可取。 因此人

    2024年02月04日
    浏览(37)
  • 数据结构篇八:二叉搜索树

      前面我们已经学习过了二叉树,二叉搜索树是在二叉树的基础上增添了一些规则,使其能完成快速查找的功能,同时它也帮助我们更好的理解后续将要学习的map和set。   二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树: 若它的左子树不为

    2024年03月13日
    浏览(48)
  • 数据结构之二叉搜索树

    满足条件: 1.对于根节点:左子树中所有节点的值小于右子树中所有节点的值 2.任意节点的左右子树也是二叉搜索树,同样满足条件1 我们将二叉搜索树封装为一个类 BinarySearchTree ,并声明一个成员变量 root ,指向树的根节点 查找节点 给定目标值 target ,我们可以根据二叉搜索

    2024年01月20日
    浏览(36)
  • 【数据结构】二叉搜索树BSTree

    二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 左根右 它的左右子树也分别为二叉搜索树 之所以又叫二叉排

    2024年02月03日
    浏览(43)
  • 数据结构:二叉搜索树(非递归实现)

    目录 1、二叉搜索树 2、二叉搜索树的相关操作。 1、查找 2、插入 3、删除 3、代码实现(非递归) 二叉搜索树(Binary Search Tree,简称BST)是一种特殊的二叉树,其中每个节点的值大于其左子树中所有节点的值,小于其右子树中所有节点的值。这种特性使得二叉搜索树具有快速

    2024年03月08日
    浏览(34)
  • 数据结构_进阶(1):搜索二叉树

    建议再看这节之前能对C++有一定了解 二叉树在前面C的数据结构阶段时有出过,现在我们对二叉树来学习一些更复杂的类型,也为之后C++学习的 map 和 set 做铺垫 1. map和set特性需要先铺垫二叉搜索树 ,而二叉搜索树也是一种树形结构 2. 二叉搜索树的特性了解,有助于更好的理

    2024年02月16日
    浏览(45)
  • 【数据结构】 二叉搜索树的实现

    二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 它的左右子树也分别为二叉搜索树 比如以下就为一个人二叉搜

    2024年02月09日
    浏览(36)
  • 【数据结构】搜索二叉树(C++实现)

    ​ ​📝个人主页:@Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:数据结构 🎯 长路漫漫浩浩,万事皆有期待 上一篇博客:【C++】C++模板进阶 —— 非类型模板参数、模板的特化以及模板的分离编译 二叉搜索树又称二叉排序树,它或者是一棵空

    2024年02月07日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包