树---二叉树(创建,求高,大小,遍历)

是一种十分常见且应用广泛的一种数据结构,其形式如下图

20190325193631151.png    

基本概念和描述

如上图,结点层次从根开始定义,根的孩子是第二层,一共有四层,树中结点的最大层次称为树的深度或者高度,当前树深度为4。每个结点都有度,有几个子节点就有几个度,如根节点A度为2,D结点度为3,G,H,I,J度都为0。。。度为0的结点又被称为叶子结点或者终端结点,度不为0的结点成为非终端结点或者分支结点,树的度是树内各结点度的最大值

树的存储结构

简单的顺序存储已经不能满足树的实现,需要结合顺序存储和链式存储来实现,有三种便是方法

双亲表示法---每个结点有自己的数据值,还有一个指针指向其父亲结点,如下图

20190325194701689.png

假设根结点A的下标为0,它没有父亲结点,所以父亲结点为-1,结点B的下标为1,父亲结点A的下标为0,结点C的下标为2,父亲结点A的下标为0。。。J结点下标为9,其父亲结点E下标为4,这样就把整个树表示完全

虽然很容易表示,但是这样有一个缺点,找父亲结点简单,只需要通过指针找下标就可以,但是找孩子结点不容易,需要进行遍历,复杂度很高

孩子表示法---只关心每一个单独的结点,结点包含自己的值和子结点的下标,从左向右遍历

 watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6eTIzMzM=,size_16,color_FFFFFF,t_70

还是刚才的那个树,那么结点A从左到右有两个子结点B,C,两个结点下标分别为1,2;结点B有一个子结点D,下标为3。。。结点E有一个子结点J,下标为9.,如果是叶子结点,那么这个链表为空。。。n个头指针又组成一个线性表,采用顺序存储结构,放在一个一维数组中,这样整个树就被表示出来了

从父亲找孩子只需要遍历以父亲结点为头指针的链表即可,那么孩子找父亲呢?其实也是一样的,只需要再设置一个指针指向父亲结点就可以啦,也就是双向链表

孩子兄弟表示法---设置两个指针,分别指向第一个孩子和自己的右兄弟

20190325201513488.png

任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的有兄弟如果存在也是唯一的。因此设置两个指针,一个指向第一个孩子结点,一个指向自己的右兄弟,那么父亲找孩子,就先找自己的孩子结点,然后再孩子结点找兄弟结点,但是同样的,孩子找爹就会比较麻烦

二叉树---十分经典的数据结构---每个结点的度最大只能为2

一般建立一个二叉树结点都会有这几个元素:下标,元素值,左孩子结点,右孩子结点

class TreeNode {
    private int index;
    private String value;
    //左右孩子初始值默认为null,因为建立这个点时,我们不知道其孩子的信息
    private TreeNode leftChild = null;
    private TreeNode rightChild = null;
}

特殊二叉树

斜树---所有节点只有左子树(左斜树)或者只有右子树(右斜树),其实可以理解为线性数据结构

20190325203220238.png

满二叉树---所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的树成为满二叉树

20190325203152903.png

完全二叉树---编号为i的结点与同样深度的满二叉树中编号为i的结点位置完全相同的树成为完全二叉树

2019032520370874.png

二叉树的性质

性质1:在二叉树的第i层最多有2的(n-1)次方个结点

性质2:深度为k的二叉树最多有2的k次方-1个结点

性质3:对任何一个二叉树,如果其终端结点数为n0,度为2的结点数位n2,那么 n0 = n2 + 1

性质4:具有n个结点的完全二叉树深度为以2为底n的对数再加1

性质5:对一棵n个结点的完全二叉树的结点按层从左到右编号,对于任意一个结点i

           (1)如果i = 1,则结点i是二叉树的根,无双亲;如果i > 1,则其双亲是结点[i / 2]

           (2)如果i = 1, 则结点i无左孩子(结点i为叶子结点),否则其左孩子是结点2i

           (3)如果2i + 1 > n,则结点无右孩子,否则其右孩子结点为2i + 1

二叉树的遍历---前序遍历,中序遍历,后序遍历

前序遍历---根-->左-->右

如果二叉树为空,则空操作返回,否则先访问根节点,然后前序遍历左子树,然后前序遍历右子树

20190326145258833.png

代码实现:使用递归的方式,先输出根结点,再输出左结点,最后输出右结点

public static void preNode(TreeNode node) {
        if(node == null) {
            return;
        }
        System.out.println(node.getValue());
        preNode(node.leftChild);
        preNode(node.rightChild);
    }

中序遍历---左-->根-->右

规则是若树为空,则空操作返回,否则从根节点开始(注意不是先访问根节点),中序遍历根节点的左子树,然后访问根节点,最后中序遍历右子树

20190326145401367.png

代码实现:先输出左结点,再输出根结点,最后输出右结点

public static void midNode(TreeNode node) {
        if(node == null) {
            return;
        }

        midNode(node.leftChild);
        System.out.println(node.getValue());
        midNode(node.rightChild);
    }

后续遍历---左-->右-->根

若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后访问根节点

2019032615035996.png

代码实现:先输出左结点,再输出右结点,最后输出根结点

public static void subNode(TreeNode node) {
        if(node == null) {
            return;
        }

        subNode(node.leftChild);
        subNode(node.rightChild);
        System.out.println(node.getValue());
    }

层序遍历

树为空则空操作返回,否则从树的第一层即根节点开始访问,从上而下逐层遍历,在同一层,按从左到右的顺序对结点逐个访问,但是不推荐,比较复杂

20190326150458932.png

对于层序遍历,由于这种方式有点复杂,所以被抛弃,没有代码(手动狗头)

遍历的另一种方式---使用栈

之前的三种遍历我们使用的是单纯迭代的方式,而我们其实也可以使用一种数据结构---栈,利用栈后进先出的特点,再结合迭代进行树的遍历,以前序遍历为例进行分析

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6eTIzMzM=,size_16,color_FFFFFF,t_70

建立一个栈,将整个树存储在栈中,当树不为空的时候,不断弹出元素,并且是先弹栈,打印元素,再添加新元素进栈

首先将根节点A弹出栈,输出A的元素值,判断A是否有右子树,如果有则进栈,然后判断A是否有左子树,如果有则进栈。然后继续进行判断树是否为空,如果树不为空,则弹出元素,此时后添加的元素先被弹出,以上面的图为例,A既有左子树B,又有右子树C,那么先添加右子树,再添加左子树,因为按照栈后进先出的原则,第二次弹栈就先弹出左子树B,按照这种方式,我们就可以实现从根节点--->左子树--->右子树的遍历啦!

如图对根节点的左子树的遍历演示,右子树遍历方式和左子树是一样的

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6eTIzMzM=,size_16,color_FFFFFF,t_70

代码演示

public static void preNodeByStack(TreeNode node) {
        if(node == null) {
            return;
        }

        //创建一个栈,存放二叉树
        Stack<TreeNode> stack = new Stack<>();
        stack.push(node);

        //将二叉树中的元素不断弹栈和压栈,直至二叉树为空
        while(!stack.isEmpty()) {
            //先弹出根节点
            TreeNode n = stack.pop();
            System.out.println(n.getValue());

            //先将根节点的右孩子压栈,再将左孩子压榨,因为想先弹出左孩子,所以左孩子后进栈
            if(n.rightChild != null) {
                stack.push(n.rightChild);
            }
            if(n.leftChild != null) {
                stack.push(n.leftChild);
            }
        }
    }

树的创建

树的创建方式有很多种,在这里我们只介绍两种

1.直接创建结点,然后根据树的关系直接将每个结点左右子树的关系说明好

(1)创建A~J一共十个结点,并标好各自的下标

TreeNode nodeA = new TreeNode(1, "A");    ........

(2)将结点与自己的孩子进行联系

nodeA.leftChild = nodeB;

这样就构建好一棵树啦

代码演示

public static void createBinaryTree(TreeNode root) {
        if(root ==null) {
            return;
        }
        TreeNode nodeB = new TreeNode(2, "B");
        TreeNode nodeC = new TreeNode(3, "C");
        TreeNode nodeD = new TreeNode(4, "D");
        TreeNode nodeE = new TreeNode(5, "E");
        TreeNode nodeF = new TreeNode(6, "F");
        TreeNode nodeG = new TreeNode(7, "G");
        TreeNode nodeH = new TreeNode(8, "H");
        TreeNode nodeI = new TreeNode(9, "I");
        TreeNode nodeJ = new TreeNode(10, "J");

        root.leftChild = nodeB;
        nodeB.leftChild = nodeD;
        nodeB.rightChild = nodeE;
        nodeD.rightChild = nodeG;
        root.rightChild = nodeC;
        nodeC.rightChild = nodeF;
        nodeF.leftChild = nodeH;
        nodeF.rightChild = nodeI;
        nodeI.rightChild = nodeJ;
    }

2.将要创建的树构成一个完全二叉树,没有子树的时候用一个符号来代替,如#,下面红圈代表井号,使用前序遍历的方式创建

watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3N6eTIzMzM=,size_16,color_FFFFFF,t_70

此时,将树的所有元素依次放入ArrayList集合中,顺序为ABD##E#GC###FHI

然后将这个集合作为参数传入创建树的方法中,详细代码如下

  //给定一个集合,用集合的元素按顺序创建树
    public static void createBinaryTreePre(ArrayList<String> list) {
        createBinaryTree2(list.size(), list);
    }

    public static TreeNode createBinaryTree2(int size, ArrayList<String> list) {
        //集合为空时,返回null
        if (list.size() == 0) {
            return null;
        }
        
        //记录当前index,下标用于创建结点
        int index = size - list.size();
        TreeNode node;
        //获取list中的第一个元素
        String s = list.get(0);
        //如果该元素为"#",也就是说这个结点为空,那么移除当前元素,然后返回值为null的结点
        if (s.equals("#")) {
            node = null;
            list.remove(0);
            return node;
        }

        //如果结点不为空,那么创建一个新结点
        node = new TreeNode(index, s);
        //新结点的下标为0,说明是根结点
        if (index == 0) {
            root = node;
        }
        //无论是不是根结点,都要移除这个元素(目的是为了控制index的值)
        list.remove(0);
        //然后创建左结点和右结点,使用迭代的方式创建好整个树
        node.leftChild = createBinaryTree2(size, list);
        node.rightChild = createBinaryTree2(size, list);

        return node;
    }

关于树的相关基本操作

20190326230659403.png

1.建立二叉树的一个结点,每个结点有下标,元素值,左子树,右子树

2.计算二叉树的高度---二叉树的高度也就是将一条路走到黑的最长的距离,如上面的的树的的高度为5(A-C-F-I-J)

3.求二叉树的大小---也就是求二叉树一共有多少个结点

4.遍历二叉树

代码实现

import java.util.Stack;

public class BinaryTree {

    public static void main(String[] args) {
        TreeNode root = new TreeNode(1, "A");
        createBinaryTree(root);
        System.out.println("Height : " + getHeight(root));
        System.out.println("Size : " + getSize(root));
//        System.out.println("preNode : ");
//        preNode(root);
//        System.out.println("midNode : ");
//        midNode(root);
//        System.out.println("subNode :");
//        subNode(root);
        System.out.println("preNodeByStack : ");
        preNodeByStack(root);
    }

    //使用栈的方式遍历树---以前序遍历为例
    public static void preNodeByStack(TreeNode node) {
        if(node == null) {
            return;
        }

        //创建一个栈,存放二叉树
        Stack<TreeNode> stack = new Stack<>();
        stack.push(node);

        //将二叉树中的元素不断弹栈和压栈,直至二叉树为空
        while(!stack.isEmpty()) {
            //先弹出根节点
            TreeNode n = stack.pop();
            System.out.println(n.getValue());

            //先将根节点的右孩子压栈,再将左孩子压榨,因为想先弹出左孩子,所以左孩子后进栈
            if(n.rightChild != null) {
                stack.push(n.rightChild);
            }
            if(n.leftChild != null) {
                stack.push(n.leftChild);
            }
        }
    }


    //用后序遍历输出二叉树
    public static void subNode(TreeNode node) {
        if(node == null) {
            return;
        }

        subNode(node.leftChild);
        subNode(node.rightChild);
        System.out.println(node.getValue());
    }

    //用中序遍历遍历二叉树并输出
    public static void midNode(TreeNode node) {
        if(node == null) {
            return;
        }

        midNode(node.leftChild);
        System.out.println(node.getValue());
        midNode(node.rightChild);
    }

    //用前序遍历遍历二叉树并输出
    public static void preNode(TreeNode node) {
        if(node == null) {
            return;
        }
        System.out.println(node.getValue());
        preNode(node.leftChild);
        preNode(node.rightChild);
    }

    //求二叉树的总结点大小,即二叉树的大小
    public static int getSize(TreeNode node) {
        if(node == null) {
            return 0;
        }
        int i = getSize(node.leftChild);
        int j = getSize(node.rightChild);
        return 1 + i + j;
    }

    //求二叉树的高度
    public static int getHeight(TreeNode node) {
        if(node == null) {
            return 0;
        }
        int i = getHeight(node.leftChild);
        int j = getHeight(node.rightChild);
        return i > j ? i + 1 : j + 1;
    }

    //创建一个二叉树
    public static void createBinaryTree(TreeNode root) {
        if(root ==null) {
            return;
        }
        TreeNode nodeB = new TreeNode(2, "B");
        TreeNode nodeC = new TreeNode(3, "C");
        TreeNode nodeD = new TreeNode(4, "D");
        TreeNode nodeE = new TreeNode(5, "E");
        TreeNode nodeF = new TreeNode(6, "F");
        TreeNode nodeG = new TreeNode(7, "G");
        TreeNode nodeH = new TreeNode(8, "H");
        TreeNode nodeI = new TreeNode(9, "I");
        TreeNode nodeJ = new TreeNode(10, "J");

        root.leftChild = nodeB;
        nodeB.leftChild = nodeD;
        nodeB.rightChild = nodeE;
        nodeD.rightChild = nodeG;
        root.rightChild = nodeC;
        nodeC.rightChild = nodeF;
        nodeF.leftChild = nodeH;
        nodeF.rightChild = nodeI;
        nodeI.rightChild = nodeJ;
    }
}

//创建一个树


//结点类
class TreeNode {
    private int index;
    private String value;
    //左右孩子初始值默认为null,因为建立这个点时,我们不知道其孩子的信息
    TreeNode leftChild = null;
    TreeNode rightChild = null;

    public TreeNode(int index, String value) {
        this.index = index;
        this.value = value;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值