数据结构---二叉搜索树的实现
一. 内容概述
二叉搜索树,又叫二叉排序/查找树。特点是普通二叉树加一定约束后形成的具有特殊性质的二叉树,其规则约束有以下:
txt
1.如果它的左⼦树不空,则左⼦树上所有结点的值均⼩于它的根结点的值。
2.如果它的右⼦树不空,则右⼦树上所有结点的值均⼤于它的根结点的值。
3.它的左、右树⼜分为二叉排序树。
对于一颗满足上述约束的树,它的中序遍历结果就是一个从小到大排好的序列。其次,二叉搜索树的优势在于当要查找一个树中存在的数据时,其查找速度等同于二分查找(最坏情况所需时间为遍历该树的高度),无需遍历整棵树,只要通过该树性质不断比较递归就可以得到答案。以下会介绍二叉搜索树的具体插入删除等操作的具体实现。
二. 代码实现
1.结构定义
c
typedef int Element;
// 定义二叉搜索树的节点结构
typedef struct _bs_node {
Element data;
struct _bs_node *left;
struct _bs_node *right;
} BSNode;
// 定义二叉搜索树的头节点
typedef struct {
BSNode *root;
int count;
const char *name;
} BSTree;
2.创建树/节点
c
BSTree* createBSTree(const char* name)
{
BSTree* tree = malloc(sizeof(BSTree));
if (tree == NULL)
{
return NULL;
}
tree->root = NULL;
tree->name = name;
tree->count = 0;
return tree;
}
static BSNode *createBSNode(Element e)
{
BSNode *node = malloc(sizeof(BSNode));
if (node == NULL)
{
return NULL;
}
node->data = e;
node->left = node->right = NULL;
return node;
}
3.插入操作(递归)
c
static BSNode* insertBSNode(BSTree *tree, BSNode *node, Element e)
{
if (node == NULL)//判空
{
tree->count++;
return createBSNode(e);
}
if (e < node->data)//插入数据比当前节点存储数据小
{
node->left = insertBSNode(tree,node->left,e);//递归直到下一个节点位置为NULL,创建新节点并插入当前位置
}
else if (e>node->data)//同理
{
node->right = insertBSNode(tree,node->right,e);
}
return node;
}
void insertBSTree(BSTree* tree, Element e)
{
tree->root = insertBSNode(tree,tree->root,e);
}
4.插入操作(非递归)*
非递归主要依靠双指针(一个指向当前检索节点,一个用来记录另一个指针的前一步位置),防止指针指空无法追踪位置。
c
void insertBSTree_2(BSTree* tree, Element e)
{
BSNode *cur = tree->root;//首先定义一个指针指向根节点
BSNode *pre = NULL;//第二个指针初始定义指空
while (cur)//若cur指针未找到e应插入位置或e在树中已存在,不断循环
{
pre = cur;//pre在cur移位前记录其位置
if (e < cur->data) cur=cur->left;
else if (e > cur->data) cur=cur->right;
else return;//若e已存在退出插入操作
}
BSNode *newNode = createBSNode(e);//cur已经到达e应插入位置且e之前并不存在
if (pre)//pre不为空(若空说明root为NULL)
{
if (e < pre->data) pre->left = newNode;//根据e与pre大小判断确定位置并进行插入操作
else if (e > pre->data) pre->right = newNode;
}
else//root为NULL,e为首个插入的节点
{
tree->root = newNode;
}
tree->count++;
}
5.删除节点操作
c
static BSNode *maxValueBSNode(BSNode *node)//从node节点开始寻找最大节点
{
while (node && node->right)
{
node = node->right;
}
return node;
}
static BSNode* deleteBSNode(BSTree* tree, BSNode *node, Element e)//从node开始寻找e
{
if (node == NULL)//非空判断或递归终止条件
{
return NULL;
}
if (e < node->data)//递归遍历寻找e在树中位置
{
node->left = deleteBSNode(tree,node->left,e);
}
else if (e > node->data)
{
node->right = deleteBSNode(tree,node->right,e);
}
else//找到e的位置
{
BSNode *temp;//定义temp记录待删除节点
if (node->left == NULL)//首先判断左节点为空,说明node节点度为1或0
{
temp = node->right;//node左节点为空,temp记录右节点防止丢失数据
free(node);//将待删除结点释放
tree->count--;//计数器减一
return temp;//将保留数据接回原树
}
if (node->right == NULL)//左右节点皆空,重复上述操作,相当于用NULL节点替换待删除节点,与上步本质一样
{
temp = node->left;
free(node);
tree->count--;
return temp;
}
//到此说明待删节点度为2,为保证搜索树性质不变,要将该节点中序遍历的前驱或后继节点替换待删节点,保证性质不变
temp = maxValueBSNode(node->left);//示例代码用前驱节点示例,即待删节点左子树最大值
node->data = temp->data;//将待删节点数据替为前驱节点数据
node->left = deleteBSNode(tree,node->left,node->data);//寻找到用来替换的节点删除(度一定为1或0)
}
return node;//将修改后节点返回
}
void deleteBSTree(BSTree* tree, Element e)
{
if (tree)
{
tree->root = deleteBSNode(tree,tree->root,e);
}
}
6.遍历操作
c
void visitBSNode(const BSNode* node)//访问节点函数,此处示例为将节点内数据输出
{
printf("\t%d",node->data);
}
static void inOrderBSNode(const BSNode *node)//中序遍历
{
if (node)
{
inOrderBSNode(node->left);
visitBSNode(node);
inOrderBSNode(node->right);
}
}
void inOrderBSTree(const BSTree* tree)
{
printf("[%s]Tree: ", tree->name);
inOrderBSNode(tree->root);
printf("\n");
}
7.求树的高度
c
static int heightBSNode(const BSNode* node)
{
if (node ==NULL)//递归终止条件
{
return 0;
}
int leftHeight = heightBSNode(node->left);//不断递归将子树高度传递回来
int rightHeight = heightBSNode(node->right);
if (leftHeight > rightHeight)//树的高度由更高的子树高度决定,总高度为子树高度+1
return leftHeight + 1;
else return rightHeight + 1;
}
int heightBSTree(const BSTree* tree)
{
return heightBSNode(tree->root);
}
8.查找操作
c
//利用二叉搜索树进行查值操作
BSNode* searchBSTree(const BSTree* tree, Element e)
{
BSNode* node = tree->root;//从根节点开始寻找
while (node)
{
if (e < node->data)
{
node = node->left;
}
else if (e > node->data)
{
node = node->right;
}
else
{
return node;
}
}
return NULL;
}
三. 内容总结
二叉搜索树的实现大部分比较容易理解,难点主要有树中度为2的节点删除操作,具体实现不能简单直接释放,而是换一个思路去选择一个前驱或者后继节点用来替换到待删除结点位置,然后将用来替换的节点找到并删除是该操作的具体实现逻辑,此外该树的实现没有其他难以理解的点。