首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C++之二叉搜索树

C++之二叉搜索树

作者头像
禁默
发布2025-12-20 18:45:56
发布2025-12-20 18:45:56
600
举报

前言

在数据结构中,我们对二叉树这个数据结构有了基本的了解,并对其的部分操作进行学习和实现,在那部分,我们对递归也进行了充分的利用。 本节我们将学习一种新的二叉树结构,叫做二叉搜索树,就如其名字一样,它的功能在于查找搜索。下面我们将进行系统的学习。

1.二叉搜索树的概念

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

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

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

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

二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等

值,multimap/multiset支持插入相等值。

97dcb11da02f4dea87138b4a20a7824d.png
97dcb11da02f4dea87138b4a20a7824d.png

2.二叉搜索树的性能分析

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其高度为: O(log2 N)

最差情况下,二叉搜索树退化为单支树(或者类似单支),其高度为: O(N/2 )

综合而言二叉搜索树增删查改时间复杂度为: O(N)

显然这样的效率显然是无法满足需求的,后续二叉搜索树的变形,平衡二叉搜索树AVL树和红黑树,才能适用于在内存中存储和搜索数据。

二分查找也可以实现 O( logN) 级别的查找效率,但是二分查找有两大缺陷:

1. 需要存储在支持下标随机访问的结构中,并且有序。

2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数据。

961eed8caa834d9c93e7307fb4159dd4.png
961eed8caa834d9c93e7307fb4159dd4.png

接下来,我们将对二叉树的基本操作进行实现。

首先,我们创建一个节点类,然后再创建树。

代码语言:javascript
复制
template <class K>
struct Node {
	K _key;
	Node<K>* _left;
	Node<K>* _right;
	Node(const K& key)
		:key(Key), _left(nullptr), _right(nullptr)
	{}
};
template<class K>
class BSTree {
	//typedef Node<K> Node;
	using Node = Node<k>;//C++11
public:
	
private:
	Node *_root=nullptr;
};

3.二叉树搜素树的插入

插入的具体过程如下:

1. 树为空,则直接新增结点,赋值给root指针

2. 树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。

3. 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。( 要注意的是要保持逻辑一致性,插⼊相等的值不要一会往右走,一会往左走

下面代码是不支持插入相同值的,若要插入相同值,改下逻辑即可。

代码语言:javascript
复制
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;
	}
	cur = new Node(key);
	if (parent->_key < key)
		parent->_right = cur;
	else
		parent->_left = cur;
	return true;
}
代码语言:javascript
复制
while (cur) {
	parent = cur;
	if (key < cur->_key) {
		cur = cur->_left;
	}
	else { // 如果键值相同或更大,则向右移动
		cur = cur->_right;
	}
}

首先我们要找到插入节点的父亲的位置,这时候再根据大小插入到左右节点。

5a977105487d4a6b8bc79df7192c1224.png
5a977105487d4a6b8bc79df7192c1224.png
e768bfc5fda147b88890af7dc0ddbba2.png
e768bfc5fda147b88890af7dc0ddbba2.png

4.二叉搜索树的查找

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

如果不支持插入相等的值,找到x即可返回

如果支持插入相等的值,意味着有多个x存在,一般要求查找中序的第一个x。如下图,查找3,要找到1的右孩子的那个3返回

355358adf0694c38b8d81b8f31ba7d13.png
355358adf0694c38b8d81b8f31ba7d13.png
代码语言:javascript
复制
bool Find(const K& key) {
	if (_root == nullptr)
		return false;
	Node* cur = _root;
	while (cur) {
		if (cur->_key > key)
			cur = cur->_left;
		else if (cur->_key < key)
			cur = cur->_right;
		else
			return true;
	}
	return false;
}
Node* findX(int key) {
	Node* cur = _root;
	Node* firstX = nullptr;

	while (cur) {
		if (key<cur->_key) {
			
			cur = cur->_left;
		}
		else if (key > cur->_key) {
			
			cur = cur->_right;
		}
		else {
			// 找到了一个值为x的节点
			firstX = cur; // 先记录下这个节点
			cur = cur->_left; //cur->_right;
		}
	}

	// 返回第一个出现的值为x的节点
	return firstX;
}

5.二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回false。

如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)

1. 要删除结点N左右孩子均为空

2. 要删除的结点N左孩子位空,右孩子结点不为空

3. 要删除的结点N右孩子位空,左孩子结点不为空

4. 要删除的结点N左右孩子结点均不为空

对应以上四种情况的解决方案:

1. 把N结点的父亲对应孩子指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是⼀样的)

2. 把N结点的父亲对应孩子指针指向N的右孩子,直接删除N结点。

3. 把N结点的父亲对应孩子指针指向N的左孩子,直接删除N结点。

4. 无法直接删除N结点,因为N的两个孩子无处安放,只能用替换法删除。找N左子树的值最大结点 R(最右结点)或者N右子树的值最小结点R(最左结点)替代N,因为这两个结点中任意一个,放到N的 位置,都满足二叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转而变成删除R结点,R结点符合情况2或情况3,可以直接删除。

b1f31ac8978f4622b569e7e171335a03.png
b1f31ac8978f4622b569e7e171335a03.png
85455242cefa4314b94ff053f7aab338.png
85455242cefa4314b94ff053f7aab338.png
be4443394cec4d83b2ae24faaf5a3320.png
be4443394cec4d83b2ae24faaf5a3320.png
代码语言:javascript
复制
bool Erase(const K& key) {
    Node* cur = _root;
    Node* parent = nullptr;

    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; // 更新根节点
                } else {
                    if (parent->_left == cur) {
                        parent->_left = cur->_right;
                    } else {
                        parent->_right = cur->_right;
                    }
                }
                delete cur;
                return true;
            } else if (cur->_right == nullptr) {
                if (cur == _root) {
                    _root = cur->_left; // 更新根节点
                } else {
                    if (parent->_left == cur) {
                        parent->_left = cur->_left;
                    } else {
                        parent->_right = cur->_left;
                    }
                }
                delete cur;
                return true;
            } else {
                // 节点左右孩子均不为空,找到右子树中的最小节点
                Node* replaceParent = cur;
                Node* replace = cur->_right;
                while (replace->_left) {
                    replaceParent = replace;
                    replace = replace->_left;
                }
                cur->_key = replace->_key; // 用右子树中最小节点的值替换当前节点的值
                if (replaceParent->_left == replace) {
                    replaceParent->_left = replace->_right;
                } else {
                    replaceParent->_right = replace->_right;
                }
                delete replace; // 删除替换节点
                return true;
            }
        }
    }
    return false; // 没有找到要删除的节点
}

在代码中我们还考虑了删除的节点就为根节点的情况,而且只有一个分支,就直接更新节点指向。

716d8ee9e3e14b8d95004620de583412.png
716d8ee9e3e14b8d95004620de583412.png

6.完整代码

代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
template <class K>
struct Node {
	K _key;
	Node<K>* _left;
	Node<K>* _right;
	Node(const K& key)
		:_key(key), _left(nullptr), _right(nullptr)
	{}
};
template<class K>
class BSTree {
	//typedef Node<K> Node;
	using Node = Node<K>;
public:
	
	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;
		}
		cur = new Node(key);
		if (parent->_key < key)
			parent->_right = cur;
		else
			parent->_left = cur;
		return true;
	}
	bool Find(const K& key) {
		if (_root == nullptr)
			return false;
		Node* cur = _root;
		while (cur) {
			if (cur->_key > key)
				cur = cur->_left;
			else if (cur->_key < key)
				cur = cur->_right;
			else
				return true;
		}
		return false;
	}
	bool Erase(const K& key) {
		Node* cur = _root;
		Node* parent = nullptr;
		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;

					else {
						if (parent->_left == cur)
							parent->_left = cur->_right;
						else
							parent->_right = cur->_right;
					}
					delete cur;
					return true;
				}
				//节点右孩子为空
				else if (cur->_right ==nullptr) {
					if (cur == _root)
						_root = cur->_left;
					else {
						if (parent->_left == cur)
							parent->_left = cur->_left;
						else
							parent->_right = cur->_left;
					}
					delete cur;
					return true;
				}
				//节点左右孩子均不为空
				else {
					//我们采用右子树最小(最左)
					Node* replaceParent = cur;
					Node* replace = cur->_right;
					while (replace->_left) {
						replaceParent = replace;
						replace = replace->_left;
					}
					cur->_key = replace->_key;
					if (replaceParent->_left == replace)
						replaceParent->_left = replace->_right;
					else
						replaceParent->_right = replace->_right;

					delete replace;
					return true;
				}			
			}	
		}
		return false;
	}
	//Node* findX(int key) {
	//	Node* cur = _root;
	//	Node* firstX = nullptr;

	//	while (cur) {
	//		if (key<cur->_key) {
	//			
	//			cur = cur->_left;
	//		}
	//		else if (key > cur->_key) {
	//			
	//			cur = cur->_right;
	//		}
	//		else {
	//			// 找到了一个值为x的节点
	//			firstX = cur; // 先记录下这个节点
	//			cur = cur->_left; //cur->_right;
	//		}
	//	}

	//	// 返回第一个出现的值为x的节点
	//	return firstX;
	//}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	} 
private:
	void _InOrder(Node* root) {
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);

	}
private:
	Node *_root=nullptr;
};


int main() {
	BSTree<int>t;
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	for (auto e : a)
		t.Insert(e);
	t.InOrder();
	t.Insert(16);
	//t.Insert(3);
	t.InOrder();
	if (t.Find(5)){
		cout << "找到了" << endl;
}
	t.Erase(3);
	t.InOrder();
	t.Erase(8);
	t.InOrder();
	for (auto e : a) {
		t.Erase(e);
		t.InOrder();
	}
	return 0;
}

结束语

本节内容就到此结束了,下节内容我们将简单列举二叉搜索树的使用场景。 最后感谢各位友友的支持!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 1.二叉搜索树的概念
  • 2.二叉搜索树的性能分析
  • 3.二叉树搜素树的插入
  • 4.二叉搜索树的查找
  • 5.二叉搜索树的删除
  • 6.完整代码
  • 结束语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档