搜索二叉树是一种比较常用的二叉树,因为它的中序遍历是按照从小到大的方式进行排列的,对于初学二叉树的学者来说,掌握搜索二叉树的查找、插入和删除是进入二叉树这一数据结构领域的一个很好切口,接下来再继续学习二叉树的其他知识以及其他的如红黑树等就有兴趣了,下面笔者运用java语言来完成搜索二叉树的这三种基本功能。
二叉树的节点定义如下:
class BTNode
{
int value; // 关键字
BTNode left; // 左子树
BTNode right; // 右子树
public BTNode(int value)
{
this.value = value;
this.left = null;
this.right = null;
}
}
二叉树的定义如下:
class BinaryTree
{
BTNode root;
public BinaryTree()
{
this.root = null;
}
public BTNode find(int data){}
public void insert(int data){}
public boolean delete(int data){}
}
一、搜索二叉树的查找
思路: 二分法查找思想。
参数:给定关键字 data。
返回:返回含有该关键字的二叉树节点,如果没找到,返回null。
public BTNode find(int data)
{
BTNode current = root;
while (current != null && current.value !=data)
{
if (data < current.value)
current = current.left;
else
current = current.right;
}
return current;
}
以上几行代码就是搜索二叉树的查找,比较给定的关键字data和当前二叉树节点的关键字value,分为以下三种情况进行讨论:
(1)、data < value:
说明要找的节点在当前节点的左子树中,将该左子树的根节
点改为当前的节点继续查找。
(2)、data > value:
说明要种的节点在当前节点的右子树中,将该右子树的根节
点该为当前的节点继续查找。
(3)、data == value:
找的就是该节点,不满足while循环条件,退出该循环并
返回该节点。
当遍历完二叉树发下没有要找的节点,那么 current == null,
此时也不满足while循环条件,退出循环,返回的是该 current
节点是null,满足返回的要求。
二、搜索二叉树的插入
思路: 依据 root 是否为null 分两种情况进行。
二分思想地去插入。
何时插入,节点没有左子树或者没有右子树的时候插入。
参数: 插入的关键字 data
返回: 无
public void insert(int data)
{
// 首先创建新的二叉树节点:
BTNode newNode = new BTNode(data);
// 分两种情况进行插入:
if (root == null)
{
root = newNode;
}
else
{
BTNode current = root,parent = null;
while (true)
{
parent = current;
if (data < current.value)
{
current = current.left;
if (current == null)
{
parent.left = newNode;
return;
}
}
else
{
current = current.right;
if (current == null)
{
parent.right = newNode;
return;
}
}
}
}
}
以上便是搜索二叉树的插入,和查找基本类似,主要的区别在于多一个辅助变量 parent,
以便再插入新的节点时找不到自己的父亲是谁!
三、搜索二叉树的删除
搜索二叉树的删除是最复杂的,分为以下三种情况:
(1)、删除的节点没有左子树和右子树。
(2)、删除的节点只有一个子树,左子树或者是右子树。
(3)、删除的节点既有左子树又有右子树。
要删一个节点,找到该节点是必须的,还有该节点的父亲也必须要找到,不过更重要的是
要记住该节点到底是父亲的左孩子还是右孩子,这得需要一个布尔类型变量,定义如下:
boolean isLeftChild = false;
先假定不是左孩子吧,那么删除节点的前奏就有了:
public boolean delete(int data)
{
// 前奏: 找到 data节点,data节点的父亲,并判断出节点是父亲的左
// 孩子还是右孩子。
BTNode current = root,parent = null;
boolean isLeftChild = false;
while (data != current.value)
{
parent = current;
if (data < current.value)
{
isLeftChild = true;
current = current.left;
}
else
{
isLeftChild = false;
current = current.right;
}
if (current == null)
return false;
}
/* 此时跳出的 while 循环的 current 节点便是我们要所要找得含有data关键字的节点,
*它的父节点保存在 parent 变量里,并可根据 isLeftChild 的真假值来判断
*current 到底是 parent 的左孩子还是右孩子。
*接下来,按照(1)(2)(3)三种情况分别删除节点。
*/
//(1) 删除两儿子都没有的节点:
if (current.left == null && current.right == null)
{
if (current == root)
root = null;
else if (isLeftChild)
parent.left = null;
else
parent.right = null;
}
// (2) 删除仅有一个儿子的二叉树节点:
// 可能是左二子,也可能是右儿子,分两种情况继续讨论:
// 只有左二子的情况:
else if (current.right == null)
{
if (current == root)
root = root.left;
else if (isLeftChild)
parent.left = current.left;
else
parent.right = current.left;
}
// 只有右儿子的情况:
else if (current.left == null)
{
if (current == root)
root = root.right;
else if (isLeftChild)
parent.left = current.right;
else
parent.right = current.right;
}
// (3) 删除既有左儿子又有右儿子的节点:
// 接下来的代码请看下文方框中的。
——
| |
——-
// 第三种情况是最为复杂的,先停在这里,进行思考谁来该接替current的位置?
return true;
}
===============================================================================
5
/ \
3 **9**
/ / \
1 7 12
/\ / \
6 8 10 14
\
11
假设我们要删除的是含有关键字 9 这个节点,那么接替 9 的位置是 7, 12 还是其他数据呢?
7和12都不合适,不合适的原因感兴趣的读者自己分析,这里有一个技巧:
即用 current 的中序后继来 10 所在节点代替 current 节点
===============================================================================
找current的中序后续节点很好找,方法类似求最小值的方法,但是找到该后继节点 successor后,要记得
把 successor 的右子树放在 successor 父亲的做孩子下,即发生了两个替换:
(1) 先让 successor 的右孩子坐在节点代替 current 的位置。
(2) 然后再让 successor 代替 current 的位置。
查找 current 的后继节点 successsor 并置换位置的代码如下:
private BTNode getSuccessor(BTNode current) // 别忘了此时的current既有左孩子又右孩子!
{
BTNode successor = current.right;
BTNode parent = current;
while (successor.left != null)
{
parent = successor;
successor = successor.left;
}
if (successor != current.right)
{
parent.left = successor.right;
successor.right = current.right;
}
return successor;
}
找到了后继节点 successor 后,删除带有两个儿子的节点 current 就容易些了,继续上面的删除代码:
————————————————————————————–
| |
| else // 删除既有左儿子也有右儿子的节点 current |
| { |
| BTNode successor = getSuccessor(current); |
| if (current == root) |
| root = successor; |
| else if (isLeftChild) |
| parent.left = successor; |
| else |
| parent.right = successor; |
| |
| successor.left = current.left; |
| } |
| |
|———————————————————————————- |
这样,把以上方框中的代码粘贴回到原代码处,搜索二叉树的删除就完整结束了。如果 sucessor 是current右子树的左后代,
搜索二叉树的删除要考虑一下四种因素:
(1)、把后继父节点的 left 字段置为 successor 的右子节点。
(2)、把 successor 的右子节点置为 要删除节点 current 的右子节点。
(3)、把 current 从它父节点的 right 删除,把这个字段置为 successor。
(4)、把 current 的左子节点从 current 移除,successor 的左子节点left置为 current 的左子节点。
明日新晨
2017.7.31 晚