[数据结构]二叉搜索树 / 二叉排序树

概述

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
① 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
② 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
③ 它的左右子树也分别为二叉搜索树

例如:
在这里插入图片描述

实现

结点的定义

template <class T>		//泛型
struct BNode {
	T data_;
	typedef BNode<T> Node;	//别名
	Node* left;
	Node* right;
	BNode(const T&data) :data_(data), left(nullptr), right(nullptr)
	{}
};

一个数据成员data_保存数据 ,两个结点指针left和right分别指向该节点的左右孩子

树的定义

template<class T>
class BTree {
public:
	typedef BNode<T> Node;
	BTree():root_(nullptr)
	{}
private:
	Node* root_;
};

root_是一个结点指针,指向该二叉搜索树的根节点

搜索

思路:根据二叉搜索树的性质,根节点左边的值都小于根节点,右边的值都大于根节点,从根节点开始进行递归地比较,直到找到对应的值。

Node* find(const T& val)
	{
		Node* cur = root_;
		while (cur)
		{
			if (cur->data_ == val)
				return cur;
			else if (cur->data_ > val)
				cur = cur->left;
			else
				cur = cur->right;
		}
		return cur;
	}

插入

思路:二叉搜索树默认不插入重复的值
首先如果树为空,直接在根节点处用要插入的值创建一个结点,然后返回;
如果树不为空就进行搜索,找到合适的位置才能进行插入:从根节点开始递归比较,找到正确的插入位置;搜索完毕后插入结点。

注意:此处在搜索完毕之后要保留其父节点的位置,因为待插入的位置一定会是一个叶子节点,所以需要保留其父节点的位置来添加链接关系;大于父节点就在右边插入,小于父节点就在左边插入。

//不插入重复的值
	bool insert(const T&val)
	{
		if (root_ == nullptr)
		{
			root_ = new Node(val);
			return true;
		}

		//搜索
		Node* cur = root_;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->data_ == val)
				return false;
			else if (cur->data_ > val)
				cur = cur->left;
			else
				cur = cur->right;
		}
		//插入
		cur = new Node(val);
		if (parent->data_ > val)
			parent->left = cur;
		else
			parent->right = cur;


		return true;
	}

中序遍历

二叉搜索树的特性:中序遍历必然是有序的

例如:下图的中序遍历为:2、3、4、6、7、9、13、15、17、18、20
在这里插入图片描述

代码实现:

void inorder()
	{
		_inorder(root_);
	}

	//搜索树中序遍历:有序
void _inorder(Node* root_)
	{
		if (root_)
		{
			_inorder(root_->left);
			cout << root_->data_ << " ";
			_inorder(root_->right);
		}
	}

销毁和析构

思路:如果该树不为空,开始递归遍历,先释放左节点,再释放右节点,最后释放根节点

void destroy(Node* root)
	{
		if (root)
		{
			destroy(root->left);
			destroy(root->right);
			cout << "destroy:" << root->data_ << " ";
			delete root;
		}
	}

	~BTree()
	{
		if (root_)
		{
			destroy(root_);
			root_ = nullptr;
		}
	}

拷贝和拷贝构造

思路:这里一定要使用深拷贝,不能只拷贝原树的根节点,要将结构和数据一起拷贝在新开的内存地址中,会带来指针二次释放的问题。

递归遍历,将原树所有的结点先创建出来,然后添加链接关系,返回新创建的树的根节点;在拷贝构造函数中调用即可

	//拷贝二叉搜索树的数据和结构
	Node* copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		//拷贝根
		Node* newnode = new Node(root->data_);
		//拷贝左子树
		newnode->left = copy(root->left);
		//拷贝右子树
		newnode->right = copy(root->right);

		return newnode;
	}

	//在成员初始化列表阶段调用copy()函数进行拷贝构造
	BTree(const BTree<T>& btree):root_(copy(btree.root_))
	{}

★删除

这里删除的情况比较复杂,可以分为以下几种情况:

1、要删除的是叶子结点
2、要删除的是非叶子结点:

2.1仅左子树为空
2.2仅右子树为空
2.3左右子树均不为空

思路:
1、查找:要删除一个结点,首先要保证该结点存在,所以先走搜索,找到该结点,如果找不到就返回false;
2、删除:在删除时要保留该结点的父结点,以保证删除后还可以建立新的链接,保证该二叉搜索树结构的完整性

		//查找
		Node* cur = root_;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->data_ == val)
				break;
			parent = cur;
			if (cur->data_ > val)
				cur = cur->left;
			else
				cur = cur->right;
		}
		//判断需要删除的结点是否找到
		if (cur == nullptr)
			return false;

1、要删除的是叶子结点
通过if语句选择叶子节点,如果要删除的是根结点,表示这个数只有这一个结点,删除即可;
如果不是根节点,就要在删除后将父节点的左子树或右子树置空(要删除的结点在父节点哪边就将哪边置空)

//1.删除的为叶子节点
		if (cur->left == nullptr&&cur->right == nullptr)
		{
			//判断是否为根结点
			if (cur == root_)
			{
				root_ = nullptr;
			}
			else
			{
				//判断需要删除的结点在父节点哪一边
				if (parent->left == cur)
					parent->left = nullptr;
				else
					parent->right = nullptr;
			}
			//删除结点
			delete cur;
		}

2、要删除的是非叶子结点:
删除非叶子结点的话,就要注意在删除之后建立其父节点与其子树的新链接。

2.1仅左子树为空
左子树为空,就要在删除后,将该节点的右子树与父节点建立新链接,该节点在父节点的哪边就将右子树链接在哪边

//2.1左子树为空
		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;
		}

2.2仅右子树为空
与仅左子树为空类似

//2.2右子树为空
		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;
		}

2.3左右子树均不为空

画图解释:
在这里插入图片描述

注意:这里不一定非要找左子树的最右结点,也可以是找右子树的最左结点

//2.3要删除的结点有左右孩子
		else
		{
			//1.假设找左子树的最右结点
			Node* leftRightMost = cur->left;
			parent = cur;
			while (leftRightMost->right)
			{
				parent = leftRightMost;
				leftRightMost = leftRightMost->right;
			}
			//2.交换
			swap(cur->data_, leftRightMost->data_);
			//3.删除最右结点
			if (parent->left == leftRightMost)
				parent->left = leftRightMost->left;
			else
				parent->right = leftRightMost->left;

			delete leftRightMost;
		}

完整代码:

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>

using namespace std;
template <class T>
struct BNode {
	T data_;
	typedef BNode<T> Node;
	Node* left;
	Node* right;

	BNode(const T&data) :data_(data), left(nullptr), right(nullptr)
	{}

};

template<class T>
class BTree {
public:
	typedef BNode<T> Node;
	BTree():root_(nullptr)
	{}

	//不插入重复的值
	bool insert(const T&val)
	{
		if (root_ == nullptr)
		{
			root_ = new Node(val);
			return true;
		}

		//搜索
		Node* cur = root_;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->data_ == val)
				return false;
			else if (cur->data_ > val)
				cur = cur->left;
			else
				cur = cur->right;
		}
		//插入
		cur = new Node(val);
		if (parent->data_ > val)
			parent->left = cur;
		else
			parent->right = cur;

		return true;
	}

	Node* find(const T& val)
	{
		Node* cur = root_;
		while (cur)
		{
			if (cur->data_ == val)
				return cur;
			else if (cur->data_ > val)
				cur = cur->left;
			else
				cur = cur->right;
		}
		return cur;
	}



	void inorder()
	{
		_inorder(root_);
	}

	//搜索树中序遍历:有序
	void _inorder(Node* root_)
	{
		if (root_)
		{
			_inorder(root_->left);
			cout << root_->data_ << " ";
			_inorder(root_->right);
		}
	}

	//拷贝二叉搜索树的数据和结构
	Node* copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		//拷贝根
		Node* newnode = new Node(root->data_);
		//拷贝左子树
		newnode->left = copy(root->left);
		//拷贝右子树
		newnode->right = copy(root->right);

		return newnode;
	}

	//删除
	bool erase(const T& val)
	{
		//查找
		Node* cur = root_;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->data_ == val)
				break;
			parent = cur;
			if (cur->data_ > val)
				cur = cur->left;
			else
				cur = cur->right;
		}
		//判断需要删除的结点是否找到
		if (cur == nullptr)
			return false;
		
		//删除
		//1.删除的为叶子节点
		if (cur->left == nullptr&&cur->right == nullptr)
		{
			//判断是否为根结点
			if (cur == root_)
			{
				root_ = nullptr;
			}
			else
			{
				//判断需要删除的结点在父节点哪一边
				if (parent->left == cur)
					parent->left = nullptr;
				else
					parent->right = nullptr;
			}
			//删除结点
			delete cur;
		}

		//2.删除的为非叶子结点:
		//2.1左子树为空
		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;
		}
		//2.2右子树为空
		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;
		}
		
		//2.3要删除的结点有左右孩子
		else
		{
			//1.找左子树的最右结点
			Node* leftRightMost = cur->left;
			parent = cur;
			while (leftRightMost->right)
			{
				parent = leftRightMost;
				leftRightMost = leftRightMost->right;
			}
			//2.交换
			swap(cur->data_, leftRightMost->data_);
			//3.删除最右结点
			if (parent->left == leftRightMost)
				parent->left = leftRightMost->left;
			else
				parent->right = leftRightMost->left;

			delete leftRightMost;
		}
		return true;
	}

	//在成员初始化列表阶段调用copy()函数进行拷贝构造
	BTree(const BTree<T>& btree):root_(copy(btree.root_))
	{}


	void destroy(Node* root)
	{
		if (root)
		{
			destroy(root->left);
			destroy(root->right);
			//cout << "destroy:" << root->data_ << " ";
			delete root;
		}
	}

	~BTree()
	{
		if (root_)
		{
			destroy(root_);
			root_ = nullptr;
		}
	}
private:
	Node* root_;
};



void test()
{
	BTree<int> b;

	b.insert(5);
	b.insert(3);
	b.insert(7);
	b.insert(1);
	b.insert(4);
	b.insert(6);
	b.insert(8);
	b.insert(0);
	b.insert(2);
	b.insert(9);
	b.insert(90);
	b.insert(19);
	b.insert(28);
	b.insert(32);
	b.insert(56);
	b.insert(34);
	b.insert(21);
	b.insert(33);
	b.erase(5);
	b.inorder();
}
int main()
{
	test();
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值