0
点赞
收藏
分享

微信扫一扫

【C++】二叉搜索树BST

彩虹_bd07 2023-05-16 阅读 32

文章目录

二叉搜索树

是什么:

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

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

例子:

image-20220412195947687

对二叉搜索树的操作:

节点定义

//K -> key
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>
struct BSTree
{
    typedef BSTreeNode<K> Node; //将二叉树节点的类型重命名为Node,方便使用
    BSTree()
        :_root(nullptr)
    {}
    private:
    Node* _root;
};

查找

查找一个值,可以返回bool值 ,也可也返回节点的指针

非递归查找

根据:左节点的值<根节点的值<右节点的值 ,对应去左树和右树中寻找

image-20220412205844865

//查找返回bool值
bool Find(const K& key)
{
    //空树不用查找
    if(_root == nullptr)
    {	
        return false;
    }
    //左 < 根 < 右
    Node* cur = _root;//从根节点位置开始往下找
    //结束条件:cur走到空 || 提前找到返回
    while (cur)
    {
        if (cur->_key < key)
        {
            //去右树找
            cur = cur->_right;
        }
        else if (cur->_key > key)
        {
            //去左树找
            cur = cur->_left;
        }
        else
        {
            //找到了
            return true;
        }
    }
    //找不到
    return false;
}

返回节点指针

Node* Find(const K& key)
{
    //空树不用查找
    if(_root == nullptr)
    {
        return nullptr;
    }
    //左 < 根 < 右
    Node* cur = _root;//从根节点位置开始往下找 
    //结束条件:cur走到空 || 提前找到返回
    while (cur)
    {
        if (cur->_key < key)
        {
            //去右树找
            cur = cur->_right;
        }
        else if (cur->_key > key)
        {
            //去左树找
            cur = cur->_left;
        }
        else
        {
            //找到了
            return cur;
        }
    }
    //找不到
    return nullptr;
}

递归查找

根据二叉搜索树的性质进行查找即可

//递归查找,public函数:给外界提供一个接口查找谁
Node* FindR(const K& key)
{
    return _FindR(_root, key);
}

private:
Node* _FindR(Node* root, const K& key)
{
    if (root == nullptr)
    {
        return root;//空树直接返回空
    }
    //如果要找的值比根节点的值大 -> 去右树找 
    //如果要找的值比根节点的值小 -> 去左树找 
    if (root->_key < key)
    {
        _FindR(root->_right, key);//递归去右树找
    }
    else if(root->_key > key)
    {
        _FindR(root->_left, key);//递归去左树找
    }
    else
    {
        return root;//找到了
    }
}

删除

非递归删除

case1:删除的是没有孩子的节点

image-20220415221807942

image-20220415222318842

case2:删除的是只有一个孩子的节点

image-20220715153341590

image-20220415221930215


本质是case1和case2可以划分为同一类型的情况 没有孩子可以认为只有一个孩子的情况


case3:删除的是有两个孩子的节点

替换删除法-和左树的最右节点||右树的最左节点

这里选择和右树的最左节点交换:(只需值替换即可)

image-20220415221942558


注意点:

image-20220715154402914

bool Erase(const K& key)
{
    //空树
    if (_root == nullptr)
    {
        return false;
    }
    //从根位置开始遍历找到删除位置,要保存其父亲的位置
    Node* cur = _root;
    Node* parent = nullptr;//初始化为空,因为根节点没有父亲
    while (cur)
    {
        if (cur->_key > key)
        {
            //去左树找
            parent = cur;
            cur = cur->_left;
        }
        else if (cur->_key < key)
        {
            //去右树找
            parent = cur;
            cur = cur->_right;
        }
        else
        {
            //找到了,开始删除,此时cur就是要删除的节点
            //case1:只有一个孩子(没有孩子也会划分进这种情况)
            //cur没有左孩子
            if (cur->_left == nullptr)
            {
                //要防止删除的是头节点 且 头节点只有右孩子的情况
                //头节点的父亲为空
                if (parent == nullptr)
                {
                    //说明此时cur为头节点,且左树为空,则把整棵树的头换成cur的右树节点
                    _root = cur->_right;
                }
                else
                {
                    //cur不是头节点,且左树为空
                    //判断此时cur是其父节点的左孩子还是右孩子
                    //让父节点的左指针 / 右孩子链接cur的右树(因为左树为空)
                    if (parent->_left == cur)
                    {
                        parent->_left = cur->_right;
                    }
                    else
                    {
                        parent->_right = cur->_right;
                    }
                }
                delete cur;//删除这个节点
            }
            //cur没有右孩子
            else if (cur->_right == nullptr)
            {
                //要防止删除的是头节点 且 头节点只有左孩子的情况
                //头节点的父亲为空
                if (parent == nullptr)
                {
                    //说明此时cur为头节点,且右树为空,则把整棵树的头换成cur的左树节点
                    _root = cur->_left;
                }
                else
                {
                    //cur不是头节点,且右树为空
                    //判断此时cur是其父节点的左孩子还是右孩子
                    //让父节点的左指针 / 右孩子链接cur的左树(因为右树为空)
                    if (parent->_left == cur)
                    {
                        parent->_left = cur->_left;
                    }
                    else
                    {
                        parent->_right = cur->_left;
                    }
                }
                delete cur;//删除这个节点
            }
            else
            {
                //cur的左右孩子双全 ->保证了cur是有左右孩子的
                //替换删除法:用删除节点的右树的最左节点 || 左树的最右节点替换删除节点
                //这里我们选择删除节点和右树的最左节点替换 (把值覆盖即可,然后删除右树的最左节点)
                //即使删的是根节点也是这么左,拿其左树的最右节点值交换,然后删除左树最右节点
                Node* minParent = cur;//不能置为空,防止cur的右孩子就是右树的最左节点,此时下面对minParent解引用就会崩溃(对空指针解引用)
                Node* min = cur->_right;//去cur的右树找最左节点
                while (min->_left)
                {
                    minParent = min;//用于记录cur右树最左节点的父亲
                    min = min->_left;
                }
                //此时min就是右树的最左节点位置
                //用该值覆盖要删除节点的值
                cur->_key = min->_key;

                //删除min这个节点,要用min的父节点链接min的孩子
                //判断min是其父节点的左孩子还是右孩子,然后让父节点对应的指针链接min的右孩子
                //因为min是cur右树的最左节点,所以min是没有左孩子的,让min的父亲链接min的右孩子
                if (minParent->_left == min)
                {
                    minParent->_left = min->_right;
                }
                else
                {
                    minParent->_right = min->_right;
                }
                delete min;//然后删除min这个节点
            }
            //只有找到了,才执行删除
            return true;//删除成功
        }
    }
    //没有找到删除的节点
    return false;
}

class Solution {
public:
    TreeNode* deleteNode(TreeNode* root, int key) {
        if(root == nullptr)
        {
            return root;
        }
        //从根节点开始找删除节点位置
        TreeNode* cur = root;
        TreeNode* parent = nullptr;
        while(cur)
        {
            if(cur->val >key)
            {
                parent = cur;
                cur = cur->left;//去左树找
            }
            else if(cur->val < key)
            {
                parent = cur;
                cur = cur->right;//去右树找
            }
            else
            {
                //找到了删除节点的位置
                //case1:只有一个孩子
                if(cur->left == nullptr)
                {
                    //说明要删的是节点cur是根节点
                    if(parent == nullptr)
                    {
                        root = cur->right;//换头
                        delete cur;
                        return root;
                    }
                    else
                    {
                        //判断删除节点是其父亲的左孩子还是右孩子
                        if(parent->left == cur)
                        {
                            parent->left = cur->right;
                        }
                        else if(parent->right == cur)
                        {
                            parent->right = cur->right;
                        }
                        delete cur;
                        return root;
                    }
                }
                //右孩子为空
                else if(cur->right == nullptr)
                {
                    //说明要删的是节点cur是根节点
                    if(parent == nullptr)
                    {
                        root = cur->left;//换头
                        delete cur;
                        return root;
                    }
                    else
                    {
                        //判断删除节点是其父亲的左孩子还是右孩子
                        if(parent->left == cur)
                        {
                            parent->left = cur->left;
                        }
                        else if(parent->right == cur)
                        {
                            parent->right = cur->left;
                        }
                        delete cur;
                        return root;
                    }
                }
                else    //左右孩子双全
                {
                    //替换删除法,和右树的最左孩子交换
                    TreeNode* minParent = cur;
                    TreeNode* min = cur->right;//去cur的右树找最左孩子
                    while(min->left)
                    {
                        minParent = min;
                        min = min->left;
                    }
                    cur->val = min->val;
                    //minParent链接min的右树 (因为min是最左孩子,所以没有左孩子)
                    if(minParent->left == min)
                    {
                        minParent->left = min->right;
                    }
                    else
                    {
                        minParent->right = min->right;
                    }
                    delete min;//删除左树最右节点
                    return root;
                }
            }
        }
        return root;
    }
};

递归删除

此处的精华也是传引用

注意:此时删除的是头节点也没有问题,因为root就是_root的别名,直接root = root->right,本质就是修改了根节点的位置了, 然后释放原来的头节点

bool EraseR(const K& key)
{
    return _EraseR(_root, key);
}
private:
//传根节点位置的引用
bool _EraseR(Node*& root, const K& key)
{
    if (root == nullptr)
    {
        return false;//空树没有该节点,返回false
    }

    //先找到删除节点的位置
    //如果要删除的值比根节点的值大 -> 到左树查找
    //如果要删除的值比根节点的值小 -> 到左树查找
    if (root->_key > key)
    {
        _EraseR(root->_left, key);
    }
    else if (root->_key < key)
    {
        _EraseR(root->_right, key);
    }
    else //找到了,开始删除
    {
        //因为此时是递归,并没有删除位置的父节点的指针
        Node* del = root;// 记录要删除的节点

        //分情况讨论:要删除的节点有几个孩子,没有孩子也可划分为只有一个孩子的情况
        if (root->_left == nullptr)	//没有左孩子
        {
            //因为root传的是引用,它不仅是删除节点的地址,也是其父节点左指针/右指针的别名
            root = root->_right;//所以我们直接更改root的指向,就是更改了它父节点左指针/右指针的指向
        }
        else if (root->_right == nullptr) //没有右孩子
        {
             //因为root传的是引用,它不仅是删除节点的地址,也是其父节点左树/右树的别名
            root = root->_left;//所以我们直接更改root的指向,就是更改了它父节点左指针/右指针的指向
        }
        else	//左右孩子都有
        {

            //替换删除法:和右树的最左节点交换值
            Node* min = root->_right;//去右树找最左节点
            while (min->_left)
            {
                min = min->_left;
            }
            swap(min->_key, root->_key);//右树最左节点的值和删除节点的值进行替换

            //转化成去root的右树删除最左节点的值(上面已经和root替换了值了),所以删的值是key
            return _EraseR(root->_right, key);//注意:这里转化为去右树删除!注意:在这里return
        }

        //释放删除节点
        delete del;
        return true;
    }
}

插入

Leetcode701

非递归插入

image-20220412205906577

情况1:空树:直接使用val的值新建一个节点,该节点成为新的头

情况2:不是空树:查找到插入节点的位置,当cur走到空位置就可以插入,要保存其父节点用于链接

image-20220415211938678

bool Insert(const K& key)
{
    Node* cur = nullptr;
    //case1:空树
    if (_root == nullptr)
    {
        cur = new Node(key);//新建一个节点插入
        _root = cur;//新节点成为头
        return true;
    } 
    //case2:已经存在节点
    //找到插入位置,要保存父亲节点方便链接
    Node* parent = nullptr;//用于标志父亲节点的位置
    cur = _root;//从根位置开始找插入位置

    //根据插入的值的大小往左树和右树走,走到空就是插入的位置
    while (cur)
    {
        if (cur->_key > key)
        {
            parent = cur;//保存父节点位置
            cur = cur->_left;//去左树
        }
        else if (cur->_key < key)
        {
            parent = cur;//保存父节点位置
            cur = cur->_right;//去右树
        }
        else
        {
            //已经存在val这个数据,所以不插入,防止数据冗余
            return false;
        }
    }
    //cur走到空,此时应该插入了,用cur标志新节点,直接用于插入
    cur = new Node(key);
    //插入在父节点的左树还是右树需要判断
    if (parent->_key > key)
    {
        parent->_left = cur;//插入在左树
    }
    else
    {
        parent->_right = cur;//插入在右树
    }
    return true;
}

递归插入

精华:root节点传引用

image-20220416105902221

此时是空树也可以完美解决,root就是_root的别名,直接为_root开辟一个节点

//递归插入
bool InsertR(const K& key)
{
    return _InsertR(_root, key);
}

private:
//注意:这里传的是引用
bool _InsertR(Node*& root, const K& key)
{
    //注意:此时root传的是引用,当root走到空时,让root自成一个节点
    if (root == nullptr)
    {
        //root也是其上级父亲的左孩子指针/右孩子指针的别名
        root = new Node(key);
        return true;
    }

    //判断插入到哪里
    //如果要插入的值比根节点的值大 -> 插入到右树
    //如果要插入的值比根节点的值小 -> 插入到左树
    if (root->_key > key)
    {
        _InsertR(root->_left, key);//递归到左树插入
    }
    else if (root->_key < key)
    {
        _InsertR(root->_right, key);// 递归到右树插入
    }
    else
    {
        //root->_key == key 不允许插入
        return false;
    }
}

后序遍历验证是否正确

要调用函数 -> 需要传根节点,而成员变量一般是私有的,我们拿不到

方法1:所以我们可以提供一个成员函数拿到根节点

//将头节点返回
Node* GetNode()
{
    return _root;
}
//子函数
void _Inorder(Node* root)
{
    if (root == nullptr)
    {
        return;
    }
    _Inorder(root->_left);
    cout << root->_key <<" ";
    _Inorder(root->_right);
}
//外部调用:
int main()
{
	int arr[] = { 326,3,532,6,5,2,32,6,547,6 };
	BSTree<int> bst;
	for (auto x : arr)
	{
		bst.Insert(x);
	}
	bst._Inorder(bst.GetNode());
	return 0;
}

方法2:使用一个函数来套子函数(递归函数)

//中序遍历的函数
void Inorder()
{
    _Inorder(_root);
} 
//子函数
void _Inorder(Node* root)
{
    if (root == nullptr)
    {
        return;
    }
    _Inorder(root->_left);
    cout << root->_key <<" ";
    _Inorder(root->_right);
}

搜索二叉树的排序 本质是排序+去重,因为不可以数据冗余(重复的值会被去掉),即重复的值会插入失败


BSTree.h

#pragma once
#include<iostream>
using namespace std;

//K -> key
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>
struct BSTree
{
	typedef BSTreeNode<K> Node; //将二叉树节点的类型重命名为Node,方便使用
	BSTree()
		:_root(nullptr)
	{}
	bool find(const K& key)
	{
		//空树不用找
		if (_root == nullptr)
		{
			return false;
		}
		Node* cur = _root;//从根位置开始找
		//结束条件:cur走到空 || 提前返回
		while (cur)
		{
			if (cur->_key > key)
			{
				//去左树找
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				//去右树找
				cur = cur->_right;
			}
			else
			{
				return true;
			}
		}
		//说明没有找到
		return false;
	}
	bool Insert(const K& key)
	{
		//空树:
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		Node* cur = _root;//从根节点位置开始
		Node* parent = nullptr;
		//当cur走到空可以插入
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		//走到空
		cur = new Node(key);
		//判断插入在父亲的哪里
		if (parent->_key > key)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}
	bool Erase(const K& key)
	{
		//空树
		if (_root == nullptr)
		{
			return false;
		}
		//从根位置开始遍历找到删除位置,要保存其父亲的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key > key)
			{
				//去左树找
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				//去右树找
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				// 找到了,开始删除,此时cur就是要删除的节点
				//case1:只有一个孩子(没有孩子也划分进这种情况)
				//cur没有左孩子
				if (cur->_left == nullptr)
				{
					//要防止删除的是头节点 且 头节点只有右孩子的情况
					//头节点的父亲为空
					if (parent == nullptr)
					{
						//说明此时cur为头节点,且左树为空,则把整棵树的头换成cur的右树节点
						_root = cur->_right;
					}
					else
					{
						//cur不是头节点,且左树为空
						//判断此时cur是其父节点的左孩子还是右孩子
						//让父节点的左指针 / 右孩子链接cur的右树(因为左树为空)
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;//删除这个节点
				}
				//cur没有右孩子
				else if (cur->_right == nullptr)
				{
					//要防止删除的是头节点 且 头节点只有左孩子的情况
					//头节点的父亲为空
					if (parent == nullptr)
					{
						//说明此时cur为头节点,且右树为空,则把整棵树的头换成cur的左树节点
						_root = cur->_left;
					}
					else
					{
						//cur不是头节点,且右树为空
						//判断此时cur是其父节点的左孩子还是右孩子
						//让父节点的左指针 / 右孩子链接cur的左树(因为右树为空)
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;//删除这个节点
				}
				else
				{
					//cur的左右孩子双全 ->保证了cur是有左右孩子的
					//替换删除法:用删除节点的右树的最左节点 || 左树的最右节点替换删除节点
					//这里我们选择右树的最左节点替换
					Node* minParent = cur;//不能置为空,防止cur的右孩子就是右树的最左节点,此时下面父亲链接就会出错
					Node* min = cur->_right;//找cur的右树,找最左节点
					while (min->_left)
					{
						minParent = min;//用于记录cur右树最左节点的父亲
						min = min->_left;
					}
					//此时min就是右树的最左节点位置
					//用该值覆盖要删除节点的值
					cur->_key = min->_key;

					//删除min这个节点,要用min的父节点链接min的孩子
					//判断min是其父节点的左孩子还是右孩子,然后让父节点对应的指针链接min的右孩子
					//因为min是cur右树的最左节点,所以min是没有右孩子的,让min的父亲链接min的右孩子
					if (minParent->_left == min)
					{
						minParent->_left = min->_right;
					}
					else
					{
						minParent->_right = min->_right;
					}
					delete min;//然后删除min这个节点
				}
				//只有找到了,才执行删除
				return true;//删除成功
			}
		}
		//没有找到删除的节点
		return false;
	}
	void inOrder()
	{
		return _inOrder(_root);
	}
	//递归插入
	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}
	//递归查找
	Node* FindR(const K& key)
	{
		return _FindR(_root, key);
	}
	//递归删除
	Node* EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}
private:
	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			return false;//空树没有该节点,返回false
		}

		//先找到删除节点的位置
		//如果要删除的值比根节点的值大 -> 到左树查找
		//如果要删除的值比根节点的值小 -> 到左树查找
		if (root->_key > key)
		{
			_EraseR(root->_left, key);
		}
		else if (root->_key < key)
		{
			_EraseR(root->_right, key);
		}
		else //找到了,开始删除
		{
			//因为此时是递归,并没有父节点的指针
			Node* del = root;// 记录要删除的节点

			//分情况讨论:要删除的节点有几个孩子,没有孩子也可划分为只有一个孩子的情况
			if (root->_left == nullptr)	//没有左孩子
			{
				root = root->_right;//因为我们的root传的是引用,所以我们直接更改root的指向
			}
			else if (root->_right == nullptr) //没有右孩子
			{
				root = root->_left;//因为我们的root传的是引用,所以我们直接更改root的指向
			}
			else	//左右孩子都有
			{

				//替换删除法:和右树的最左节点交换
				Node* min = root->_right;//去左树找最右节点
				while (min->_left)
				{
					min = min->_left;
				}
				swap(min->_key, root->_key);//右树最左节点的值和删除节点的值进行替换

				//转化成去右树删除最左节点的值(上面已经替换了值了),也就是key
				return _EraseR(root->_right, key);
			}

			//释放删除节点
			delete del;
			return true;
		}
	}
	bool _InsertR(Node*& root, const K& key)
	{
		//注意:此时root传的是引用,当root走到空时,就可以开辟节点进行插入了
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}

		//判断插入到哪里
		//如果要插入的值比根节点的值大 -> 插入到右树
		//如果要插入的值比根节点的值小 -> 插入到左树
		if (root->_key > key)
		{
			_InsertR(root->_left, key);//递归到左树插入
		}
		else if (root->_key < key)
		{
			_InsertR(root->_right, key);// 递归到右树插入
		}
		else
		{
			//root->_key == key 不允许插入
			return false;
		}
	}
	Node* _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
		{
			return root;//空树直接返回空
		}
		//如果要找的值比根节点的值大 -> 去右树找 
		//如果要找的值比根节点的值小 -> 去左树找 
		if (root->_key < key)
		{
			_FindR(root->_right, key);//递归去右树找
		}
		else if(root->_key > key)
		{
			_FindR(root->_left, key);//递归去左树找
		}
		else
		{
			return root;//找到了
		}
	}
	void _inOrder(Node* root)
	{
		if (root == nullptr) return;
		_inOrder(root->_left);
		cout << root->_key << " ";
		_inOrder(root->_right);
	}
	Node* _root;
};

搜索树的应用

1.搜索: key搜索模型 + key/value搜索模型 2.排序+去重


K模型

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

即key在不在搜索二叉树

  • 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下
    • 以单词集合中的每个单词作为key,构建一棵二叉搜索树
    • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误

KV模型

KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对,和搜索二叉树的代码差不多,只不过是多了一个模板参数V


查找的时候:此时返回的是Node*,而不是bool,因为要得到对应的value值

删除的时候:替换法删除时,不仅要替换key,还要替换value值

打印的时候:还要把对应的value打印出来

//K -> key V->value
//节点类型
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)
		:_key(key)
		,_value(value)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

搜索二叉树的框架:

template<class K,class V>
struct BSTree
{
	typedef BSTreeNode<K,V> Node; //将二叉树节点的类型重命名为Node,方便使用
	BSTree()
		:_root(nullptr)
	{}
    
    Node* _root;//节点指针,整棵树的头节点
};

查找:

Node* find(const K& key)
{
    //空树不用找
    if (_root == nullptr)
    {
        return nullptr;
    }
    Node* cur = _root;//从根位置开始找
    //结束条件:cur走到空 || 提前返回
    while (cur)
    {
        if (cur->_key > key)
        {
            //去左树找
            cur = cur->_left;
        }
        else if (cur->_key < key)
        {
            //去右树找
            cur = cur->_right;
        }
        else
        {
            return cur;
        }
    }
    //说明没有找到
    return false;
}

删除:

替换法删除的时候,把value也替换,其余的和二叉搜索树一样

else
{
    Node* minParent = cur;
    Node* min = cur->_right;
    while (min->_left)
    {
        minParent = min;
        min = min->_left;
    }

    cur->_key = min->_key;
    cur->_value = min->_value;//_val也替换


    if (minParent->_left == min)
        minParent->_left = min->_right;
    else
        minParent->_right = min->_right;

    delete min;
}

打印: 把value也对应的打出来

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

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

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

void TestBSTree1()
{
    // 字典KV模型
    BSTree<string, string> dict;
    dict.Insert("sort", "排序");
    dict.Insert("left", "左边");
    dict.Insert("right", "右边");
    dict.Insert("map", "地图、映射");
    //...

    string str;
    while (cin>>str)
    {
        BSTreeNode<string, string>* ret = dict.Find(str);//在类外面不能直接用Node,因为Node是在里面typedef
        if (ret)
        {
            cout << "对应中文解释:" << ret->_value << endl;
        }
        else
        {
            cout << "无此单词" << endl;
        }
    }
}

注意:二叉搜索树需要比较,键值对比较时只比较Key


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

void TestBSTree2()
{
    // 统计水果出现次数
    string arr[] = { "苹果", "西瓜","草莓", "苹果", "西瓜", "苹果", 
                    "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
    //key:水果名称 value:次数
    BSTree<string, int> countTree;
    //水果没有出现过->新建水果词频,次数置1
    //出现过->出现次数++
    for (auto& str : arr)
    {
        //BSTreeNode<string, int>* ret = countTree.Find(str);
        auto ret = countTree.Find(str);//用auto更方便
        if (ret != nullptr)
        {
            ret->_value++;//说明出现过,次数+1
        }
        else
        {
            countTree.Insert(str, 1);//新建水果词频,次数置1
        }
    }

    countTree.InOrder();
}

注意:二叉搜索树需要比较,键值对比较时只比较Key


性能分析

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

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


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

image-20220412203041923

最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:O(log N)

2^30 = 10亿 2^31 = 20亿 最差情况下,二叉搜索树退化为单支树(相当于链表),其查找效率变为:O(N)


问题:如果退化成单支树,二叉搜索树的性能就失去了 那能否进行改进,不论按照什么次序插入关键码,都可以是二叉搜索树的性能最佳?


举报

相关推荐

0 条评论