二叉树的类型:
二叉树:至多有2个分支的结点组成的树叫做二叉树
满二叉树:当其中所有结点的分支均为2时就是满二叉树;
完全二叉树:当这棵树除了最底层外,剩下的部分全是满的,并且最底层的结点全集中在该层最左边的位置,那么这样的树可称为完全二叉树。
二叉搜索树:当某一结点的左子树全部小于当前结点,且右子树全部大于当前结点,这样的树叫做二叉搜索树
往树里面添加元素
步骤:
用了两个重载的put方法实现,第一个put方法用于根节点的初始化,并将添加后的新树返回;第二个结点用于实现具体的插入功能,并将添加后的新树返回
判断子树是否是空的,如果为空则创建结点,如果不是空的,则递归插入新结点
1.首先找到结点应该插入的位置
2.遍历树,如果新结点的值小于当前遍历结点的值,则继续遍历当前结点的左子结点,如果新结点的值大于当前遍历结点的值,则继续遍历当前结点的右子结点(因为二叉搜索树具有左子树的值均小于根节点,右子树的值均大于根节点的特点)
3.将插入元素后的新树返回
4.注意一点,如果在遍历过程中要插入的新结点的值和遍历到的结点值相同,就将树中结点用新结点值覆盖(因为在此代码中结点的实现是键值对的形式)
//在整个树中添加元素<key,val>,用于初始化根结点
public void put(Key key,Value val){
root=put(root,key,val);
}
//在指定的子树x中添加元素<key,val>,并返回新的子树
public Node put(Node x,Key key,Value val){
//1.如果子树为空
if(x==null){
N++;
//创建一个新结点,用于根结点的初始化和左右结点的添加
return new Node(key,val,null,null);
}
//2.如果子树不为空
int cmp = key.compareTo(x.key);
if (cmp<0){
//比较key和x键的值,如果key小于x键的值,则继续遍历x的左子树
x.leftNode=put(x.leftNode,key,val);
}else if(cmp>0){
//如果key大于x键的值,则继续遍历x的右子树
x.rightNode=put(x.rightNode,key,val);
}else{
//如果key等于x键的值,则替换x的值为val即可
x.val=val;
}
return x;
}
从树中获取结点
步骤:很简单,只需要按照搜索二叉树的特点递归查找。如果新结点的值小于当前遍历结点的值,则继续遍历当前结点的左子结点,如果新结点的值大于当前遍历结点的值,则继续遍历当前结点的右子结点。
//在整个树中,根据键找出对应的值
public Value get(Key key){
return get(root,key);
}
//在子树x中,找出键为key的值
public Value get(Node x,Key key){
//如果x为空,证明没有找到
if (x==null){
return null;
}
int cmp = key.compareTo(x.key);
if (cmp<0){
//比较key和当前结点键的值,如果key小于x结点的键,则继续遍历x结点的左结点,找到就返回即可
return get(x.leftNode,key);
}else if(cmp>0){
//如果key大于当前结点的键,则继续遍历X结点的右结点,找到就返回即可
return get(x.rightNode,key);
}else{
//如果key等于当前结点的键,则返回当前结点的值
return x.val;
}
}
删除树中某一结点
步骤:
1.判断要删除的结点是否为空,如果是空,则返回空,如果不为空,则继续操作
2.找到要删除的结点的位置
3.判断要删除结点的子节点的情况(四种情况对应四种不同的操作)
4.找到该结点的右子树中的最小值作为最小结点(搜索二叉树的特点)
5.删除最小结点
6.用最小结点去代替要删除的结点
//在整个树中,删除指定键的结点
public Node delete(Key key){
return delete(root,key);
}
//在指定的子树中,删除指定键的结点,并返回删除后新的树
public Node delete(Node x,Key key){
//1.要删除的结点为空
if(x==null){
return null;
}
//2.要删除的结点不为空
//3.找到要删除的结点的位置
int cmp = key.compareTo(x.key);
if(cmp<0){
//如果要删除的key值比当前结点的键小,则继续遍历当前结点的左子树
x.leftNode= delete(x.leftNode,key);
}
else if(cmp>0){
//如果要删除的key值比当前结点的键值大,则继续遍历当前结点的右子树
x.rightNode=delete(x.rightNode,key);
}else{
//元素个数减一
N--;
//4.找到了要删除的结点的位置
//5.如果删除结点的左子树为空,则直接让删除结点的父节点指向删除结点的右子树
if (x.leftNode==null){
return x.rightNode;
}
//6.如果删除结点的右子树为空,则直接让删除结点的父节点指向删除结点的左子树
if (x.rightNode==null){
return x.leftNode;
}
//如果删除结点的左右子数都为空,则直接让删除结点的父节点指向空结点
if(x.rightNode==null && x.leftNode==null){
return null;
}
//7.如果删除结点的左右子树都不为空,则去找删除结点右子树的最小值(在右子树的左子树中找),用来替换当前删除的结点
Node minNode=x.rightNode;
while(minNode.leftNode!=null){
minNode=minNode.leftNode;
}
//8.删除最小元素,n代表倒数第二个元素
Node n=x.rightNode;
while(n.leftNode!=null){
//找到倒数第二个元素
if(n.leftNode.leftNode==null){
n.leftNode=null;
}
else{
n=n.leftNode;
}
}
//9.用minNode去替换要删除的X结点
x.key=minNode.key;
x.val=minNode.val;
}
return x;
}
测试
public class BinaryTest {
public static void main(String[] args) {
BinaryTree<Integer, String> tree = new BinaryTree<>();
tree.put(1,"张三");
tree.put(2,"李四");
tree.put(3,"王五");
System.out.println("树的元素总数是"+tree.size());
System.out.println("键为3的结点是"+tree.get(3));
tree.delete(3);
System.out.println("删除后键为3的结点是"+tree.get(3));
System.out.println("删除后树的元素总数是"+tree.size());
}
}
结果
相关术语:
父结点:指向当前结点的结点(一个结点有且仅有一个父节点)
左子结点:当前结点指向的结点,且该结点位于左侧
右子结点:当前结点指向的结点,且该结点位于右侧
兄弟结点:拥有同一个父结点的结点之间互相称作兄弟结点
叶子结点:没有子结点的结点称作叶子结点
内部结点:一棵树内,除了根节点和叶子结点之外的其他结点叫做内部结点
根结点:没有父结点的结点称作父结点,位于树的最上方
祖宗结点:从根节点指向到当前结点的路径所经过的所有结点称为当前结点的祖宗结点
子孙结点:从当前结点拓展出的所有结点,称为当前结点的子孙结点