二叉搜索树BST ——(C++)

        本篇将会讲解有关二叉树的搜索原理,以及关于二叉搜索树的建立,以及二叉树搜索树的插入、删除和查找等基本操作。最后我们还会对二叉搜索树进行功能扩展,介绍有关搜索二叉树的 K 模型和 KV 模型。目录如下:

目录

1. 搜索二叉树

二叉搜索树概念

二叉树类框架

搜索二叉树的插入

搜索二叉树的查找

搜索二叉树的遍历

搜索二叉树的删除

搜索二叉树所有代码

测试

 2. 搜索二叉树的扩展

中英文查找测试代码

统计单词次数测试代码

1. 搜索二叉树

二叉搜索树概念

        二叉搜索树又称二叉排序树,也可以是一棵空树。对于搜索二叉树具有以下性质:

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

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

                3. 它的左右子树也分别是二叉搜索树。

        关于二叉搜索树为什么叫做二叉排序树,这是因为左子树小于根节点,右子树大于根节点(二叉搜索树中的元素默认不会重复),当我们使用中序遍历(左 中 右)的时候,遍历刚好出来是有序的。

二叉树类框架

        建立一颗二叉树,首先需要一个结点的类,然后我们需要使用一个根节点将其维护起来。如下:

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 {
public:
	typedef BSTreeNode<K> Node;
	

private:
	Node* _root;
};

搜索二叉树的插入

        关于搜索二叉树的插入,我们只需要找到合适的位置将其插入即可。也就是当我们需要插入的元素大于当前元素的时候,我们就继续往右子树放,反之放在左值树,直到到空结点的时候,我们还需要记录当前搜索结点的父亲结点,便于之后将其连接起来,我们就可以插入元素了。

        注:默认搜索二叉树不含有重复元素,所以当插入重复元素的时候,插入失败。

bool insert(const K& key) {
	if (_root == nullptr) {
		_root = new Node(key);
		return true;
	}
	// 左子树小于根节点,右子树大于根节点
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur) {
		if (key > cur->_key) {
			parent = cur;
			cur = cur->_right;
		}
		else if (key < cur->_key) {
			parent = cur;
			cur = cur->_left;
		}
		else {
			return false;
		}
	}
	cur = new Node(key);
	if (key < parent->_key)
		parent->_left = cur;
	else
		parent->_right = cur;
	return true;
}

搜索二叉树的查找

        查找遵循搜索二叉树的性质,当需要查找的数小于当前结点的时候,我们往左子树查找,当需要查找的数大于当前结点的时候,我们往右边查找。若直到空结点都还没有查找到,那么就查找失败了。如下:

bool find(const K& key) {
	Node* cur = _root;
	while (cur) {
		if (key > cur->_key) {
			cur = cur->_right;
		}
		else if (key < cur->_key) {
			cur = cur->_left;
		}
		else {
			return true;
		}
	}
	return false;
}

搜索二叉树的遍历

        搜索二叉树的遍历我们采用中序遍历,因为遍历出来的结果就是有序的。我们使用递归遍历。但是我们需要注意的一点是,我们在遍历的时候,需要访问到根节点,但是若我们在类外想要遍历的时候,我们并不能传一个被 private 保护的根节点的,所以我们需要进行如下的封装,就不需要进行传参了。如下:

template <class K>
class BSTree {
public:
	typedef BSTreeNode<K> Node;
    // 中序遍历
	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;
};

搜索二叉树的删除

        关于搜索二叉树结点的删除,会存在很多的情况,如:删除的位置是叶子结点,删除的位置左子树为空,删除的位置右子树为空,删除的位置左右子树都不为空,删除的位置为根节点,且左子树或右子树为空。

        所以搜索二叉树的删除实现较为复杂,首先需要找到该位置,若直到空结点都还未找到,则二叉树中并无该元素,删除失败,返回 false;

        若删除的位置是叶子结点,删除的位置左子树为空,删除的位置右子树为空:我们先讨论删除位置的左子树为空,那么删除位置右节点可能不为空,所以删除该位置之后需要将删除位置的父亲结点的指针(可能是左,也可能是右)指向删除结点的右子树。删除位置的右子树为空,那么删除位置左节点可能不为空,所以删除该位置之后需要将删除位置的父亲结点的指针(可能是左,也可能是右)指向删除结点的左子树。当我们实现以上的两种情况的时候,我们发现删除位置是叶子结点的问题也迎刃而解了。

        删除的位置左右子树都不为空:当我们删除位置的左子树和右子树都不为空的时候,我们就需要讨论一个问题,删除该位置之后,左右子树该如何进行连接?答案是找到左子树的最大节点(最右结点)或者找到右子树的最小结点(最左结点)将其替换即可,替换之后在将其删除即可,但是其中还有一个不可忽视的问题,当我们替换之后删除的位置并不是叶子结点的时候,又该如何进行连接呢?以替换删除的结点为右子树的最小结点为例子,我们需要将删除结点的父亲结点指向(可能是左指针也可能是右指针,需要判断)删除结点的右子树。

        删除的位置为根节点,且左子树或右子树为空:当需要删除的结点为根结点且一端的子树为空的时候,我们只需要将根节点往另一个相反的结点移位即可。如下:

        将会对每种情况在代码中注释:

bool erase(const K& key) {
	// 先寻找key,找到删除,没找到直接返回false
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur) {
		if (key > cur->_key) {
			parent = cur;
			cur = cur->_right;
		}
		else if (key < cur->_key) {
			parent = cur;
			cur = cur->_left;
		}
		else {
			break;
		}
	}
	if (cur == nullptr) return false;
    // 需要删除位置的左子树或右子树为空
	if (cur->_left == nullptr) {
        // 删除位置为根节点
		if (parent == nullptr) {
			parent = cur;
			_root = cur->_right;

			delete parent;
			return true;
		}
		else {
			if (parent->_right == cur)
				parent->_right = cur->_right;
			else
				parent->_left = cur->_right;
			delete cur;
		}
	}
	else if (cur->_right == nullptr) {
		if (parent == nullptr) {
			parent = cur;

			_root = cur->_left;
			delete parent;
			return true;
		}
		else {
			if (parent->_right == cur)
				parent->_right = cur->_left;
			else
				parent->_left = cur->_left;
			delete cur;
		}
	}
	else {
		// 删除左右子树都有元素的结点
		// 找到右边最小的
		Node* rightMin = cur->_right;
		Node* rightMinParent = cur;
		while (rightMin->_left) {
			rightMinParent = rightMin;
			rightMin = rightMin->_left;
		}
		// 现在的rightMin为右子树最小结点元素
		std::swap(cur->_key, rightMin->_key);
		// 若要删除的结点如父亲结点的左结点,链接左边
		if (rightMinParent->_right == rightMin)
			rightMinParent->_right = rightMin->_right;
		else
			rightMinParent->_left = rightMin->_right;
		delete rightMin;
	}
	return true;
}

搜索二叉树所有代码

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 {
public:
	typedef BSTreeNode<K> Node;
	// 构造函数
	BSTree() : _root(nullptr) {}
	// 插入、删除、查找、遍历函数
	bool insert(const K& key) {
		if (_root == nullptr) {
			_root = new Node(key);
			return true;
		}
		// 左子树小于根节点,右子树大于根节点
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) {
			if (key > cur->_key) {
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key) {
				parent = cur;
				cur = cur->_left;
			}
			else {
				return false;
			}
		}
		cur = new Node(key);
		if (key < parent->_key)
			parent->_left = cur;
		else
			parent->_right = cur;
		return true;
	}

	bool find(const K& key) {
		Node* cur = _root;
		while (cur) {
			if (key > cur->_key) {
				cur = cur->_right;
			}
			else if (key < cur->_key) {
				cur = cur->_left;
			}
			else {
				return true;
			}
		}
		return false;
	}

	bool erase(const K& key) {
		// 先寻找key,找到删除,没找到直接返回false
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) {
			if (key > cur->_key) {
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key) {
				parent = cur;
				cur = cur->_left;
			}
			else {
				break;
			}
		}
		if (cur == nullptr) return false;
		// 现在的 cur 是我们需要删除的结点
		// 若该结点为根节点

		if (cur->_left == nullptr) {
			if (parent == nullptr) {
				parent = cur;
				_root = cur->_right;

				delete parent;
				return true;
			}
			else {
				if (parent->_right == cur)
					parent->_right = cur->_right;
				else
					parent->_left = cur->_right;
				delete cur;
			}
		}
		else if (cur->_right == nullptr) {
			if (parent == nullptr) {
				parent = cur;

				_root = cur->_left;
				delete parent;
				return true;
			}
			else {
				if (parent->_right == cur)
					parent->_right = cur->_left;
				else
					parent->_left = cur->_left;
				delete cur;
			}
		}
		else {
			// 删除左右子树都有元素的结点
			// 找到右边最小的
			Node* rightMin = cur->_right;
			Node* rightMinParent = cur;
			while (rightMin->_left) {
				rightMinParent = rightMin;
				rightMin = rightMin->_left;
			}
			// 现在的rightMin为右子树最小结点元素
			std::swap(cur->_key, rightMin->_key);
			// 若要删除的结点如父亲结点的左结点,链接左边
			if (rightMinParent->_right == rightMin)
				rightMinParent->_right = rightMin->_right;
			else
				rightMinParent->_left = rightMin->_right;
			delete rightMin;
		}
		return true;
	}

	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;
};

int main() {
	BSTree<int> bs;
	vector<int> v({ 8, 3, 1, 10, 6, 4, 7, 14, 13 });
	for (auto e : v) {
		bs.insert(e);
	}
	bs.InOrder();
	bs.erase(1);
	for (auto e : v) {
		bs.erase(e);
		bs.InOrder();

	}
	bs.InOrder();

	return 0;
}
测试

 2. 搜索二叉树的扩展

        关于搜索二叉树一共存在两种模型,一种为 K 模型,另一种为 KV 模型,如下:

        K 模型:K 模型即只有 key 作为关键码,结构中只需要存储 key 即可,关键码即为需要搜索到的值。(也就是上文中实现的搜索二叉树)

        KV 模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对
        关于 KV 模型,比如:英汉词典就是英文与中文的对应关系统计单词次数,统计成功后,给定单词就可快速找到其出现的次数。

        关于 KV 模型的实现和 K 模型大同小异,如下:

template <class K, class V>
struct BSTreeNode {
	BSTreeNode<K, V>* _left;
	BSTreeNode<K, V>* _right;
	K _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 {
public:
	typedef BSTreeNode<K, V> Node;
	// 构造函数
	BSTree() : _root(nullptr) {}
	// 插入、删除、查找、遍历函数
	bool insert(const K& key, const V& value) {
		if (_root == nullptr) {
			_root = new Node(key, value);
			return true;
		}
		// 左子树小于根节点,右子树大于根节点
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) {
			if (key > cur->_key) {
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key) {
				parent = cur;
				cur = cur->_left;
			}
			else {
				return false;
			}
		}
		cur = new Node(key, value);
		if (key < parent->_key)
			parent->_left = cur;
		else
			parent->_right = cur;
		return true;
	}

	Node* find(const K& key) {
		Node* cur = _root;
		while (cur) {
			if (key > cur->_key) {
				cur = cur->_right;
			}
			else if (key < cur->_key) {
				cur = cur->_left;
			}
			else {
				return cur;
			}
		}
		return nullptr;
	}
	
	bool erase(const K& key) {
		// 先寻找key,找到删除,没找到直接返回false
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) {
			if (key > cur->_key) {
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key) {
				parent = cur;
				cur = cur->_left;
			}
			else {
				break;
			}
		}
		if (cur == nullptr) return false;
		// 现在的 cur 是我们需要删除的结点
		// 若该结点为根节点

		if (cur->_left == nullptr) {
			if (parent == nullptr) {
				parent = cur;
				_root = cur->_right;
				
				delete parent;
				return true;
			}
			else {
				if (parent->_right == cur)
					parent->_right = cur->_right;
				else
					parent->_left = cur->_right;
				delete cur;
			}
		}
		else if (cur->_right == nullptr) {
			if (parent == nullptr) {
				parent = cur;
				
				_root = cur->_left;
				delete parent;
				return true;
			}
			else {
				if (parent->_right == cur)
					parent->_right = cur->_left;
				else
					parent->_left = cur->_left;
				delete cur;
			}
		}
		else {
			// 删除左右子树都有元素的结点
			// 找到右边最小的
			Node* rightMin = cur->_right;
			Node* rightMinParent = cur;
			while (rightMin->_left) {
				rightMinParent = rightMin;
				rightMin = rightMin->_left;
			}
			// 现在的rightMin为右子树最小结点元素
			std::swap(cur->_key, rightMin->_key);
			// 若要删除的结点如父亲结点的左结点,链接左边
			if(rightMinParent->_right == rightMin)
				rightMinParent->_right = rightMin->_right;
			else
				rightMinParent->_left = rightMin->_right;
			delete rightMin;
		}
		return true;
	}

	void InOrder() {
		_InOrder(_root);
		cout << endl;
	}

private:
	void _InOrder(Node* root) {
		// 左中右
		if (root == nullptr) return;
		_InOrder(root->_left);
		cout << root->_key << " " << root->_value << endl;
		_InOrder(root->_right);
	}
private:
	Node* _root;
};
中英文查找测试代码
int main() {
	BSTree<string, string> bs;
	string s1 = "insert";
	string v1 = "插入";
	string s2 = "right";
	string v2 = "右边";
	string s3 = "left";
	string v3 = "左边";
	bs.insert(s1, v1);
	bs.insert(s2, v2);
	bs.insert(s3, v3);
	string s;
	while (cin >> s) {
		if (bs.find(s))
			cout << bs.find(s)->_value << endl;
		else
			cout << "没有该单词的含义" << endl;
	}
	return 0;
}
统计单词次数测试代码
int main() {
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
	BSTree<string, int> bs;
	for (auto& str : arr) {
		auto ret = bs.find(str);
		if (ret == nullptr)
			bs.insert(str, 1);
		else
			ret->_value++;
	}
	bs.InOrder();
	return 0;
}
  • 37
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值