「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」
之前写过二叉树、二叉搜索树、平衡二叉树的联系和区别,实现了二叉树的四种遍历方式,这篇文章主要讲的是二叉搜索树的具体实现,主要包括节点的插入、查找和删除这三个方法,删除这个方法暂时先写思想,还没具体实现。
1.二叉搜索树的定义和特点
对于任一节点Node,其左子树上的数据值均小于Node的值,右子树上所有数据均大于Node的值。二叉搜索树里面没有任何节点的值是相同的,若对二叉搜索树进行中序遍历输出节点的数值,就能得到从小到大排序的序列。以下就是二叉树的特点,也可以是它的定义:
- 任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意节点的左、右子树也分别为二叉查找树。
- 没有键值相等的节点(no duplicate nodes)。
2.代码实现
2.1 节点类型
public class BNode {
public Object element; //节点值
public BNode lChild; //左孩子节点
public BNode rChild; //右孩子节点
public BNode(Object element){
this.element = element;
}
public BNode(){}
}
2.2查找节点
查找方法类似二分查找,通俗的讲,从中间也就是根节点开始,小的往左找,大的往右找,找到最后为null则查找失败,反之则查找成功。代码以及注释如下:
public BNode search(int num){
BNode current = root; //定义一个辅助变量,保存当前节点
//查找数值与根节点数值比较,不等则继续分情况讨论,相等则直接返回
while ((int)current.element != num){
//大于当前节点,则将current指向当前节点的右孩子节点,
//反之,则指向左孩子节点,以便进行下轮比较
if(num > (int)current.element){
current = current.rChild;
}else {
current = current.lChild;
}
if(current == null){ //如果最后current为空,表示查找失败,返回null
return null;
}
}
return current;
}
2.3插入节点
插入方法的思想,根据二叉树的性质,左孩子节点小于父亲节点小于右孩子节点,插入的元素先同根节点进行比较,小于根节点则与左孩子节点比较,大于根节点则与右孩子比较,等于根节点的话则直接提示元素重复。依据二叉树天然的递归特性,再进行比较直到找到空位置进行插入。代码以及注释如下:
public void insert(int data) {
BNode node = new BNode(data); //待插入的节点
if(root==null)
{
root=node; //根节点为空就直接插入
size++; //size是二叉树的节点数
}
else
{
BNode exists = search(data);
if (exists!=null){
System.out.println("节点"+data+"已存在,不可重复插入");
return; //元素重复则直接结束
}
BNode parent=new BNode();//辅助变量,表示当前节点的父节点
BNode current=root;//辅助变量,表示当前节点
while(true)//虽说是循环,其实是递归
{
parent=current;
if(data>(int)current.element)
{
current=current.rChild; // 不为空则与右子树继续比较
if(current==null) //为空则直接插入
{
parent.rChild=node;
size++;
return;
}
}
else
{
current=current.lChild; //同上
if(current==null)
{
parent.lChild=node;
size++;
return;
}
}
}
}
}
2.4删除节点
删除节点则分为三种情况,删除的节点是叶子节点,删除的节点只有一个子节点,删除的节点有两个子节点。
- 删除的节点是叶子结点则直接删除该节点。
- 删除的节点有一个子节点,先将该节点的父节点指向该节点的子节点,再删除该节点。
- 删除的节点有两个子节点,则需要从该节点的右子树找到最小值来代替该节点,或者从该节点的左子树找出值最大的节点。为什么要这样找呢?因为二叉搜索树的中序遍历得到的是从小到大的一个序列,在序列中距离要删除节点最进的两个值就是左子树的最大值和右子树的最小值,用这两个值来代替被删除节点最合适,也能让删除后的二叉树保持二叉搜索树的特性。暂时想到的写法比较复杂,后面想到新的方法会更新,代码如下:
public void delete(int num){
BNode parent = null;
BNode current = root;
while ((int)current.element != num){
if(num > (int)current.element){
parent = current;
current = current.rChild;
}else {
parent = current;
current = current.lChild;
}
if(current == null){
System.out.println("要删除的元素不存在");
return;
}
}
if(current.rChild==null&¤t.lChild==null){ //叶子结点
if(parent.lChild == current){
parent.lChild = null;
}
if(parent.rChild == current){
parent.rChild = null;
}
return;
}
if (current.lChild == null&¤t.rChild!=null){ //只有右子树
if(parent.lChild == current){
parent.lChild = current.rChild;
//current.rChild=null;
}
if(parent.rChild == current){
parent.rChild = current.rChild;
}
return;
}
if (current.lChild != null && current.rChild == null){ //只有左子树
if(parent.lChild == current){
parent.lChild = current.lChild;
//current.rChild=null;
}
if(parent.rChild == current){
parent.rChild = current.lChild;
}
return;
}
if (current.lChild != null && current.rChild != null){ //左右子树都有
BNode right = current.rChild;
BNode rightParent = current;
if(right.lChild==null && right.rChild==null){
current.element = right.element;
current.rChild = null;
return;
}
if(right.lChild ==null && right.rChild!=null){
current.element = right.element;
current.rChild = right.rChild;
}
if (right.lChild !=null && right.rChild != null){
while (right.lChild!=null){
rightParent = right;
right = right.lChild;
}
current.element = right.element;
rightParent.lChild = null;
}
}
}