使用Java实现二叉树的添加,删除,获取以及遍历

一段来自百度百科的对二叉树的解释:

在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。

一棵深度为k,且有2^k-1个节点的二叉树,称为满二叉树。这种树的特点是每一层上的节点数都是最大节点数。而在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则此二叉树为完全二叉树。具有n个节点的完全二叉树的深度为floor(log2n)+1。深度为k的完全二叉树,至少有2k-1个叶子节点,至多有2k-1个节点。

二叉树的结构:

image

二叉树节点的声明:

  static final class Entry<T extends Comparable<T>>{ //保存的数据
        private T item; //左子树
        private Entry<T> left; //右子树
        private Entry<T> right; //父节点
        private Entry<T> parent;
        Entry(T item,Entry<T> parent){ this.item = item; this.parent = parent;
        }
    }

类属性:

    //根节点
    private Entry<T> root; 
    //数据量
    private int size = 0;

二叉树添加数据:

/**
     * 添加元素
     * @param item 待添加元素
     * @return 已添加元素
     */
    public T put(T item){
        //每次添加数据的时候都是从根节点向下遍历
        Entry<T> t = root;
  if (t == null){
            //当前的叉树树的为空,将新节点设置为根节点,父节点为null
            root = new Entry<>(item,null);       size++; 
            return root.item;
        }
        //自然排序结果,如果传入的数据小于当前节点返回-1,大于当前节点返回1,否则返回0
        int ret = 0;
        //记录父节点
        Entry<T> p = t;
        while (t != null){
            //与当前节点比较
            ret = item.compareTo(t.item);
            p = t;
            //插入节点比当前节点小,把当前节点设置为左子节点,然后与左子比较,以此类推找到合适的位置
            if (ret < 0)
                t = t.left;
            //大于当前节点
            else if (ret > 0)
                t = t.right;
            else {
                //相等就把旧值覆盖掉
                t.item = item;
                return t.item;
            }
        }
        //创建新节点
        Entry<T> e = new Entry<>(item,p);
        //根据比较结果将新节点放入合适的位置
        if (ret < 0)
            p.left = e;
        else
            p.right = e;     size++;
        return e.item;
    }

在put的过程中,使用Comparable<T>中的compareTo来比较两个元素的大小的,所以在二叉树中存储的元素必须要继承Comparable<T> 类,覆写compareTo方法。

二叉树删除数据

/**
     * 删除元素
     * 删除元素如果细分的话,可以分为4中:没有子节点,只有左节点,只有右节点,有两个子节点
     * 1)没有子节点这种情况比较简单,直接删除就可以了
     * 2)只有左节点或右节点,这两种情况处理方式是一致的,只是方向相反,所以在一起讲了,
     * 将删除节点的父节点的左节点(右节点)指向删除节点的子节点,将左节点(右节点)指向删除节点的父节点
     * 3)有两个子节点,这种情况相对来说比较复杂一点:
     * 找到删除节点的前驱节点或后继节点,然后将前驱或后继节点的值赋给删除节点,然后将前驱或后继节点删除。本质是删除前驱或后继节点
     * 前驱节点的特点:
     * 1)删除的左子节点没有右子节点,那么左子节点即为前驱节点
     * 2)删除节点的左子节点有右子节点,那么最右边的最后一个节点即为前驱节点
     * 后继节点的特点:
     * 与前驱节点刚好相反,总是右子节点,或则右子节点的最左子节点;例如:删除节点为c ,那么前驱节点为 m,后继节点为n
     *                                          a
     *                                       /     \
     *                                    b          c
     *                                  / \         /  \
     *                                d    e       f    g
     *                              /  \  / \     / \   / \
     * @param item 删除元素          h   i  j  k   l   m n   o
     * @return 删除结果 */
    public boolean remove(T item){ //获取删除节点
        Entry<T> delEntry = getEntry(item); if (delEntry == null) return false; //删除节点的父节点
        Entry<T> p = delEntry.parent;
        size--; //情况1:没有子节点
        if (delEntry.left == null && delEntry.right == null){ //删除节点为根节点
            if (delEntry == root){
                root = null;
            }else {//非根节点 //删除的是父节点的左节点
                if (delEntry == p.left){
                    p.left = null;
                }else {//删除右节点
                    p.right = null;
                }
            } //情况2:删除的节点只有左节点
        }else if (delEntry.right == null){
            Entry<T> lc = delEntry.left; //删除的节点为根节点,将删除节点的左节点设置成根节点
            if (p == null) {
                lc.parent = null;
                root = lc;
            } else {//非根节点
                if (delEntry == p.left){//删除左节点
                    p.left = lc;
                }else {//删除右节点
                    p.right = lc;
                }
                lc.parent = p;
            } //情况3:删除节点只有右节点
        }else if (delEntry.left == null){
            Entry<T> rc = delEntry.right; //删除根节点
            if (p == null) {
                rc.parent = null;
                root = rc;
            }else {//删除非根节点
                if (delEntry == p.left)
                    p.left = rc; else p.right = rc;
                rc.parent = p;
            } //情况4:删除节点有两个子节点
        }else {//有两个节点,找到后继节点,将值赋给删除节点,然后将后继节点删除掉即可
            Entry<T> successor = successor(delEntry);//获取到后继节点
            delEntry.item = successor.item; //后继节点为右子节点
            if (delEntry.right == successor){ //右子节点有右子节点
                if (successor.right != null) {
                    delEntry.right = successor.right;
                    successor.right.parent = delEntry;
                }else {//右子节点没有子节点
                    delEntry.right = null;
                }
            }else {//后继节点必定是左节点
                successor.parent.left = null;
            } return true;
        } //让gc回收
        delEntry.parent = null;
        delEntry.left = null;
        delEntry.right = null; return true;
    } /**
     * 获取节点节点
     * @param item 获取节点
     * @return 获取到的节点,可能为null */
    private Entry<T> getEntry(T item){
        Entry<T> t = root; int ret; //从根节点开始遍历
        for (;t != null;){
            ret = item.compareTo(t.item); if (ret < 0)
                t = t.left; else if (ret > 0)
                t = t.right; else
                return t;
        } return null;
    } /**
     * 查找后继节点
     * @param delEntry 删除节点
     * @return 后继节点 */
    private Entry<T> successor(Entry<T> delEntry) {
        Entry<T> r = delEntry.right;//r节点必定不为空
        while (r.left != null){
            r = r.left;
        } return r;
    }

二叉树获取节点

   /**
     * 判断是否存在该元素
     * @param item 查找元素
     * @return 结果 */
    public boolean contains(T item){ return getEntry(item) != null;
    } /**
     * 获取节点节点
     * @param item 获取节点
     * @return 获取到的节点,可能为null */
    private Entry<T> getEntry(T item){
        Entry<T> t = root; int ret; //从根节点开始遍历
        for (;t != null;){
            ret = item.compareTo(t.item); if (ret < 0)
                t = t.left; else if (ret > 0)
                t = t.right; else
                return t;
        } return null;
    }

因为我这个例子是存储一个元素,获取到的元素和传进去的元素是一致的,所以我用contains方法来判断返回true即表示获取成功了,不返回获取到的结果了。当然,如果将entry存储的元素改为kv形式的话,就可以使用get方法了。

二叉树的遍历

二叉树的遍历可以分为三种:前序遍历、中序遍历和后续遍历,其中中序遍历是最常用的遍历方式,因为它遍历出来的结果的升序的。

前序遍历:

    /**
     * 前序遍历
     * @param e 开始遍历元素 */
    public void prevIterator(Entry<T> e){ if (e != null) {
            System.out.print(e.item + " ");
            prevIterator(e.left);
            prevIterator(e.right);
        }
    }</pre>

  中序遍历:

<pre style="color: rgb(0, 0, 0); font-family: &quot;Courier New&quot;; font-size: 12px; margin: 5px 8px; padding: 5px;">   /**
     * 中序遍历
     * @param e */
    public void midIterator(Entry<T> e){ if (e != null){
            midIterator(e.left);
            System.out.print(e.item + " ");
            midIterator(e.right);
        }
    }

后序遍历:

    /**
     * 后续遍历
     * @param e 开始遍历元素 */
    public void subIterator(Entry<T> e){ if (e != null) {
            subIterator(e.left);
            subIterator(e.right);
            System.out.print(e.item + " ");
        }
    }

demo完整的代码:也可以到我的github中下载代码:https://github.com/rainple1860/MyCollection

 /**
 * 二叉树
 * @param <T> */
public class BinaryTree<T extends Comparable<T>> { //根节点
    private Entry<T> root; //数据量
    private int size = 0; public BinaryTree(){} /**
     * 添加元素
     * @param item 待添加元素
     * @return 已添加元素 */
    public T put(T item){ //每次添加数据的时候都是从根节点向下遍历
        Entry<T> t = root;
        size++; if (t == null){ //当前的叉树树的为空,将新节点设置为根节点,父节点为null
            root = new Entry<>(item,null); return root.item;
        } //自然排序结果,如果传入的数据小于当前节点返回-1,大于当前节点返回1,否则返回0
        int ret = 0; //记录父节点
        Entry<T> p = t; while (t != null){ //与当前节点比较
            ret = item.compareTo(t.item);
            p = t; //插入节点比当前节点小,把当前节点设置为左子节点,然后与左子比较,以此类推找到合适的位置
            if (ret < 0)
                t = t.left; //大于当前节点
            else if (ret > 0)
                t = t.right; else { //相等就把旧值覆盖掉
                t.item = item; return t.item;
            }
        } //创建新节点
        Entry<T> e = new Entry<>(item,p); //根据比较结果将新节点放入合适的位置
        if (ret < 0)
            p.left = e; else p.right = e; return e.item;
    } public void print(){
        midIterator(root);
    } /**
     * 中序遍历
     * @param e */
    public void midIterator(Entry<T> e){ if (e != null){
            midIterator(e.left);
            System.out.print(e.item + " ");
            midIterator(e.right);
        }
    } /**
     * 获取根节点
     * @return 根节点 */
    public Entry<T> getRoot(){return root;} /**
     * 前序遍历
     * @param e 开始遍历元素 */
    public void prevIterator(Entry<T> e){ if (e != null) {
            System.out.print(e.item + " ");
            prevIterator(e.left);
            prevIterator(e.right);
        }
    } /**
     * 后续遍历
     * @param e 开始遍历元素 */
    public void subIterator(Entry<T> e){ if (e != null) {
            subIterator(e.left);
            subIterator(e.right);
            System.out.print(e.item + " ");
        }
    } /**
     * 获取节点节点
     * @param item 获取节点
     * @return 获取到的节点,可能为null */
    private Entry<T> getEntry(T item){
        Entry<T> t = root; int ret; //从根节点开始遍历
        for (;t != null;){
            ret = item.compareTo(t.item); if (ret < 0)
                t = t.left; else if (ret > 0)
                t = t.right; else
                return t;
        } return null;
    } /**
     * 判断是否存在该元素
     * @param item 查找元素
     * @return 结果 */
    public boolean contains(T item){ return getEntry(item) != null;
    } /**
     * 删除元素
     * 删除元素如果细分的话,可以分为4中:没有子节点,只有左节点,只有右节点,有两个子节点
     * 1)没有子节点这种情况比较简单,直接删除就可以了
     * 2)只有左节点或右节点,这两种情况处理方式是一致的,只是方向相反,所以在一起讲了,
     * 将删除节点的父节点的左节点(右节点)指向删除节点的子节点,将左节点(右节点)指向删除节点的父节点
     * 3)有两个子节点,这种情况相对来说比较复杂一点:
     * 找到删除节点的前驱节点或后继节点,然后将前驱或后继节点的值赋给删除节点,然后将前驱或后继节点删除。本质是删除前驱或后继节点
     * 前驱节点的特点:
     * 1)删除的左子节点没有右子节点,那么左子节点即为前驱节点
     * 2)删除节点的左子节点有右子节点,那么最右边的最后一个节点即为前驱节点
     * 后继节点的特点:
     * 与前驱节点刚好相反,总是右子节点,或则右子节点的最左子节点;例如:删除节点为c ,那么前驱节点为 m,后继节点为n
     *                                          a
     *                                       /     \
     *                                    b          c
     *                                  / \         /  \
     *                                d    e       f    g
     *                              /  \  / \     / \   / \
     * @param item 删除元素       h   i  j  k   l   m n   o
     * @return 删除结果 */
    public boolean remove(T item){ //获取删除节点
        Entry<T> delEntry = getEntry(item); if (delEntry == null) return false; //删除节点的父节点
        Entry<T> p = delEntry.parent;
        size--; //情况1:没有子节点
        if (delEntry.left == null && delEntry.right == null){ //删除节点为根节点
            if (delEntry == root){
                root = null;
            }else {//非根节点 //删除的是父节点的左节点
                if (delEntry == p.left){
                    p.left = null;
                }else {//删除右节点
                    p.right = null;
                }
            } //情况2:删除的节点只有左节点
        }else if (delEntry.right == null){
            Entry<T> lc = delEntry.left; //删除的节点为根节点,将删除节点的左节点设置成根节点
            if (p == null) {
                lc.parent = null;
                root = lc;
            } else {//非根节点
                if (delEntry == p.left){//删除左节点
                    p.left = lc;
                }else {//删除右节点
                    p.right = lc;
                }
                lc.parent = p;
            } //情况3:删除节点只有右节点
        }else if (delEntry.left == null){
            Entry<T> rc = delEntry.right; //删除根节点
            if (p == null) {
                rc.parent = null;
                root = rc;
            }else {//删除非根节点
                if (delEntry == p.left)
                    p.left = rc; else p.right = rc;
                rc.parent = p;
            } //情况4:删除节点有两个子节点
        }else {//有两个节点,找到后继节点,将值赋给删除节点,然后将后继节点删除掉即可
            Entry<T> successor = successor(delEntry);//获取到后继节点
            delEntry.item = successor.item; //后继节点为右子节点
            if (delEntry.right == successor){ //右子节点有右子节点
                if (successor.right != null) {
                    delEntry.right = successor.right;
                    successor.right.parent = delEntry;
                }else {//右子节点没有子节点
                    delEntry.right = null;
                }
            }else {//后继节点必定是左节点
                successor.parent.left = null;
            } return true;
        } //让gc回收
        delEntry.parent = null;
        delEntry.left = null;
        delEntry.right = null; return true;
    } /**
     * 查找后继节点
     * @param delEntry 删除节点
     * @return 后继节点 */
    private Entry<T> successor(Entry<T> delEntry) {
        Entry<T> r = delEntry.right;//r节点必定不为空
        while (r.left != null){
            r = r.left;
        } return r;
    } public int size(){return size;} public boolean isEmpty(){return size == 0;} public void clear(){
        clear(getRoot());
        root = null;
    } private void clear(Entry<T> e){ if (e != null){
            clear(e.left);
            e.left = null;
            clear(e.right);
            e.right = null;
        }
    } static final class Entry<T extends Comparable<T>>{ //保存的数据
        private T item; //左子树
        private Entry<T> left; //右子树
        private Entry<T> right; //父节点
        private Entry<T> parent;
        Entry(T item,Entry<T> parent){ this.item = item; this.parent = parent;
        }
    }

}

测试代码示例:

public static void main(String[] args) {
       BinaryTree<Integer> binaryTree = new BinaryTree<>(); //放数据
        binaryTree.put(73);
        binaryTree.put(22);
        binaryTree.put(532);
        binaryTree.put(62);
        binaryTree.put(72);
        binaryTree.put(243);
        binaryTree.put(42);
        binaryTree.put(3);
        binaryTree.put(12);
        binaryTree.put(52);

        System.out.println("size: " + binaryTree.size());
        binaryTree.put(52);
        System.out.println("添加相同元素后的size: " + binaryTree.size()); //判断数据是否存在
        System.out.println("数据是否存在:" + binaryTree.contains(12)); //中序遍历
        System.out.print("中序遍历结果: ");
        binaryTree.midIterator(binaryTree.getRoot());
        System.out.println(); //前序遍历
        System.out.print("前遍历结果: ");
        binaryTree.prevIterator(binaryTree.getRoot());
        System.out.println(); //后序遍历
        System.out.print("后续遍历结果: ");
        binaryTree.subIterator(binaryTree.getRoot()); //删除数据
        System.out.println();
        binaryTree.remove(62);
        System.out.println("删除数据后判断是否存在:" + binaryTree.contains(62)); //清空二叉树
 binaryTree.clear();
        System.out.print("清空数据后中序遍历: ");
        binaryTree.midIterator(binaryTree.getRoot());
    }

测试结果:

size: 10 
添加相同元素后的size: 10 
数据是否存在:true 
中序遍历结果: 3 12 22 42 52 62 72 73 243 532 
前遍历结果: 73 22 3 12 62 42 52 72 532 243 
后续遍历结果: 12 3 52 42 72 62 22 243 532 73 
删除数据后判断是否存在:false 
清空数据后中序遍历: 

纯手写的demo,有什么错误的地方欢迎指正,谢谢大家的阅读!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容