【数据结构取经之路】二叉搜索树的实现

目录

前言

二叉搜索树

概念

性质

二叉搜索树的实现

结点的定义

插入

查找

删除

operator= 

拷贝构造 

析构函数 

二叉搜索树完整代码


前言

首先,二叉搜索树是一种数据结构,了解二叉搜素树有助于理解map和set的特性。

二叉搜索树

概念

二叉搜索树又称二叉排序树,它也可能是一棵空树,走中序遍历得到的结果是有序的。

性质

二叉搜索树如果不为空,它应满足一下性质:

每一个节点都有一个键值,而且每一个键值唯一标识一个节点(即二叉搜索树所有节点中没有重复的值)

● 假设根节点的值为X,那么它的左子树上的所有节点的值都小于X,右子树上的所有的结点的值都大于X

● 根的左右子树都是二叉搜索树(递归性质)

二叉搜索树的实现

二叉搜索树的基本操作有插入、查找、删除,其中插入和查找好说,最麻烦的是它的删除操作。

结点的定义

template <class K>
struct BSTNode
{
	BSTNode<K>* _left;
	BSTNode<K>* _right;
	K _key;

	BSTNode(const K& key) :_left(nullptr), _right(nullptr), _key(key) {}
};

插入

例如要插入0,0比8小,往左走,遇到3,0比3小,往左走,遇到1,0比1小,往左走,遇到空, 此时可以进行插入了。 总结起来就是:遇到空就可以插入。

直接上代码,后边再总结细节~

bool Insert(const K& key)
{
    //如果根节点为空,第一个插入的结点就充当根节点
	if (_root == nullptr)
	{
		Node* newNode = new Node(key);
		_root = newNode;
		return true;
	}

	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
		{
			return false;//cur->_key == key,该值插入进入会破坏键值的唯一性,故插入失败
		}
	}

    //不知道上述代码是往左走到空还是往右走到空,
    //所以不确定是插入左边还是右边,需要判断
	Node* newNode = new Node(key);
	if (parent->_key > key)
		parent->_left = newNode;
	else
		parent->_right = newNode;

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

删除

上面的都好说,删除才是重头戏。

首先,查找所删除的结点是否在二叉搜索树中,如果不存在则返回false,如果存在就有以下几种情况。

1)要删除的节点为叶子结点

2)要删除的结点只有左孩子或只有右孩子

3)要删除的结点左右孩子均存在

针对第一种情况,直接删除就好,没什么好说的。

针对第二种情况,请看下面分析。

例如要删除的结点为14,根据上图,我们只需要将结点10的指向调整一下,让它指向13就可以了。当然,我们还要记录被删除结点的父节点才可以正确调整指向。仔细想想,就会返现,情况1和情况2是可以一起处理的。

第三种情况:

 

例如要删除结点3,根据上图,如果我们直接删除肯定是不对的,得用替换法来删除。用谁来替换是有讲究的,我们可以选择使用节点3的左子树的最大值或者是节点3右子树的最小值,简记:左的最大,右的最小。处理成下面这样:

 处理成这样后,删除掉rightMin节点即可。当然,rightMin节点也可能有右孩子(它不可能有左孩子,因为我们找的是右子树的最小值,必然是右子树中的最左节点),我们在删除的时候考虑进去就好了,这个并不难解决。下面,我们按照上面的逻辑来把代码写出来~


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 (parent->_left == cur)
					parent->_left = cur->_right;
				else
					parent->_right = cur->_right;

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

				delete cur;
				return true;
			}
			else
			{
				Node* rightMin = cur->_right;
				Node* rightMinParent = nullptr;

				while (rightMin->_left)
				{
					rightMinParent = rightMin;
					rightMin = rightMin->_left;
				}

				//可以交换,也可以直接赋值
				cur->_key = rightMin->_key;

				rigntMinParent->_left = rightMin->_right;

				delete rightMin;
				return true;
			}
		}
	}
	return false;
}

我试着用上述代码把所有插入的结点都删掉,但是程序崩了~有以下几个原因。

在这种情况下,分析删除值为8的节点。该节点的parent为空, 程序执行下面的代码:

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

	delete cur;
	return true;
}

这时候就是对空指针的解引用了,程序当然会崩掉~所以需要对parent为空的情况单独处理。把上述代码改成这样:

if (cur->_left == nullptr)
{
	  //左为空的情况习下,parent为可能空,即单支的树,所以需要判断
	  if (parent == nullptr)
	  {
		_root = cur->_right;
	  }
	  else
	  {
		if (parent->_left == cur)
			parent->_left = cur->_right;
		else
			parent->_right = cur->_right;
	  }

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

		delete cur;
		return true;
}

上面分析的是只有左支的情况,只有右支的情况同样如此,需要做单独处理。

解决了只有单支的情况后,我又试着删除所有节点,程序还是崩了~原因如下:

3为要删除的结点。此时,按照代码逻辑,rightMin为6,rightMinParent为空, 后面执行这句代码:

rigntMinParent->_left = rightMin->_right;

 所以会崩掉~但此时我们可以看到,rightMin(节点6)的父亲结点为3,并不是空,所以rightMinParent初始化为空并不合理,应该初始化为cur。

完善到这步,也还有一个小问题,按照上面修改之后的逻辑,rightMin为6,rightMinParent为3,执行这句代码:

rigntMinParent->_left = rightMin->_right;

 我们可以很容易发现这是错的,应该是rightMinParent->_right = rightMin->_right; 所以我们还需要加判断,具体如下:

if (rightMinParent->_left == rightMin)
	rightMinParent->_left = rightMin->_right;
else
	rightMinParent->_right = rightMin->_right;

完整的修改后的代码如下:


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)
			{
				//左为空的情况习下,parent为可能空,即单支的树,所以需要判断
				if (parent == nullptr)
				{
					_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 (parent == nullptr)
				{
					_root = cur->_left;
				}
				else
				{
					if (parent->_left == cur)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;
				}

				delete cur;
				return true;
			}
			else
			{
				Node* rightMin = cur->_right;
				Node* rightMinParent = cur;

				while (rightMin->_left)
				{
					rightMinParent = rightMin;
					rightMin = rightMin->_left;
				}

				//可以交换,也可以直接赋值
				cur->_key = rightMin->_key;

				//不一定是rigntMinParent的左边
				if (rightMinParent->_left == rightMin)
					rightMinParent->_left = rightMin->_right;
				else
					rightMinParent->_right = rightMin->_right;

				delete rightMin;
				return true;
			}
		}
	}
	return false;
}

operator= 

赋值重载,只需交换两棵树的根节点即可。

BSTree<K>& operator=(BSTree& t)
{
	swap(_root, t._root);
	return *this;
}

拷贝构造 

拷贝构造需要进行深拷贝,可以递归创建节点。

BSTree(const BSTree<K>& t)
{
	_root = Copy(t._root);
}

Node* Copy(Node* root)
{
	if (root == nullptr)
		return nullptr;
	Node* newRoot = new Node(root->_key);
	newRoot->_left = Copy(root->_left);
	newRoot->_right = Copy(root->_right);

	return newRoot;
}

析构函数 

释放节点时,走的是后序,递归释放即可。

~BSTree()
{
	Destroy(_root);
	_root = nullptr;
}

void Destroy(Node* root)
{
	if (root == nullptr)
		return;
	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
}

二叉搜索树完整代码

//BSTree.h
#pragma once
#include <iostream>

using namespace std;

template <class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	BSTreeNode(const K& key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
};

template <class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	BSTree() = default;

	BSTree(const BSTree<K>& t)
	{
		_root = Copy(t._root);
	}

	BSTree<K>& operator=(BSTree& t)
	{
		swap(_root, t._root);
		return *this;
	}

	~BSTree()
	{
		Destroy(_root);
		_root = nullptr;
	}

	bool Insert(const K& value)
	{
		if (_root == nullptr)
		{
			_root = new Node(value);
			return true;
		}

		Node* newNode = new Node(value);
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key < value)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > value)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		if (parent->_key > value)
			parent->_left = newNode;
		else
			parent->_right = newNode;
		return true;
	}

	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}

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

		return false;
	}

	bool Erase(const K& value)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key < value)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > value)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				if (cur->_left == nullptr)
				{
					if (parent == nullptr)
						_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 (parent == nullptr)
						_root = cur->_left;
					else
					{
						if (parent->_left == cur)
							parent->_left = cur->_left;
						else
							parent->_right = cur->_left;
					}

					delete cur;
					return true;
				}
				else
				{
					Node* rightMin = cur->_right;
					Node* rightMinParent = cur;
					while (rightMin->_left)
					{
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}

					cur->_key = rightMin->_key;

					if (rightMinParent->_left == rightMin)
						rightMinParent->_left = rightMin->_right;
					else
						rightMinParent->_right = rightMin->_right;

					delete rightMin;
					return true;
				}
			}
		}
		return false;
	}

private:
	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
	}

	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		cout << root->_key << " ";
		_Inorder(root->_right);
	}

	void swap(Node*& root1, Node*& root2)
	{
		Node* tmp = root1;
		root1 = root2;
		root2 = tmp;
	}

	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* newRoot = new Node(root->_key);
		newRoot->_left = Copy(root->_left);
		newRoot->_right = Copy(root->_right);

		return newRoot;
	}

private:
	Node* _root = nullptr;
};


//test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "BSTree.h"

void Test()
{
	BSTree<int> bst;
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	for (auto val : a)
	{
		bst.Insert(val);
	}

	bst.Inorder();

	BSTree<int> bst2;
	int arr[] = { 8,3,1,10,6,4 };
	for (auto val : arr)
	{
		bst2.Insert(val);
	}

	bst2.Inorder();

	bst2 = bst;

	bst2.Inorder();

	BSTree<int> bst3 = bst2;
	bst3.Inorder();

	cout << bst3.Find(100) << endl;

	for (auto val : a)
	{
		bst3.Erase(val);
		bst3.Inorder();
	}
}

int main()
{
	Test();
	return 0;
}

完~

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值