深入理解二叉搜索树(BST)及其删除操作详解(C++实现)

在这里插入图片描述

欢迎来到我的:世界

希望作者的文章对你有所帮助,有不足的地方还请指正,大家一起学习交流 !


前言


内容

一、什么是二叉搜索树?

二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树数据结构,满足以下性质:

  • 左子树所有节点的值 < 根节点的值

  • 右子树所有节点的值 > 根节点的值

左右子树也分别是二叉搜索树

这种结构使得查找、插入、删除操作的时间复杂度可以达到O(h),其中h为树的高度
如图所示:这是一棵标准的二叉搜索树:
在这里插入图片描述

二、BST基本操作

2.1 节点结构

这段代码使用 C++ 语言定义了一个模板结构体 BsNode,通常用于表示二叉搜索树(Binary Search Tree,BST)中的节点。

template <class K>
struct BsNode
{
	K _key;
	BsNode<K>* _left;
	BsNode<K>* _right;
	BsNode(const K& k)
		:_key(k)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

解释:

template <class K>
  • template 是 C++ 中的模板关键字,用于定义模板。模板允许我们编写通用的代码,这些代码可以处理不同的数据类型。
  • class K 表示定义了一个模板参数 Kclass 在这里等同于 typename,意味着 K 可以是任何类型,例如 intdoublestd::string 等。

struct 是 C++ 中用于定义结构体的关键字。在这里我就不详细解释这个了;

K _key;
BsNode<K>* _left;
BsNode<K>* _right;

K _key:定义了一个类型为 K 的成员变量 _key,用于存储节点的键值。由于使用了模板,_key 可以是任何类型,具体类型取决于在使用 BsNode 时指定的模板参数。
BsNode<K>* _left;:定义了一个指向 BsNode<K> 类型的指针 _left,用于指向当前节点的左子节点。如果当前节点没有左子节点,_left nullptr
BsNode<K>* _right;:定义了一个指向 BsNode<K> 类型的指针 _right,用于指向当前节点的右子节点。如果当前节点没有右子节点,_rightnullptr

BsNode(const K& k)
        :_key(k)
        , _left(nullptr)
        , _right(nullptr)
    {}

BsNode(const K& k):这是 BsNode 结构体的构造函数,用于创建 BsNode 对象。构造函数接受一个类型为 const K& 的参数 k,使用常量引用可以避免不必要的拷贝,提高效率。
:_key(k), _left(nullptr), _right(nullptr):这是构造函数的初始化列表,用于在对象创建时初始化成员变量。_key 被初始化为传入的参数 k_left _right 被初始化为 nullptr,表示该节点初始时没有左子节点和右子节点。

2.2 插入操作

通过比较节点值选择插入方向,保持BST性质,在这里我就不让具有相同键值插入进来了!!!,保持每只值都是单一值

// 该函数用于向二叉搜索树中插入一个新的键值
// 参数 key 是要插入的键值,类型为 const K&,使用常量引用避免不必要的拷贝
bool INSERT(const K& key)
{
    // 检查二叉搜索树的根节点是否为空
    // 如果为空,说明这是一棵空树,直接创建一个新节点作为根节点
    if (_root == nullptr)
    {
        // 使用 new 操作符动态分配一个新的 Node 对象,将 key 作为参数传递给构造函数
        _root = new Node(key);
        // 插入成功,返回 true
        return true;
    }

    // 定义一个指针 front,用于记录 cur 的父节点
    // 初始化为 nullptr,在后续遍历中会被更新
    Node* front = nullptr;
    // 定义一个指针 cur,用于遍历二叉搜索树
    // 初始化为根节点,从根节点开始查找插入位置
    Node* cur = _root;

    // 开始遍历二叉搜索树,直到 cur 为 nullptr
    while (cur)
    {
        // 如果当前节点的键值大于要插入的键值
        if (cur->_key > key)
        {
            // 更新 front 为当前节点
            front = cur;
            // 移动 cur 到当前节点的左子节点
            cur = cur->_left;
        }
        // 如果当前节点的键值小于要插入的键值
        else if (cur->_key < key)
        {
            // 更新 front 为当前节点
            front = cur;
            // 移动 cur 到当前节点的右子节点
            cur = cur->_right;
        }
        // 如果当前节点的键值等于要插入的键值
        else
        {
            // 输出错误信息,提示不允许插入相同元素
            perror("不允许相同元素插入");
            // 插入失败,返回 false
            return false;
        }
    }

    // 当 cur 为 nullptr 时,说明已经找到了插入位置
    // 根据 front 的键值和要插入的键值的大小关系,决定将新节点插入到 front 的左子节点还是右子节点
    if (front->_key > key)
        // 如果 front 的键值大于要插入的键值,将新节点插入到 front 的左子节点
        front->_left = new Node(key);
    else
        // 如果 front 的键值小于要插入的键值,将新节点插入到 front 的右子节点
        front->_right = new Node(key);

    // 插入成功,返回 true
    return true;
}

2.3 查找操作

利用BST性质进行二分查找

// 该函数用于在二叉搜索树中查找指定的键值
// 参数 key 是要查找的键值,类型为 const K&,使用常量引用避免不必要的拷贝
bool FIND(const K& key) {
    // 定义一个指针 cur,用于遍历二叉搜索树
    // 初始化为根节点,从根节点开始查找指定的键值
    Node* cur = _root;

    // 开始遍历二叉搜索树,只要 cur 不为 nullptr 就继续循环
    while (cur)
    {
        // 如果当前节点的键值大于要查找的键值
        if (cur->_key > key)
        {
            // 根据二叉搜索树的性质,要查找的键值可能在当前节点的左子树中
            // 因此将 cur 移动到当前节点的左子节点,继续查找
            cur = cur->_left;
        }
        // 如果当前节点的键值小于要查找的键值
        else if (cur->_key < key)
        {
            // 根据二叉搜索树的性质,要查找的键值可能在当前节点的右子树中
            // 因此将 cur 移动到当前节点的右子节点,继续查找
            cur = cur->_right;
        }
        // 如果当前节点的键值等于要查找的键值
        else if (cur->_key == key)
        {
            // 表示已经找到了指定的键值,返回 true
            return true;
        }
    }

    // 当 cur 为 nullptr 时,说明已经遍历完整个二叉搜索树,没有找到指定的键值
    // 返回 false 表示查找失败
    return false;
}

三、BST删除操作详解(重点)

⾸先查找元素是否在⼆叉搜索树中,如果不存在,则返回false。
如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)

  1. 要删除结点N左右孩⼦均为空
  2. 要删除的结点N左孩⼦位空,右孩⼦结点不为空
  3. 要删除的结点N右孩⼦位空,左孩⼦结点不为空
  4. 要删除的结点N左右孩⼦结点均不为空

对应以上四种情况的解决⽅案:

  1. 把N结点的⽗亲对应孩⼦指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是⼀样
    的)
  2. 把N结点的⽗亲对应孩⼦指针指向N的右孩⼦,直接删除N结点
  3. 把N结点的⽗亲对应孩⼦指针指向N的左孩⼦,直接删除N结点
  4. ⽆法直接删除N结点,因为N的两个孩⼦⽆处安放,只能⽤替换法删除。找N左⼦树的值最⼤结点
    R(最右结点)或者N右⼦树的值最⼩结点R(最左结点)替代N,因为这两个结点中任意⼀个,放到N的
    位置,都满⾜⼆叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转⽽变成删除R结
    点,R结点符合情况2或情况3,可以直接删除。

非递归方式:

// 该函数用于在二叉搜索树中删除指定键值的节点
// 参数 key 是要删除的节点的键值,使用常量引用避免不必要的拷贝
bool Erase(const K& key)
{
	 // 定义一个指针 front,用于记录 cur 的父节点,初始化为 nullptr
    Node* front = nullptr;
    // 定义一个指针 cur,用于遍历二叉搜索树,初始化为根节点
    Node* cur = _root;

	while (cur)
	{
// 如果当前节点的键值小于要删除的键值
		if (cur->_key < key)
		{
			front = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			front = cur;
			cur = cur->_left;
		}
		else
		{
			if (cur->_left == nullptr)
			{
				//if (cur->_right == nullptr)
				//{
				//	//第一种情况:删除的是叶子节点
				//	if (front->_left == cur)
				//		front->_left = nullptr;
				//	else
				//		front->_right = nullptr;
				//}
				//else
				//{
				//	//第二种情况:删除的的节点只用右节点
				//	if (front->_left == cur)
				//		front->_left = cur->_right;
				//	else
				//		front->_right = cur->_right;
				//}
/
				//第一种情况和第二种情况可以合并
				if (cur == _root)
				{
					//忘了,还有删除根的一种情况
					_root = cur->_right;
				}
				else
				{
					if (front->_left == cur)
						front->_left = cur->_right;
					else
						front->_right = cur->_right;
				}
				delete cur;
				return true;
			}
			else
			{
				if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						//还有删除根的一种情况
						_root = cur->_left;
					}
					else
					{
						//第三种情况:删除的结点只有左节点
						if (front->_left == cur)
							front->_left = cur->_left;
						else
							front->_right = cur->_left;
					}
					delete cur;
					return true;
				}
				else
				{
					//第四种情况:删除的结点有左右节点
					//这种情况比较特殊:
					//要么找其左子树的最大节点,替代他/或者找最右子树的最小节点,代替他,就可以

					//在这里我来找右子树的最小节点
					Node* replace = cur->_right;
					Node* replacefront = cur;
					while (replace->_left)
					{
						replacefront = replace;
						replace = replace->_left;
					}

					cur->_key = replace->_key;
					if (replacefront->_left == replace)
						replacefront->_left = replace->_left;
					else
						replacefront->_right = replace->_right;

					delete replace;
					return true;
				}
			}
		}
	}
	return false;
}

递归方式:

TreeNode* deleteNode(TreeNode* root, int key) {
    if (!root) return nullptr;

    if (key < root->val) {
        root->left = deleteNode(root->left, key);
    } else if (key > root->val) {
        root->right = deleteNode(root->right, key);
    } else {
        // Case 1 & 2: 无子节点或只有一个子节点
        if (!root->left) {
            TreeNode* temp = root->right;
            delete root;
            return temp;
        } else if (!root->right) {
            TreeNode* temp = root->left;
            delete root;
            return temp;
        }
        
        // Case 3: 两个子节点都存在
        TreeNode* temp = findMin(root->right);
        root->val = temp->val;
        root->right = deleteNode(root->right, temp->val);
    }
    return root;
}

TreeNode* findMin(TreeNode* node) {
    while (node->left) {
        node = node->left;
    }
    return node;
}

以删除节点3为例:

  1. 找到右子树最小节点4
  2. 将节点3的值替换为4
  3. 递归删除原节点4
删除前:
     5
    / \
   3   6
  / \   \
 2   4   7

删除后:
     5
    / \
   4   6
  /     \
 2       7

总结


到了最后:感谢支持

------------对过程全力以赴,对结果淡然处之

以下是C语言BST树的基本操作代码: ```c #include <stdio.h> #include <stdlib.h> //定义二叉搜索树的节点结构体 typedef struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; } TreeNode; //创建一个新的节点 TreeNode* createNode(int val) { TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode)); node->val = val; node->left = NULL; node->right = NULL; return node; } //向二叉搜索树中插入一个节点 TreeNode* insertNode(TreeNode* root, int val) { if (root == NULL) { return createNode(val); } if (val < root->val) { root->left = insertNode(root->left, val); } else if (val > root->val) { root->right = insertNode(root->right, val); } return root; } //在二叉搜索树中查找一个节点 TreeNode* searchNode(TreeNode* root, int val) { if (root == NULL || root->val == val) { return root; } if (val < root->val) { return searchNode(root->left, val); } else { return searchNode(root->right, val); } } //删除二叉搜索树中的一个节点 TreeNode* deleteNode(TreeNode* root, int val) { if (root == NULL) { return root; } if (val < root->val) { root->left = deleteNode(root->left, val); } else if (val > root->val) { root->right = deleteNode(root->right, val); } else { if (root->left == NULL) { TreeNode* temp = root->right; free(root); return temp; } else if (root->right == NULL) { TreeNode* temp = root->left; free(root); return temp; } TreeNode* temp = root->right; while (temp->left != NULL) { temp = temp->left; } root->val = temp->val; root->right = deleteNode(root->right, temp->val); } return root; } //中序遍历二叉搜索树 void inorderTraversal(TreeNode* root) { if (root != NULL) { inorderTraversal(root->left); printf("%d ", root->val); inorderTraversal(root->right); } } int main() { TreeNode* root = NULL; root = insertNode(root, 5); insertNode(root, 3); insertNode(root, 7); insertNode(root, 1); insertNode(root, 9); printf("中序遍历二叉搜索树:"); inorderTraversal(root); printf("\n"); root = deleteNode(root, 5); printf("删除节点5后的中序遍历二叉搜索树:"); inorderTraversal(root); printf("\n"); return 0; } ``` 以上代码实现二叉搜索树的基本操作,包括创建节点、插入节点、查找节点、删除节点和中序遍历等。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值