本文章主要介绍二叉搜索树的概念以及二叉搜索树的相关操作:按值插入、按值查找、按值删除。
1. 二叉搜索树的概念
二叉搜索树又称为二叉排序树,它或者是一棵空树,或者具有如下性质的二叉树:
(1)若它的左子树不为空,则左子树上的所有节点值都小于根节点的值。
(2)若它的右子树不为空,则右子树上的所有节点值都大于根节点的值。
(3)它的左右子树也为二叉搜索树。
画图举例关于二叉搜索树:
2. 二叉搜索树前期准备工作
二叉搜索树的特点:左子树值小于根节点值,根节点值小于右子树值。
2.1 头文件search_tree.h
二叉搜索树是以链表的形式组织起来,二叉搜索树节点的结构体类型包括:节点值,节点的左子树的指向、节点的右子树的指向。
//二叉搜索树 #pragma once //用于测试打印函数名 #define HEADER printf("===========%s=========\n",__FUNCTION__); //便于代码的修改,重定义二叉搜索树节点的元素类型 typedef char SearchNodeType; //定义二叉搜索树的节点的结构体类型 typedef struct SearchNode { SearchNodeType data; struct SearchNode* lchild; struct SearchNode* rchild; }SearchNode; //函数声明 void SearchTreeInit(SearchNode** proot); void SearchTreeDestroy(SearchNode** proot); void DestroySearchNode(SearchNode* root);
2.2 初始化
初始化二叉搜索树,即将二叉搜索树初始化为空树,故将指向二叉搜索树的指针置为NULL。
//1.初始化 //思路:初始化二叉搜索树即二叉搜索树为空树,故将其根节点置为NULL即可 void SearchTreeInit(SearchNode** proot) { //非法输入 if(proot==NULL) return; //将根节点置为NULL *proot=NULL; }
2.3 销毁
销毁其实和初始化所做的工作是类似的,即将以有的二叉搜索树置为空树即可。
//2.销毁 //思路:将已有的二叉搜索树置为空树,需要释放创建的每个节点 void SearchTreeDestroy(SearchNode** proot) { //非法输入 if(proot==NULL) return; //空树,直接return if(*proot==NULL) return; //非空树 SearchNode* root=*proot; SearchTreeDestroy(&root->lchild); SearchTreeDestroy(&root->rchild); DestroySearchNode(root); *proot=NULL; }
2.4 创建二叉搜索树的节点
//创建节点 SearchNode* CreateSearchNode(SearchNodeType value) { SearchNode* new_node=(SearchNode*)malloc(sizeof(SearchNode)); new_node->data=value; new_node->lchild=NULL; new_node->rchild=NULL; return new_node; }
2.5 销毁二叉搜索树的节点
//销毁节点 void DestroySearchNode(SearchNode* root) { free(root); }
3. 二叉搜索树的相关操作(递归)
3.1 按值插入
(1)当为空树时,直接在其根节点处插入即可
(2)当为非空树时,由于二叉搜索树的特点:左子树值<根节点值,根节点值<右子树值。故将分为三种情况:
①当插入元素值<根节点值时,在其左子树中继续寻找合适的位置插入。
②当插入元素值>根节点值时,在其右子树中继续寻找合适的位置插入。
③当插入元素值=根节点值时,我们约定插入失败,即二叉搜索树中没有重复值。
//思路:1.空树时直接插入在其根节点位置 // 2.非空树时:(1)当插入元素值<当前节点值时,递归插入到当前节点的左子树中 // (2)当插入元素值>当前节点值时,递归插入到当前节点的右子树中 // (3)当相等时,我们约定为插入失败! void SearchTreeInsert(SearchNode** proot,SearchNodeType value) { //非法输入 if(proot==NULL) { return; } //1.空树时直接插入在其根节点位置 if(*proot==NULL) { //创建新节点 SearchNode* new_node=CreateSearchNode(value); //插入到根节点 *proot=new_node; } //2.非空树时 SearchNode* cur=*proot; //(1)当插入元素值<当前节点的值时,将其递归插入到当前节点的左子树中 if(value<cur->data) { SearchTreeInsert(&cur->lchild,value); } //(2)当插入元素值>当前节点的值时,将其递归插入到当前节点的右子树中 else if(value>cur->data) { SearchTreeInsert(&cur->rchild,value); } //(3)当相等时,我们约定插入失败 else { return; } }
3.2 按值查找
(1)当为空树时,直接return NULL
(2)当为非空树时,由于二叉搜索树的特点:左子树值<根节点值,根节点值<右子树值。故将分为三种情况:
①当查找元素值<根节点值时,在其左子树中继续查找。
②当查找元素值>根节点值时,在其右子树中继续查找。
③当插入元素值=根节点值时,找到了。
(3)需要注意的是,当遍历完二叉搜索树还没有找到,则表示不存在直接return NULL
//思路:1.空树时直接返回NULL // 2.非空树时:(1)当查找的值find<当前节点的data时,递归在当前节点的左子树中查找 // (2)当查找的值find>当前节点的data时,递归在当前节点的右子树中查找 // (3)当查找的值find=当前节点的data时,说明找到了故返回当前节点的指向 // 3.若还没有返回,则表示找不到,直接return NULL SearchNode* SearchTreeFind(SearchNode* root,SearchNodeType find) { //1.空树时 if(root==NULL) { return NULL; } //2.非空树时 //(1)当查找的值find<当前节点data时,递归在当前节点的左子树中查找 if(find<root->data) { return SearchTreeFind(root->lchild,find); } //(2)当查找的值find>当前节点data时,递归在当前节点的右子树中查找 else if(find>root->data) { return SearchTreeFind(root->rchild,find); } //(3)相等时表示找到了 else { return root; } //3.没找到 return NULL; }
3.3 按值删除
假设要删除的元素为to_remove,先找到to_remove元素对应的节点remove,分情况讨论如何删除remove节点:
(1)to_remove元素未找到,即remove为NULL时,直接return
(2)remove无子树时,直接将其指向NULL
(3)remove只有左子树时,直接将其指向remove的左子树
(4)remove只有右子树时,直接将其指向remove的右子树
(5)remove有左右子树时,将remove与remove的右子树中的最小值交换,再去尝试删除交换的节点//思路:假设要删除的元素为to_remove //1.先找到to_remove元素对应的节点remove //2.分情况讨论如何删除remove节点: //(1)to_remove元素未找到,即remove为NULL时,直接return //(2)remove无子树时,直接将其指向NULL //(3)remove只有左子树时,直接将其指向remove的左子树 //(4)remove只有右子树时,直接将其指向remove的右子树 //(5)remove有左右子树时,将remove与remove的右子树中的最小值交换,再去尝试删除交换的节点 void SearchTreeRemove(SearchNode** proot,SearchNodeType to_remove) { //非法输入 if(proot==NULL) { return; } //空树时 if(*proot==NULL) { return; } //非空树 //1.先找到to_remove元素对应的节点remove SearchNode* root=*proot; //(1)当to_remove<root->data时,在root的左子树下删除 if(to_remove<root->data) { SearchTreeRemove(&root->lchild,to_remove); return; } //(2)当to_remove>root->data时,在root的右子树下删除 else if(to_remove>root->data) { SearchTreeRemove(&root->rchild,to_remove); return; } //(3)当找到时 else { //(1)删除的节点没有子树时,直接将当前节点的指向NULL if(root->lchild==NULL&&root->rchild==NULL) { *proot=NULL; //销毁要删除的节点 DestroySearchNode(root); } //(2)删除的节点只有左子树时,直接将当前节点指向其左子树 else if(root->lchild!=NULL&&root->rchild==NULL) { *proot=root->lchild; //销毁要删除的节点 DestroySearchNode(root); } //(3)删除的节点只有右子树时,直接将当前节点指向其右子树 else if(root->lchild==NULL&&root->rchild!=NULL) { *proot=root->rchild; //销毁要删除的节点 DestroySearchNode(root); } //(4)删除的节点有左右子树时 else { //先找到其右子树中最小节点,由于二叉搜索树的特点,故最小节点一定在其右子树的最左边 SearchNode* min_node=root->rchild; while(min_node->lchild!=NULL) { min_node=min_node->lchild; } //将要删除的节点的值置为最小节点的值 root->data=min_node->data; //从当前节点的右子树出发删除最小节点 SearchTreeRemove(&root->rchild,min_node->data); } } }
以上就是递归实现的关于二叉搜索树的相关操作!