资讯专栏INFORMATION COLUMN

二叉树就是这么简单

Cruise_Chan / 3109人阅读

前言

</>复制代码

  1. 只有光头才能变强。

</>复制代码

  1. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y
一、二叉树就是这么简单

本文撇开一些非常苦涩、难以理解的概念来讲讲二叉树,仅入门观看(或复习)....

首先,我们来讲讲什么是树:

树是一种非线性的数据结构,相对于线性的数据结构(链表、数组)而言,树的平均运行时间更短(往往与树相关的排序时间复杂度都不会高)

在现实生活中,我们一般的树长这个样子的:

但是在编程的世界中,我们一般把树“倒”过来看,这样容易我们分析:

一般的树是有很多很多个分支的,分支下又有很多很多个分支,如果在程序中研究这个会非常麻烦。因为本来树就是非线性的,而我们计算机的内存是线性存储的,太过复杂的话我们无法设计出来的。

因此,我们先来研究简单又经常用的---> 二叉树

1.1树的一些概念

我就拿上面的图来进行画来讲解了:

二叉树的意思就是说:每个节点不能多于有两个儿子,上面的图就是一颗二叉树。

一棵树至少会有一个节点(根节点)

树由节点组成,每个节点的数据结构是这样的:

因此,我们定义树的时候往往是->定义节点->节点连接起来就成了树,而节点的定义就是:一个数据、两个指针(如果有节点就指向节点、没有节点就指向null)

1.2静态创建二叉树

上面说了,树是由若干个节点组成,节点连接起来就成了树,而节点由一个数据、两个指针组成

因此,创建树实际上就是创建节点,然后连接节点

首先,使用Java类定义节点:

</>复制代码

  1. public class TreeNode {
  2. // 左节点(儿子)
  3. private TreeNode lefTreeNode;
  4. // 右节点(儿子)
  5. private TreeNode rightNode;
  6. // 数据
  7. private int value;
  8. }

下面我们就拿这个二叉树为例来构建吧:

为了方便构建,我就给了它一个带参数的构造方法和set、get方法了:

</>复制代码

  1. public TreeNode(int value) {
  2. this.value = value;
  3. }

那么我们现在就创建了5个节点:

</>复制代码

  1. public static void main(String[] args) {
  2. //根节点-->10
  3. TreeNode treeNode1 = new TreeNode(10);
  4. //左孩子-->9
  5. TreeNode treeNode2 = new TreeNode(9);
  6. //右孩子-->20
  7. TreeNode treeNode3 = new TreeNode(20);
  8. //20的左孩子-->15
  9. TreeNode treeNode4 = new TreeNode(15);
  10. //20的右孩子-->35
  11. TreeNode treeNode5 = new TreeNode(35)
  12. }

它们目前的状态是这样子的:

于是下面我们去把它连起来:

</>复制代码

  1. //根节点的左右孩子
  2. treeNode1.setLefTreeNode(treeNode2);
  3. treeNode1.setRightNode(treeNode3);
  4. //20节点的左右孩子
  5. treeNode3.setLefTreeNode(treeNode4);
  6. treeNode3.setRightNode(treeNode5);

连接完之后,那么我们的树就创建完成了。

1.3遍历二叉树

上面说我们的树创建完成了,那怎么证明呢??我们如果可以像数组一样遍历它(看它的数据),那就说明它创建完成了

值得说明的是:二叉树遍历有三种方式

先序遍历

先访问根节点,然后访问左节点,最后访问右节点(根->左->右)

中序遍历

先访问左节点,然后访问根节点,最后访问右节点(左->根->右)

后序遍历

先访问左节点,然后访问右节点,最后访问根节点(左->右->根)

以上面的二叉树为例:

如果是先序遍历10->9->20->15->35

如果是中序遍历9->10->15->20->35

可能需要解释地方:访问完10节点过后,去找的是20节点,但20下还有子节点,因此访问的是20的左儿子15节点。由于15节点没有儿子了。所以就返回20节点,访问20节点。最后访问35节点

如果是后序遍历9->15->35->20->10

可能需要解释地方:先访问9节点,随后应该访问的是20节点,但20下还有子节点,因此访问的是20的左儿子15节点。由于15节点没有儿子了。所以就去访问35节点,由于35节点也没有儿子了,所以返回20节点,最终返回10节点

一句话总结:先序(根->左->右),中序(左->根->右),后序(左->右->根)。如果访问有孩子的节点,先处理孩子的,随后返回

无论先中后遍历,每个节点的遍历如果访问有孩子的节点,先处理孩子的(逻辑是一样的)

因此我们很容易想到递归

递归的出口就是:当没有子节点了,就返回

因此,我们可以写出这样的先序遍历代码

</>复制代码

  1. /**
  2. * 先序遍历
  3. * @param rootTreeNode 根节点
  4. */
  5. public static void preTraverseBTree(TreeNode rootTreeNode) {
  6. if (rootTreeNode != null) {
  7. //访问根节点
  8. System.out.println(rootTreeNode.getValue());
  9. //访问左节点
  10. preTraverseBTree(rootTreeNode.getLefTreeNode());
  11. //访问右节点
  12. preTraverseBTree(rootTreeNode.getRightNode());
  13. }
  14. }

结果跟我们刚才说的是一样的:

我们再用中序遍历调用一遍吧:

</>复制代码

  1. /**
  2. * 中序遍历
  3. * @param rootTreeNode 根节点
  4. */
  5. public static void inTraverseBTree(TreeNode rootTreeNode) {
  6. if (rootTreeNode != null) {
  7. //访问左节点
  8. inTraverseBTree(rootTreeNode.getLefTreeNode());
  9. //访问根节点
  10. System.out.println(rootTreeNode.getValue());
  11. //访问右节点
  12. inTraverseBTree(rootTreeNode.getRightNode());
  13. }
  14. }

结果跟我们刚才说的是一样的:

有意思的是:通过先序和中序或者中序和后序我们可以还原出原始的二叉树,但是通过先序和后序是无法还原出原始的二叉树的

也就是说:通过中序和先序或者中序和后序我们就可以确定一颗二叉树了

二、动态创建二叉树

上面我们是手动创建二叉树的,一般地:都是给出一个数组给你,让你将数组变成一个二叉树,此时就需要我们动态创建二叉树了。

二叉树中还有一种特殊的二叉树:二叉查找树(binary search tree)

定义:当前根节点的左边全部比根节点小,当前根节点的右边全部比根节点大

明眼人可以看出,这对我们来找一个数是非常方便快捷的

往往我们动态创建二叉树都是创建二叉查找树

2.1动态创建二叉树体验

假设我们有一个数组:int[] arrays = {3, 2, 1, 4, 5};

那么创建二叉树的步骤是这样的:

首先将3作为根节点

随后2进来了,我们跟3做比较,比3小,那么放在3的左边

随后1进来了,我们跟3做比较,比3小,那么放在3的左边,此时3的左边有2了,因此跟2比,比2小,放在2的左边

随后4进来了,我们跟3做比较,比3大,那么放在3的右边

随后5进来了,我们跟3做比较,比3大,那么放在3的右边,此时3的右边有4了,因此跟4比,比4大,放在4的右边

那么我们的二叉查找树就建立成功了,无论任何一颗子树,左边都比根要小,右边比根要大

2.2代码实现

我们的代码实现也很简单,如果比当前根节点要小,那么放到当前根节点左边,如果比当前根节点要大,那么放到当前根节点右边。

因为是动态创建的,因此我们得用一个类来表示根节点

</>复制代码

  1. public class TreeRoot {
  2. private TreeNode treeRoot;
  3. public TreeNode getTreeRoot() {
  4. return treeRoot;
  5. }
  6. public void setTreeRoot(TreeNode treeRoot) {
  7. this.treeRoot = treeRoot;
  8. }
  9. }

比较与根谁大,大的往右边,小的往左边:

</>复制代码

  1. /**
  2. * 动态创建二叉查找树
  3. *
  4. * @param treeRoot 根节点
  5. * @param value 节点的值
  6. */
  7. public static void createTree(TreeRoot treeRoot, int value) {
  8. //如果树根为空(第一次访问),将第一个值作为根节点
  9. if (treeRoot.getTreeRoot() == null) {
  10. TreeNode treeNode = new TreeNode(value);
  11. treeRoot.setTreeRoot(treeNode);
  12. } else {
  13. //当前树根
  14. TreeNode tempRoot = treeRoot.getTreeRoot();
  15. while (tempRoot != null) {
  16. //当前值大于根值,往右边走
  17. if (value > tempRoot.getValue()) {
  18. //右边没有树根,那就直接插入
  19. if (tempRoot.getRightNode() == null) {
  20. tempRoot.setRightNode(new TreeNode(value));
  21. return ;
  22. } else {
  23. //如果右边有树根,到右边的树根去
  24. tempRoot = tempRoot.getRightNode();
  25. }
  26. } else {
  27. //左没有树根,那就直接插入
  28. if (tempRoot.getLefTreeNode() == null) {
  29. tempRoot.setLefTreeNode(new TreeNode(value));
  30. return;
  31. } else {
  32. //如果左有树根,到左边的树根去
  33. tempRoot = tempRoot.getLefTreeNode();
  34. }
  35. }
  36. }
  37. }
  38. }

测试代码:

</>复制代码

  1. int[] arrays = {2, 3, 1, 4, 5};
  2. //动态创建树
  3. TreeRoot root = new TreeRoot();
  4. for (int value : arrays) {
  5. createTree(root, value);
  6. }
  7. //中序遍历树
  8. inTraverseBTree(root.getTreeRoot());
  9. System.out.println("---------------公众号:Java3y");
  10. //先序遍历树
  11. preTraverseBTree(root.getTreeRoot());
  12. System.out.println("---------------公众号:Java3y");
三、查询二叉查找树相关 3.1查询树的深度

查询树的深度我们可以这样想:左边的子树和右边的字数比,谁大就返回谁,那么再接上根节点+1就可以了

</>复制代码

  1. public static int getHeight(TreeNode treeNode) {
  2. if (treeNode == null) {
  3. return 0;
  4. } else {
  5. //左边的子树深度
  6. int left = getHeight(treeNode.getLefTreeNode());
  7. //右边的子树深度
  8. int right = getHeight(treeNode.getRightNode());
  9. int max = left;
  10. if (right > max) {
  11. max = right;
  12. }
  13. return max + 1;
  14. }
  15. }
3.1查询树的最大值

从上面先序遍历二叉查找树的时候,细心的同学可能会发现:中序遍历二叉查找树得到的结果是排好顺序的~

那么,如果我们的二叉树不是二叉查找树,我们要怎么查询他的最大值呢

可以这样:

左边找最大值->递归

右边找最大值->递归

</>复制代码

  1. /**
  2. * 找出树的最大值
  3. *
  4. * @param rootTreeNode
  5. */
  6. public static int getMax(TreeNode rootTreeNode) {
  7. if (rootTreeNode == null) {
  8. return -1;
  9. } else {
  10. //找出左边的最大值
  11. int left = getMax(rootTreeNode.getLefTreeNode());
  12. //找出右边的最大值
  13. int right = getMax(rootTreeNode.getRightNode());
  14. //与当前根节点比较
  15. int currentRootValue = rootTreeNode.getValue();
  16. //假设左边的最大
  17. int max = left;
  18. if (right > max) {
  19. max = right;
  20. }
  21. if (currentRootValue > max) {
  22. max = currentRootValue;
  23. }
  24. return max ;
  25. }
  26. }
四、最后

无论是在遍历树、查找深度、查找最大值都用到了递归,递归在非线性的数据结构中是用得非常多的...

树的应用也非常广泛,此篇简单地说明了树的数据结构,高级的东西我也没弄懂,可能以后用到的时候会继续深入...

</>复制代码

  1. 乐于输出干货的Java技术公众号:Java3y。公众号内有200多篇原创技术文章、海量视频资源、精美脑图,不妨来关注一下!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/76367.html

相关文章

  • 堆排序就这么简单

    摘要:一堆排序介绍来源百度百科堆排序是指利用堆积树堆这种数据结构所设计的一种排序算法,它是选择排序的一种。 一、堆排序介绍 来源百度百科: 堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆分为大根堆和小根堆,是完全二叉树。 前面我已经有二叉树入门的文章了,当时讲解的是二叉查找树,那上面所说的完全二...

    NickZhou 评论0 收藏0
  • Map集合、散列表、红黑树介绍

    摘要:并且,我们的底层都是红黑树来实现的。红黑树是一种平衡二叉树因此它没有节点。 前言 声明,本文用得是jdk1.8 前面已经讲了Collection的总览和剖析List集合: Collection总览 List集合就这么简单【源码剖析】 原本我是打算继续将Collection下的Set集合的,结果看了源码发现:Set集合实际上就是HashMap来构建的! showImg(https:/...

    2json 评论0 收藏0

发表评论

0条评论

Cruise_Chan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<