二叉树-树形结构-天然的查找语义

目录

二叉树-树形结构-天然的查找语义

二叉树的主要难点        

什么时候能用递归?

如何写递归程序:

一,为何要有树结构?

二,数据结构常用的树结构 logN

三,关于树的基础概念

概念

树与非树?

树中需要了解的概念

其余:

四,二叉树***

概念:

 二叉树的特点:

两种特殊的二叉树

五,二叉树的性质

六,二叉树的存储方式

七,二叉树的遍历

遍历:

深度优先遍历

广度优先遍历 

八,二叉树的其他基础操作

获取结点个数:

求一颗二叉树中叶子结点个数:

注意:

判断当前二叉树中是否含有寻找元素:

求二叉树的高度:

九,用代码实现一颗完整的二叉树,并实现其基础功能进行测试


二叉树的主要难点        


        二叉树的主要难点在于递归的使用。


什么时候能用递归?


        1.大问题可以拆分为小问题


        2.拆分后小问题和原问题除了规模不同,解决思路完全一样


        3.存在递归的终止条件,拆分拆到底的条件


如何写递归程序:


        千万不要纠结递归具体怎么运行的,要会使用递归的语义来解决问题。


一,为何要有树结构?


因为树结构可以高效的进行查找和搜索。


比如


        电脑中的文件系统,就是一个树形结构。


为什么不用线性结构?


        线性结构是构成文件逻辑上连续,成一条直线排列。如果检索特定的某个文件,就要遍历这个集合复杂度为O(n)。


        而用树结构查找特定文件其时间复杂度实际上就是文件数的深度logN。


因此,将数据使用树形结构来存储后,再次进行检索或查找,效率比线性结构高得多!



二,数据结构常用的树结构 logN

(回溯算法解决树形问题时,也是logN级别;排序:nlogN的排序算法,堆排序、快速排序、归并排序)以后看到logN就要首先想到树结构


BST(二分搜索树)- 二叉树的元素查找


平衡二分搜索树 - AVL(严格平衡树),红黑树(非严格平衡)



并查集


字符串 - 线段树;字典树(Trie)


三,关于树的基础概念

概念


        树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成的具有层次关系的集合,把它叫做树是因为它看起来像一颗倒挂着的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:


  • 有一个特殊的节点,成为根节点(A),根节点没有前驱节点。
  • 除根节点外,其余节点被分成M(M>0)个互不相交的集合T1、T2、…….、Tm,其中每一个集合T又是一颗与树类似的子树,每棵子树的根节点有且只有一个前驱,可以有0个或多个后继。
  • 树是递归定义的。


java生成树形结构并导出excel_数据结构

 

树与非树?


子树是不相交的。


        除了根节点外,每个结点有且仅有一个父结点。


        一颗N个结点的树有N-1条边。


上图中,图中每个单元称为节点,A,B都称为节点


树中需要了解的概念


a,节点的度:该节点含有的子树的个数就称为节点的度


        A节点的度6


        D节点的度1


        F节点的度3


b,树的度:该树中最大的结点的度就是该树的度


        上图中的树度为6,成为6叉树


c,叶子结点(终端节点):度为零的节点称为叶子结点


        上图中的BCHIPQKLMN都属于叶子结点


d,对于A和B这两个结点而言


        双亲结点/父节点 A


        孩子节点/子节点 B


e,根节点:树中没有父节点的结点称为根节点,A就是根节点


f,节点的层次:从根节点开始计算,根节点是第一层,A的层次就是1,以此类推


g,树的高度:结点的最大层次,上图中树的高度就为4


其余:

  • 非终端节点:度不为0的节点
  • 兄弟节点:具有相同的父节点互称为兄弟节点
  • 唐兄弟节点:父节点在同一层的节点
  • 节点的祖先:从根到该节点所经分支的所有节点
  • 以某节点为根的子树中任一节点都称为该节点的子孙
  • 森林: 由 m ( m>=0 )棵互不相交的树的集合

四,二叉树***

概念:


一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。


 二叉树的特点:


        1. 每个结点最多有两棵子树,即二叉树不存在度大于 2 的结点。


        2. 二叉树的子树有左右之分,其子树的次序不能颠倒,因此二叉树是有序树。


两种特殊的二叉树


满二叉树:满二叉树中,所有非叶子结点的度都为2。


                  一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果 一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树。



java生成树形结构并导出excel_java_02



对于一颗二叉树来讲,


        1,高度为k,则该二叉树最多有2^k-1个结点(最多的情况就是满二叉树)


        2,层次为k,第k层最多有2^(k-1)个结点,上图中,第三层最多为2^(3-1) = 4


        3,边长和节点个数关系


                边长 = 结点个数 -  1


                设度为0的节点个数为n0,度为1的结点个数为n1,度为2的结点个数为n2。


                = > n0 = n2 + 1 (叶子结点的个数 = 度为2的结点个数 + 1)


                设总的结点个数为n


                = > 边长 = n - 1


完全二叉树:


        实际上就是满二叉树缺了个右下角(只能缺叶子结点的最右边,不能中间有隔开空缺)不存在只有右子树没有左子树的情况


完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全 二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。



java生成树形结构并导出excel_数据结构_03



五,二叉树的性质

  1. 若规定 根节点的层数为 1 ,则一棵 非空二叉树的第 i 层上最多有  (i>0) 个结点
  2. 若规定只有 根节点的二叉树的深度为 1 ,则 深度为 K 的二叉树的最大结点数是  (k>=0)
  3. 对任何一棵二叉树 ,  如果其 叶结点个数为  n0,  度为 2 的非叶结点个数为  n2, 则有 n0 = n2 + 1
  4. 具有 n 个结点的完全二叉树的深度 k 为 上取整
  5. 对于具有 n 个结点的完全二叉树 ,如果按照 从上至下从左至右的顺序对所有节点从 0 开始编号 ,则对于 序号为 i  的结点有 :
  • 若i>0,双亲序号:(i-1)/2;   i=0,i为根节点编号,无双亲节点
  • 若2i+1<n,左孩子序号:   2i+1,否则无左孩子 
  • 若2i+2<n,右孩子序号:   2i+2,否则无右孩子

六,二叉树的存储方式


和链表一样,有顺序存储和链式存储(引用)


        顺序存储,将二叉树采用数组方式存储(只能存储完全二叉树,在堆里使用)


        普通的二叉树我们采用引用方式来存储


class TreeNode<E> {
    E val;
    TreeNode left;//左子树根节点
    TreeNode right;//右子树根节点
    //还可以定义父节点,平衡树会用到
    TreeNode parent;//父节点的地址
}


七,二叉树的遍历

遍历:


        按照一定的顺序“访问”(根据不同的场景,访问的需求是不同的,打印结点值或是计算结点个数)这个集合的所有元素,不重复,不遗漏。


        对于二叉树这种非线性结构而言,遍历比线性结构复杂的多,四大遍历方式。对于二叉树而言,遍历是其他操作的基础


java生成树形结构并导出excel_二叉树_04


深度优先遍历


前序遍历(preOrder):根左右


        先访问根节点,递归访问左子树,递归访问右子树


        以上图为例前序遍历结果为:ABDEHCFG。


结论:


        前序遍历结果集中,第一个输出的一定是当前树的根节点。


代码实现


public static void preOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    //先访问根节点
    System.out.print(root.val + " ");
    //递归访问左树
    preOrder(root.left);
    //递归访问右树
    preOrder(root.right);
}


中序遍历(inOrder):左根右


        先递归访问左子树,然后访问根节点,最后递归访问右子树


        以上图为例中序遍历结果为:DBEHAFCG


结论:


        在中序遍历结果中,左子树的遍历结果在根节点的左侧,右子树的遍历结果在根节点的右侧


代码实现


public static void inOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    //先递归访问左树
    inOrder(root.left);
    //然后访问根节点
    System.out.print(root.val + " ");
    //最后递归访问右树
    inOrder(root.right);
}


后序遍历(postOrder):左右根        


        先递归访问左子树,然后递归访问右子树,最后访问根节点


        以上图为例后序遍历结果为:DHEBFGCA


结论:


        后序结果集中,最后一个才是根节点,后序结果集的转置输出恰好是前序遍历的镜像 - 根右左


代码实现


public static void postOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    //先递归访问左树
    postOrder(root.left);
    //然后递归访问右树
    postOrder(root.right);
    //最后访问根节点
    System.out.print(root.val + " ");
}


广度优先遍历 


层序遍历(leveOrder):


        按照二叉树的层次一层层访问结点,先左再右


代码实现


public static void leveOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    //此时树不为空,将根节点入队
    queue.offer(root);
    while (!queue.isEmpty()) {
        //取出队首元素
        TreeNode cur = queue.poll();
        //输出队首元素的的值
        System.out.print(cur.val + " ");
        //当前节点还有子节点,将子节点从左到右依次入队
        if (cur.left != null) {
            queue.offer(cur.left);
        }
        if (cur.right != null) {
            queue.offer(cur.right);
        }
    }
}


八,二叉树的其他基础操作

获取结点个数:


getNodes(TreeNode root)传入根节点获取二叉树节点个数


代码实现


/**
* 传入根节点,统计出节点个数
* @param root
* @return 结点个数
*/
public static int getNodes(TreeNode root) { 
    //当前节点为空,边界条件
    if (root == null) {
        return 0;
    }
    //当前节点不为空,统计当前节点,然后加上左右子树节点数
    return 1 + getNodes(root.left) + getNodes(root.right);
}


求一颗二叉树中叶子结点个数:


getLeafNodes(TreeNode root)入根节点获取二叉树叶子节点个数


/**
* 传入根节点,统计叶子结点个数
* @param root
* @return
*/
public static int getLeafNode(TreeNode root) {
    if (root == null) {
        return 0;
    }
    //当前为叶子结点,加一
    if (root.left == null && root.right == null) {
        return 1;
    }
    //当前树不为空,且存在子树
    return getLeafNode(root.left) + getLeafNode(root.right);
}


注意:

        递归函数实际上具体的处理过程都在终止条件,递归过程只是将多个结果拼接起来而已

判断当前二叉树中是否含有寻找元素:


Contains(TreeNode root, char val)传入二叉树和寻找值


/**
* 传入一个树和一个值,判断当前树中是否含有该元素
* @param root
* @param val
* @return true/false
*/
public static boolean contains(TreeNode root,char val) {
    if (root == null) {
        return false;
    }
    //当前节点值等于所传入值
    if (root.val == val) {
        return true;
    }
    //二叉数不为空,且当前节点值不等于所传入值,在子树中继续寻找
    return contains(root.left,val) || contains(root.right,val);
}


求二叉树的高度:


height(TreeNode root)传入二叉树根节点


/**
* 传入一个以root为根节点的二叉树,就能求出该树的高度
*
* @param root
* @return 树的高度
*/
public static int height(TreeNode root) {
    if (root == null) {
        return 0;
    }
    return 1 + Math.max(height(root.left), height(root.right));
}