一、二插查找树
在上一篇的文章中,介绍了树、二叉树、以及二叉树的遍历的相关知识。本文将介绍一种特殊的二叉树,二叉查找树。这种数据结构支持动态数据集合的快速插入、删除、查找操作。
二叉查找树又称二叉搜索树,通过字面意思我们可以了解到,这种数据结构是为了实现快速查找而应运而生的。不过,它不仅仅支持快速查找,而且还支持快速插入和删除一个数据。
二叉查找树的要求: 在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。如下图所示
二、二叉查找树的查找操作
首先,我们来看如何在一个二叉搜索树中查找一个节点。我们先取根节点,如果等于我们要查找的数据,那么直接返回根节点。如果要查找的数据比根节点小,那就在左子树中递归查找。如果要查找的数据比根节点大,那么就在右子树中递归查找。不知你有没有发现,这种查找方式与二分查找十分类似。背后的逻辑可以说是换汤不换药。对应的代码如下:
public class BinarySearchTree {
//引入成员变量
private Node tree;
//查找方法,传入的参数为要查找的数据
public Node find(int data){
Node node = tree;
//循环结束的条件,当节点为空,停止递归
while (node != null){
//如果要查找的数据比根节点小,递归查找左子树
if(data < node.data){
node = node.left;
}
//如果要查找的数据比根节点小,递归查找左子树
else if(data > node.data){
node = node.right;
}
//如果要查找的数据等于根节点的数据,直接返回根节点
else {
return node;
}
}
//循环结束还没有找到,只能返回null
return null;
}
//定义内部节点类,采用链式存储
class Node{
private int data;
private Node left;
private Node right;
public Node(int data){
this.data = data;
}
}
}
三、二叉查找树的插入操作
插入的过程优点类似于查找的过程。新插入的数据一般都是在叶子节点上,所以插入操作只需要从根节点开始,依次比较要插入的数据和节点的关系。
如果要插入的数据比节点大,并且节点的右子节点为空,那么就将要插入的数据放入右子节点;若右子节点不为空,就再递归遍历右子树。
如果插入的数据比节点小,并且节点的左子节点为空,那么就将要插入的数据放入左子节点。如果做子节点不为空,就递归遍历左子树。代码如下:
public class BinarySearchTreeInsert {
private Node tree;
public void insert(int data){
//如果要插入的树为空,那么直接将要插入的数据插入到新的节点
if(tree==null){
tree = new Node(data);
return;
}
Node tmp = tree;
while(tmp!=null){
//如果要插入的数据比根节点的数据要大
if (data > tmp.data){
//如果根节点的右子节点为空,直接插入
if(tmp.right==null){
tmp.right = new Node(data);
return;
}
//否则继续遍历右子节点
tmp = tmp.right;
}
//如果要插入的数据比根节点的数据要小
else{
//如果根节点的左子节点为空,那么直接插入到左子节点
if (tmp.left==null){
tmp.left = new Node(data);
return;
}
//否则继续遍历左子树
tmp = tmp.left;
}
}
}
//内部节点类
class Node{
private int data;
private Node left;
private Node right;
public Node(int data){
this.data = data;
}
}
}
四、二叉查找树的删除操作
删除操作就比较复杂了,首先考虑最简单的。
- 要删除的节点没有子节点,这种情况最简单,我们直接将该节点的父节点指向该节点的指针修改为null
- 要删除的节点只有一个子节点,这种情况我们直接把要删除节点的父节点直接指向要删除节点的子节点就可以了
- 要删除的节点有两个子节点,我们需要找到该节点右子树中的最小节点,然后用该节点替换要删除的节点,之后调整对应的指针就可以了
代码如下:
public class BinarySearchTreeDelete {
private Node tree;
public void delete(int data){
Node p = tree;//p指向要删除的节点,初始化指向根节点
Node pp = null; //pp记录的是p的父节点
//当根节点不为空以及不是要删除的数据
while (p!=null && p.data!=data){
//父节点向下转换为p
pp = p;
//如果要删除的数据大于p的数据,p转为右子节点,否则转为左子节点
if(data > p.data) p = p.right;
else p = p.left;
}
//如果根节点为空,则没有找到,结束
if(p==null) return;
//如果要删除的节点有两个子节点,那么需要找到这个节点右子树中的最小节点
if(p.left!=null && p.right!=null){
//最小节点设为minp
Node minp = p.right;
//minpp为最小节点的父节点
Node minpp = p;
//要删除节点的右子节点的左子节点不为空,则minp设为此节点
while(minp.left !=null){
minpp = minp;
minp = minp.left;
}
//要删除的节点的数据和最小节点的数据进行交换
p.data = minp.data;
//此时要删除的节点变为minp,要删除的节点的父节点变为minpp
p = minp;
pp = minpp;
}
//删除节点是叶子节点或者仅有一个子节点
Node child;//要删除节点p的子节点,用来删除节点后拼接到父节点
//如果左子节点不为空
if(p.left != null) child=p.left;
//如果右子节点不为空
else if(p.right != null) child = p.right;
//否则child为null
else child = null;
//如果要删除的节点的父节点为空,则直接拼接到根节点
if(pp==null) tree = child;
//如果要删除的节点是左子节点,则拼接到左子树
else if (pp.left == p) pp.left = child;
//否则拼接到右子树
else pp.right = child;
}
//内部节点类
class Node{
private int data;
private Node left;
private Node right;
public Node(int data){
this.data = data;
}
}
}
五、二叉查找树的其他操作
除了查找、插入、删除外,二叉查找树还可以快速的查找最大节点和最小节点、前驱节点和后驱节点。 思路如下:
查找最大节点:一直遍历右子节点,知道右子节点不为空
查找最小节点:一直遍历左子节点,知道右子节点不为空
查找前驱节点:先遍历左子节点,然后再遍历右子节点,直到右子节点不为空
查找后驱节点:先遍历右子节点,然后再遍历左子节点,知道左子节点不为空