二叉树 - 树形结构(Java语言详解)概念及具体代码实现

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

目录

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

二叉树的主要难点        

什么时候能用递归?

如何写递归程序:

一,为何要有树结构?

二,数据结构常用的树结构 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个或多个后继。
  • 树是递归定义的。

 

树与非树?

        子树是不相交的。
        除了根节点外,每个结点有且仅有一个父结点。
        一颗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 )棵互不相交的树的集合

四,二叉树***

概念:

        每个结点最多只有两颗子树,二叉树中节点的度不超过2,两颗子树有左右之分。一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉树组成。

 二叉树的特点:

        1. 每个结点最多有两棵子树,即二叉树不存在度大于 的结点。
        2. 二叉树的子树有左右之分,其子树的次序不能颠倒,因此二叉树是有序树。

两种特殊的二叉树

满二叉树:满二叉树中,所有非叶子结点的度都为2。
                  一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果 一个二叉树的层数为K,且结点总数是 ,则它就是满二叉树
对于一颗二叉树来讲,
        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的,有个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1n的结点一一对应时称之为完全 二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

五,二叉树的性质

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

六,二叉树的存储方式

和链表一样,有顺序存储和链式存储(引用)
        顺序存储,将二叉树采用数组方式存储(只能存储完全二叉树,在堆里使用)
        普通的二叉树我们采用引用方式来存储
class TreeNode<E> {
    E val;
    TreeNode left;//左子树根节点
    TreeNode right;//右子树根节点
    //还可以定义父节点,平衡树会用到
    TreeNode parent;//父节点的地址
}

七,二叉树的遍历

遍历:

        按照一定的顺序“访问”(根据不同的场景,访问的需求是不同的,打印结点值或是计算结点个数)这个集合的所有元素,不重复,不遗漏。
        对于二叉树这种非线性结构而言,遍历比线性结构复杂的多,四大遍历方式。对于二叉树而言,遍历是其他操作的基础 

深度优先遍历

前序遍历(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));
}

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

二叉树代码实现

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是啊秋啊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值