数据结构——二叉搜索树(BST)
二叉搜索树(BST,Binary Search Tree)又称二叉排序树、二叉查找树首先,它可以是一棵空树;1. 非空左子树的所有键值小于其根节点的键值2. 非空右子树的所有键值大于其根节点的键值3. 左右子树也分别为二叉搜索树二叉搜索树一般不支持key值冗余(不允许有重复的key值)如图为一棵标准的二叉搜索树:相比较于常规的二叉树,二叉搜索树具有明显的大小区分度,对二叉搜索树进行中序遍历,所获取
前言
关于初上手做二叉搜索树和平衡二叉树but没有相关了解而被干碎了,遂抓紧学习基本概念而写博客
关于树方面更加深入的了解,如现在的二叉搜索树(BST),后来的平衡二叉树(AVL)、B树、B+树、红黑树等。除去对于习题方面的提高外,更重要的是能应用于实际开发中的,从中获取的优化程序的能力和拆解问题建立抽象模型的思维能力。
一、基本概念——什么是二叉搜索树
二叉搜索树(BST,Binary Search Tree)又称二叉排序树、二叉查找树
首先,它可以是一棵空树;
若不是,则它需要满足以下条件:
1. 非空左子树的所有键值小于其根节点的键值
2. 非空右子树的所有键值大于其根节点的键值
3. 左右子树也分别为二叉搜索树
二叉搜索树一般不支持key值冗余(不允许有重复的key值)
如图为一棵标准的二叉搜索树:

相比较于常规的二叉树,二叉搜索树具有明显的大小区分度,对二叉搜索树进行中序遍历,所获取的键值便是严格从小到大排序的。
也因此,二叉搜索树较普通的二叉树具有显著高效的搜索效率,平均时间复杂度一般为O(logn),当退化为普通二叉树或不“平衡”时,最坏会退化为O(n)。图示为普通数组、普通二叉树、二叉搜索树之间,对元素操作的平均时间复杂度:

二、二叉搜索树的应用——K模型与KV模型
K即key,V即value,分别对应的是单键值和键值对的情况;
1、K模型
K 模型即 key 模型,在这种模型下,二叉搜索树的每个节点仅存储一个关键码(key)。其主要功能是判断某个关键码是否存在于树中,也就是进行查找操作。
特点
- 存储单一:每个节点只保存一个关键码,没有与之关联的其他数据。
- 查找为主:主要用途是查找某个关键码是否存在,插入和删除操作也是围绕关键码进行的。
#include <stdio.h>
typedef struct TreeNodeK {
int key; // 单键值
struct TreeNodeK *left;
struct TreeNodeK *right;
} TreeNodeK;
int main()
{
return 0;
}
2、KV模型
KV 模型即 key-value 模型,在这种模型下,二叉搜索树的每个节点除了存储关键码(key)之外,还会存储与之关联的值(value)。可以把它想象成一个字典,通过关键码来查找对应的值。
如C++中stl的std::map和std::set,都是基于红黑树来创建的,以实现哈希方法或存储唯一元素并自动排序,而红黑树本质上也是一棵自平衡的二叉搜索树(或干脆称之为平衡二叉搜索树)
特点
- 存储关联:每个节点存储一个关键码和一个与之关联的值,形成键值对。
- 数据映射:不仅可以判断关键码是否存在,还能获取关键码对应的关联值。
#include <stdio.h>
typedef struct TreeNodeKV {
int key; // 键值
int value; // 键值对应的value
struct TreeNodeKV *left;
struct TreeNodeKV *right;
} TreeNodeKV;
int main()
{
return 0;
}
三、二叉搜索树的基本操作(K模型为例)
1、查找元素
给定目标节点值 num ,可以根据二叉搜索树的性质来查找。我们声明一个节点 curr,从二叉树的根节点 root 出发,循环比较节点值 curr.val 和 num 之间的大小关系。由于二叉搜索树不支持键值冗余,不会存在父节点、孩子结点相等的情况,且每次都会根据大小排除一半的可能性,因此对于平衡的二叉搜索树,其查找效率和二分查找是相同的O(logn)。如图所示:

// 查找函数的实现
TreeNode *search(TreeNode *bst, int num)
{
TreeNode *curr = bst->root;
while (curr != NULL)
{
if (curr->val < num)
{
// 目标节点在 curr 的右子树中
curr = curr->right;
}
else if (curr->val > num)
{
// 目标节点在 curr 的左子树中
curr = curr->left;
}
else
{
// 找到目标节点,跳出循环
break;
}
}
// 返回目标节点
return curr;
}
通过对于查找操作的学习,可以完成 98. 验证二叉搜索树 题目:


bool func(struct TreeNode* root, long low, long high)
{
if (root == NULL) // 空树必然满足条件
{
return true; // 或者递归遍历寻找到叶节点仍然满足条件
}
long num = root->val;
if (num <= low || num >= high)
{
return false; // 超出限定范围,则不满足条件
}
// 递归的调用,以判断左子树和右子树性质
return func(root->left, low, num) && func(root->right, num, high);
}
bool isValidBST(struct TreeNode* root)
{
// 考虑到不支持键值冗余,且root->val可以达到int的MIN和MAX,故使用long
return func(root, LONG_MIN, LONG_MAX);
}
2、插入操作
当树为空树,则可以以结点为根建立树
若不为空,则需按照二叉搜索树的性质查找临界的结点并插入
在进行插入操作的代码实现过程中,应当注意以下的点:
1、二叉搜索树不支持键值冗余,因此对于树中已存在的键值,应当直接返回退出
2、由于需要进行插入的操作,因此每遍历查找至一层,都需要另外的一个指针记录当前结点的父节点(或者直接使用递归的方式,不过应当考虑到递归可能带来的栈溢出情况,这里采用递归法)
// 创建新节点
TreeNode* createNode(int val)
{
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
newNode->val = val;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
// 插入节点
TreeNode* insert(TreeNode* root, int val)
{
if (root == NULL)
{
return createNode(val);
}
if (val < root->val)
{
root->left = insert(root->left, val);
}
else
{
root->right = insert(root->right, val);
}
return root;
}
对应着 701. 二叉搜索树中的插入操作 题目


struct TreeNode* createTreeNode(int val)
{
struct TreeNode* ret = malloc(sizeof(struct TreeNode));
ret->val = val;
ret->left = ret->right = NULL;
return ret;
}
// 迭代法的实现
struct TreeNode* insertIntoBST(struct TreeNode* root, int val)
{
if (root == NULL)
{
root = createTreeNode(val);
return root;
}
struct TreeNode* pos = root;
while (pos != NULL)
{
if (val < pos->val)
{
if (pos->left == NULL)
{
pos->left = createTreeNode(val);
break;
}
else
{
pos = pos->left;
}
}
else
{
if (pos->right == NULL)
{
pos->right = createTreeNode(val);
break;
}
else
{
pos = pos->right;
}
}
}
return root;
}
3、删除操作
查找元素是否在二叉搜索树中,如果不存在,则返回
否则就应当遵守以下规则:
1. 要删除的节点无孩子节点 ——可以直接删除该节点
2. 要删除的节点只有左孩子节点——让左孩子节点替代该节点位置,并删除该节点
3. 要删除的节点只有右孩子节点——让右孩子节点替代该节点位置,并删除该节点
4. 要删除的节点有左右孩子节点——由二叉树的性质,查找删除节点的右子树key值最小节点,让最小节点的key覆盖掉删除节点的key。因为右子树是全部大于父节点的结点,而右子树最小的结点不存在孩子(否则一定不为最小结点),且可以始终满足大于原父节点左子树,小于原父节点右子树的性质
TreeNode* findMin(TreeNode* root)
{
while (root->left != NULL)
{
root = root->left;
}
return root;
}
TreeNode* deleteNode(TreeNode* root, int key)
{
if (root == NULL)
{
return root;
}
if (key < root->val)
{
root->left = deleteNode(root->left, key);
}
else if (key > root->val)
{
root->right = deleteNode(root->right, key);
}
else
{
// 情况 1: 没有子节点或只有一个子节点
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;
}
// 情况 2: 有两个子节点
TreeNode* temp = findMin(root->right);
root->val = temp->val;
root->right = deleteNode(root->right, temp->val);
}
return root;
}
以及对应的题目 450. 删除二叉搜索树中的节点



struct TreeNode* deleteNode(struct TreeNode* root, int key)
{
if (root == NULL)
{
return NULL;
}
if (root->val > key)
{
root->left = deleteNode(root->left, key);
return root;
}
if (root->val < key)
{
root->right = deleteNode(root->right, key);
return root;
}
if (root->val == key)
{
if (!root->left && !root->right)
{
return NULL;
}
if (!root->right)
{
return root->left;
}
if (!root->left)
{
return root->right;
}
struct TreeNode *successor = root->right;
while (successor->left)
{
successor = successor->left;
}
root->right = deleteNode(root->right, successor->val);
successor->right = root->right;
successor->left = root->left;
return successor;
}
return root;
}
四、K模型与KV模型的具体实现
1、K模型
#include <stdio.h>
#include <stdlib.h>
// 定义 K 模型二叉搜索树的节点结构
typedef struct TreeNodeK
{
int key;
struct TreeNodeK *left;
struct TreeNodeK *right;
} TreeNodeK;
// 创建新节点
TreeNodeK* createNodeK(int key)
{
TreeNodeK* newNode = malloc(sizeof(TreeNodeK));
newNode->key = key;
newNode->left = newNode->right = NULL;
return newNode;
}
// 插入节点
TreeNodeK* insertK(TreeNodeK* root, int key)
{
if (root == NULL)
{
return createNodeK(key);
}
if (key < root->key)
{
root->left = insertK(root->left, key);
}
else if (key > root->key)
{
root->right = insertK(root->right, key);
}
return root;
}
// 查找节点
TreeNodeK* searchK(TreeNodeK* root, int key)
{
if (root == NULL || root->key == key)
{
return root;
}
if (key < root->key)
{
return searchK(root->left, key);
}
return searchK(root->right, key);
}
// 中序遍历
void inorderK(TreeNodeK* root)
{
if (root != NULL)
{
inorderK(root->left);
printf("%d ", root->key);
inorderK(root->right);
}
}
// 释放树的内存
void freeTreeK(TreeNodeK* root)
{
if (root != NULL)
{
freeTreeK(root->left);
freeTreeK(root->right);
free(root);
}
}
2、KV模型
#include <stdio.h>
#include <stdlib.h>
// 定义 KV 模型二叉搜索树的节点结构
typedef struct TreeNodeKV
{
int key;
int value;
struct TreeNodeKV *left;
struct TreeNodeKV *right;
} TreeNodeKV;
// 创建新节点
TreeNodeKV* createNodeKV(int key, int value)
{
TreeNodeKV* newNode = (TreeNodeKV*)malloc(sizeof(TreeNodeKV));
newNode->key = key;
newNode->value = value;
newNode->left = newNode->right = NULL;
return newNode;
}
// 插入节点
TreeNodeKV* insertKV(TreeNodeKV* root, int key, int value)
{
if (root == NULL)
{
return createNodeKV(key, value);
}
if (key < root->key)
{
root->left = insertKV(root->left, key, value);
}
else if (key > root->key)
{
root->right = insertKV(root->right, key, value);
}
return root;
}
// 查找节点
int* searchKV(TreeNodeKV* root, int key)
{
if (root == NULL || root->key == key)
{
return root ? &root->value : NULL;
}
if (key < root->key)
{
return searchKV(root->left, key);
}
return searchKV(root->right, key);
}
// 中序遍历
void inorderKV(TreeNodeKV* root)
{
if (root != NULL)
{
inorderKV(root->left);
printf("(%d, %d) ", root->key, root->value);
inorderKV(root->right);
}
}
// 释放树的内存
void freeTreeKV(TreeNodeKV* root)
{
if (root != NULL)
{
freeTreeKV(root->left);
freeTreeKV(root->right);
free(root);
}
}
五、浅总结
总而言之,二叉搜索树BST是后续的树结构的基础,尤其是较为接近的平衡二叉树AVL,其除去平衡因子的限制外,本身就是一棵二叉搜索树,以及后续的红黑树(弱平衡二叉树)等,因此对于它的掌握与理解,是非常关键而重要的。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)