二叉树的实现及其基础遍历(前序、中序、后序)实现
1.查找二叉树的实现
定义一个查找二叉树的类:
以键值对形式存储元素,让键(泛型)继承Comparable接口,方便在插入时使得结点的键之间的比较(有序)
public class BinaryTree<Key extends Comparable<Key>, Value> {
// 记录根结点
private Node root;
// 记录树中的结点个数
private int N;
//结点内部类
//插入删除方法
//基础遍历方法
}
定义内部类 结点类Node
// 定义结点内部类
private class Node {
public Key key;// 存储键值对的键
private Value value;// 存储键值对的值
public Node left;// 左子树
public Node right;// 右子树
public Node(Key key, Value value, Node left, Node right) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
}
}
定义方法实现向树中插入键值对(结点):
// 定义方法,向(整颗)树中插入键值对(结点)
public void put(Key key, Value value) {
root = put(root, key, value);// 调用重载方法向树中插入键值对(向整颗树插入时指定的树x传入根结点)
}
// 向指定的子树中插入键值对结点
private Node put(Node x, Key key, Value value) {
// 如果x子树为空,则直接将要插入的键值对赋值给x
if (x == null) {
N++;
return new Node(key, value, null, null);
}
// 如果x子树不为空,则比较要插入的键值对的键和x结点的键的大小
int cmp = key.compareTo(x.key);
// <0,即要插入的键小于x结点的键,继续找x结点的左子树
if (cmp < 0) {
x.left = put(x.left, key, value);// 让返回的结点成为x的左子树
} else if (cmp > 0) {
// >0,即要插入的键大于x结点的键,继续找x结点的右子树
x.right = put(x.right, key, value);// 让返回的结点成为x的右子树(在x==null处返回)
} else {
// cmp==0,则证明要插入的结点的键和x结点的键相同,直接将该结点的值替换即可
x.value = value;
}
// 插入完成后将树中的元素个数加1
// N++;//error,每次添加52行已经元素加1了
// 返回插入完成后的子树结点
return x;
}
定义方法实现根据键查找对应结点的值:
// 查找指定键的值(从根结点开始查找)
public Value get(Key key) {
return get(root, key);// 在整颗树中查找,即从根结点开始查找
}
// 从指定子树中查找指定的键对应的值
public Value get(Node x, Key key) {
// 如果指定的子树为空,则直接返回null
if (x == null) {
return null;
}
// 子树不为null,则比较要查找的键和树的大小
int cmp = key.compareTo(x.key);
if (cmp < 0) {
// 如果cmp<0,即要查找的键小于x结点的键,继续找x结点的左子树
return get(x.left, key);// 超找到要找的键对应的值,直接返回值
} else if (cmp > 0) {
// 如果cmp>0,即要查找的键小于x结点的键,继续找x结点的左子树
return get(x.right, key);
} else {
// 如果cmp=0,即要查找的键等于x结点的键,直接返回该结点的值即可
return x.value;
}
}
定义方法实现 删除指定键所对应的结点:
// 删除树中指定的键的结点
public void delete(Key key) {
delete(root, key);
}
// 删除指定子树x的键的结点,并返回该树
public Node delete(Node x, Key key) {
//如果x为空
if (x == null) {
return null;
}
//如果x不为空,则比较要删除的结点的键和x的键的大小
int cmp = key.compareTo(x.key);
if (cmp < 0) {
//要删除的结点的键小于x的键,则继续找x的左子树
x.left = delete(x.left, key);//返回的结点作为x的左子树
}else if (cmp > 0) {
//要删除的结点的键大于x的键,则继续找x的右子树
x.right = delete(x.right, key);//返回的结点作为x的右子树
}else {
//元素减1再删除
N--;
//删除的键和x的键相同,即x结点为要删除的结点(真正地删除操作)
//如果要删除的结点的左子树为null,则将右子树返回
if (x.left == null) {
return x.right;
}
//如果要删除的结点的右子树为null,则返回左子树(即让x结点的父结点指向x的左子树)
if (x.right == null) {
return x.left;
}
//查找右子树中的最小结点,即最小的左子树(记录最小结点)
Node minNode = x.right;//初始让x的右子树成为最小结点
while(minNode.left != null) {//查找右子树中的最小结点,即最小的左子树
minNode = minNode.left;
}
//删除右子树中的最小结点
Node n = x.right;
while(n.left != null) {//不是叶子结点,则继续向下找
if (n.left.left == null && n.left.right == null) {//如果n的左子树的左子树为空,则将n的左子树删除,即置为空
n.left = null;
} else if (n.left.left == null && n.left.right != null) {//最小结点还有右子树
n.left = n.left.right;//将最小结点的右子树作为最小结点的父结点的左子树
}else {
n = n.left;//变换n
}
}
//删除最小结点后,让x结点的左子树成为该结点的左子树
minNode.left = x.left;
//让x结点的左子树成为该结点的左子树
minNode.right = x.right;
//最后让x结点的父结点指向最小结点
x = minNode;//直接将最小结点赋值给x结点
}
return x;//返回该结点,即返回新的树x
}
2.实现二叉树的基础遍历
先序中序和后续的区别在于根结点的次序,先序遍历即先打印根结点(根-左-右),中序即(左-根-右),后续(左-右-根)
注:本文代码都为递归方法,非递归方法可使用栈来实现(类似于……可以说是模拟递归吧,后续整理了的话会单放一篇,因为相比起来递归更容易理解和实现一些)
先序遍历
先序遍历,即先输出根结点——再左子结点——再右子结点。
思路:获取树中的所有结点,按照先根后左子树再右子树的逻辑,存储到队列或列表中,最后遍历队列或列表输出。此处使用递归遍历法,将获取到的结点存储在队列中。
//获取指定的树x的所有键,并放到队列中(先序遍历)
public Queue<Key> preErgodic(){
Queue<Key> keys = new Queue<>();//创建队列,存储树的结点的键
preErgodic(root,keys);//调用重载方法,查找树中的所以键
return keys;//返回存储了树中节点的键的队列
}
public void preErgodic(Node x, Queue keys) {
//判断x树是否为空
if (x == null) {
return;
}
//不为空则将x(先根结点)结点的键存入队列中
keys.enqueue(x.key);
//判断x的左子树(再左子树)是否为空
if (x.left != null) {
//不为空则递归遍历左子树
preErgodic(x.left, keys);
}
//(最后右子树)判断x的右子树是否为空
if (x.right != null) {
//不为空则递归遍历右子树
preErgodic(x.right,keys);
}
}
中序遍历
同理,中序遍历为:先左子树——再根结点——最后右子树
//获取指定的树x的所有键,并放到队列中(中序遍历)
public Queue<Key> midErgodic(){
//创建队列对象
Queue<Key> keys = new Queue<>();
//调用重载方法实现中序遍历
midErgodic(root,keys);
return keys;
}
public void midErgodic(Node x, Queue keys) {//先左子树、再根,再右子树
//先判断x树是否为空
if (x == null) {
return;
}
//先判断x的左子树是否为空,不为空则递归遍历左子树
if (x.left != null) {
midErgodic(x.left,keys);
}
//将当前结点的键存入队列中
keys.enqueue(x.key);
//判断x的右子树是否为空,不为空则递归遍历右子树
if (x.right != null) {
midErgodic(x.right, keys);
}
}
后续遍历
后序遍历为:先左子树——再右子树——最后根结点
//后续遍历
public Queue<Key> afterErgodic(){
//创建队列对象
Queue<Key> keys = new Queue<>();
//调用重载方法实现中序遍历
afterErgodic(root,keys);
return keys;
}
public void afterErgodic(Node x, Queue keys) {
//判断x是否为空
if (x == null) {
return;
}
//不为空
//判断x左子树是否为空,不为空则递归遍历左子树
if (x.left != null) {
afterErgodic(x.left, keys);
}
//再判断x的右子树是否为空,不为空则递归遍历右子树
if (x.right != null) {
afterErgodic(x.right, keys);
}
//将当前结点x的键存入队列中
keys.enqueue(x.key);
}
基础遍历测试
如树:
先序遍历结果:E B A D C G F H
中序遍历:A B C D E F G H
后续遍历:A C D B F H G E