- 为什么需要二叉排序树:
对于数组,查找的效率较高,但是删除和添加的效率较低;而对于链表,查找的效率较低,删除和增加的效率较高。那么有没有一种方法使得各种效率都较高呢?使用二叉树的存储结构可以做到 - java中表示树
创建一个名为Node的类,表示数的节点
class Node<T>{
//数据
T data;
//左孩子
Node<T> leftChild;
//右孩子
Node<T> rightChild;
//构造方法
public Node(T data)
{
this.data=data;
//这两部实际上在类加载的时候已经初始化为null
leftChild=null;
rightChild=null;
}
}
声明为泛型类的目的是考虑到树存储的数据可以由使用者来确定,但是这里要求数据对象必须实现了Comparable接口并重写了compareTo()方法,这样才能比较对象的大小
//这里只是前面的代码
public class BinaryTree<T extends Comparable<T>>{
//树的根root
Node<T> root;
}
- 节点的插入(也就是二叉树的构建过程)
这里假设插入的节点为node,里面
(1)如果根节点不存在,那么先建立根节点
(2)否则,令current=root,判断node的数据对象和current数据对象的大小,如果小于,则判断current的左子树是否为空,如果为空则把node节点插入current的左子树,否则current=current.leftChild;如果大于,则判断current的右子树是否为空,如果为空则把node节点插入current的右子树,否则current=current.rightChild
public void insert(Node<T> node)
{
//如果root为空,先插入root,否则根据大小插入子树中
if(root==null)
root=node;
else
{
Node<T> current=root;
//node比root小
while(true)
{
if(node.data.compareTo(root.data)<0)
{
//如果左节点已经为空,那么插入到这来,如果不是空,那就是继续比较
if(current.leftChild==null)
{
current.leftChild=node;
return;
}
current=current.leftChild;
}//node大于等于root,查找的时候是返回第一个找到的节点
else
{
if(current.rightChild==null)
{
current.rightChild=node;
return;
}
current=current.rightChild;
}
}
}
}
- 节点的查找
(1)如果根节点为空,那么return null;表示查找失败
(2)如果根节点不为空,令current=root,然后开始判断node的数据对象和current数据对象是否相等;如果相等,则return current;如果不相等,则判断两者大小,然后根据大小来决定查找左子树还是右子树。在这个过程中,如果需要查找的左子树或者右子树为空,那么return null表示找不到。
public Node<T> select(Node<T> node)
{
if(root==null)
return null;
else
{
Node<T> current=root;
while(node.data.compareTo(current.data)!=0)
{
if(node.data.compareTo(current.data)<0)
{
if(current.leftChild==null)
return null;
current=current.leftChild;
}
if(node.data.compareTo(current.data)>0)
{
if(current.rightChild==null)
return null;
current=current.rightChild;
}
}
return current;
}
}
- 节点的删除
这里设要删除的节点为node,要删除的节点的父节点为parent
这个部分是最复杂的,这里首先需要看node具有的子节点个数
(1)如果没有子节点,那么直接parent指向node的位置赋null,表示删除掉了node
(2)如果node有一个子节点,那么只需要把parent指向node的位置指向node这个子节点解可以了
(3)如果node有2个子节点,这种情况是最复杂的,这里把last声明为node节点安装中序遍历的后继节点,lastParent是last的父节点
这个表示要被删除的节点node的右节点没有左节点,那么上图的last就是node的后继节点,所以只需要把last设置到node的位置就能完成删除node的目的(parent.XXX=last,last.leftChild=node.leftChild)
这个表示node的后继节点不是node的右节点,因为last是node中序遍历的后继节点,所以last的左子节点是null的,但是last右子节点不一定为空,所以需要先把last.rightChild赋值给parent.leftChild,然后修改last为node,然后把parent指向last,这样就能完成删除node的任务
(4)这里有一种特殊的节点,也就是root根节点,因为一开始current=root,parent=null,所以需要对根节点做特殊处理,否则删除根节点将会尝试空指针异常
下面是源码,因为没有提取相似的代码所有有点长
public boolean delete(Node<T> node)
{
if(root==null||node==null)
{
return false;
}
else {
//要删除的节点
Node<T> current=root;
//记录这个节点的父节点,root节点的父节点我null
Node<T> parent=null;
//记录current节点所在的方向
Direct direct=Direct.left;
//找到这个节点和他的父节点
while(node.data.compareTo(current.data)!=0)
{
System.out.println("node.data"+node.data);
System.out.println("current.data"+current.data);
parent=current;
if(node.data.compareTo(current.data)<0)
{
current=current.leftChild;
direct=Direct.left;
}
else
{
current=current.rightChild;
direct=Direct.right;
}
//左子树和右子树都为空则表明找不到这个要删除的节点,return false
if(current==null)
return false;
}
//删除的节点左右子树都为空,那么直接把父节点的Direct方向设置null即可
if(current.leftChild==null&¤t.rightChild==null)
{
//如果删除打的是根节点
if(current==root)
{
root=null;
}
else if(direct==Direct.left)
parent.leftChild=null;
else
parent.rightChild=null;
return true;
}
//要删除的节点当且仅有一个子树
//右子树不空,那么把父节点的Direct方向设为其右子树即可
if(current.leftChild==null)
{
//如果删除打的是根节点
if(current==root)
{
root=current.rightChild;
}
else if(direct==Direct.left)
parent.leftChild=current.rightChild;
else
parent.rightChild=current.rightChild;
return true;
}
//左子树不空,那么把父节点的Direct方向设为其左子树即可
if(current.rightChild==null)
{
//如果删除打的是根节点
if(current==root)
{
root=current.leftChild;
}
else if(direct==Direct.left)
parent.leftChild=current.leftChild;
else
parent.rightChild=current.leftChild;
return true;
}
//左右子树都不为空的删除情况
//过程是把要删除节点的先序下个节点A放在删除节点的位置
//如果A是要删除节点的右节点,那么直接把parent Direct方向的子节点赋为A即可
//如果A不是要删除节点的右节点,除了parent Direct需要设置,A的右子树设置为删除节点的右子树
//表示A的父节点
Node<T> lastParent=current;
//A
Node<T> last=current.rightChild;
//A的左节点为空,也就是A就是要删除节点的下个节点
if(last.leftChild==null)
{
//如果删除的是根节点
if(current==root)
{
root=last;
root.leftChild=current.leftChild;
}//把parent的Direct方向指向last,也就是A
else if(direct==Direct.left)
parent.leftChild=last;
else
parent.rightChild=last;
//A的左子树指为要删除节点的左子树
last.leftChild=current.leftChild;
}//A的左节点不为空,那么要删除节点的下个节点是A的最左左节点,然后就是新的A
else
{
//找到这个下个节点
while(last.leftChild!=null)
{
lastParent=last;
last=last.leftChild;
}
//把的A的父节点的左子树设为A的右子树
lastParent.leftChild=last.rightChild;
//A的左子树指为要删除节点的左子树
last.leftChild=current.leftChild;
//A的右子树指向要删除节点的右子树
last.rightChild=current.rightChild;
//如果是根节点
if(current==root)
{
root=last;
}//把parent的Direct方向指向last,也就是A
else if(direct==Direct.left)
parent.leftChild=last;
else
parent.rightChild=last;
}
return true;
}
}
7.节点的遍历(前序,中序,后序)
这个就是使用递归来做,这里会传入一个实现了List 接口的对象过程,然后把遍历的数据封装到List里面(判断使用哪个方法遍历是根据枚举来判断的)
public void traverse(Traverse traverse,List<T> list)
{
switch (traverse) {
case preorder:preorder(root,list); break;
case inorder:inorder(root,list); break;
case postorder:postorder(root,list);break;
default:
break;
}
}
/**
* 前序遍历
* @param current
*/
public void preorder(Node<T> current,List<T> list)
{
if(current!=null)
{
list.add(current.data);
preorder(current.leftChild,list);
preorder(current.rightChild,list);
}
}
/**
* 中序遍历
* @param current
*/
public void inorder(Node<T> current,List<T> list)
{
if(current!=null)
{
preorder(current.leftChild,list);
list.add(current.data);
preorder(current.rightChild,list);
}
}
/**
* 后序遍历
* @param current
*/
public void postorder(Node<T> current,List<T> list)
{
if(current!=null)
{
preorder(current.leftChild,list);
preorder(current.rightChild,list);
list.add(current.data);
}
}