Java学数据结构(2)——树Tree 二叉树binary tree 二叉查找树 AVL树 树的遍历

目录

  • 引出
  • 什么是树Tree?
  • 树的实现
  • 二叉树binary tree
  • 查找树ADT——二叉查找树Binary Search Tree
    • 1.contains方法
    • 2.findMax和findMin方法
    • 3.insert方法
    • 4.remove方法(复杂)
    • 二叉查找树的深度
  • AVL(Adelson-Velskii和Landis)树——平衡条件(balance condition)的二叉查找树
    • 插入元素-旋转(rotation)
    • 单旋转:左左,右右
    • 双旋转:左右,右左
  • 树的遍历
    • 1.中序遍历:依序列
    • 2.后序遍历:树的深度
    • 3.先序遍历
  • 完整代码
  • 总结

引出


1.树的出现:解决链表线性访问时间太慢,树的时间复杂度O(logN);
2.二叉树的定义,最多两个儿子节点;
3.二叉查找树,左小,右大,中居中;remove方法,两种,只有一个儿子节点,有两个儿子节点;
4.AVL树,在二叉查找树基础上加平衡条件,旋转方法,单旋转,双旋转;
5.树的遍历方式,中序遍历,左根右;后续遍历,左右根;先序遍历,根左右;

在这里插入图片描述

什么是树Tree?

对于大量的输入数据,链表的线性访问时间太慢,不宜使用。本章讨论一种简单的数据结构,其大部分操作的运行时间平均为O(logN)。

这种数据结构叫作二叉查找树(binary search tree)。二又查找树是两种库集合类TreeSet和TreeMap实现的基础,它们用于许多应用之中。在计算机科学中树(tree)是非常有用的抽象概念,因此,我们将讨论树在其他更一般的应用中的使用。

  • 看到树是如何用于实现几个流行的操作系统中的文件系统的。
  • 看到树如何能够用来计算算术表达式的值。
  • 指出如何利用树支持以O(logN)平均时间进行的各种搜索操作,以及如何细化以得到最
    坏情况时间界O(logN)。我们还将讨论当数据被存放在磁盘上时如何来实现这些操作。
  • 讨论并使用TreeSet类和TreeMap类。

树(Tree)可以用几种方式定义。定义树的一种自然的方式是递归的方式。一棵树是一些节点的集合。这个集合可以是空集;若不是空集,则树由称作根 (root) 的节点 r 以及0个或多个非空的(子)树 T1,T2,…,Tk 组成,这些子树中每一棵的根都被来自根r的一条有向的边(edge)所连结。

每一棵子树的根叫作根r的儿子(child),而r是每一棵子树的根的父亲(parent)。

在这里插入图片描述

树的实现

实现树的一种方法可以是在每一个节点除数据外还要有一些链,使得该节点的每一个儿子都有一个链指向它。然而,由于每个节点的儿子数可以变化很大并且事先不知道,因此在数据结构中建立到各(儿)子节点直接的链接是不可行的,因为这样会产生太多浪费的空间。实际上解决方法很简单:将每个节点的所有儿子都放在树节点的链表中。

下图指出一棵树如何用这种实现方法表示出来。图中向下的箭头是指向firstChild(第一儿子)的链,而水平箭头是指向nextSibling(下一兄弟)的链。因为null链太多了,所以没有把它们画出。

在这里插入图片描述

树的深度是指从根节点到最远叶子节点的路径上的节点数。换句话说,树的深度表示树中最长路径的长度。

在一棵树中,根节点位于第一层,它没有父节点。每个非叶子节点的深度是其父节点的深度加1。叶子节点的深度是从根节点到该叶子节点的路径上的节点数。

树的深度可以用来衡量树的高度或者树的层数。深度为0的树只有一个根节点,深度为1的树有一个根节点和一个子节点,以此类推。

二叉树binary tree

二叉树(binary tree)是一棵树,其中每个节点都不能有多于两个的儿子。一棵由一个根和两棵子树组成的二叉树,子树TL,和TK均可能为空。

在这里插入图片描述

因为一个二叉树节点最多有两个子节点,所以我们可以保存直接链接到它们的链。树节点的声明在结构上类似于双链表的声明,在声明中,节点就是由element(元素)的信息加上两个到其他节点的引用( Left 和 Right)组成的结构。

在这里插入图片描述

在这里插入图片描述

节点的实现

在这里插入图片描述

toString方法

在这里插入图片描述

  /*** 内部类,节点,元素,左节点,右节点。类似于链表* @param <T>*/public static class BinaryNode<T>{T element; // 当前节点的数据BinaryNode<T> left; // 左侧的节点BinaryNode<T> right; // 右侧的节点public BinaryNode(T element) {this(element,null,null);}public BinaryNode(T element, BinaryNode<T> left, BinaryNode<T> right) {this.element = element;this.left = left;this.right = right;}@Overridepublic String toString() {String leftStr = null;String rightStr = null;if (left!=null) {leftStr = left.element.toString();}if (right!=null){rightStr = right.element.toString();}String sTree = "     %s\n" +"   /   \\\n" +"  %s     %s";System.out.printf(sTree,element.toString(),leftStr,rightStr);System.out.println();return "BinaryNode{" +"element=" + element +", left=" + leftStr +", right=" + rightStr +'}';}}

查找树ADT——二叉查找树Binary Search Tree

二叉树的一个重要的应用是它们在查找中的使用。假设树中的每个节点存储一项数据。在我们的例子中,虽然任意复杂的项在Jva中都容易处理,但为简单起见还是假设它们是整数。还将假设所有的项都是互异的,以后再处理有重复元的情况。

使二叉树成为二叉查找树的性质是,对于树中的每个节点X,它的左子树中所有项的值小于X中的项,而它的右子树中所有项的值大于X中的项。注意,这意味着该树所有的元素可以用某种一致的方式排序。

在这里插入图片描述

二叉查找树(Binary Search Tree,简称BST)是一种特殊的二叉树,它具有以下性质:

  1. 对于树中的每个节点,其左子树中的所有节点的值都小于该节点的值,而右子树中的所有节点的值都大于该节点的值。
  2. 对于树中的每个节点,其左子树和右子树也都是二叉查找树。

基于这些性质,二叉查找树可以支持高效的查找、插入和删除操作。

       6/     \2       8/  \
1   4/3

提供一个生成一颗树的方法

    /****        6*     /     \*    2       8*  /  \* 1   4*    /*   3* @return 获得一二叉树*/public BinarySearchTree<Integer> getIntegerTree() {BinaryNode<Integer> root = new BinaryNode<>(6);root.left = new BinaryNode<>(2);root.right = new BinaryNode<>(8);root.left.left = new BinaryNode<>(1);root.left.right = new BinaryNode<>(4);root.left.right.left = new BinaryNode<>(3);return new BinarySearchTree<>(root);}

1.contains方法

如果在树T中存在含有项X的节点,那么这个操作需要返回true,如果这样的节点不存在则返回false。树的结构使得这种操作很简单。如果T是空集,那么可以就返回false。否则,如果存储在T处的项是X,那么可以返回true。否则,我们对树T的左子树或右子树进行一次递归调用,这依赖于X与存储在T中的项的关系。

关键的问题是首先要对是否空树进行测试,否则我们就会生成一个企图通过null引用访问数据域的NullPointerException异常。

    /*** 从根节点开始,递归查找,判断x是否在树中* @param x 判断的元素* @param node 从哪个节点开始判断* @return*/public boolean contains(T x,BinaryNode<T> node){if (node==null){// 根节点为null,空树return false;}// 进行比较int compareResult = x.compareTo(node.element);if (compareResult >0){// 说明x大于当前节点,则往右走System.out.println("right: "+x+"--node: "+node.element);return contains(x, node.right);}else if(compareResult <0){System.out.println("left: "+x+"--node: "+node.element);return contains(x, node.left); // 往左走}else {return true; // 找到了}}

在这里插入图片描述

查找的顺序

在这里插入图片描述

2.findMax和findMin方法

最大

在这里插入图片描述

最小

在这里插入图片描述

/*** 查找最大值,需要保证不是空树* @return*/public T findMax(){if (isEmpty()){System.out.println("空树");return null;}// 根据二叉查找树的定义,最大值就是不断往右找BinaryNode<T> right = root.right;T max = root.element;while (right!=null){max = right.element;right = right.right;}return max;}/*** 从某个节点开始找最大值* @param node 开始的节点* @return*/public T findMax(BinaryNode<T> node){if (isEmpty()){System.out.println("空树");return null;}if (node!=null){while (node.right!=null){node = node.right;}return node.element;}return null;}/**** @param node 开始查找的节点* @return*/public T findMin(BinaryNode<T> node){if (isEmpty()){System.out.println("空树");return null;}if (node==null){return null;}else if (node.left==null){return node.element;}return findMin(node.left);}

3.insert方法

进行插入操作的例程在概念上是简单的。为了将X插入到树T中,你可以像用contains那样沿着树查找。如果找到X,则什么也不用做(或做一些“更新”)。否则,将X插入到遍历的路径上的最后一点上。

下图显示实际的插入情况。为了插入5,我们遍历该树就好像在运行contains。在具有关键字4的节点处,我们需要向右行进,但右边不存在子树,因此5不在这棵树上,从而这个位置就是所要插入的位置。

在这里插入图片描述

在这里插入图片描述

进行插入的测试

在这里插入图片描述

    /*** 获取insert操作后的运行脚本*/String scriptJ = "root";/*** 插入元素,和判断是否包含某个元素的思路一样* @param x 想要插入的元素* @param t 从哪个节点开始* @return*/public BinaryNode<T> insert(T x,BinaryNode<T> t){// 最后5又到了这里if (t==null){
//            System.out.println("isHere???");return new BinaryNode<T>(x);}// 从t节点开始把x元素插入到树中int compareTo = x.compareTo(t.element);if (compareTo>0){// x比当前比较的节点的元素大,向右查找scriptJ = scriptJ + ".right";
//            System.out.println("Turn to right:"+t.element);return insert(x, t.right);}else if (compareTo<0){
//            System.out.println("Turn to left:"+t.element);scriptJ = scriptJ + ".left";return insert(x,t.left);}else {;}return t;}

进行测试:flag脚本执行未成功

        System.out.println("插入新的元素");BinarySearchTree.BinaryNode<Integer> insert = binarySearchTree.insert(5, binarySearchTree.root);System.out.println(insert);System.out.println(binarySearchTree.scriptJ); // .left.right.rightSystem.out.println(binarySearchTree.root.left.right);System.out.println("执行插入:binarySearchTree."+binarySearchTree.scriptJ + "=insert");binarySearchTree.root.left.right.right = insert; // 根节点的左右为插入的新节点System.out.println(binarySearchTree.root.left.right.right);System.out.println("执行插入后的节点4:"+binarySearchTree.root.left.right);System.out.println("#######################################");ScriptEngineManager manager = new ScriptEngineManager();ScriptEngine engine = manager.getEngineByName("js");Bindings bindings = new SimpleBindings();bindings.put("binarySearchTree", binarySearchTree);//        String script = "binarySearchTree.root.left.right.right=insert";String script = "binarySearchTree.root";Object result = engine.eval(script, bindings);System.out.println(result);

4.remove方法(复杂)

正如许多数据结构一样,最困难的操作是remove(删除)。一旦我们发现要被删除的节点,就需要考虑几种可能的情况。如果节点是一片树叶,那么它可以被立即删除。如果节点有一个儿子,则该节点可以在其父节点调整自己的链以绕过该节点后被删除(为了清楚起见,我们将明确地画出链的指向)

在这里插入图片描述

复杂的情况是处理具有两个儿子的节点。一般的删除策略是用其右子树的最小的数据(很容易找到)代替该节点的数据并递归地删除那个节点(现在它是空的)。因为右子树中的最小的节点不可能有左儿子,所以第二次remove要容易。图4-24显示一棵初始的树及其中一个节点被删除后的结果。要被删除的节点是根的左儿子;其关键字是2。它被其右子树中的最小数据3所代替,然后关键字是3的原节点如前例那样被删除。

       6/     \2       8/  \
1   5/3\4

在这里插入图片描述

    /*** 删除元素* @param x 要删除的元素* @param t 开始遍历的节点* @return*/public BinaryNode<T> remove(T x,BinaryNode<T> t){if (t==null){return null; // 要删除的元素未找到}int compareTo = x.compareTo(t.element);if (compareTo>0){// 要删除的元素比当前节点的元素的值大,往右走return remove(x, t.right);}else if (compareTo<0){return remove(x,t.left); // 往左走}else{// 找到了要删除的节点System.out.println(t);if (t.left!=null && t.right!=null){System.out.println("该节点有两个儿子节点");t.element = findMin(t.right); // 用右侧的最小数据代替当前的数据System.out.println("用右侧最小数据代替当前数据后的节点");System.out.println(t);// 还要把3节点删除t.right = remove(t.element, t.right);}else {System.out.println("该节点只有一个儿子节点");// 如果该节点只有1个儿子t = (t.left != null) ? t.left : t.right;System.out.println("删除原有节点后,该节点被儿子节点替换后");System.out.println(t);}return t;}}

删除只有一个儿子的节点4

在这里插入图片描述

删除有两个儿子的节点

在这里插入图片描述

二叉查找树的深度

在这里插入图片描述

如果向一棵树输入预先排好序的数据,那么一连串insert操作将花费二次的时间,而链表实现的代价会非常巨大,因为此时的树将只由那些没有左儿子的节点组成。一种解决办法就是要有一个称为平衡(balance)的附加的结构条件:任何节点的深度均不得过深。许多一般的算法都能实现平衡树。

但是,大部分算法都要比标准的二叉查找树复杂得多,而且更新要平均花费更长的时间。不过,它们确实防止了处理起来非常麻烦的一些简单情形。下面,我们将介绍最古老的一种平衡查找树,即AVL树。

AVL(Adelson-Velskii和Landis)树——平衡条件(balance condition)的二叉查找树

AVL(Adelson-Velskii和Landis)树是带有**平衡条件(balance condition)**的二叉查找树。这个平衡条件必须要容易保持,而且它保证树的深度须是O(1ogN)。最简单的想法是要求左右子树具有相同的高度。如图4-28所示,这种想法并不强求树的深度要浅。

另一种平衡条件是要求每个节点都必须有相同高度的左子树和右子树。虽然这种平衡条件保证了树的深度小,但是它太严格而难以使用,需要放宽条件。

一棵AVL树是其每个节点的左子树和右子树的高度最多差1的二叉查找树(空树的高度定义为-1)。在图4-29中,左边的树是AVL树,但是右边的树不是。

在这里插入图片描述

插入元素-旋转(rotation)

因此,除去可能的插入外(我们将假设懒惰删除),所有的树操作都可以以时间O(logN)执行。当进行插入操作时,我们需要更新通向根节点路径上那些节点的所有平衡信息,而插人操作隐含着困难的原因在于,插入一个节点可能破坏AVL树的特性(例如,将6插入到图4-29中的AVL树中将会破坏关键字为8的节点处的平衡条件)。如果发生这种情况,那么就要在考虑
这一步插入完成之前恢复平衡的性质。事实上,这总可以通过对树进行简单的修正来做到,我们称其为旋转(rotation)

在插入以后,只有那些从插入点到根节点的路径上的节点的平衡可能被改变,因为只有这些节点的子树可能发生变化。当我们沿着这条路径上行到根并更新平衡信息时,可以发现一个节点,它的新平衡破坏了AVL条件。我们将指出如何在第一个这样的节点(即最深的节点)重新平衡这棵树,并证明这一重新平衡保证整个树满足AVL性质。

在这里插入图片描述

单旋转:左左,右右

在这里插入图片描述

图4-32显示了在将6插人左边原始的AVL树后节点8便不再平衡。于是,我们在7和8之间做一次单旋转,结果得到右边的树。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

双旋转:左右,右左

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

树的遍历

            7/      \4         13/  \     /      \2    6   11       15/ \   /   / \     /  \1   3  5   9  12  14  16/ \8   10

1.中序遍历:依序列

中序遍历(Inorder Traversal)是一种树的遍历方式,它的遍历顺序是先递归地遍历左子树,然后访问根节点,最后递归地遍历右子树。中序遍历的顺序可以表示为:左子树 -> 根节点 -> 右子树。

正如我们前面看到的,这类例程当用于树的时候则称为中序遍历(由于它依序列出了各项,因此是有意义的)。一个中序遍历的一般方法是首先处理左子树,然后是当前的节点,最后处理右子树。这个算法的有趣部分除它简单的特性外,还在于其总的运行时间是O(N)。这是因为在树的每一个节点处进行的工作是常数时间的。每一个节点访问一次,而在每一个节点进行的工作是检测是否ull、建立两个方法调用、并执行println。由于在每个节点的工作花费常数时间以及总共有N个节点,因此运行时间为O(N)。

在这里插入图片描述

2.后序遍历:树的深度

后序遍历(Postorder Traversal)是一种树的遍历方式,它的遍历顺序是先递归地遍历左子树,然后递归地遍历右子树,最后访问根节点。后序遍历的顺序可以表示为:左子树 -> 右子树 -> 根节点。

有时我们需要先处理两棵子树然后才能处理当前节点。例如,为了计算一个节点的高度,首先需要知道它的子树的高度程序就是计算高度的。由于检查一些特殊的情况总是有益的——当涉及递归时尤其重要,因此要注意这个例程声明树叶的高度为零,这是正确的。这种一般的遍历顺序叫作后序遍历,我们在前面也见到过。因为在每个节点的工作花费常
数时间,所以总的运行时间也是O(N)。

树的深度是指从根节点到最远叶子节点的路径上的节点数。换句话说,树的深度表示树中最长路径的长度。

在一棵树中,根节点位于第一层,它没有父节点。每个非叶子节点的深度是其父节点的深度加1。叶子节点的深度是从根节点到该叶子节点的路径上的节点数。

在这里插入图片描述

    /*** 后续遍历:左子树 -> 右子树 -> 根节点* @param t* @return*/public int height(BinaryNode<T> t){if (t==null){return -1;}else {return 1+Math.max(height(t.left), height(t.right));}}

3.先序遍历

先序遍历(Preorder Traversal)是一种树的遍历方式,它的遍历顺序是先访问根节点,然后递归地遍历左子树,最后递归地遍历右子树。先序遍历的顺序可以表示为:根节点 -> 左子树 -> 右子树。

在这里插入图片描述

    /*** 先序遍历:根节点 -> 左子树 -> 右子树* @param root*/public void preorderTraversal(BinaryNode<T> root) {if (root == null) {return;}System.out.print(root.element + " ");preorderTraversal(root.left);preorderTraversal(root.right);}

完整代码

BinarySearchTree<T extends Comparable<? super T>>实体类

package com.tianju.book.jpa.tree;import com.baomidou.mybatisplus.extension.api.R;
import com.github.pagehelper.PageHelper;public class BinarySearchTree<T extends Comparable<? super T> >{public BinaryNode<T> root; // 根节点/*** 内部类,节点,元素,左节点,右节点。类似于链表* @param <T>*/public static class BinaryNode<T>{T element; // 当前节点的数据BinaryNode<T> left; // 左侧的节点BinaryNode<T> right; // 右侧的节点public BinaryNode(T element) {this(element,null,null);}public BinaryNode(T element, BinaryNode<T> left, BinaryNode<T> right) {this.element = element;this.left = left;this.right = right;}@Overridepublic String toString() {String leftStr = null;String rightStr = null;if (left!=null) {leftStr = left.element.toString();}if (right!=null){rightStr = right.element.toString();}String sTree = "     %s\n" +"   /   \\\n" +"  %s     %s";System.out.printf(sTree,element.toString(),leftStr,rightStr);System.out.println();return "BinaryNode{" +"element=" + element +", left=" + leftStr +", right=" + rightStr +'}';}}/****        6*     /     \*    2       8*  /  \* 1   4*    /*   3* @return 获得一二叉树*/public BinarySearchTree<Integer> getIntegerTree() {BinaryNode<Integer> root = new BinaryNode<>(6);root.left = new BinaryNode<>(2);root.right = new BinaryNode<>(8);root.left.left = new BinaryNode<>(1);root.left.right = new BinaryNode<>(4);root.left.right.left = new BinaryNode<>(3);return new BinarySearchTree<>(root);}/***        6*     /     \*    2       8*  /  \* 1   5*    /*   3*    \*     4* @return*/public BinarySearchTree<Integer> getIntegerTreeR() {BinaryNode<Integer> root = new BinaryNode<>(6);root.left = new BinaryNode<>(2);root.right = new BinaryNode<>(8);root.left.left = new BinaryNode<>(1);root.left.right = new BinaryNode<>(5);root.left.right.left = new BinaryNode<>(3);root.left.right.left.right = new BinaryNode<>(4);return new BinarySearchTree<>(root);}/*** 调用可获得一颗平衡AVL树*             7*          /      \*        4         13*      /  \     /      \*     2    6   11       15*    / \   /   / \     /  \*   1   3  5   9  12  14  16*             / \*            8   10* @return 平衡AVL树*/public BinarySearchTree<Integer> getBalanceTree() {BinaryNode<Integer> root = new BinaryNode<>(7);root.left = new BinaryNode<>(4);root.left.left = new BinaryNode<>(2);root.left.left.left = new BinaryNode<>(1);root.left.left.right = new BinaryNode<>(3);root.left.right = new BinaryNode<>(6);root.left.right.left = new BinaryNode<>(5);root.right = new BinaryNode<>(13);root.right.left = new BinaryNode<>(11);root.right.left.left = new BinaryNode<>(9);root.right.left.left.left = new BinaryNode<>(8);root.right.left.left.right = new BinaryNode<>(10);root.right.left.right = new BinaryNode<>(12);root.right.right = new BinaryNode<>(15);root.right.right.left = new BinaryNode<>(14);root.right.right.right = new BinaryNode<>(16);return new BinarySearchTree<>(root);}public BinarySearchTree(BinaryNode<T> root) {this.root = root;}public BinarySearchTree() {}public void makeEmpty(){root = null;}public boolean isEmpty(){return root == null;}/*** 从根节点开始,递归查找,判断x是否在树中* @param x 判断的元素* @param node 从哪个节点开始判断* @return*/public boolean contains(T x,BinaryNode<T> node){if (node==null){// 找完了还没找到return false;}// 进行比较int compareResult = x.compareTo(node.element);if (compareResult >0){// 说明x大于当前节点,则往右走System.out.println("right: "+x+"--node: "+node.element);return contains(x, node.right);}else if(compareResult <0){System.out.println("left: "+x+"--node: "+node.element);return contains(x, node.left); // 往左走}else {return true; // 找到了}}/*** 查找最大值,需要保证不是空树* @return*/public T findMax(){if (isEmpty()){System.out.println("空树");return null;}// 根据二叉查找树的定义,最大值就是不断往右找BinaryNode<T> right = root.right;T max = root.element;while (right!=null){max = right.element;right = right.right;}return max;}/*** 从某个节点开始找最大值* @param node 开始的节点* @return*/public T findMax(BinaryNode<T> node){if (isEmpty()){System.out.println("空树");return null;}if (node!=null){while (node.right!=null){node = node.right;}return node.element;}return null;}/**** @param node 开始查找的节点* @return*/public T findMin(BinaryNode<T> node){if (isEmpty()){System.out.println("空树");return null;}if (node==null){return null;}else if (node.left==null){return node.element;}return findMin(node.left);}/*** 获取insert操作后的运行脚本*/String scriptJ = "root";/*** 插入元素,和判断是否包含某个元素的思路一样* @param x 想要插入的元素* @param t 从哪个节点开始* @return*/public BinaryNode<T> insert(T x,BinaryNode<T> t){// 最后5又到了这里if (t==null){
//            System.out.println("isHere???");return new BinaryNode<T>(x);}// 从t节点开始把x元素插入到树中int compareTo = x.compareTo(t.element);if (compareTo>0){// x比当前比较的节点的元素大,向右查找scriptJ = scriptJ + ".right";
//            System.out.println("Turn to right:"+t.element);return insert(x, t.right);}else if (compareTo<0){
//            System.out.println("Turn to left:"+t.element);scriptJ = scriptJ + ".left";return insert(x,t.left);}else {;}return t;}/*** 删除元素* @param x 要删除的元素* @param t 开始遍历的节点* @return*/public BinaryNode<T> remove(T x,BinaryNode<T> t){if (t==null){return null; // 要删除的元素未找到}int compareTo = x.compareTo(t.element);if (compareTo>0){// 要删除的元素比当前节点的元素的值大,往右走return remove(x, t.right);}else if (compareTo<0){return remove(x,t.left); // 往左走}else{// 找到了要删除的节点System.out.println(t);if (t.left!=null && t.right!=null){System.out.println("该节点有两个儿子节点");t.element = findMin(t.right); // 用右侧的最小数据代替当前的数据System.out.println("用右侧最小数据代替当前数据后的节点");System.out.println(t);// 还要把3节点删除t.right = remove(t.element, t.right);}else {System.out.println("该节点只有一个儿子节点");// 如果该节点只有1个儿子t = (t.left != null) ? t.left : t.right;System.out.println("删除原有节点后,该节点被儿子节点替换后");System.out.println(t);}return t;}}/*** 中序遍历* @param root*/public void printTree(BinaryNode<T> root){if (isEmpty()){System.out.println("Empty tree");}else {printTreeP(root);}}/*** 中序遍历:左子树 -> 根节点 -> 右子树* @param node*/private void printTreeP(BinaryNode<T> node){if (node!=null){printTreeP(node.left);System.out.print(node.element+" ");printTreeP(node.right);}}/*** 后续遍历:左子树 -> 右子树 -> 根节点* @param t* @return*/public int height(BinaryNode<T> t){if (t==null){return -1;}else {return 1+Math.max(height(t.left), height(t.right));}}/*** 先序遍历:根节点 -> 左子树 -> 右子树* @param root*/public void preorderTraversal(BinaryNode<T> root) {if (root == null) {return;}System.out.print(root.element + " ");preorderTraversal(root.left);preorderTraversal(root.right);}}

测试代码

package com.tianju.book.jpa.tree;import javax.script.*;public class TestTreeDemo {public static void main(String[] args) throws ScriptException {BinarySearchTree.BinaryNode<Integer> root = new BinarySearchTree.BinaryNode<>(1);BinarySearchTree<Integer> tree = new BinarySearchTree<>(root);BinarySearchTree<Integer> binarySearchTree = new BinarySearchTree<>().getIntegerTree();Integer element = binarySearchTree.root.left.element;System.out.printf("根节点 %d 的左侧节点的元素为 %d ",binarySearchTree.root.element,element);System.out.println();String s = "     6\n" +"   /   \\\n" +"  2     8\n" +" / \\\n" +"1   4\n" +"   /\n" +"  3";System.out.println(s);System.out.println("contains方法测试:");boolean contains = binarySearchTree.contains(3, binarySearchTree.root);System.out.println(contains);System.out.println("查找最大值");Integer max = binarySearchTree.findMax();System.out.println(max);System.out.println("从某个节点开始找最大值");Integer max2 = binarySearchTree.findMax(binarySearchTree.root.left);System.out.println(max2);System.out.println("查找最小值");Integer min = binarySearchTree.findMin(binarySearchTree.root);System.out.println(min);//        System.out.println("插入新的元素");
//        BinarySearchTree.BinaryNode<Integer> insert = binarySearchTree.insert(5, binarySearchTree.root);
//        System.out.println(insert);
//
//        System.out.println(binarySearchTree.scriptJ); // .left.right.right
//        System.out.println(binarySearchTree.root.left.right);
//
//        System.out.println("执行插入:binarySearchTree."+binarySearchTree.scriptJ + "=insert");
//
//        binarySearchTree.root.left.right.right = insert; // 根节点的左右为插入的新节点
//
//        System.out.println(binarySearchTree.root.left.right.right);
//        System.out.println("执行插入后的节点4:"+binarySearchTree.root.left.right);
//
//
//        System.out.println("#######################################");
//        ScriptEngineManager manager = new ScriptEngineManager();
//        ScriptEngine engine = manager.getEngineByName("js");
//        Bindings bindings = new SimpleBindings();
//
//        bindings.put("binarySearchTree", binarySearchTree);
//
        String script = "binarySearchTree.root.left.right.right=insert";
//        String script = "binarySearchTree.root";
//
//        Object result = engine.eval(script, bindings);
//        System.out.println(result);System.out.println("删除节点");BinarySearchTree.BinaryNode<Integer> remove = binarySearchTree.remove(4, binarySearchTree.root);System.out.println(remove);System.out.println("删除有两个儿子的节点");BinarySearchTree<Integer> treeR = new BinarySearchTree<>().getIntegerTreeR();treeR.remove(2, treeR.root);}
}

树的遍历测试

package com.tianju.book.jpa.tree;public class TestFindAll {public static void main(String[] args) {BinarySearchTree<Integer> balanceTree = new BinarySearchTree<Integer>().getBalanceTree();System.out.println("            7\n" +"         /      \\\n" +"       4         13\n" +"     /  \\     /      \\\n" +"    2    6   11       15\n" +"   / \\   /   / \\     /  \\\n" +"  1   3  5   9  12  14  16\n" +"            / \\\n" +"           8   10");System.out.println(balanceTree.root.left.right);System.out.println("中序遍历:按顺序");balanceTree.printTree(balanceTree.root);System.out.println();System.out.println("后序遍历:计算节点的高度");System.out.println(balanceTree.height(balanceTree.root.right));System.out.println("先序遍历");balanceTree.preorderTraversal(balanceTree.root);}
}

总结

1.树的出现:解决链表线性访问时间太慢,树的时间复杂度O(logN);
2.二叉树的定义,最多两个儿子节点;
3.二叉查找树,左小,右大,中居中;remove方法,两种,只有一个儿子节点,有两个儿子节点;
4.AVL树,在二叉查找树基础上加平衡条件,旋转方法,单旋转,双旋转;
5.树的遍历方式,中序遍历,左根右;后续遍历,左右根;先序遍历,根左右;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/54269.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

流处理详解

【今日】 目录 一 Stream接口简介 Optional类 Collectors类 二 数据过滤 1. filter()方法 2.distinct()方法 3.limit()方法 4.skip()方法 三 数据映射 四 数据查找 1. allMatch()方法 2. anyMatch()方法 3. noneMatch()方法 4. findFirst()方法 五 数据收集…

Day43|leetcode 1049.最后一块石头的重量II、494.目标和、474.一和零

leetcode 1049.最后一块石头的重量II 题目链接&#xff1a;1049. 最后一块石头的重量 II - 力扣&#xff08;LeetCode&#xff09; 视频链接&#xff1a;动态规划之背包问题&#xff0c;这个背包最多能装多少&#xff1f;LeetCode&#xff1a;1049.最后一块石头的重量II_哔哩…

date_range()函数--Pandas

1. 函数功能 生成连续的日期时间序列 2. 函数语法 pandas.date_range(startNone, endNone, periodsNone, freqNone, tzNone, normalizeFalse, nameNone, inclusiveboth, *, unitNone, **kwargs)3. 函数参数 参数含义start可选参数&#xff0c;起始日期end可选参数&#xff…

01-Flask-简介及环境准备

Flask-简介及环境准备 前言简介特点Flask 与 Django 的比较环境准备 前言 本篇来介绍下Python的web框架–Flask。 简介 Flask 是一个轻量级的 Web 框架&#xff0c;使用 Python 语言编写&#xff0c;较其他同类型框架更为灵活、轻便且容易上手&#xff0c;小型团队在短时间内…

QtCreator指定Windows Kits版本

先说下事件起因&#xff1a;之前一直在用Qt5.12.6&#xff0b;vs2017在写程序&#xff0c;后面调研了一个开源库Qaterial&#xff0c;但是翻来覆去的编译都有问题&#xff0c;后面升级到了Qt5.15.2&#xff0b;vs2019来进行cmake的编译&#xff0c;搞定了Qaterial&#xff0c;但…

软考A计划-系统集成项目管理工程师-小抄手册(共25章节)-下

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

深度学习经典检测方法的概述

深度学习经典的检测方法 two-stage&#xff08;两阶段&#xff09;&#xff1a;Faster-rcnn Mask-Rcnn系列 两阶段&#xff08;two-stage&#xff09;是指先通过一个区域提取网络&#xff08;region proposal network&#xff0c;RPN&#xff09;生成候选框&#xff0c;再通过…

SLAM十四讲学习笔记 第二期:部分课后实践代码

持续更新.... 前期准备第二讲实验一&#xff1a;简单输出 第五讲任务一&#xff1a;imageBasics&#xff08;Ubuntu配置opencv&#xff09;任务二&#xff1a;双目匹配点云&#xff08;Ubuntu配置pangolin&#xff09;检验部分我认为可以加深对CMake的理解 任务三&#xff1a;r…

pandas数据分析——groupby得到分组后的数据

groupbyagg分组聚合对数据字段进行合并拼接 Pandas怎样实现groupby聚合后字符串列的合并&#xff08;四十&#xff09; groupby得到分组后的数据 pandas—groupby如何得到分组里的数据 date_range补齐缺失日期 在处理时间序列的数据中&#xff0c;有时候会遇到有些日期的数…

springboot源码编译问题

问题一 Could not find artifact org.springframework.boot:spring-boot-starter-parent:pom:2.2.5.RELEASE in nexus-aliyun (http://maven.aliyun.com/nexus/content/groups/public/) 意思是无法在阿里云的镜像仓库中找到资源 解决&#xff1a;将配置的镜像删除即可&#…

STM32 CAN 波特率计算分析

这里写目录标题 前言时钟分析时钟元到BIT 前言 CubeMX中配置CAN波特率的这个界面刚用的时候觉得非常难用&#xff0c;怎么都配置不到想要的波特率。接下来为大家做一下简单的分析。 时钟分析 STM32F4的CAN时钟来自APB1 在如下界面配置&#xff0c;最好配置为1个整一点的数。…

cpolar做一个内网穿透

因为不在公司&#xff0c;需要访问公司的数据库&#xff0c;所以做一个内网穿透 下载安装 下载地址&#xff1a; https://dashboard.cpolar.com/get-started 下载后是个压缩包&#xff0c;解压后傻瓜式安装 操作隧道 安装后打开Cpolar Web UI 登录账号&#xff0c;查看隧…

如何评估分类模型的好坏

如何评估分类模型的好坏 评估分类预测模型的质量&#xff0c;常用一个矩阵、三条曲线和六个指标。 一个矩阵&#xff1a;混淆矩阵&#xff1b;三条曲线&#xff1a;ROC曲线、PR曲线、KS曲线&#xff1b;六个指标&#xff1a;正确率Acc、查全率R、查准率P、F值、AUC、BEP值、KS…

【设计模式--原型模式(Prototype Pattern)

一、什么是原型模式 原型模式&#xff08;Prototype Pattern&#xff09;是一种创建型设计模式&#xff0c;它的主要目的是通过复制现有对象来创建新的对象&#xff0c;而无需显式地使用构造函数或工厂方法。这种模式允许我们创建一个可定制的原型对象&#xff0c;然后通过复制…

SVM详解

公式太多了&#xff0c;就用图片用笔记呈现&#xff0c;SVM虽然算法本质一目了然&#xff0c;但其中用到的数学推导还是挺多的&#xff0c;其中拉格朗日约束关于α>0这块证明我看了很长时间&#xff0c;到底是因为悟性不够。对偶问题也是&#xff0c;用了一个简单的例子才明…

软考A计划-系统集成项目管理工程师-小抄手册(共25章节)-上

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

求生之路2私人服务器开服搭建教程centos

求生之路2私人服务器开服搭建教程centos 大家好我是艾西&#xff0c;朋友想玩求生之路2(left4dead2)重回经典。Steam玩起来有时候没有那么得劲&#xff0c;于是问我有没有可能自己搭建一个玩玩。今天跟大家分享的就是求生之路2的自己用服务器搭建的一个心路历程。 &#xff0…

React+Typescript 父子组件事件传值

好 之前我们将 state 状态管理简单过了一下 那么 本文 我们来研究一下事假处理 点击事件上文中我们已经用过了 这里 我们就不去讲了 主要来说说 父子之间的事件 我们直接来编写一个小dom 我们父组件 编写代码如下 import Hello from "./components/hello";functio…

记录 JSONObject.parseObject json对象转换 对象字段为null

1.业务背景 使用websocket 接收消息都是String类型&#xff0c;没办法自定义实体类接收&#xff0c;所以接发都必须将json 转 对象 对象转 json。 这是我最开始的实体类&#xff0c;也就是转换的类型 package com.trinity.system.domain;import lombok.AllArgsConstructor; im…

【Midjourney电商与平面设计实战】创作效率提升300%

不得不说&#xff0c;最近智能AI的话题火爆圈内外啦。这不&#xff0c;战火已经从IT行业燃烧到设计行业里了。 刚研究完ChatGPT&#xff0c;现在又出来一个AI作图Midjourney。 其视觉效果令不少网友感叹&#xff1a;“AI已经不逊于人类画师了!” 现如今&#xff0c;在AIGC 热…