二叉搜索树

目录

1.二叉搜索树的概念

2.二叉搜索树的基本操作 

2.1 二叉搜索树的查找

2.2 二叉搜索树的中序遍历 

2.3 二叉搜索树的插入

2.4 二叉搜索树的删除

 在左节点为空,parent指向cur的右节点(修复bug版) :

cur的左右节点都不为空(修复bug版) : 

2.5 二叉搜索树的模拟实现

 3. 二叉搜索树的应用

3.1 K模型 :

3.2 KV模型: 

K模型,KV模型的实际应用 和 递归的模拟实现二叉搜索树

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


1.二叉搜索树的概念

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

        1. 非空左子树的所有键值小于其根节点的键值;

        2. 非空右子树的所有键值大于其根节点的键值;

        3. 左右子树都是二叉搜索树.

eg. 

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

2.二叉搜索树的基本操作 

2.1 二叉搜索树的查找

steps:

       1. 从根节点开始比较, 查找, 比根节点大则往右边走进行查找; 比根节点小则往左边走进行查找;

       2. 最多查找高度次, 走到空还没找到,这个值不存在

template<class T>
struct BSTNode
{
	BSTNode(const T& data = T())
		: left(nullptr),rigth(nullptr),data(data)
	{}
	BSTNode<T>* left;
	BSTNode<T>* right;
	T data;
};

template<class T>
class BSTree
{
	typedef BSTNode<T> Node;
	typedef Node* PNode;
public:
	BSTree(): root(nullptr)
	{}

	~BSTree();

	bool Find(const T& data) {
		Node* cur = root;
		while (cur) {
			if (cur->data < key)
				cur = cur->right;
			else if (cur->data > key)
				cur = cur->left;
			else
				return true;
		}
		return false;
	}
};
2.2 二叉搜索树的中序遍历 

中序遍历 : 根节点 -- >> 左子树 -->> 右子树 

// InOrder
template<class T>
struct BSTNode
{
	BSTNode(const T& data = T())
		: left(nullptr), rigth(nullptr), data(data)
	{}
	BSTNode<T>* left;
	BSTNode<T>* right;
	T data;
};

template<class T>
class BSTree
{
	typedef BSTNode<T> Node;
	typedef Node* PNode;
public:
	BSTree() : root(nullptr)
	{}

	~BSTree();

	void InOrder(Node* root) {
		if (root == = nullptr)
			return;
		InOder(root->left);
		cout << root->data << endl;
		InOrder(root->right);
	}
};
2.3 二叉搜索树的插入

steps:

        1.  树为空,  新增节点,赋值给 root 指针;

        2. 树不空, 依据二叉搜索树性质查找插入位置, 插入新节点

                review : 二叉搜索树的性质: 

                        1. 非空左子树的所有键值小于其根节点的键值;

                        2. 非空右子树的所有键值大于其根节点的键值;

                        3. 左右子树都是二叉搜索树.

eg. 依次插入16, 0 :

// Insert
template<class T>
struct BSTNode
{
	BSTNode(const T& data = T())
		: left(nullptr), rigth(nullptr), data(data)
	{}
	BSTNode<T>* left;
	BSTNode<T>* right;
	T data;
};

template<class T>
class BSTree
{
	typedef BSTNode<T> Node;

public:
	BSTree() : root(nullptr)
	{}

	~BSTree();

	bool Insert(const T& data)
	{
		// 如果树为空,直接插入
		if (nullptr == root)
		{
			root = new Node(data);
			return true;
		}

		//按照二叉搜索树的性质查找data在树中的插入位置
		// cur : data ; 记录cur的父节点, 最终判断cur是左节点还是右节点
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (data < cur->data)
				cur = cur->left;
			else if (data > cur->data)
				cur = cur->right;
			else
				return false; // 元素已经存在
		}
		cur = new Node(data);
		if (data < parent->data)
			parent->left = cur;
		else
			parent->right = cur;

		return true;
	}
};
2.4 二叉搜索树的删除

steps:

        1. 查找元素是否在二叉搜索树中,如果不在则返回; 否则可能分4种情况进行讨论:

1) 需要删除的节点没有子节点

2) 需要删除的节点只有左孩子节点

3) 需要删除的节点只有右孩子节点

4) 需要删除的节点有左右孩子节点

 图解 :  eg.

注意 4)情况 ( 图解中的<3>情况 ), 在要删除的节点的右子树下寻找中序的第一个最小值的节点 或 在要删除的节点的左子树下寻找中序下的第一个最大值的节点, 用它的值覆盖要删除节点的值,再删除寻找的节点.

( 先来删除的前置工作:    重点讨论 <2> <3> 的删除情况

	// Erase
	bool Erase(const T& data)
	{
		// 树为空, 删除失败
		if (root == nullptr)
			return false;

		// 查找data在树中的位置
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (data == cur->data)
				break;
			else if (data < cur->data)
			{
				parent = cur;
				cur = cur->left;
			}
			else {
				parent = cur;
				cur = cur->right;
			}
		}

		// data不在树中, 删除失败
		if (nullptr == cur)
			return false;
        
        //左节点为空,parent指向cur的右节点
        if (nullptr == cur->left)
        {
	        // 
        }
        //右节点为空, parent指向cur的左节点
        else if (nullptr == cur->right)
        {
	        //
        }
        // cur的左右节点都有
        else
        {
	        // ...
        }

};

<2> 当左节点为空, parent指向cur的右节点:

if (nullptr == cur->left)
{
	if (cur->left == nullptr) {
		if (cur == parent->left)
			parent->left = cur->right;
		else
		{
			parent->right = cur->right;
		}
	}
	delete cur;
}

这个代码对吗 ?

在大部分时候是可以进行删除的, 然而在删除二叉搜索树的最后一个结点的时候代码会崩溃.

 在左节点为空,parent指向cur的右节点(修复bug版) :
//左节点为空,parent指向cur的右节点
if (nullptr == cur->left)
{
	if (cur == root)
		root == cur->right;
	else {
		if (cur->left == nullptr) {
			if (cur == parent->left)
				parent->left = cur->right;
			else
			{
				parent->right = cur->left;
			}
		}
	}
	
	delete cur;
}

<3> cur的左右节点都不为空 : 

---->>>> 查找右子树的最左节点替代删除

Node* rightMinParent = cur;
Node* rightMin = cur->right;
while (rightMin->left) {
	rightMinParent = rightMin;
	rightMin = rightMin->left;
}

swap(cur->data, rightMin->data);
rightMinParent->left = rightMin->right;

delete rightMin;

注意 : 

1. 找到右子树的最左节点, swap之后 不能通过递归查找 ==>> 可能找不到要删的值

2. 在rightMin的左子树为空时,代码崩溃

图解 :  在rightMin的左子树为空时,代码崩溃

 右侧二叉搜索树结构图的旁边的笔记颜色对应左侧相应颜色代码快执行结束后的结果.

cur的左右节点都不为空(修复bug版) : 
Node* rightMinParent = cur;
Node* rightMin = cur->right;
while (rightMin->left) {
	rightMinParent = rightMin;
	rightMin = rightMin->left;
}
swap(cur->data, rightMin->data);
if (rightMinParent->left == rightMin)
	rightMinParent->left = rightMin->right;
else
	rightMinParent->right = rightMin->right;
delete rightMin;
2.5 二叉搜索树的模拟实现
template<class T>
struct BSTNode
{
	BSTNode(const T& data = T())
		: left(nullptr), rigth(nullptr), data(data)
	{}
	BSTNode<T>* left;
	BSTNode<T>* right;
	T data;
};

template<class T>
class BSTree
{
	typedef BSTNode<T> Node;

public:
	BSTree() : root(nullptr)
	{}

	~BSTree();

	// InOrder
	void InOrder(Node* root) {
		if (root == = nullptr)
			return;
		InOder(root->left);
		cout << root->data << endl;
		InOrder(root->right);
	}

	// Find
	bool Find(const T& data) {
		Node* cur = root;
		while (cur) {
			if (cur->data < key)
				cur = cur->right;
			else if (cur->data > key)
				cur = cur->left;
			else
				return true;
		}
		return false;
	}

	// Insert
	bool Insert(const T& data)
	{
		// 如果树为空,直接插入
		if (nullptr == root)
		{
			root = new Node(data);
			return true;
		}

		//按照二叉搜索树的性质查找data在树中的插入位置
		// cur : data ; 记录cur的父节点, 最终判断cur是左节点还是右节点
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (data < cur->data)
				cur = cur->left;
			else if (data > cur->data)
				cur = cur->right;
			else
				return false; // 元素已经存在
		}
		cur = new Node(data);
		if (data < parent->data)
			parent->left = cur;
		else
			parent->right = cur;

		return true;
	}


	// Erase
	bool Erase(const T& data)
	{
		// 树为空, 删除失败
		if (root == nullptr)
			return false;

		// 查找data在树中的位置
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (data == cur->data)
				break;
			else if (data < cur->data)
			{
				parent = cur;
				cur = cur->left;
			}
			else {
				parent = cur;
				cur = cur->right;
			}
		}

		// data不在树中, 删除失败
		if (nullptr == cur)
			return false;

		//左节点为空,parent指向cur的右节点
		if (nullptr == cur->left)
		{
			if (cur == root)
				root == cur->right;
			else {
				if (cur->left == nullptr) {
					if (cur == parent->left)
						parent->left = cur->right;
					else
					{
						parent->right = cur->left;
					}
				}
			}
			
			delete cur;
		}
		//右节点为空, parent指向cur的左节点
		else if (nullptr == cur->right)
		{
			if (cur == root)
				root == cur->left;
			else {
				if (cur->right == nullptr) {
					if (cur == parent->left)
						parent->left = cur->left;
					else
						parent->right = cur->left;
				}
			}
			delete cur;
		}
		// cur的左右节点都有
		else
		{
			Node* rightMinParent = cur;
			Node* rightMin = cur->right;
			while (rightMin->left) {
				rightMinParent = rightMin;
				rightMin = rightMin->left;
			}

			swap(cur->data, rightMin->data);
			if (rightMinParent->left == rightMin)
				rightMinParent->left = rightMin->right;
			else
				rightMinParent->right = rightMin->right;
			delete rightMin;
		}
    }
};

 3. 二叉搜索树的应用

3.1 K模型 :

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

eg. 检查一个单词是否拼写正确 : 

       1.以词库中所有单词集合中的每个单词作为key, 构建二叉搜索树

       2. 在二叉搜索树中检索该单词是否存在,存在则拼写正确;不存在则拼写错误.

3.2 KV模型: 

每一个关键码key, 都有与之对应的value, 即 <key, value>键值对.

eg1.  英汉词典: 英汉词典就是中英文对照的关系. 通过英文可以快速找到与其对应的中文.英文单词和其对应的中文释义 <word, Chinese>就构成了一种键值对.

eg2. 统计单词次数 : 统计成功后, 给定单词就可以快速找到其出现的次数, 单词与其出现的次数就是 <word,count>键值对.

// 紧急修改!! ( 之前的实现有误!

K模型,KV模型的实际应用 和 递归的模拟实现二叉搜索树
// 英汉词典
#include <iostream>
#include <string>
#include <utility>
using namespace std;


#pragma once

Binary_Search_Tree
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
{
	typedef BSTreeNode<K, V> Node;
public:
	bool Insert(const K& key, const V& value)
	{
		if (_root == nullptr)
		{
			_root = new Node(key, value);
			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, value);
		if (parent->_key < cur->_key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}


		return true;
	}


	Node* 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 cur;
		}


		return NULL;
	}


	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
			{
				/* 1.左为空
				 2.右为空
				 3.左右都不为空*/
				if (cur->_left == nullptr)
				{
					if (parent == nullptr)
					{
						_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 (parent == nullptr)
					{
						_root = cur->_left;
					}
					else
					{
						if (cur == parent->_left)
							parent->_left = cur->_left;
						else
							parent->_right = cur->_left;
					}


					delete cur;
				}
				else
				{
					// 替换法删除  左树的最大节点(最右节点) 或者是右树的最小节点(最左节点)
					Node* minNodeParent = cur;  // 这里要注意不能初始化给空
					Node* minNode = cur->_right;
					while (minNode->_left)
					{
						minNodeParent = minNode;
						minNode = minNode->_left;
					}


					swap(cur->_key, minNode->_key);
					// 转换成删除minNode


					// 因为minNode是作为空的节点,可以直接删除
					if (minNodeParent->_right == minNode)
						minNodeParent->_right = minNode->_right;
					else
						minNodeParent->_left = minNode->_right;


					delete minNode;
				}


				return true;
			}
		}


		return false;
	}


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


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


	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	Node* _root = nullptr;
};


void TestBSTree()
{
	BSTree<string, string> dict;
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");
	dict.Insert("left", "左边");
	dict.Insert("string", "字符串");


	string str;
	while (cin>>str)
	{
		auto ret = dict.Find(str);
		if (ret)
		{
			cout << str << ":" << ret->_value << endl;
		}
		else
		{
			cout << "单词拼写错误" << endl;
		}
	}


	string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };
	// 统计水果出现的次
	BSTree<string, int> countTree;
	for (auto str : strs)
	{
		auto ret = countTree.Find(str);
		if (ret == NULL)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	countTree.InOrder();
}
//Binary_Search_Tree  递归实现--了解一下
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)
		{
			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 < cur->_key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}


		return true;
	}


	Node* 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 cur;
		}


		return NULL;
	}

	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
			{
				// 1.左为空
				// 2.右为空
				// 3.左右都不为空
				if (cur->_left == nullptr)
				{
					if (parent == nullptr)
					{
						_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 (parent == nullptr)
					{
						_root = cur->_left;
					}
					else
					{
						if (cur == parent->_left)
							parent->_left = cur->_left;
						else
							parent->_right = cur->_left;
					}


					delete cur;
				}
				else
				{
					// 替换法删除  左树的最大节点(最右节点) 或者是右树的最小节点(最左节点)
					Node* minNodeParent = cur;  // 这里要注意不能初始化给空
					Node* minNode = cur->_right;
					while (minNode->_left)
					{
						minNodeParent = minNode;
						minNode = minNode->_left;
					}


					swap(cur->_key, minNode->_key);
					// 转换成删除minNode


					// 因为minNode是作为空的节点,可以直接删除
					if (minNodeParent->_right == minNode)
						minNodeParent->_right = minNode->_right;
					else
						minNodeParent->_left = minNode->_right;


					delete minNode;
				}


				return true;
			}
		}


		return false;
	}


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


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


	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
private:
	Node* _root = nullptr;
};


void TestBSTree()
{
	int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
	BSTree<int> bst;
	for (auto e : a)
	{
		//bst.Insert(e);
		bst.InsertR(e);
	}
}

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

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

最优情况下 : 二叉搜索树是完全二叉树, 查找平均次数 :  log2(n)

最差情况下 : 二叉搜索树退化为单支树( 接近有序插入,退化厉害,效率下降 ), 查找平均次数 :  n

为了解决退化的情况, 解决方案 ===>>>  AVL树 : 平衡搜索二叉树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值