java实现二叉树(打印直观,易于理解)

本文介绍了一种简单的Java二叉树实现方式,包括节点类、二叉树主体类及其核心方法,如插入、查找、删除节点及遍历等。通过实例演示了二叉树的基本操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java实现二叉树(较简单的实现)

二叉树的术语:

无序数组:查找删除慢,大小固定
有序数组:插入慢,删除慢
链表:插入和删除慢,查找慢

二叉树:树中每个节点最多只能有两个子节点,这样的树就称为二叉树
树:由边连接着节点构成

术语:
根: 树顶端的节点称为根。一棵树只有一个根。

父节点:每个节点(除了根)都恰好有一条边向上连接到另外一个节点,
上面的这个节点就叫做下面这个节点的“父节点”

子节点:每个节点都可能有一条或多条向下连接的其他节点,下面的这
些节点称为它的子节点

叶子节点:没有子节点的节点称为“叶子节点”

子树:每个节点都可以作为“子树”根,它和它所有的子孙节点都含在子树
中,就像在家族中那样,一个节点的子树包含了它的所有子孙。

注意:
1.在100w个数据项当中找一个数据项,最多只需要比较20层(次)就Ok了,
2.如果采用无序数组或者无序链表当中,查找100w个数据项的话,最多则
需要比较50w次比较
3.同理,如果在有序数组当中,查找很快,但是需要插入的时候同样需要
移动50w次



代码下载地址:

https://github.com/Jahvey/javaSECodingForWanda/tree/master/2017-12-21/TreeDemo01/src/wh

1.创建二叉树的节点类



/**
 * 创建一个二叉树的节点
 */
public class Node {
    public int iKey;
    public double iValue;//可以修改成其他的object类型,对象,或者数据项
    public Node leftChild;
    public Node rightChild;

    /**
     * 将该节点的详细信息打印出来
     */
    public void show(){
        System.out.print('[');
        System.out.print("iKey = " + iKey);
        System.out.print(',');
        System.out.print("iValue = " + iValue);
        System.out.print(']');
    }



}

2.创建二叉树主体类



import java.util.Stack;

public class Tree {
    private  Node root;

    public Tree() {
        this.root = null;
    }
public void insert(int key,double value){
        Node newNode=new Node();
        newNode.iKey=key;
        newNode.iValue=value;
        if (root==null){//tree is Empty
            root=newNode;
        }else {
            Node current=root;
            Node parent;
            while(true){
                parent=current;
                if (key<current.iKey){//如果要插入的数据key小于当前的数据项的话
                    current=current.leftChild;//更新current
                    if (current==null){
                        parent.leftChild=newNode;
                        return;
                    }
                }else {//如果要插入的数据key,大于或者等于当前的数据项,则往右边找
                    current=current.rightChild;
                    if (current==null){
                        parent.rightChild=newNode;
                        return;
                    }
                }
            }
        }

}

    /**
     * 删除的时候需要注意的几点:
     * 1.如果要删除的节点的右子节点不为空.(current.rightChild!=null)
     * 1.1删除的是parent.rightChild(父节点的右子节点),或者是
     * parent.leftChild(父节点的左子节点),则都可以将current节点的
     * 右子树的最左边的子节点移动到current节点的位置上。
     *
     * 2.如果要删除的节点的右子节点为空(current.rightChild==null)
     * 则直接将current=current.leftChild,即直接将要删除的节点的左
     * 子节点替换掉current节点的位置,即可。
     *
     * @param key
     * @return
     */
        public  boolean delete(int key){
            Node current=root;
            Node parent=root;//需要父节点作为判断的依据
            boolean isLeftChild=true;//添加这个数据项的目的是为了判断current和parent的左右子树关系

            //如果跳出这个while循环,则找到了,可以删除该节点
            while(current.iKey!=key){
                parent=current;//先保存一份数据到parent中
                if (key<current.iKey){
                    //如果要查找的值小于当前节点的值,则往左找,修改isLeftChild的值
                    isLeftChild=true;//修改标签,确定是往parent=current的左边找,同时更新parent的值
                    current=current.leftChild;
                }else {
                    //往右边搜索,不存在key==current.iKey的情况,因为while条件的限定,就会直接跳出while循环,
                    //,执行delete操作
                    isLeftChild=false;
                    current=current.rightChild;
                }

                //如果没找到,即current==null,直接return false
                if (current==null)return  false;//没有找到要删除的数据项,或者是节点

            }

            //由于程序的执行顺序,跳出while循环,则一定表示找到了对应的数据项所对应的节点

            /**
             * 现在进入删除的几种情况:
             * 1.要删除的节点是叶子节点
             * 1.1如果要删除的节点恰好是根节点,则root=null;
             * 1.2如果要删除的叶子节点是父节点的左节点,则将父节点的左子节点置为Null
             * 1.3如果要删除的叶子节点是父节点的右子节点,则将父节点的右子节点置为null
             *
             * 2.如果要删除的节点的右子节点为null
             * 2.1如果要删除的节点恰好是根节点,则root=current.leftChild
             *2.2如果要删除的节点是该节点父节点的左子节点,则将该父节点的左子节点的指针
             * 指向当前节点的左子节点
             * 2.3 如果要删除的节点是该节点的父节点的右子节点,则将该父节点的右子节点
             * 的指针指向当前节点的左子节点
             *
             *
             * 3.如果要删除的节点的左子节点为null
             * 3.1如果要删除的节点恰好是根节点,则root=current.rightChild
             * 3.2如果要删除的节点是父节点的左子节点,则将父节点的左子节点的指针指向
             * 当前节点的右子节点
             * parent.leftChild=current.rightChild;
             * 3.3如果要删除的节点是父节点的右子节点,则将父节点的右子节点的指针指向
             * 当前节点的右子节点
             * parent.rightChild=current.rightChild;
             *
             *
             * 4.要删除的节点,左右两个子树都有
             *
             *
             *
             */
            //要删除的节点是叶子节点,直接删除
            if (current.leftChild==null&&current.rightChild==null){
                    if (current==null)root=null;//删除的是根节点,并且没有子节点
                     else if(isLeftChild){//删除的叶子节点是父节点的左子节点,则将父节点的左子节点置为Null
                        parent.leftChild=null;
                    }else {
                         //要删除的叶子节点是父节点的右子节点,则将该叶子节点的的父节点的右子节点置为null
                        parent.rightChild=null;
                    }
            }else  if(current.rightChild==null){
                //表明要删除的节点只有一个左子节点

                /**
                 * 要删除的节点只有一个左子节点
                 * 3种情况:
                 * 1.要删除的节点恰好是root节点,则root=current.leftChild
                 * 2.要删除的节点是当前节点父节点的左子节点,则将父节点的左子节点的指针指向
                 * 当前节点的左子节点 parent.leftChild=current.leftChild;
                 * 3.要删除的节点是当前节点父节点的右子节点,则将父节点的右子节点的指针指向
                 * 当前节点的左子节点(你没有看错!)
                 * parent.rightChild=current.leftChild;
                 *
                 *
                 */
                if(current==root)root=current.leftChild;
                else if(isLeftChild)parent.leftChild=current.leftChild;
                else parent.rightChild=current.leftChild;

            }else  if (current.leftChild==null){
                //要删除的节点只有一个右子节点
                if (current==root)root=current.rightChild;
                else  if (isLeftChild)parent.leftChild=current.rightChild;
                else parent.rightChild=current.rightChild;


            }else {
                //要删除的节点有两个子节点
                //调用getSuccessor(要删除的节点(while找到的current节点))
                Node successor=getSuccessor(current);
                //if ()
                if (current==root)root=successor;
                else if (isLeftChild)parent.leftChild=successor;
                else parent.rightChild=successor;

                //最后需要考虑到successor的左孩子,跟要删除的current的左孩子相关联
                successor.leftChild=current.leftChild;
            }
            return true;


        }


    /**
     * 则需要使用调用该方法获取要删除的节点的右子树的最左边的叶子节点,
     * 将其挪到要删除的current节点的位置上,则该方法需要传入要删除的
     * 节点的引用
     *向右子节点向下找继承者节点
     * @param delNode
     * @return
     */
        private  Node getSuccessor(Node delNode){
                //申明一个successor的原父节点
            //作用:便于修改指针引用,因为(successor.parent.leftChild=successParent)=null;才行
                Node successorParent=delNode;
               Node successor=delNode;
               // Node successor=delNode.rightChild;//指向要删除的节点的右子树的树根,进行迭代查找

                Node current=delNode.rightChild;//声明要查找的节点的临时变量
                //开始搜索,初始化搜索参数,当while循环结束的时候,表明找到了
            //要删除的节点的右子树,的最左边下面的叶子节点,才能跳出循环
                while(current!=null){
                    successorParent=successor;
                    successor=current;
                    //注意,这次是查找current左子节点的最下面的叶子节点
                    current=current.leftChild;
                }

                //进入到该行代码,表明已经跳出了while循环。
            // 并且找到了要删除的节点的右子树的最左边下面的叶子节点

            /**
             * 这里有种情况是如下所示;
             * 看着篇博客里面的几种情况
             * http://blog.csdn.net/wuwenxiang91322/article/details/12231657
             * 1.       root
             *         /     \
             *        a       b
             *      /  \
             *     c   d
             *        /  \
             *     null   e
             *  如图所示,如果要删除a节点,则根据以上的算法,就会successor的指针指向d
             *  此时,successor==delNode.rightChild如果要删除a节点,则只需要将d移动
             *  到a处即可。但是如果是如下情况:
             * 2.
             *          root
             *         /     \
             *        a       b
             *      /  \
             *     c   d
             *        /  \
             *       e    f
             *      /  \
             *     null g
             *
             *   则根据上面的算法,successor指向的节点是e,而delNode.rightChild为d
             *  successor!=delNode.rightChild,故还得将
             *  successorParent.leftChild=successor.rightChild;
             *  successor.rightChild=delNode.rightChild;
             *  即如图所示:
             *           root
             *         /     \
             *        e       b
             *      /  \
             *     c   d
             *        /  \
             *       g    f
             *则删除才算完成
             *
             */
            if (successor!=delNode.rightChild){
                   successorParent.leftChild=successor.rightChild;
                   successor.rightChild=delNode.rightChild;
            }
            return successor;

        }

    /**
     * 选择要查找的节点
     * @param key
     * @return
     */
    public Node find(int key){
        Node current=root;
        while (current.iKey!=key){
            if (key<current.iKey){
                current=current.leftChild;
            }else {
                current=current.rightChild;
            }
            //注意:这是必须的,要在while循环里面处理找不到的情况

            if (current==null)return null;

        }

        //代码若正确执行到这行,表示正确找到了要查找的节点
        return  current;


    }


    /**
     * 遍历方法类型
     * @param traverseType
     */
    public void traverse(int traverseType){
        switch (traverseType){
            case 1://从上至下,从左至右
                System.out.print("\nPreorder traversal:");
                PreOrder(root);
                break;
            case 2://从下至上,从左至右
                System.out.print("\nInorder traversal:");
                InOrder(root );
                break;
            case 3://从下至上,从右至左(从大到小
                System.out.print("\nPostorder traversal:");
                postOrder(root);
                break;

        }
        System.out.println();




    }


    private  void PreOrder(Node localRoot){
        //从上到下,从左到右的遍历
        if (localRoot!=null){
            System.out.print(localRoot.iKey+" ");
            PreOrder(localRoot.leftChild);
            PreOrder(localRoot.rightChild);
        }


    }


    private void InOrder(Node localRoot){

        //从下至上,从左至右(从小到大)
        if (localRoot!=null){
            InOrder(localRoot.leftChild);
            System.out.print(localRoot.iKey+" ");
            InOrder(localRoot.rightChild);
        }

    }

    private  void postOrder(Node localRoot){
        //从下至上,从右至左(从大到小

        if (localRoot!=null){
            postOrder(localRoot.rightChild);
            postOrder(localRoot.leftChild);
            System.out.print(localRoot.iKey+" ");
        }
    }


    /**
     * 使用树形结构显示
     */
    public void displayTree(){
        Stack globalStack=new Stack();
        globalStack.push(root);
       // int nBlank=32;
        int nBlank=32;
        boolean isRowEmpty=false;
        String dot="............................";
        System.out.println(dot+dot+dot);
        while (isRowEmpty==false){
            Stack localStack=new Stack();
            isRowEmpty=true;
            for (int j=0;j<nBlank;j++)//{
            {

                System.out.print("-");
            }
            while (globalStack.isEmpty()==false){
                //里面的while循环用于查看全局的栈是否为空
                Node temp=(Node)globalStack.pop();
                if (temp!=null){
                    System.out.print(temp.iKey);

                    localStack.push(temp.leftChild);
                    localStack.push(temp.rightChild);
                    //如果当前的节点下面还有子节点,则必须要进行下一层的循环
                    if (temp.leftChild!=null||temp.rightChild!=null){
                        isRowEmpty=false;

                    }
                }else {
                    //如果全局的栈则不为空
                    System.out.print("#!");
                    localStack.push(null);
                    localStack.push(null);

                }


                //打印一些空格
                for (int j=0;j<nBlank*2-2;j++){
                    //System.out.print("&");
                    System.out.print("*");
                }




            }//while end


            System.out.println();
            nBlank/=2;
            //这个while循环用来判断,local栈是否为空,不为空的话,则取出来放入全局栈中
            while (localStack.isEmpty()==false){
                globalStack.push(localStack.pop());
            }

            // }
        }//大while循环结束之后,输出换行
        System.out.println(dot+dot+dot);

    }





}

3.创建测试案例



import java.io.BufferedReader;
import java.io.InputStreamReader;

public class XMain {
    public  static String getString()throws Exception{

        InputStreamReader isr= new InputStreamReader(System.in);

        BufferedReader br=new BufferedReader(isr);
        return br.readLine();


    }

    public  static  char getChar()throws  Exception{
        return getString().charAt(0);
    }

    public static  int getInt()throws Exception {
        return Integer.parseInt(getString());
    }

    public static void main(String[] args)throws  Exception {

        int value;

        Tree the=new Tree();
        the.insert(50,1.5);
        the.insert(25,1.2);
        the.insert(75,1.7);
        the.insert(12,1.5);
        the.insert(37,1.2);
        the.insert(43,1.7);
        the.insert(30,1.5);

        the.insert(33,1.2);
        the.insert(87,1.7);
        the.insert(93,1.5);
        the.insert(97,1.5);


        while (true){
            System.out.print("Enter first letter of show,insert,find,delete or traversal");

            int choice=getChar();
            switch (choice){
                case 's'://显示
                    the.displayTree();
                    break;
                case 'i'://插入
                    System.out.print("Enter the value of insert:");
                    value=getInt();
                    the.insert(value,value+0.9);
                    break;

                case 'f'://查找

                    System.out.print("Enter value to find:");
                    value=getInt();
                    Node found=the.find(value);
                    if (found!=null){
                        System.out.print("Fount:");
                        found.show();
                        System.out.println();
                    }else {
                        System.out.print("Sorry,We don't find "+value+".");
                        System.out.println();
                    }

                    break;
                case 'd'://删除
                    System.out.print("Enter value to delete:");
                    value=getInt();
                    boolean didDelete=the.delete(value);

                    if (didDelete) System.out.print(value+"has been deleted!!"+"\n");
                    else System.out.print("Could not delete"+value+"!!No find "+value+".\n");
                    break;

                case 't'://遍历
                    System.out.print("Enter type 1,2 or 3:");
                    value=getInt();
                    the.traverse(value);
                    break;
                default:
                    System.out.println("Invalid entry!\n");
            }


        }

    }
}

4.运行结果如下所示
这里写图片描述

/* 这是一个在字符环境中,用ASCII码打印二叉树形状的算法。 在Linux控制台下写的例题,在DOS中稍有点乱。 采用层次遍法。 算法拙劣,仅供初学者做练习,(本人也是初学者,自学数据结构,刚好学到这二叉树这一章, 半路出家,基础差有点吃力头大,搞几个二叉的例题,却不知道其构造形状, 想调用图形API做个美观点的,却有点偏离本章的学习目的,只好用字符打印, linux环境中打印的还可以,DOS中有点不稳定,如果您有更好的算法一定不吝赐教。 我的QQ:137241638 mail:[email protected] */ #include <stdio.h> #include <stdlib.h> #define MaxSize 100 //Pstart是二叉树根结点在一行中的位置,一行最能打印124个字符,取其1/2。 //如果你的屏不够宽的话,可以输出文本文件里, aa.exe>>aa.txt #define Pstart 40 typedef struct bstnode { int key, data, bf; struct bstnode *lchild, *rchild; }BSTNode; typedef struct pnode //为打印二叉树建了一个结构。 { int key; //关键字数据1 int data; //关键字数据2 struct pnode *lchild, //左孩子 *rchlid, //右孩子 *parent; //父节点 int lrflag, //标记本节点是左孩子(等于0时),还是右孩子(等于1时) space, //存储本节点打印位置 level; //存储本节点所在层次。 }PBSTNode; /*建立二叉树。 用括号表示法表示二叉树字符串,创建二叉树。 */ BSTNode* CreateBSTNode(char *s) { char ch; BSTNode *p=NULL, *b=NULL, *ps[MaxSize]; int top=-1, tag=-1; ch=*s; while(ch) { switch(ch) { case '(':ps[++top]=p;tag=1;break; case ',':tag=2;break; case ')':top--;break; default: p=(BSTNode*)malloc(sizeof(BSTNode)); p->data=ch; p->lchild=p->rchild=NULL; if(b==NULL) b=p; else { switch(tag) { case 1:ps[top]->lchild=p;break; case 2:ps[top]->rchild=p;break; } } } ch=*(++s); } return b; } //用适号表示法打印二叉树。 void DispBSTNode(BSTNode *b) { if(b!=NULL) { printf("%d",b->key); if(b->lchild!=NULL||b->rchild!=NULL) { printf("("); DispBSTNode(b->lchild); if(b->rchild!=NULL)printf(","); DispBSTNode(b->rchild); printf(")"); } } } int BSTNodeHeight(BSTNode *b) { int lchildh,rchildh; if(b==NULL)return 0; else { lchildh=BSTNodeHeight(b->lchild); rchildh=BSTNodeHeight(b->rchild); return (lchildh>rchildh)?(lchildh+1):(rchildh+1); } } /*建立一个二叉树打印结点的信息, 只被int CreatePBSTNode(BSTNode *b,PBSTNode *pqu[])调用*/ void SetPBSTNodeInfo(BSTNode *b,PBSTNode *parent,PBSTNode *pb,int level,int lrflag) { int f=3; pb->data=b->data; pb->key =b->key; pb->parent=parent; pb->level=level; pb->lrflag=lrflag; pb->space=-1; } /*用层次遍历法,BSTNode结构存储的二叉树转换为,PBSTNode结构的二叉树*/ int CreatePBSTNode(BSTNode *b,PBSTNode *pqu[]) { BSTNode *p; BSTNode *qu[MaxSize]; int front=-1, rear=-1; rear++; qu[rear]=b; pqu[rear]=(PBSTNode*)malloc(sizeof(PBSTNode)); SetPBSTNodeInfo(b,NULL,pqu[rear],1,-1); while(rear!=front) { front++; p=qu[front]; if(p->lchild!=NULL) { rear++; qu[rear]=p->lchild; pqu[rear]=(PBSTNode*)malloc(sizeof(PBSTNode)); SetPBSTNodeInfo(p->lchild,pqu[front],pqu[rear],pqu[front]->level+1,0); } if(p->rchild!=NULL) { rear++; qu[rear]=p->rchild; pqu[rear]=(PBSTNode*)malloc(sizeof(PBSTNode)); SetPBSTNodeInfo(p->rchild,pqu[front],pqu[rear],pqu[front]->level+1,1); } } return rear; } //打印一层结点,及该层结点与父结点的连线路径。 void PBSTNodePrint_char(PBSTNode *pb[],int n,int h) { int l=-1, r=0, i,j,k, end; char c; PBSTNode *p; if(n<=0||h<=0) { return; } else if(pb[0]->level==1) { for(i=0;i<pb[0]->space;i++) printf(" "); printf("%c",pb[0]->data); printf("\n"); return; } h=h-pb[0]->level+2; for(k=0;k<h;k++) { j=0; l--; r++; for(i=0;i<n;i++)//打印线条 { p=pb[i]; end=(p->lrflag==0)?l:r; end+=p->parent->space; for(;j<end;j++) printf(" "); c=(p->lrflag==0)?'/':'\\'; printf("%c",c); } printf("\n"); } for(i=0;i<n;i++)//计算本层结点打印位置 { p=pb[i]; if(p->lrflag==0) p->space=p->parent->space+l; else p->space=p->parent->space+r; } for(i=0,j=0;i<n;i++)//打印关键字数据 { p=pb[i]; for(;j<p->space;j++) printf(" "); printf("%c",p->data); } printf("\n"); } //循环打印所有层的数据 void DispBTree(BSTNode *b) { int n,i,j,high, level; PBSTNode *p; PBSTNode *pqu[MaxSize]; PBSTNode *levelpqu[MaxSize]; n=CreatePBSTNode(b,pqu); high=BSTNodeHeight(b); j=0; level=1; pqu[0]->space=Pstart; for(i=0;i<=n;i++) { p=pqu[i]; if(p->level==level) { levelpqu[j]=p; j++; } else { PBSTNodePrint_char(levelpqu,j,high); level=p->level; j=0; levelpqu[j]=p; j++; } } PBSTNodePrint_char(levelpqu,j,high); } void main() { int iDepth=0, iWidth=0, iCount=0; char *str1="A(B(D,E(H,X(J,K(L,M(T,Y))))),C(F,G(X,I)))"; char *str2="A(B(D(,G)),C(E,F))"; BSTNode *b=CreateBSTNode(str1); DispBSTNode(b);printf("\n"); iDepth=BSTNodeHeight(b); printf("Depth:%d\n",iDepth); DispBTree(b); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

deywós

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值