文章目录
二叉搜索树
1. 二叉搜索树的概念
二叉搜索树(Binary Search Tree):也叫做二叉查找树、有序二叉树或排序二叉树。是指一棵空树或者具有下列性质的二叉树:
- 如果任意节点的左子树不为空,则左子树上所有节点的值均小于它的根节点的值。
- 如果任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值。
- 任意节点的左子树、右子树均为二叉搜索树。
说白了就是,任意一个节点的左子树上的值都小于这个节点,而右子树上的值都大于这个节点,这就是二叉搜索树。
比如下面这张图就是一个二叉搜索树:
那么这个二叉搜索树有什么其他亮点呢?当然有!回忆一下我们学过的二叉树的三种遍历,如果你用这三种方法去遍历二叉搜索树就会发现,二叉搜索树的中序遍历是有序的!这也很好理解,中序遍历是按照 “左根右” 的顺序来遍历的而任意一个节点的左子树的值都比根要小,右子树的值比它要大,所以对于一个树上的节点来说,比它小的数会先被遍历,而比它大的数则会后被遍历,因此中序遍历的结果是有序的,这也是为什么二叉搜索树也叫排序二叉树。
注:在实际应用中,二叉搜索树中的值一般是不会重复的,所以一下代码都不考虑树中有两个值相同的节点。
2. 二叉搜索树的基本操作
定义和创建节点
// BinarySearchTree.h
// 定义节点结构体
typedef struct BinarySearchTreeNode {
int data;
struct BinarySearchTreeNode* left;
struct BinarySearchTreeNode* right;
} BSTNode;
// BinarySearchTree.c
// 创建节点
BSTNode* CreateNode(int data) {
BSTNode* newNode = (BSTNode*)malloc(sizeof(BSTNode)); // 开辟新空间
newNode->data = data; // 初始化数据
newNode->left = newNode->right = NULL;
return newNode;
}
二叉搜索树的销毁
有创建就一定要有销毁
void DestroyTree(BSTNode** proot){
if(*proot == NULL) return;
DestroyTree(&((*proot)->left)); // 注意取地址
DestroyTree(&((*proot)->right));
free(*proot);
*proot = NULL;
}
插入操作
首先要明确的一点是,对于一个二叉搜索树而言,要插入的节点一定是插入到叶子节点上。下面是实现思路:
这里我采用返回类型
void
,传入两个参数BSTNode** proot
和int data
,只要调用了这个函数就自动插入到相应的位置。传入二级指针是因为有可能原二叉树为空,那么我们就需要改变这个指针的地址。接下来如果树不为空,那么如果就用传入的参数
data
和当前节点的data
作比较,如果传入的值小就插入到左子树当中,如果大就插入到右子树当中。
void InsertNode(BSTNode** proot, int data){
if(*proot == NULL){ // 如果为空就开一个节点作为新的根
*proot = CreateNode(data);
return;
}
else if(data < (*proot)->data){ // 如果小就递归插入到左子树
InsertNode(&((*proot)->left), data);
}
else{ // 大就递归插入到右子树
InsertNode(&((*proot)->right), data);
}
}
构建二叉搜索树
对于一个随机给出的数组,我们可以使用依次插入的方式来构建出一个二叉搜索树。
BSTNode* CreateBSTree(int* a, int size){
if(a == NULL)
return NULL;
BSTNode* root = NULL;
for(int i = 0; i < size; i++){
InsertNode(&root, a[i]);
}
return root;
}
删除操作
删除操作会稍微复杂一点,我们来三种情况来讨论——删除度为 0,,度为1,度为 2 的节点。
这里我还是采用传入二级指针和递归的方式来实现,先来看看这个函数长什么样:
void EraseNode(BSTNode** proot, int data){ if(*proot == NULL){ return; } if(data < (*proot)->data){ EraseNode(&((*proot)->left), data); } else if(data > (*proot)->data){ EraseNode(&((*proot)->right), data); } else{ // 删除度为 0 的节点 if((*proot)->left == NULL && (*proot)->right == NULL){ free(*proot); *proot = NULL; return; } // 删除度为 1 的节点 else if((*proot)->left == NULL || (*proot)->right == NULL){ BSTNode* temp = (*proot)->left!= NULL? (*proot)->left : (*proot)->right; free(*proot); *proot = temp; return; } // 删除度为 2 的节点 BSTNode *temp = PreDecessor(*proot); (*proot)->data = temp->data; EraseNode(&((*proot)->left), temp->data); } }
接下来我们对此来分析一下。
if(*proot == NULL){
return;
} // 或者断言也行
如果此节点的值更大,则递归调用到它的左子树;如果此节点的值更小,就递归调用到它的右子树。
if(data < (*proot)->data){
EraseNode(&((*proot)->left), data);
}
else if(data > (*proot)->data){
EraseNode(&((*proot)->right), data);
}
如果此时相等了,就要开始删除操作了。
这个 so easy!直接释放掉这个节点就完事儿了。
if((*proot)->left == NULL && (*proot)->right == NULL){
free(*proot);
*proot = NULL;
return;
}
为什么这里不用管此节点的双亲结点?因为这里的proot
其实就是上一层递归 &(*proot)->left
(或者&(*proot)->right
)传过来的参数。你用 *
解引用这里的 proot
相当于就是在改变双亲结点的左指针(或右指针)。
这个也不难。我只需要记录一下他的那个孩子节点
temp
,然后释放掉它之后,解引用proot
让它的双亲节点对应的指针指向temp
就好了。
else if((*proot)->left == NULL || (*proot)->right == NULL){
BSTNode* temp = (*proot)->left!= NULL? (*proot)->left : (*proot)->right;
free(*proot);
*proot = temp;
return;
}
这个需要稍微思索一下。既然直接删除度为 2 的节点不方便,那我们可不可以让它转换成删除度为 1 或者度为 0 的节点呢?这个时候我们就可以利用此节点的前驱或者后继。
不难发现,在二叉搜索树当中,一个节点的左子树的值全都比这个节点的值小,而这棵左子树中最大的那个数则会出现在这棵左子树的最右边,右子树同理。所以:一个节点如果有左右子树, 那么它的前驱一定是在左子树的最右边,后继一定是在右子树的最左边。
而这个前驱或者后继节点不可能是一个度为 2 的节点,否则前驱就不是左子树中最右边的,后继也不会是右子树中最左边的!
现在我们有了前驱和后继,我们只需要将这个要删除的度为 2 的节点跟它的前驱或者后继中的任意一个进行交换,然后再将这个节点删除即可。因为这样并不会影响它们在二叉搜索树中的 “相对位置”。并且这样做还可以把问题转换成了删除度为 1 或者度为 0 的节点的问题。
下面是动图演示:
所以在删除之前我们要找到它的前驱或者后继节点,可以单独写一个函数来找,这里我以前驱为例:
// 找到当前节点的前驱节点并返回
BSTNode* PreDecessor(BSTNode* root){
BSTNode* temp = root->left; // 先往左走
while(temp->right!= NULL){ // 再一直往右走
temp = temp->right;
}
return temp;
}
// 删除度为 2 的节点
BSTNode *temp = PreDecessor(*proot); // 记录一下前驱节点
(*proot)->data = temp->data; // 把前驱节点的值赋给当前节点
EraseNode(&((*proot)->left), temp->data); // 然后去删除前驱节点那个位置的值
说明一下,虽然上面说是说的 “交换”,但实际上其实并不用严格的交换,只需要让当前节点的值等于前驱节点的值然后删除前驱节点位置的节点,这样也可以达到同样的目的。
至此,我们也就完成了删除操作。
// 找前驱节点
BSTNode* PreDecessor(BSTNode* root){
BSTNode* temp = root->left; // 先往左走
while(temp->right!= NULL){ // 再一直往右走
temp = temp->right;
}
return temp;
}
// 删除节点
void EraseNode(BSTNode** proot, int data){
if(*proot == NULL){ // 根节点为空直接返回(或通过断言的方式)
return;
}
if(data < (*proot)->data){ // 小的话就去它的左子树
EraseNode(&((*proot)->left), data);
}
else if(data > (*proot)->data){ // 大的话就去它的右子树
EraseNode(&((*proot)->right), data);
}
else{ // 找到了就开始进行删除
// 删除度为 0 的节点
if((*proot)->left == NULL && (*proot)->right == NULL){
free(*proot); // 直接释放即可
*proot = NULL;
return;
}
// 删除度为 1 的节点
else if((*proot)->left == NULL || (*proot)->right == NULL){
// 记录一下它的孩子节点(哪个不为空就记录哪个)
BSTNode* temp = (*proot)->left!= NULL? (*proot)->left : (*proot)->right;
free(*proot); // 然后直接释放当前节点
*proot = temp; // 再让它的双亲指向它的孩子
return;
}
// 删除度为 2 的节点
BSTNode *temp = PreDecessor(*proot); // 记录一下前驱节点
(*proot)->data = temp->data; // 把前驱节点的值赋给当前节点
EraseNode(&((*proot)->left), temp->data); // 然后去删除前驱节点那个位置的值
}
}
中序遍历输出
直接中序遍历然后打印对应节点位置数据即可。
void InOrder(BSTNode* root){
if (root == NULL) return;
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
查找元素(递归)
目标数据比当前节点数据小,就递归往左子树找,大了就递归往右子树找,找到了就返回当前节点即可。
BSTNode* SearchNode_1(BSTNode* root, int target) {
if (root == NULL)
return NULL;
if (target == root->data) {
return root;
}
return target > root->data ? SearchNode_1(root->right, target) : SearchNode_1(root->left, target);
}
查找元素(非递归)
通过迭代的方式往下找。
BSTNode* SearchNode_2(BSTNode* root, int target) {
BSTNode* p = root;
while(p){
if(p->data == target)
return p;
p = target < p->data? p->left : p->right;
}
return NULL;
}
获取最大值
一直往右走就是最大值
int GetMax(BSTNode* root){
assert(root); // 确保树不为空
BSTNode* p = root;
while(p->right){
p = p->right;
}
return p->data;
}
获取最小值
一直往左走就是最小值
int GetMin(BSTNode* root){
assert(root); // 确保树不为空
BSTNode* p = root;
while(p->left){
p = p->left;
}
return p->data;
}
3. 完整代码
// BinarySearchTree.h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct BinarySearchTreeNode {
int data;
struct BinarySearchTreeNode* left;
struct BinarySearchTreeNode* right;
} BSTNode;
// 创建节点
BSTNode* CreateNode(int data);
// 销毁二叉搜索树
void DestroyTree(BSTNode** proot);
// 构建二叉搜索树
BSTNode* CreateBSTree(int* a, int size);
// 插入节点
void InsertNode(BSTNode** root, int data);
// 删除操作
void EraseNode(BSTNode** root, int data);
// 中序遍历输出
void InOrder(BSTNode* root);
// 查找元素(递归)
BSTNode *SearchNode_1(BSTNode *root, int target);
// 查找元素(非递归)
BSTNode* SearchNode_2(BSTNode* root, int target);
// 获取最大值
int GetMax(BSTNode *root);
// 获取最小值
int GetMin(BSTNode *root);
// BinarySearchTree.c
#include "BinarySearchTree.h"
BSTNode* CreateNode(int data) {
BSTNode* newNode = (BSTNode*)malloc(sizeof(BSTNode));
newNode->data = data;
newNode->left = newNode->right = NULL;
return newNode;
}
void DestroyTree(BSTNode** proot){
if(*proot == NULL) return;
DestroyTree(&((*proot)->left));
DestroyTree(&((*proot)->right));
free(*proot);
*proot = NULL;
}
BSTNode* CreateBSTree(int* a, int size){
if(a == NULL)
return NULL;
BSTNode* root = NULL;
for(int i = 0; i < size; i++){
InsertNode(&root, a[i]);
}
return root;
}
void InsertNode(BSTNode** proot, int data){
if(*proot == NULL){
*proot = CreateNode(data);
return;
}
else if(data < (*proot)->data){
InsertNode(&((*proot)->left), data);
}
else{
InsertNode(&((*proot)->right), data);
}
}
BSTNode* PreDecessor(BSTNode* root){
BSTNode* temp = root->left;
while(temp->right!= NULL){
temp = temp->right;
}
return temp;
}
void EraseNode(BSTNode** proot, int data){
if(*proot == NULL){
return;
}
if(data < (*proot)->data){
EraseNode(&((*proot)->left), data);
}
else if(data > (*proot)->data){
EraseNode(&((*proot)->right), data);
}
else{
// 删除度为 0 的节点
if((*proot)->left == NULL && (*proot)->right == NULL){
free(*proot);
*proot = NULL;
return;
}
// 删除度为 1 的节点
else if((*proot)->left == NULL || (*proot)->right == NULL){
BSTNode* temp = (*proot)->left!= NULL? (*proot)->left : (*proot)->right;
free(*proot);
*proot = temp;
return;
}
// 删除度为 2 的节点
BSTNode *temp = PreDecessor(*proot);
(*proot)->data = temp->data;
EraseNode(&((*proot)->left), temp->data);
}
}
void InOrder(BSTNode* root){
if (root == NULL) return;
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
BSTNode* SearchNode_1(BSTNode* root, int target) {
if (root == NULL)
return NULL;
if (target == root->data) {
return root;
}
return target > root->data ? SearchNode_1(root->right, target) : SearchNode_1(root->left, target);
}
BSTNode* SearchNode_2(BSTNode* root, int target) {
BSTNode* p = root;
while(p){
if(p->data == target)
return p;
p = target < p->data? p->left : p->right;
}
return NULL;
}
int GetMax(BSTNode* root){
assert(root);
BSTNode* p = root;
while(p->right){
p = p->right;
}
return p->data;
}
int GetMin(BSTNode* root){
assert(root);
BSTNode* p = root;
while(p->left){
p = p->left;
}
return p->data;
}
// BinarySearchTreeTest.c
#include "BinarySearchTree.h"
void test1(){
int a[] = {20, 17, 29, 19, 28, 32, 18};
BSTNode* Node = CreateBSTree(a, sizeof(a)/sizeof(int));
InOrder(Node); // 17 18 19 20 28 29 32
printf("\n");
EraseNode(&Node, 20);
InOrder(Node); // 17 18 19 28 29 32
printf("\n");
printf("max: %d\nmin: %d\n", GetMax(Node), GetMin(Node));
// max: 32
// min: 17
}
int main(){
test1();
return 0;
}