二叉搜索树的实现(C++)

二叉搜索树又称为二叉排序树,它可能是一棵空树,亦或是一棵具有以下性质的二叉树:

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

二叉搜索树的操作

1.二叉搜索树的查找

实现思路:

若结点不为空:

如果结点的值key == 查找的key,返回该结点;

如果结点的值key > 查找的key,在其左子树去查找;

如果结点的值key < 查找的key,在其右子树去查找。

如果遍历了整棵树都没找到就返回nullptr。

2.二叉搜索树的插入

实现思路:

如果树为空,直接插入,然后返回true,

如果树不为空,按二叉搜索树性质查找要插入的位置,然后插入新结点。

3.二叉搜索树的删除(重、难点)

1)如果树为空,删除失败,返回false;

2)树不为空,查找要删除元素的位置,如果此元素不在树中,则返回false;否则要删除的结点分为下面三种情况:

A.要删除的结点只有左孩子

    a.如果该结点是树的根,更新根结点(_root = _root->_left)

    b.否则该结点是子树,如果该结点在parent->_left,则parent->_left = cur->_left;如果该结点在parent->_right,则parent->_right = cur->_left

B.要删除的结点只有右孩子

    a.如果该结点是树的根,更新根结点(_root = _root->_right)

    b.否则该结点是子树,如果该结点在parent->_right,则parent->_right = cur->right;如果该结点在parent->_right,则parent->_left = cur->_right

C.要删除的结点左右孩子都存在

此情况直接删除不好删,可以在其子树中找一个替代结点,比如找其左子树中的最大结点,即左子树中最右侧的结点,或者找其右子树中最小的结点,即右子树中最小的结点。替换结点找到后,将替代结点中的值交给待删除结点,转换成删除替代结点。具体代码见下面。

BinarySearchTree.h
#pragma once 
//二叉搜索数的模拟实现
#include<iostream>
using std::cout;
using std::endl;

template<class T>
struct BSTNode
{
	BSTNode(const T& key = T())
    	: _left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}
	
	BSTNode<T>* _left;
	BSTNode<T>* _right;
	T _key;
};


template<class T>
class BSTree
{
	typedef BSTNode<T> Node;
public:
	BSTree()
		: _root(nullptr)
	{}
	
	~BSTree()
	{
		Destroy(_root);
	}
	//拷贝构造
	BSTree(const BSTree<T>& tree)
	{
		_root = Copy(tree._root);
	}
	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* tmp = new Node;
		tmp->_key = root->_key;
		tmp->_left = Copy(root->_left);

		tmp->_right = Copy(root->_right);
		return tmp;
	}
	BSTree& operator=(const BSTree& tree)
	{
		if (this != &tree)
		{
			Destroy(this->_root);
			this->_root = Copy(tree._root);
		}
		return *this;
	}
	bool Insert(const T& key)
	{
		//如果树为空,直接创建一个新结点进行插入
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		//查找要插入的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				cur = cur->_right;
			}
			else
			{
				return false;//元素在树中已经存在,不用重新插入
			}
		}
		//插入元素
		cur = new Node(key);
		if (key < parent->_key)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}
	Node* Find(const T& key)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key == key)
			{
				return cur;
			}
			else if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else
			{
				cur = cur->_right;
			}
		}
		return nullptr;
	}
	bool Erase(const T& key)
	{
		//如果树为空,删除失败
		if (_root == nullptr)
		{
			return false;
		}
		//查找key在树中的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key == cur->_key)
			{
				break;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				parent = cur;
				cur = cur->_right;
			}
		}
		
		//遍历了整棵树,如果key不在树中,无法删除
		if (cur == nullptr)
		{
			return false;
		}
		//如果在树中找到了key,进行删除结点,要分三种情况:
		//1.该结点只有右孩子
		//2.该结点只有左孩子
		//3.该结点左右子树都存在
		if (cur->_left == nullptr)
		{
			//情况1:
			//只有根结点和根的右孩子,此时要删除的结点正好是树的根
			if (cur == _root)
			{
				_root = cur->_right;
			}
			else
			{
				//或该结点不是树的根
				if (cur == parent->_left)
				{
					parent->_left = cur->_right;
				}
				else
				{
					parent->_right = cur->_right;
				}
			}
		}
		//情况2:
		else if (cur->_right == nullptr)
		{
			if (cur == _root)
			{
				_root = cur->_left;
			}
			else
			{
				if (cur == parent->_right)
				{
					parent->_right = cur->_left;
				}
				else
				{
					parent->_left = cur->_left;
				}
			}

		}
		else
		{
			//当前结点左右孩子都存在,直接删除不好删,可以在其子树中找一个替代结点,比如找其左子树中的最大结点,即左子树中最右侧的结点,或者找其右子树中最小的结点,即右子树中最小的结点。替换结点找到后,将替代结点中的值交给待删除结点,转换成删除替代结点。
				if (cur->_left != nullptr || cur->_right != nullptr)
				{
					//找右子树中最小的结点替换待删除的结点
					Node* repalce = cur->_right;
					parent = cur;
					while (repalce->_left)
					{
						parent = repalce;
						repalce = repalce->_left;
					}
					cur->_key = repalce->_key;
					if (repalce == parent->_left)
					{
						parent->_left = repalce->_right;
					}
					else
					{
						parent->_right = repalce->_right;
					}
					delete repalce;
					repalce = nullptr;
				}
				return true;
		}
		return false;
	}
	void Inorder()
	{
		_Inorder(_root);
	}

private:
	void _Inorder(Node* root)
	{
		if (root)
		{
			_Inorder(root->_left);
			cout << root->_key << " ";
			_Inorder(root->_right);
		}
	}
	void Destroy(Node*& root)
	{
		if (root)
		{
			Destroy(root->_left);
			Destroy(root->_right);
			root = nullptr;
		}
	}
private:
	Node* _root;
};
void TestBSTree()
{
	BSTree<int> BST1;
	BST1.Insert(5);
	BST1.Insert(3);
	BST1.Insert(4);
	BST1.Insert(1);
	BST1.Insert(7);
	BST1.Insert(8);
	BST1.Insert(2);
	BST1.Insert(6);
	BST1.Insert(0);
	BST1.Insert(9);
	BST1.Inorder();
	cout << endl;
	BST1.Find(10);
	BST1.Erase(7);
	BST1.Inorder();
	cout << endl;
	BSTree<int> copy(BST1);
	BSTree<int> BST2;
	BST2 = BST1;
	copy.Inorder();
	cout << endl;
	BST2.Inorder();
}
test.cpp
#include"BinarySearchTree.h"
int main()
{
	TestBSTree();
	return 0;
}

二叉搜索树的性能分析

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

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

在最优的情况下,二叉搜索树为完全二叉树,其平均比较次数为logN,在最差情况下,二叉搜索树退化为单支树,其平均比较次数为N。

如果退化成单支树,二叉搜索树的性能就失去了,那能否改进,不论按照什么次序插入关键码,都可以使二叉搜索树的性能最佳呢?,这里就引入了AVL树。AVL树的详细描述请看下一节博客。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值