基于java实现的二叉树经典的增删改问题,通俗易懂,适合入门

二叉树

什么是二叉树?


二叉树是一种很简单的结构,根节点延伸出左子节点和右子节点.这些子节点又有自己的子节点,自己当成子节点的根节点,不断延伸,可以无穷无尽.

二叉树的作用有很多,比如在一些数据的查找上设计得很巧妙,我们规定左节点存放的值小于根节点,右节点存放的值大于根节点,这样子在进行查找的时候,就可以让该传入的值直接与最开始的根节点进行比较,从判断去哪个子节点寻找,不用遍历所有节点.

遍历

二叉树的遍历又分为前序遍历,中序遍历,后序遍历,这些遍历往往会让人很乱,且看小编给你分析得头头是道.

前序遍历

前序遍历就是以根节点为起点,然后开始遍历其左子树,和右子树,每个子树又以这样的规则遍历,就得到了前序遍历.

比如上面图示,先遍历根节点,然后是左子树,之后是右子树,每个子树又以该规则为例子
我们可以遍历出一个路径14->12->11->13(左子树完毕)->15->8->16(右子树完毕)

中序遍历

中序遍历的遍历顺序是左子树->根节点->右子树,每个子树里面也以这种规则是遍历.
比如还是前序遍历的子树结构,先是以12为根节点的左子树,12又有左子树11,所以先输出11,之后是12,然后是13,这个时候12这棵左子树遍历完毕,然后才遍历到14,之后是14的右子树15,15也有左子树,所以显示8,然后是15,之后才是16.
所以整体的遍历顺序是11->12->13->14->8->15->16

后序遍历

后序遍历的顺序是 左子树->右子树->根节点,每个子树也要以这种规则遍历.
比如还是上面的图:
14的左子树是12,12的左子树是11,所以先输出11,之后是12的右子树13,然后才是12,12遍历完毕后,到14的右子树15,15的左子树是8,所以先输出左子树,然后是右子树16,之后才是根节点15,然后才是15的父节点14.
所以,该顺序数值是: 11->13->12->8->16->15->14

代码分析

代码其实很简单,我们从上面的分析可以知道,这些都可以用递归去实现,只是递归的位置不一样,才造就了遍历顺序的不同:

public class Search {

    private List<Integer> list=new ArrayList<>();

//    树结构
    public static class Node{
        int value;
        Node left;
        Node right;

        public Node(int v,Node l,Node r){
            this.value=v;
            this.left=l;
            this.right=r;
        }

    }

    //前序遍历
    public List<Integer> forwardFind(Node node){

        list.add(node.value);

        if (node.left!=null){
            forwardFind(node.left);
        }
        if (node.right!=null){
            forwardFind(node.right);
        }
        return this.list;
    }

    //中序遍历
    public List<Integer> midleFind(Node node){
        if (node.left!=null){
            midleFind(node.left);
        }
        list.add(node.value);
        if (node.right!=null){
            midleFind(node.right);
        }
        return list;
    }

    //后序遍历
    public List<Integer> reverFind(Node node){
        if (node.left!=null){
            reverFind(node.left);
        }
        if (node.right!=null){
            reverFind(node.right);
        }
        list.add(node.value);
        return list;
    }

}

class Test{
    public static void main(String[] args) {
        Search.Node nodel1 = new Search.Node(2,null,null);
        Search.Node nodel2 = new Search.Node(31,null,null);
        Search.Node noder3 = new Search.Node(35,null,null);
        Search.Node noder4 = new Search.Node(47,null,null);
        Search.Node nodel = new Search.Node(30,nodel1,nodel2);
        Search.Node noder = new Search.Node(36,noder3,noder4);
        Search.Node node = new Search.Node(32, nodel, noder);
        Search search = new Search();
        List<Integer> integers = search.reverFind(node);
        for (Integer i :
                integers) {
            System.out.println(i);
        }


    }
}

存放节点

    public void put(int value){
        if (headNode==null){
            System.out.println("根节点为空");
            return;
        }
        put(headNode,value);
        modCount=0;
    }

    private void put(Node node, int value) {

        if (node.value==value || modCount==1)
            return;
        if (node.left==null && value<node.value){
            node.left= new Node(value, null, null);
            modCount++;
        }
        if (node.right==null && value> node.value){
            node.right=new Node(value,null,null);
            modCount++;
        }
        if (node.left!=null){
            put(node.left,value);
        }
        if (node.right!=null){
            put(node.right,value);
        }

    }

删除节点

代码分析

删除节点其实听起来是挺简单的,但是你会被绕乱,因为我们涉及到递归,所以要考虑到底是先删除根节点还是先删除子节点.
我们先来看看错误版本

//删除节点
    public void delNode(Node node,int value) {

        if (node.value==value){
            node=null;
            return ;
        }
        if (node.left!=null){
            delNode(node.left,value);
        }
        if (node.right!=null){
            delNode(node.right,value);
        }
    }

这样子你会不会觉得很完美,一个节点进来,先判断当前节点是否与要删除的值相等,如果不是,直接判断左右子节点进行递归.
但是这样的删除逻辑你倒是要注意了:
这里假设传入的node是根节点root,需要删除的值为30,而我们的树结构是上一小节的树结构,数据也一样,
接下来我们直接来debug:

  1. 首先是root节点是2,该不与30相等,所以进入下一步,判断node.left是否为空,不为空,则直接进入delNode(node.left,value)这个递归函数,注意看在进入回调之前的node怎样的:

image.png

  1. 进入后,再看该node是怎样的:

image.png
你会发现node改变了,跳到了30所在节点,也就是此刻root节点变为了30.
之后就执行代码逻辑,将root置于null,然后return,乍一看没啥问题,可是往往大问题就出现return上

  1. 因为此刻return是返回上一级函数,也就是最开始那个函数执行完delNode(node.left,value)之后,这个时候你会发现,跳出该函数后node变量又变为:

image.png
这回问题了吧?node跳回根节点是32的时候,发现其30的那个子节点还在,并没有删除,这是什么情况?
这样bug就出来了,你会发现无论你怎么下去,30这个节点一直都删除不了,但是逻辑感觉是没有什么错误的.

  1. 为什么会这样的问题?

首先你得想一下,我们删除节点其实并不是真实将数据给删除了,这个数据还是会存在内存里,只不过是将指向该数据的引用给删除.所以当前在进入递归的时候,这个形参node其实只是一个引用,如果你直接判断该node是为想要删除的节点,那么只是将指向该节点的引用node指向了空.而没有将该节点的父节点的子节点引用消除,所以就造成了等你返回的时候,父节点的子节点引用还是存在.
比如我举个例子:

你看上面这张图,如果是按照先判断当前节点来说,我们要删除11,那么这个时候,虽然可以指向了null,但是只是将承接11的变量引用指向了node,而没有真实的将12的左子节点引用删除,这就导致了返回的时候12的左子节点引用一种存在.也就是11没有删除.

  1. 所以小伙伴大概都猜到,我们要删除的11的话,其实要把12的左子节点引用删除,也就是12指向11的箭头,这个时候,才真正的删除11在该树里的引用,如果遍历树的时候是找不到11了.

上面的场景可以模拟一下: 如果递归跳到12这个节点,不是判断12节点本身,而是将12节点的左右节点判断,也就是将12.left删除,就可以取消指向11的引用.

  1. 所以真正的代码是这样的:
    //
    public void del(Node node,int value){
        if (node.left!=null){
            if (node.left.value==value) {
                node.left = null;
                return;
            }
            del(node.left,value);
        }
        if (node.right!=null){
            if (node.right.value==value){
                node.right=null;
                return;
            }
            del(node.right,value);
        }
    }

顺序存储二叉树

如果你存储二叉树时是按照数组来存储的,那么需要你将数组按照二叉树的方式遍历出来,也就是前序后序中序遍历
image.png
这里的n是下标

    //前序遍历二叉树数组
    private List<Integer> ArraySearch(int index,int[] a){
        if (a.length == 0){
            return list;
        }
        list.add(a[index]);
        if (index*2+1<a.length){
            //左子树遍历
            ArraySearch(index*2+1,a);
        }
        if (index*2+2<a.length){
            ArraySearch(index*2+2,a);
        }
        return list;
    }

经典问题(折纸问题)

要求一个纸张向上对折,然后在平摊出来,若是折痕往下,记为下,折痕往上,记为上:
image.png
要求输入一个整合n,然后输出相应折痕的中序遍历.

其实这个问题就可以演化成二叉树问题,你会发现,第一次折痕是down,若是看成root节点的话,那么以后每次对折,左节点就是down折痕,右节点就是up折痕.
image.png
而且,一次对折产生一个层次节点,比如两次对折就产生两个层次的节点,一共是三个节点.
那么这个问题就变得简单了.

import sun.misc.Queue;

import java.util.List;

public class PageTree {

    private static Node head;

    public static class Node<T>{
        T item;
        Node<T> left;
        Node<T> right;
        public Node(T item,Node<T> left,Node<T>right){
            this.item=item;
            this.left=left;
            this.right=right;
        }
    }

    public static Node<String> create(int n) throws InterruptedException {
        Node<String> root=null;
        for (int i = 0; i < n; i++) {
            if (i==0){
                root=new Node<>("down",null,null);
                continue;
            }
            Queue<Node<String>> nodeQueue = new Queue<>();
            nodeQueue.enqueue(root);
            while (!nodeQueue.isEmpty()){
                Node<String> deNode = nodeQueue.dequeue();
                if (deNode.left!=null){
                    nodeQueue.enqueue(deNode.left);
                }
                if (deNode.right!=null){
                    nodeQueue.enqueue(deNode.right);
                }
                if (deNode.left==null&&deNode.right==null){
                    deNode.left=new Node<>("down",null,null);
                    deNode.right=new Node<>("up",null,null);
                }
            }

        }
        return root;
    }

    public static void print(Node<String> node){
        System.out.println(node.item);
        if (node.left!=null){
            print(node.left);
        }
        if (node.right!=null){
            print(node.right);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Node<String> stringNode = PageTree.create(3);
        PageTree.print(stringNode);
    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* * 基于链表节点实现二叉树节点 */ package dsa; public class BinTreeNode implements BinTreePosition { protected Object element;//该节点中存放的对象 protected BinTreePosition parent;//父亲 protected BinTreePosition lChild;//左孩子 protected BinTreePosition rChild;//右孩子 protected int size;//后代数目 protected int height;//高度 protected int depth;//深度 /**************************** 构造方法 ****************************/ public BinTreeNode() { this(null, null, true, null, null); } public BinTreeNode( Object e,//节点内容 BinTreePosition p,//父节点 boolean asLChild,//是否作为父节点的左孩子 BinTreePosition l,//左孩子 BinTreePosition r)//右孩子 { size = 1; height = depth = 0; parent = lChild = rChild = null;//初始化 element = e;//存放的对象 //建立与父亲的关系 if (null != p) if (asLChild) p.attachL(this); else p.attachR(this); //建立与孩子的关系 if (null != l) attachL(l); if (null != r) attachR(r); } /**************************** Position接口方法 ********************************/ //返回当前节点中存放的对象 public Object getElem() { return element; } //将对象obj存入当前节点,并返回此前的内容 public Object setElem(Object obj) { Object bak = element; element = obj; return bak; } /**************************** BinTreePosition接口方法 *************************/ //判断是否有父亲(为使代码描述简洁) public boolean hasParent() { return null != parent; } //返回当前节点的父节点 public BinTreePosition getParent() { return parent; } //设置当前节点的父节点 public void setParent(BinTreePosition p) { parent = p; } //判断是否为叶子 public boolean isLeaf() { return !hasLChild() && !hasRChild(); } //判断是否为左孩子(为使代码描述简洁) //若当前节点有父亲,而且是左孩子,则返回true;否则,返回false public boolean isLChild() { return (hasParent() && this == getParent().getLChild()) ? true : false; } //判断是否有左孩子(为使代码描述简洁) public boolean hasLChild() { return null != lChild; } //返回当前节点的左孩子 public BinTreePosition getLChild() { return lChild; } //设置当前节点的左孩子(注意:this.lChild和c.parent都不一定为空) public void setLChild(BinTreePosition c) { lChild = c; } //判断是否为右孩子(为使代码描述简洁) //若当前节点有父亲,而且是右孩子,则返回true;否则,返回false public boolean isRChild() { return (hasParent() && this == getParent().getRChild()) ? true : false; } //判断是否有右孩子(为使代码描述简洁) public boolean hasRChild() { return null != rChild; } //返回当前节点的右孩子 public BinTreePosition getRChild() { return rChild; } //设置当前节点的右孩子(注意:this.rChild和c.parent都不一定为空) public void setRChild(BinTreePosition c) { rChild = c; } //返回当前节点后代元素的数目 public int getSize() { return size; } //在孩子发生变化后,更新当前节点及其祖先的规模 public void updateSize() { size = 1;//当前节点 if (hasLChild()) size += getLChild().getSize();//左子树的规模 if (hasRChild()) size += getRChild().getSize();//右子树的规模 if (hasParent()) getParent().updateSize();//递归更新各个真祖先的规模记录 } //返回当前节点的高度 public int getHeight() { return height; } //在孩子发生变化后,更新当前节点及其祖先的高度 public void updateHeight() { height = 0;//先假设没有左、右孩子 if (hasLChild()) height = Math.max(height, 1+getLChild().getHeight());//左孩子 if (hasRChild()) height = Math.max(height, 1+getRChild().getHeight());//右孩子 if (hasParent()) getParent().updateHeight();//递归更新各个真祖先的高度记录 } //返回当前节点的深度 public int getDepth() { return depth; } //在父亲发生变化后,更新当前节点及其后代的深度 public void updateDepth() { depth = hasParent() ? 1+getParent().getDepth() : 0;//当前节点 if (hasLChild()) getLChild().updateDepth();//沿孩子引用逐层向下, if (hasRChild()) getRChild().updateDepth();//递归地更新所有后代的深度记录 } //按照中序遍历的次序,找到当前节点的直接前驱 public BinTreePosition getPrev() { //若左子树非空,则其中的最大者即为当前节点的直接前驱 if (hasLChild()) return findMaxDescendant(getLChild()); //至此,当前节点没有左孩子 if (isRChild()) return getParent();//若当前节点是右孩子,则父亲即为其直接前驱 //至此,当前节点没有左孩子,而且是左孩子 BinTreePosition v = this;//从当前节点出发 while (v.isLChild()) v = v.getParent();//沿左孩子链一直上升 //至此,v或者没有父亲,或者是父亲的右孩子 return v.getParent(); } //按照中序遍历的次序,找到当前节点的直接后继 public BinTreePosition getSucc() { //若右子树非空,则其中的最小者即为当前节点的直接后继 if (hasRChild()) return findMinDescendant(getRChild()); //至此,当前节点没有右孩子 if (isLChild()) return getParent();//若当前节点是左孩子,则父亲即为其直接后继 //至此,当前节点没有右孩子,而且是右孩子 BinTreePosition v = this;//从当前节点出发 while (v.isRChild()) v = v.getParent();//沿右孩子链一直上升 //至此,v或者没有父亲,或者是父亲的左孩子 return v.getParent(); } //断绝当前节点与其父亲的父子关系 //返回当前节点 public BinTreePosition secede() { if (null != parent) { if (isLChild()) parent.setLChild(null);//切断父亲指向当前节点的引用 else parent.setRChild(null); parent.updateSize();//更新当前节点及其祖先的规模 parent.updateHeight();//更新当前节点及其祖先的高度 parent = null;//切断当前节点指向原父亲的引用 updateDepth();//更新节点及其后代节点的深度 } return this;//返回当前节点 } //将节点c作为当前节点的左孩子 public BinTreePosition attachL(BinTreePosition c) { if (hasLChild()) getLChild().secede();//摘除当前节点原先的左孩子 if (null != c) { c.secede();//c脱离原父亲 lChild = c; c.setParent(this);//确立新的父子关系 updateSize();//更新当前节点及其祖先的规模 updateHeight();//更新当前节点及其祖先的高度 c.updateDepth();//更新c及其后代节点的深度 } return this; } //将节点c作为当前节点的右孩子 public BinTreePosition attachR(BinTreePosition c) { if (hasRChild()) getRChild().secede();//摘除当前节点原先的右孩子 if (null != c) { c.secede();//c脱离原父亲 rChild = c; c.setParent(this);//确立新的父子关系 updateSize();//更新当前节点及其祖先的规模 updateHeight();//更新当前节点及其祖先的高度 c.updateDepth();//更新c及其后代节点的深度 } return this; } //前序遍历 public Iterator elementsPreorder() { List list = new List_DLNode(); preorder(list, this); return list.elements(); } //中序遍历 public Iterator elementsInorder() { List list = new List_DLNode(); inorder(list, this); return list.elements(); } //后序遍历 public Iterator elementsPostorder() { List list = new List_DLNode(); postorder(list, this); return list.elements(); } //层次遍历 public Iterator elementsLevelorder() { List list = new List_DLNode(); levelorder(list, this); return list.elements(); } /**************************** 辅助方法 ****************************/ //在v的后代中,找出最小者 protected static BinTreePosition findMinDescendant(BinTreePosition v) { if (null != v) while (v.hasLChild()) v = v.getLChild();//从v出发,沿左孩子链一直下降 //至此,v或者为空,或者没有左孩子 return v; } //在v的后代中,找出最大者 protected static BinTreePosition findMaxDescendant(BinTreePosition v) { if (null != v) while (v.hasRChild()) v = v.getRChild();//从v出发,沿右孩子链一直下降 //至此,v或者为空,或者没有右孩子 return v; } //前序遍历以v为根节的(子)树 protected static void preorder(List list, BinTreePosition v) { if (null == v) return;//递归基:空树 list.insertLast(v);//访问v preorder(list, v.getLChild());//遍历左子树 preorder(list, v.getRChild());//遍历右子树 } //中序遍历以v为根节的(子)树 protected static void inorder(List list, BinTreePosition v) { if (null == v) return;//递归基:空树 inorder(list, v.getLChild());//遍历左子树 list.insertLast(v);//访问v inorder(list, v.getRChild());//遍历右子树 } //后序遍历以v为根节的(子)树 protected static void postorder(List list, BinTreePosition v) { if (null == v) return;//递归基:空树 postorder(list, v.getLChild());//遍历左子树 postorder(list, v.getRChild());//遍历右子树 list.insertLast(v);//访问v } //层次遍历以v为根节的(子)树 protected static void levelorder(List list, BinTreePosition v) { Queue_List Q = new Queue_List();//空队 Q.enqueue(v);//根节点入队 while (!Q.isEmpty()) { BinTreePosition u = (BinTreePosition) Q.dequeue();//出队 list.insertLast(u);//访问v if (u.hasLChild()) Q.enqueue(u.getLChild()); if (u.hasRChild()) Q.enqueue(u.getRChild()); } } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值