5.1.1 二叉搜索树
二叉树是指任何节点最多只有两个子节点;而二叉搜索树(也称二叉查找树)的规则如下:
- 对数时间的元素插入和访问;(O(h) == O(lgn))
- 任何节点的
键值
一定大于其左子树
中的每一个节点的键值,一定小于其右子树
节点的键值。
根据上面的定义,判断下图中的树哪个不是二叉搜索树:
左边的是,右边的不是;因为根节点的键值6小于左子树中其中一个节点的键值7。请记住:从根节点一直往左(右)走,直到无左(右)路可走,即得最小(大)元素。如下图所示:
5.1.2 操作
二叉搜索树的常用操作有:查找最小/大元素,插入及删除元素。下面给出具体的操作步骤。注意:这里以int型数据作为研究对象,当然可以写成模板,这里一切从简,以聚焦于算法的原理。树中存放节点数据的结构体如下:
struct Node
{
int key; // 以int型数据为例
Node *Left, *Right; // 指向左右子节点的指针
};
每个节点的数据结构包含一个int型键值,两个分别指向左、右子节点的节点指针,当不存在左(或右)子节点时,Left(或Right)为NULL。
5.1.2.1 查找最小节点
要在一颗二叉搜索树中查找最大值或最小值,一直往右或往左走即可。这里只给出查找最小节点的程序,查找最大节点类似,下面代码中的Left换成Right即可;注意边界条件的判断也相当重要,尤其在面试时,不可忘记!!!
// 递归实现
Node* findMin(Node* T)
{
// 边界条件判断
if(T == NULL)
return NULL;
else if(T->Left == NULL)
return T;
else
return findMin(T->Left);
}
// 非递归实现
Node* findMin(Node* T)
{
if(T == NULL)
return NULL;
while(T->Left != NULL)
{
T = T->Left;
}
return T;
}
这里找到了最小的节点(Node* node),那么结构体中的数据轻而易得(node->key)!
5.1.2.2 插入
插入新元素到二叉搜索树中的规则很简单:从根节点开始,遇到键值大的(即新元素的值<节点的键值)往左走,遇到键值小的往右走。
// 递归实现
Node* insert(Node* T, int x)
{
if(!T)// T == NULL
{// 空树->构造一个节点
T = malloc(sizeof(Node));
if(T){
T->key = x;
T->Left = NULL;
T->Right = NULL;
return T;
}else{
//FetalError("Out of space!");
}
}
if(x < T->key) // 向左走
insert(T->Left, x);
else if(x > T->key) // 向右走
insert(T->Right, x);
return T;
}
// 非递归实现
Node* insert(Node* T, int x)
{
if(!T)// T == NULL
{// 空树->构造一个节点
T = malloc(sizeof(Node));
if(T){
T->key = x;
T->Left = NULL;
T->Right = NULL;
return T;
}else{
//FetalError("Out of space!");
}
}
while(x != T->key)
{
if(x < T->key)
T = T->Left;
else
T = T->Right;
if(!T){// 判断是否到达尾端
T = malloc(sizeof(Node));
T->key = x;
T->Left = T->Right = NULL;
break;
}
}
return T;
}
5.1.2.3 删除
图5-6是二叉搜索树的元素移除操作图解。欲删除旧节点A,有两种情况:
- 情形1:节点A(当前根节点)只有一个子节点。即要么有左子节点,要么有子节点;那么,仅需把该子节点连至A节点的父节点,而后再删除节点A;
- 情形2:节点A(当前根节点)有两个子节点。根据二叉搜索树的性质,左子树所有节点的键值 < 根节点的键值 < 右子树所有节点的键值;那么,欲删除旧节点A,需找到右子树中的最小元素节点(使用findMin函数),而后更新旧节点A的键值(不需要更新指向
左
子节点的指针),最后递归调用delete函数删除右子树的最小元素的节点并更新旧节点A的右子树的根节点(A->Right)。
程序如下:
// 《数据结构和算法分析——C语言描述》,该方法永远返回根节点
Node* Delete(Node* T, int x)
{
Node* tmpNode;
// 依然是边界条件判断
if(!T)
return NULL;
else{// 先找到要删除元素的节点
if(x < T->key)
T->Left = delete(T->Left, x);
else if(x > T->key)
T->Right = delete(T->Right, x);
else{// 找到,要删除的节点为T
if(T->Left && T->Right){// 2个孩子
tmpNode = findMin(T->Right);// 复制一份
T->key = tmpNode->key;
T->Right = Delete(T->Right, T->key);
}else{ // 0或1个孩子
tmpNode = T; // 复制一份
if(!T->Left)
T = T->Right;
else if(!T->Right)
T = T->Left;
free(tmpNode);
}
}
}
return T;
}