二叉搜索树

目录

一、二叉搜索树的引入

1.1二叉搜索树的定义

二、二叉搜索树的操作(key模型)

2.1二叉搜索树的查找

2.2二叉搜索树的插入 

2.3二叉搜索树的删除

2.4二叉搜索树的中序遍历

 2.5实现二叉搜索树中的构造、析构、拷贝、赋值运算符重载函数

三、二叉搜索树实现的递归版本

四、二叉搜索树的应用场景

 五、搜索二叉树的性能分析


一、二叉搜索树的引入

1.1二叉搜索树的定义

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

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值

若它的右子树不为空,则右子树上所有节点的值都大于根节点的值

它的左右子树也分别为二叉搜索树 

二、二叉搜索树的操作(key模型)

2.1二叉搜索树的查找

  • 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
  • 最多查找高度次,走到到空,还没找到,这个值不存在。
bool Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			cur=cur->_left;
		}
		else
		{
			return true;
		}
	}

	return false;
}

2.2二叉搜索树的插入 

  • 树为空,则直接新增节点,赋值给root指针
  • 树不空,按二叉搜索树性质查找插入位置,插入新节点
bool Insert(const K& key)
{
	if (_root==nullptr)
	{
		_root = new Node(key);
		return true;
	}

	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}
	}

	if (parent->_key < key)
	{
		parent->_right = new Node(key);
	}
	else
	{
		parent->_left = new Node(key);
	}
}

2.3二叉搜索树的删除

  • 首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情 况:
  • a要删除的结点无孩子结点
  • b要删除的结点只有左孩子结点
  • c要删除的结点只有右孩子结点
  • d要删除的结点有左、右孩子结点
  • 看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
  • 情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
  • 情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
  • 情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),或者在它的左子树中寻找中序下的最后一个结点(关键码最大),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除
bool Erase(const K& key)
{
	Node* cur = _root;
	Node* parent = cur;
	while (cur)
	{
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur=cur->_left;
		}
		else
		{
			//如果左为空
			if (cur->_left == nullptr)
			{
				//如果为根节点
				if (cur == _root)
				{
					_root = cur->_right;
				}

				if (parent->_left == cur)
				{
					parent->_left = cur->_right;
				}
				else
				{
					parent->_right = cur->_right;
				}
			}
			//如果右为空
			else if (cur->_right == nullptr)
			{
				//如果为根节点
				if (cur == _root)
				{
					_root = cur->_left;
				}
				if (parent->_left == cur)
				{
					parent->_left = cur->_left;
				}
				else
				{
					parent->_right = cur->_left;
				}
			}
			//如果左右都不为空--替换法--找左子树的最大值或者右子树的最小值
			else
			{
				//找左子树的最大值
				Node* leftmax = cur->_left;
				while (leftmax->_right)
				{
					parent = leftmax;
					leftmax = leftmax->_right;
				}

				swap(cur->_key, leftmax->_key);

				//删除交换后的leftmax
				if (parent->_left == leftmax)
				{
					parent->_left = leftmax->_left;
				}
				else
				{
					parent->_right = leftmax->_left;
				}

				cur = leftmax;

			}
			delete cur;
			return true;
		}
	}
	return false;
}

2.4二叉搜索树的中序遍历

  • 按照左子树-->跟-->右子树的顺序打印就好了
void _InOrder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}

	_InOrder(root->_left);
	cout << root->_key << " ";
	_InOrder(root->_right);
}

 2.5实现二叉搜索树中的构造、析构、拷贝、赋值运算符重载函数

  • 先定义两个私有成员函数destroy和copy,实现递归删除节点和递归拷贝节点
  • destroy函数使用引用传参的好处是可以直接在函数内将最后的根节点置空,不用在外部进行操作
void Destroy(Node*& root)
{
	if (root == nullptr)
		return;

	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
	root = nullptr;
}

Node* copy(Node* root)
{
	if (root == nullptr)
		return root;

	Node* copyroot = new Node(root->_key);
	copyroot->_left = copy(root->_left);
	copyroot->_right = copy(root->_right);

	return copyroot;
}
  • 实现二叉搜索树中的构造、析构、拷贝、赋值运算符重载函数
BSTree()
	:_root(nullptr)
{

}

BSTree(const BSTree<K>& t)
{
	_root = copy(t._root);
}

BSTree& operator=(BSTree<K> t)
{
	swap(_root, t._root);

	return *this;
}

~BSTree()
{
	Destroy(_root);
}

三、二叉搜索树实现的递归版本

bool _FindR(Node* root, const K& key)
{
	if (root == nullptr)
		return false;

	if (root->_key < key)
	{
		return _FindR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _FindR(root->_left, key);
	}
	else
	{
		return true;
	}
}

bool _InsertR(Node*& root, const K& key)
{
	if (root == nullptr)
	{
		root = new Node(key);
		return true;
	}

	if (root->_key < key)
	{
		return _InsertR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _InsertR(root->_left, key);
	}
	else
	{
		return false;
	}
}

bool _EraseR(Node*& root, const K& key)
{
	if (root == nullptr)
		return false;

	if (root->_key < key)
	{
		return _EraseR(root->_right, key);
	}

	else if (root->_key > key)
	{
		return _EraseR(root->_left, key);
	}

	else
	{
		Node* del = root;
		if (root->_right == nullptr)
		{
			root = root->_left;
		}
		else if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else
		{
			Node* leftmax = root->_left;
			while (leftmax->_right)
			{
				leftmax = leftmax->_right;
			}

			swap(leftmax->_key, root->_key);
			
			return _EraseR(root->_left, key);
		}
		delete del;
		return true;
	}
}
  • 递归版本二叉搜索树的增删查改,注意要用引用传参,传指针引用,这样可以做到直接修改每个节点的指针(进而改变节点的内容),而不需要重新链接节点关系
  • return _EraseR(root->_left, key);这句代码不能写成return _EraseR(leftmax, key);因为leftmax是一个局部变量,他不存在链接关系,如果leftmax刚好是没有孩子节点的节点,可以正常删除,如果有孩子节点需要改变链接关系的,在特殊情况下无法正常实现下一层结点的删除,会导致链接错乱。

四、二叉搜索树的应用场景

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到 的值。 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下: 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  2. KV模型:每一个关键码key,都有与之对应的值Value,即的键值对。该种方式在现实生活中非常常见: 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文就构成一种键值对; 再比如统计水果次数,统计成功后,给定水果就可快速找到其出现的次数,水果与其出现次数就是就构成一种键值对。

 五、搜索二叉树的性能分析

  • 插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
  • 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二 叉搜索树的深度的函数,即结点越深,则比较次数越多。
  • 但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

  1. 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:O(logN)
  2. 最差情况下,二叉搜索树退化为单支树(或者类似单支或类似链表),其平均比较次数为:O(N)
  3. 问题:如果退化成单支树,二叉搜索树的性能就失去了。后续使用AVL和红黑树来控制高度就可以实现O(logN)的时间复杂度,保证性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值