二叉查找树
定义:二叉查找树是一个二叉树结构,对于这个二叉树中的每一个节点X,它的左子树中的节点都小于X节点的关键字值,而右子树中的节点都大于X节点的关键字值。
二叉查找树也称作二叉排序树。
根据上面的定义,下面的一个二叉树就是一个二叉查找树:
可以看到,按照中序遍历这个二叉树可以得到一个有序递增的序列。
定义二叉查找树的结构
与二叉树类似,其定义代码如下:
typedef struct BSNode {
char ch;
struct BSNode *left, *right;
} BSNode, *BSTree;
插入
构建二叉查找树的过程就是一个递归(或迭代)调用插入操作的处理过程,类似于二叉树的构建,但是二叉查找树的插入则需要先找到合适的插入位置,然后再插入节点。如上图所示的二叉查找树,可以按照这个序列来插入:6、4、8、3、5、7、9,首先插入节点6,此时这个节点是根节点,然后插入节点4,从树根开始遍历找到节点6的左孩子的位置,再插入节点,依此类推。
这个插入节点的函数如下:
void insert(BSTree *T, char c) {
if(*T == NULL) {
*T = createBSNode(c);
} else {
if((*T)->ch > c) {
insert(&((*T)->left), c);
} else if((*T)->ch < c) {
insert(&((*T)->right), c);
} else {
return;
}
}
}
/*创建节点函数*/
BSNode* createBSNode(char c) {
BSNode *node = malloc(sizeof(BSNode));
node->ch = c;
node->left = NULL;
node->right = NULL;
return node;
}
在上面的代码中每次插入一个节点就先与二叉查找树的根节点比较,如果根节点为NULL,说明这个二叉查找树为空树,那么就直接插入这个一个节点,如果要插入的节点元素比根节点的元素小,那么就在根节点的左子树中找到合适的插入位置,如果插入节点的元素比根节点的元素大,则在根节点的右子树中找到合适的插入位置,否则这个带插入的节点的元素已经存在于这个二叉查找树中,那么直接返回。
删除
二叉查找树的节点删除过程较插入稍微复杂一些,因为删除一个节点时,需要考虑三种情况:
- 待删除的节点为叶子节点;
- 待删除的节点有一个子树为空;
- 待删除的节点的左右子树均不为空;
以下图为例:
对于情况1,只需要从二叉查找树中直接删除这个节点,调整其父节点的指针为NULL,如图中的节点2,6,9这三个节点,如果要删除节点2,直接将节点3的左孩子指针设置为NULL,然后删除节点2,对于情况2,只需要调整将待删除节点的父节点的指针,指向待删除节点的不为空的子树根节点,如图中的节点1,7, 3, 5,如果要删除节点3,直接将节点1的右孩子指针指向节点2,然后删除节点3,对于情况3,要保证删除节点之后,不改变二叉查找树的结构,所以删除节点后,树中元素的相对位置不能改变,如节点4和8,可以有以下三种方式,假设待删除节点为p:
- 使用p的左孩子代替p,p的右子树称为p的左子树的最右节点的右子树,如果要删除节点4,那么将节点8的左孩子指针指向节点1,然后节点7代表的子树成为节点3的右子树,这个结果所代表的树也为二叉查找树;
- 使用p的中序遍历的直接前驱代替p,然后删除p的直接前驱节点,如果要删除节点4,可以使用节点4的中序遍历的直接前驱节点3代替节点4,然后删除节点3;
- 使用p的中序遍历的直接后继代替p,然后删除p的直接后继节点,如果要删除节点4,可以使用节点4的中序遍历的直接后继节点5,代替节点4,然后删除节点5;
对于方式2和方式3,因为p的中序遍历的直接前驱或者直接后继节点至少有一个子树为空,所以使用p的中序遍历的直接前驱或者直接后继替换后,又将这个删除问题转化成上面3中情况中的情况1或者情况2。
基于上面的分析,删除节点的代码如下:
//删除节点
void deleteNode(BSNode *pre, BSNode *p) {
if(pre->left == p) {
pre->left = p->left != NULL ? p->left : p->right;
} else {
pre->right = p->left != NULL ? p->left : p->right;
}
}
void delete(BSTree *T