【C++航海王:追寻罗杰的编程之路】一篇文章带你了解二叉搜索树

目录

1 -> 二叉搜索树概念

2 -> 二叉搜索树操作

3 -> 二叉搜索树的应用

4 -> 二叉搜索树的性能分析


1 -> 二叉搜索树概念

二叉搜索树(BST, Binary Search Tree)又称二叉排序树或二叉查找树,它或者是一棵空树,或者具有以下性质的二叉树:

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

2 -> 二叉搜索树操作

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

1. 二叉搜索树的查找

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

2. 二叉搜索树的插入

插入具体过程:

  1. 树为空,则直接新增节点,赋值给root指针。
  2. 树不空,按二叉搜索树性质查找插入位置,插入新节点。

3. 二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回,否则要删除的节点可能分以下四种情况:

  1. 要删除的节点无孩子节点
  2. 要删除的节点只有左孩子节点
  3. 要删除的节点只有右孩子节点
  4. 要删除的节点有左、右孩子节点

看起来有4种情况,实际情况1可以与情况2或者3合并起来,因此真正的删除过程如下:

  • 删除该节点且使删除节点的双亲节点指向被删除节点的左孩子节点——直接删除
  • 删除该节点且使删除节点的双亲节点指向被删除节点的右孩子节点——直接删除
  • 在它的右子树中寻找中序下的第一个节点(关键码最小),用它的值填补到被删除节点中,再来处理该节点的删除问题——替换法删除

3 -> 二叉搜索树的应用

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

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

  • 以词库中所有单词集合中的每个单词作为Key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
#pragma once
#include <iostream>
using namespace std;

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:
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			parent = cur;
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				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)
	{
		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;
	}

	bool Erase(const K& key)
	{
		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
			{
				// 准备删除
				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* parent = cur;
					Node* subLeft = cur->_right;
					while (subLeft->_left)
					{
						parent = subLeft;
						subLeft = subLeft->_left;
					}

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

					if (subLeft == parent->_left)
						parent->_left = subLeft->_right;
					else
						parent->_right = subLeft->_right;

					delete subLeft;
				}

				return true;
			}
		}

		return false;
	}

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

	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}

	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}

	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}

private:
	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
		{
			// 删除
			if (root->_left == nullptr)
			{
				Node* del = root;
				root = root->_right;
				delete del;

				return true;

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

				return true;
			}
			else
			{
				Node* subLeft = root->_right;
				while (subLeft->_left)
				{
					subLeft = subLeft->_left;
				}

				swap(root->_key, subLeft->_key);

				// 转换成在子树去递归删除
				return _EraseR(root->_right, key);
			}
		}
	}

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

	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;
};

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

4 -> 二叉搜索树的性能分析

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

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

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

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树)。

最差情况下,二叉搜索树退化为单支树(或者类似单支树)。


感谢各位大佬支持!!!

互三啦!!!

  • 114
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 86
    评论
设二叉排序树的二叉链表存储结构的类型定义如下: typedef struct node{ int data; //用整数表示一个结点的名 struct node *LChild,*RChild; //左右指针域 }BSTNode,*BSTree; 设计算法并编写程序求解以下几个问题。 8 12 14 10 7 3 15 6 2 4 1 5 11 9 13 16 13 (1)键盘输入一个元素序列创建一棵二叉排序树,输出该二叉排序树的中序遍历序列; 例如,若输入 45,24,55,12,37,53,60,23,40,70 则创建的二叉排序树为: 输出结果为:12 23 24 37 40 45 53 55 60 70 (2)在(1)中所得的二叉排序树中插入一个值为 58 的结点,再输出它的中序遍历序列,输出 结果为:12 23 24 37 40 45 53 55 58 60 70 (3)在(1)中所得的二叉排序树中删除值为 45 的结点,再输出它的中序遍历序列,输出结果 为:12 23 24 37 40 53 55 58 60 70 (4)利用(1)中所得的二叉排序树的所有叶子结点构造一个头结点的单链表 L。要求不能 破坏这棵二叉排序树。所得的单链表 L 如下。 输出该链表各结点的值,输出结果为:23 40 53 70 (5)设计算法将(1)中所得的二叉排序树的左右子树进行交换,由于二叉树是一种递归定义, 所以子树的左右两棵子树也要相交换,依此类推。最后输出所得到的二叉树的中序遍历序列。 例如,经过上述操作后,(1)中所得的二叉排序树变为如下形式。 输出该二叉树的中序序列,结果为:70 60 55 53 45 40 37 24 23 12 (6)设计算法统计并输出(1)中所得的二叉排序树中只有一个孩子结点的结点个数。输出结 果为:3(7)在(1)中所得的二叉排序树中,设计算法并编写程序输出结点 40 的所有祖先结点。输 出结果为:45 24 37
以下是一个简单的 C++ 代码示例,用于实现二叉搜索树: ```cpp #include <iostream> using namespace std; // 定义二叉搜索树节点 struct BSTNode { int data; BSTNode* left; BSTNode* right; }; // 创建新节点 BSTNode* createNode(int value) { BSTNode* newNode = new BSTNode(); newNode->data = value; newNode->left = nullptr; newNode->right = nullptr; return newNode; } // 插入节点 BSTNode* insertNode(BSTNode* root, int value) { if (root == nullptr) { root = createNode(value); return root; } else if (value <= root->data) { root->left = insertNode(root->left, value); } else { root->right = insertNode(root->right, value); } return root; } // 查找节点 bool searchNode(BSTNode* root, int value) { if (root == nullptr) { return false; } if (root->data == value) { return true; } else if (value < root->data) { return searchNode(root->left, value); } else { return searchNode(root->right, value); } } // 打印二叉搜索树(中序遍历) void printTree(BSTNode* root) { if (root == nullptr) { return; } printTree(root->left); cout << root->data << " "; printTree(root->right); } int main() { // 创建根节点 BSTNode* root = nullptr; // 插入节点 root = insertNode(root, 8); root = insertNode(root, 3); root = insertNode(root, 10); root = insertNode(root, 1); root = insertNode(root, 6); root = insertNode(root, 14); root = insertNode(root, 4); root = insertNode(root, 7); root = insertNode(root, 13); // 打印二叉搜索树 cout << "二叉搜索树: "; printTree(root); cout << endl; // 查找节点 int value = 6; if (searchNode(root, value)) { cout << value << " 在二叉搜索树中" << endl; } else { cout << value << " 不在二叉搜索树中" << endl; } return 0; } ``` 以上代码创建了一个二叉搜索树,并实现了插入节点、查找节点和打印二叉搜索树等基本操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值