JAVA数据结构和算法:第六章(树)

xiaoxiao2021-02-27  202

树是n个结点的有限集。n=0时称为空树。在任意一颗非空树中:(1)有且仅有一个特定的称为根的结点;(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2…..Tm,其中每一个集合本身又是一棵树,并且称为根的子树。如下图

结点: 树的结点包含一个数据元素及若干指向其子树的分支。

结点的度:结点拥有的子树数称为结点的度。

叶结点:度为0的结点称为叶结点或终端结点。

分支节点:度不为0的结点称为非终端结点或分支结点。除根节点之外,分支结点也称为内部结点。

树的度:树中各结点度的最大值。如下图,结点度的最大值是D的度,所以树的度也为3

孩子结点:结点的直接后继称为该结点的孩子结点。

双亲结点:一个结点的直接前驱称为该结点的双亲结点。

兄弟结点:同一双亲结点的孩子结点之间互称兄弟结点。

祖先结点:结点的祖先结点是指从根结点到该结点路径上的所有结点。

子孙结点:一个结点的直接后继和间接后继称为该结点的子孙结点。

结点的层次:从根结点开始定义,根结点的层次为1,根的直接后继层次为2,以此类推。

堂兄弟结点:父亲是兄弟关系或堂兄关系的结点称为堂兄弟结点。

树的高度(深度):树中结点的最大层次称为树的高度或者深度。

有序树:如果将树中结点的各子树看成从左到右是有次序的,不能互换的,则称该树为有序树,否则称为无序树。

森林:m颗互不相交的树的集合称为森林。将一棵非空树的根结点删去,树就变成森林;反之,给森林增加一个统一的根结点,森林就变成一棵树。

树的存储结构

说到存储结构,就很自然的想到我们前面讲过的顺序存储和链式存储两种结构。树是一种一对多的结构,那么如何存储能表示结点之间的关系呢?

树的主要存储方法有三种:

双亲表示法

这种方法用一组连续的空间存储树的结点,同时在每个结点中,附设一个指示器指示其双亲结点在表中的位置,也就是说每个结点除了保存自己的数据,还知道其双亲的位置。

由于根结点是没有双亲的,所以我们默认设置根结点的的双亲位置设置为-1.

这种存储方式,利用了树中每个结点(根结点除外)只有一个双亲结点的性质,如果查找结点的双亲结点,时间复杂度仅为O(1),很容易找到根结点,但是如果要找某个结点的孩子结点就需要遍历整个结构才行。

孩子表示法

这种方法通常是把每个结点的孩子结点排列起来,构成一个单链表,称为孩子链表。n个结点共有n个孩子链表(叶子结点的孩子链表为空表),而n个结点的数据和n个孩子链表的头指针组成一个顺序表。

孩子链表中的结点结构,child为该结点在数组中的下标,next指向下一个孩子结点。

数组中的结点结构,data为数据,firstchild指向孩子链表的第一个结点

这样的结构对于我们要查找某个结点的孩子或者兄弟比较方便,只需要查找该结点的孩子链表即可,可是如果要查找某个结点的父亲,那就比较麻烦,需要遍历整个结构。那么我们可不可以把双亲表示法和孩子表示法结合一下呢?当然可以

孩子兄弟表示法

这种方法是每个结点设置两个指针域,指向该结点的第一个孩子结点和右兄弟结点,这种表示法又被称为树的二叉表示法,或者二叉链表表示法。

这种存储结构便于实现树的各种操作,当然要想找到某个结点的双亲也是有一定的缺陷,不过我们完全可以添加一个parent指针来解决这个问题。

二叉树

二叉树是一种简单而重要的树结构,任何树都可以转化为二叉树进行处理,二叉树非常适合计算机处理。

二叉树是n个结点的有限集合,该集合为空时被称为空二叉树,二叉树每个结点的度数都不能大于2,每个结点的孩子结点次序不能任意颠倒。

二叉树每个结点最多有两棵子树,没有子树或者一棵都可以,左子树和右子树是有顺序的,次序不能任意颠倒,即使树中某结点只有一棵子树,也要区分左右。

特殊二叉树

斜树:所有的结点都只有右子树的二叉树被称为右斜树,所有的结点都只有左子树的二叉树称为左斜树。两者统称为斜树。

满二叉树:一颗二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都在同一层上,这样的二叉树称为满二叉树。 满二叉树的顺序表示,从二叉树的根开始,从上到下从左到右从1顺序编号。

完全二叉树:对一棵具有n个结点的二叉树编号,如果编号为i的结点和同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则称为完全二叉树。满二叉树肯定为完全二叉树,完全二叉树不一定是满二叉树。

二叉树的性质

性质一:在二叉树的第i层上最多有2^(i-1)个结点(i >=1)

性质二:深度为k的二叉树至多有2^k-1个结点(k>=1)

性质三:对任意一棵二叉树T,若终端结点数为n0,而其度数为2的结点数为n2,则n0=n2+1.终端结点数其实就是叶子结点数

性质四:具有n个结点的完全二叉树的深度为[log2n]+1([x]符号表示不大于x的最大整数)

性质五: 对于具有n个结点的完全二叉树,如果按照从上到下从左到右的顺序对二叉树中的所有结点从1开始进行顺序编号,则对于任意的序号为i的结点有: (1)如i=1,则序号为i的结点是根结点,无双亲结点;如i>1,则序号为i的结点的双亲结点为 [i/2] (2)如2i>n,则序号为i的结点无左孩子;如2i<=n,则序号为i的结点的左孩子结点的序号为2i。 (3)如2i+1>n,则序号为i的结点无右孩子;如2i+1<=n,则序号为i的结点的右孩子结点的序号为2i+1.

这几个公式证明还是蛮简单的,如果有兴趣可以参考百度证明一下。

二叉树的顺序存储结构

二叉树的顺序存储结构就是用数组来存储二叉树中的结点,并且数组的下标要体现结点之间的存储关系。

这种存储方式对完全二叉树而言是非常方便的,因为完全二叉树的定义比较严格,我们来看一下。

但是如果是一般的二叉树呢?

如果是一般的二叉树,我们需要将不存在的结点设置为”^”,将其补成一颗完全二叉树来看待。如下图:颜色浅的是不存在的。

顺序结构对一般的二叉树而言,空间浪费太大,所以一般只用于完全二叉树。

二叉树的链式存储结构

对于任意二叉树来说,每个结点最多有两个孩子,所以结点的结构应该设计数据域、左孩子域、右孩子域。

如果有需要,可以增加一个parent域,指向该结点的双亲,这种结构就称为二叉树的三叉链表存储结构。

二叉树的遍历

二叉树的遍历是指按照某种规律对二叉树中的每个结点进行访问且仅访问一次。访问可指计算二叉树中的数据信息,打印该结点信息,包括对结点进行任何其他操作。

二叉树遍历方式:

先序遍历

规则是若二叉树为空,则空操作返回。否则先访问根节点,然后先序遍历左子树,再先序遍历右子树。如下图:遍历顺序为ABDGHCEIF

中序遍历

规则是若二叉树为空,则空操作返回。否则先中序遍历根节点的左子树,然后访问根节点,然后中序遍历右子树。如下图,遍历顺序为GDHBAEICF

后序遍历 规则是若二叉树为空,则空操作返回。否则后序遍历左子树,然后后续遍历右子树,然后访问根节点。如下图,遍历顺序为GHDBIEFCA

层序遍历

规则是若二叉树为空,则空操作返回。否则从树的第一层,即根节点开始,从上到下从左到右逐层遍历。如下图,遍历顺序为:ABCDEFGHI

二叉树的遍历算法

二叉树的遍历操作可以采用递归的方式来运行,极其简洁。

先序遍历算法

void preOrder(TreeNode root){ if(root!=null){ //访问根节点 System.out.println(root.data); //先序遍历左子树 preOrder(root.leftNode); //先序遍历右子树 preOrder(root.rightNode); } }

中序遍历算法

void inOrder(TreeNode root){ if(root!=null){ //中序遍历左子树 inOrder(root.leftNode); //访问根节点 System.out.println(root.data); //中序遍历右子树 inOrder(root.rightNode); } }

后序遍历算法

void postOrder(TreeNode root){ if(root!=null){ //后序遍历左子树 postOrder(root.leftNode); //后序遍历右子树 postOrder(root.rightNode); //访问根节点 System.out.println(root.data); } }

递归遍历算法的时间复杂度分析:设二叉树有n个结点,对每个结点都要执行访问和后退操作,对结点的访问为n次,所以时间复杂度为O(n)。

基于栈的递归消除,我们可以不用递归的方式进行遍历

先序遍历二叉树的非递归算法

非递归算法需要设置一个堆栈,保留当前结点指针,然后根据先序遍历规则进行访问。 具体看代码,一目了然。

public void preOrderTraverse() { System.out.print("先序遍历(非递归)"); // 创建一个空栈,可以用jdk提供的,也可以用我们自己写的 LinkedStack<TreeNode<T>> stack = new LinkedStack<TreeNode<T>>(); //获取根节点 TreeNode<T> p = this.root; // 根节点非空或栈非空时 while (p != null || !stack.isEmpty()) { //如果根节点不为空 if (p != null) { // 访问根结点 System.out.print(p.data + " "); // 将根结点入栈,来保存结点的指向 stack.push(p); // 然后让当前根节点等于根节点的左孩子,相当于进入左子树 p = p.left; } else { // 出栈获取根结点 p = stack.pop(); // 访问根节点的右子树 p = p.right; } } System.out.println(); }

中序遍历二叉树的非递归算法

中序和先序都差不多,只不过位置需要变化一下。

public void inOrderTraverse() { System.out.print("中序遍历(非递归) : "); LinkedStack<TreeNode<T>> stack = new LinkedStack<TreeNode<T>>(); TreeNode<T> p = this.root; // 根节点非空或栈非空时 while (p != null || !stack.isEmpty()) { if (p != null) { // 根结点入栈 stack.push(p); // 进入根结点 p = p.left; } else { // p指向出栈结点 p = stack.pop(); System.out.print(p.data + " "); // 进入右子树 p = p.right; } } System.out.println(); }

后序遍历二叉树的非递归算法 后序遍历的非递归算法比较复杂,因为后序遍历要求左、右子树都访问完后,再访问根结点,那么如何判断左、右子树都已经访问过了呢?解决方法有很多种。这里我们采用的是判断刚访问过的结点是不是当前栈顶结点的右孩子。

有三种情况

(1):栈顶结点无右孩子,此时应该访问根结点 (2):栈顶结点的右孩子是刚被访问过的结点,表明右子树已经遍历过,此时也访问根节点 (3):其余情况都应该继续访问右子树 public void postOrderTraverse() { System.out.print("后序遍历(非递归): "); LinkedStack<TreeNode<T>> stack = new LinkedStack<TreeNode<T>>(); //p指向根结点,front结点用来标识当前结点是否被访问过 TreeNode<T> p = this.root, front = null; while (p != null || !stack.isEmpty()) { if (p != null) { // 根结点入栈 stack.push(p); // 进入左子树 p = p.left; } else { // 从左子树返回根结点,根结点不出栈,只是获取根结点而已 p=stack.get(); // 根结点有右孩子,且右孩子没有被访问过 if (p.right != null && p.right != front) { // 进入右子树 p = p.right; stack.push(p); // 进入左子树 p = p.left; } else { //弹出根结点 p = stack.pop(); System.out.print(p.data + " "); //让front等于当前结点,表示当前结点已经遍历过 front = p; p = null; } } } System.out.println(); }

由遍历序列确定二叉树

有这样一个问题,已知一棵二叉树的先序遍历序列为ABCDEF,中序遍历序列为CBAEDF,那么这棵二叉树的后序遍历结果是什么?

(1)由于先序遍历先访问根节点,所以可知根节点为A (2)由于中序遍历先访问左子树,再访问根节点,所以可知CB是左子树的结点,EDF是右子树的结点 (3)由于先序遍历顺序可知,B为A的左孩子,由中序遍历顺序可知,C为B的左孩子 (4)由于先序遍历顺序可知,D是A的右孩子,由中序遍历顺序可知,EF为D的左右孩子 (4)所以后序遍历为CBEFDA

只有一种遍历序列是无法确定二叉树的,至少需要两种遍历序列。

先序+中序 能唯一确定二叉树 后序+中序 能唯一确定二叉树 先序+后序 不能唯一确定二叉树

二叉树的实现

//树的结点类 public class TreeNode<T> { //存储数据 public T data; //指向左孩子和右孩子结点 public TreeNode<T> left,right; public TreeNode(T data, TreeNode<T> left, TreeNode<T> right) { super(); this.data = data; this.left = left; this.right = right; } public TreeNode(T data) { super(); this.data = data; } public TreeNode() { this.data = null; this.left = null; this.right =null; } public String toString() { return this.data.toString(); } } //二叉树实现 public class BinaryTree<T> { // 根结点,结点结构为二叉链表 public TreeNode<T> root; /** * 构造空二叉树 */ public BinaryTree() { this.root = null; } /** * 判断二叉树是否为空 */ public boolean isEmpty() { return this.root == null; } /** * 先序遍历二叉树 */ public void preOrder() { System.out.print("先序遍历二叉树:"); // 调用先序遍历二叉树的递归方法 preOrder(root); System.out.println(); } /** * 先根次序遍历以p结点为根的子二叉树,递归方法 * * @param p */ private void preOrder(TreeNode<T> p) { if (p != null) { // 访问当前结点 System.out.print(p.data.toString() + " "); // 按先序遍历当前结点的左子树,递归调用 preOrder(p.left); // 按先序遍历当前结点的右子树,递归调用 preOrder(p.right); } } /** * 中序遍历二叉树 */ public void inOrder() { System.out.print("中序遍历二叉树: "); inOrder(root); System.out.println(); } /** * 中序遍历以p结点为根的子二叉树,递归方法 * * @param p */ private void inOrder(TreeNode<T> p) { if (p != null) { // 中序遍历左子树 inOrder(p.left); System.out.print(p.data.toString() + " "); // 中序遍历右子树 inOrder(p.right); } } /** * 后序遍历二叉树 */ public void postOrder() { System.out.print("后序遍历二叉树: "); postOrder(root); System.out.println(); } /** * 后序遍历以p结点为根的子二叉树,递归方法 * * @param p */ private void postOrder(TreeNode<T> p) { if (p != null) { postOrder(p.left); postOrder(p.right); System.out.print(p.data.toString() + " "); } } /** * 二叉树的层次遍历 * * 按层次遍历二叉树 */ public void levelOrder() { System.out.print("层次遍历二叉树: "); // 创建一个空队列 Queue<TreeNode<T>> que = new ArrayDeque<TreeNode<T>>(); TreeNode<T> p = this.root; while (p != null) { // 访问当前结点 System.out.print(p.data + " "); // 如果根节点有左孩子,p的左孩子结点入队 if (p.left != null) que.add(p.left); // 如果根节点有右孩子,p的右孩子结点入队 if (p.right != null) que.add(p.right); // p指向出队结点,若队列空返回null p = que.poll(); } System.out.println(); } /** * 返回二叉树的结点个数 */ public int count() { return count(root); } /** * 返回以p结点为根的子树的结点个数 * * @param p * @return */ private int count(TreeNode<T> p) { if (p == null) { return 0; } return 1 + count(p.left) + count(p.right); } /** * 返回二叉树的高度 */ public int height() { return height(root); } /** * 返回以p结点为根的子树高度,后序遍历 * * @param p * @return */ private int height(TreeNode<T> p) { if (p == null) { return 0; } // 返回左子树的高度 int lh = height(p.left); // 返回右子树的高度 int rh = height(p.right); // 当前子树高度为较高子树的高度加1 return (lh >= rh) ? lh + 1 : rh + 1; } /** * 查找并返回首次出现的关键字key元素 */ public T search(T key) { return searchNode(root, key).data; } /** * 查找并返回首次出现的关键字为key元素结点 * * @param key * @return */ public TreeNode<T> searchNode(T key) { return searchNode(root, key); } /** * 在以p为根的子树中查找并返回首次出现的关键字为key元素结点,若未找到返回null,先序遍历 * * @param p * @param key * @return */ private TreeNode<T> searchNode(TreeNode<T> p, T key) { if (p == null || key == null) return null; // 查找成功,返回找到结点 if (p.data.equals(key)) { return p; } // 在左子树中查找,递归调用 TreeNode<T> find = searchNode(p.left, key); // 若在左子树中未找到,怎继续在右子树中查找,递归调用 if (find == null) { find = searchNode(p.right, key); } // 返回查找结果 return find; } /** * 返回node结点的父母结点,若空树、未找到或node为根,则返回null */ public TreeNode<T> getParent(TreeNode<T> node) { if (root == null || node == null || node == root) { return null; } return getParent(root, node); } /** * 在以p为根的子树中查找并返回node结点的父母结点 * * @param p * @param node * @return */ private TreeNode<T> getParent(TreeNode<T> p, TreeNode<T> node) { if (p == null) return null; if (p.left == node || p.right == node) return p; TreeNode<T> find = getParent(p.left, node); if (find == null) { find = getParent(p.right, node); } return find; } /** * 插入元素x作为根结点,原根结点作为其左孩子 */ public void insertRoot(T x) { this.root = new TreeNode<T>(x, this.root, null); } /** * 插入元素x作为p结点的孩子,若leftChild为true,插入结点作为左孩子,否则作为右孩子 返回插入结点,若p == null, * 则抛出空对象异常 */ public TreeNode<T> insertChild(TreeNode<T> p, T x, boolean leftChild) { if (x == null) return null; if (leftChild) { // 插入x结点作为p的左孩子,p原左孩子成为x的左孩子 p.left = new TreeNode<T>(x, p.left, null); return p.left; } // 插入x结点作为p的右孩子,p原右孩子成为x的右孩子 p.right = new TreeNode<T>(x, null, p.right); return p.right; } /** * 删除p结点的左或右子树,若leftChild为true,删除左子树,否则删除右子树 * * 若p == null,将抛出空对象异常 */ public void removeChild(TreeNode<T> p, boolean leftChild) { if (leftChild) p.left = null; else p.right = null; } /** * 删除二叉树的所有结点 */ public void removeAll() { this.root = null; } public String toString() { return toString(root); } private String toString(TreeNode<T> p) { if (p == null) { return ""; } // 递归调用 return p.data.toString() + " " + toString(p.left) + toString(p.right); } /** * 以先根和中根序列构建二叉树 * * @param prelist * @param inlist */ public BinaryTree(T[] prelist, T[] inlist) { this.root = create(prelist, inlist, 0, 0, prelist.length); } private TreeNode<T> create(T[] prelist, T[] inlist, int preStart, int inStart, int n) { System.out.print("prelist: "); print(prelist, preStart, n); System.out.print(", inlist: "); print(inlist, inStart, n); System.out.println(); if (n <= 0) { return null; } // 根结点值 T elem = prelist[preStart]; // 创建叶子结点 TreeNode<T> p = new TreeNode<T>(elem); int i = 0; // 在中根序列中查找根值所在位置 while (i < n && elem.equals(inlist[inStart + i])) i++; // 创建左子树 p.left = create(prelist, inlist, preStart + 1, inStart, i); // 创建右子树 p.right = create(prelist, inlist, preStart + 1, inStart + i + 1, n - 1 - i); return p; } private void print(T[] table, int start, int n) { for (int i = 0; i < n; i++) { System.out.print(table[start + i]); } } /** * 以标明空子树的先根序列构造二叉树 * * @param prelist */ public BinaryTree(T[] prelist) { this.root = create(prelist); } /** * 以标明空子树的先根序列构造一棵子二叉树,子的根值是prelist[i],返回所创建子树的根结点 */ private int i = 0; private TreeNode<T> create(T[] prelist) { TreeNode<T> p = null; if (i < prelist.length) { T elem = prelist[i]; i++; if (elem != null) { // 创建叶子结点 p = new TreeNode<T>(elem); // 创建p的左子树 p.left = create(prelist); // 创建p的右子树 p.right = create(prelist); } } return p; } /** * 二叉树遍历的非递归算法 * * 先序遍历二叉树的非递归算法 */ public void preOrderTraverse() { System.out.print("先根次序遍历(非递归)"); // 创建一个空栈 LinkedStack<TreeNode<T>> stack = new LinkedStack<TreeNode<T>>(); TreeNode<T> p = this.root; // p非空或栈非空时 while (p != null || !stack.isEmpty()) { if (p != null) { // 方位结点 System.out.print(p.data + " "); // p结点入栈 stack.push(p); // 进入左子树 p = p.left; } else { // p指向出栈结点 p = stack.pop(); // 进入右子树 p = p.right; } } System.out.println(); } /** * 中序遍历二叉树的非递归算法 */ public void inOrderTraverse() { System.out.print("中序遍历(非递归) : "); LinkedStack<TreeNode<T>> stack = new LinkedStack<TreeNode<T>>(); TreeNode<T> p = this.root; // p非空或栈非空时 while (p != null || !stack.isEmpty()) { if (p != null) { // p结点入栈 stack.push(p); // 进入左结点 p = p.left; } else { // p指向出栈结点 p = stack.pop(); System.out.print(p.data + " "); // 进入右子树 p = p.right; } } System.out.println(); } /** * 遍历输出叶子结点 */ public void leaf() { leaf(root); } private void leaf(TreeNode<T> p) { if (p != null) { if (p.left == null && p.right == null) System.out.print(p.data.toString() + " "); leaf(p.left); leaf(p.right); } } /** * 返回以p结点为根的子树的叶子结点个数 * * @param p * @return */ public int countLeaf(TreeNode<T> p) { if (p == null) { return 0; } if (p.left == null && p.right == null) { return 1; } return countLeaf(p.left) + countLeaf(p.right); } /** * 将首次出现的值为x的结点替换为y * * @param x * @param y */ public void replace(T x, T y) { TreeNode<T> find = searchNode(x); if (find != null) { find.data = y; } } /** * 将值为x的结点值全部替换为y * * @param x * @param y */ public void replaceAll(T x, T y) { if (x != null && y != null) { replaceAll(root, x, y); } } /** * 在以p为根的子树中实现全部替换 * * @param p * @param x * @param y */ private void replaceAll(TreeNode<T> p, T x, T y) { if (p != null) { if (p.data.equals(x)) { p.data = y; } replaceAll(p.left, x, y); replaceAll(p.right, x, y); } } public void postOrderTraverse() { System.out.print("后序遍历(非递归): "); LinkedStack<TreeNode<T>> stack = new LinkedStack<TreeNode<T>>(); TreeNode<T> p = this.root, front = null; while (p != null || !stack.isEmpty()) { if (p != null) { // p结点入栈 stack.push(p); // 进入左子树 p = p.left; } else { // 从左子树返回p结点,p结点不出栈 p=stack.get(); // p有右孩子,且右孩子没有被访问过 if (p.right != null && p.right != front) { // 进入右子树 p = p.right; stack.push(p); // 向左走 p = p.left; } else { p = stack.pop(); System.out.print(p.data + " "); front = p; // front 是p在后根遍历次序下的前驱结点 p = null; } } } System.out.println(); } /** * 返回x结点所在的层次,若空树或未查找到x返回-1 * * @param x * @return */ public int getLevel(T x) { // 令根结点的层次为1 return getLevel(root, 1, x); } /** * 在以p结点(层次为i)为根的子树中,求x结点所在的层次 * * @param p * @param i * @param x * @return */ private int getLevel(TreeNode<T> p, int i, T x) { // 空树或查找不成功 if (p == null) { return -1; } if (p.data.equals(x)) return i; // 在左子树查找 int level = getLevel(p.left, i + 1, x); if (level == -1) { // 继续在右子树中查找 level = getLevel(p.right, i + 1, x); } return level; } /** * 比较两棵二叉树是否相等 * * @param obj * @return */ @SuppressWarnings("unchecked") public boolean equals(Object obj) { return obj == this || obj instanceof BinaryTree && equals(this.root, ((BinaryTree<T>) obj).root); } /** * 判断以p和q结点为根的两棵子树是否相等,递归方法 * @param p * @param q * @return */ private boolean equals(TreeNode<T> p, TreeNode<T> q) { return p == null && q == null || p != null && q != null && p.data.equals(q.data) && equals(p.left, q.left) && equals(p.right, q.right); } /** * 深拷贝,以已知的bitree构造二叉树 * @param bitree */ public BinaryTree(BinaryTree<T> bitree) { this.root = copy(bitree.root); } /** * 复制以p为根的子二叉树,返回新建子二叉树的根结点 * @param p * @return */ private TreeNode<T> copy(TreeNode<T> p) { if (p == null) return null; TreeNode<T> q = new TreeNode<T>(p.data); //复制左子树,递归调用 q.left = copy(p.left); //复制右子树,递归调用 q.right = copy(p.right); //返回建立子树的根结点 return q; } /** * 判断是否完全二叉树 * @return */ boolean isCompleteBinaryTree() { if (this.root == null) return true; LinkedQueue<TreeNode<T>> que = new LinkedQueue<TreeNode<T>>(); que.enqueue(root); TreeNode<T> p = null; while (!que.isEmpty()) { //p指向出对结点 p = que.dequeue(); //p的非空孩子结点入队 if (p.left != null) { que.enqueue(p.left); if (p.right != null) { //发现空子树,须检测队列中是否都是叶子结点 que.enqueue(p.right); } else { break; } } else { if (p.right != null) { //p的左子树空而右子树不空 return false; } else { //p是叶子,须检测队列中是否都是叶子结点 break; } } } //检测队列中是否都是叶子结点 while(!que.isEmpty()) { p= que.dequeue(); //发现非叶子,确定不是 if (p.left != null || p.right != null) { return false; } } return true; } } //测试二叉树 public class Test { public static void main(String[] args) { BinaryTree<String> bitree = new BinaryTree<String>(); TreeNode<String> child_f, child_d, child_b, child_c; child_d = new TreeNode<String>("D", null, new TreeNode<String>("G")); child_b = new TreeNode<String>("B", child_d, null); child_f = new TreeNode<String>("F", new TreeNode<String>("H"), null); child_c = new TreeNode<String>("C", new TreeNode<String>("E"), child_f); bitree.root = new TreeNode<String>("A", child_b, child_c); bitree.preOrder(); bitree.preOrderTraverse(); bitree.inOrder(); bitree.inOrderTraverse(); bitree.postOrder(); bitree.postOrderTraverse(); bitree.levelOrder(); } }

线索二叉树

若结点有左孩子,则left指针指向左孩子,否则left指针指向其前驱结点。若结点有右孩子,则right指针指向其右孩子,否则指向其后继结点。这种结构中,指向前驱和后继的指针称为线索,对二叉树以某种次序进行遍历并且加上线索的过程称为线索化,线索化了的二叉树称为线索二叉树。

这种结构我们可以在结点上设置标记,当标记为false时说明指向的为孩子结点,当标记为true时说明指向的为前驱或后继。

//线索二叉树结点 public class ThreadNode<T> { //存储数据 public T data; //指向左孩子和右孩子结点 public TreeNode<T> left,right; //标志位,标志着左右孩子是指向前驱后继还是左右孩子 public boolean isLeftThread,isRightLeft; public ThreadNode() { this.data = null; this.left = null; this.right =null; } public ThreadNode(T data) { this.data=data; } public ThreadNode(T data, TreeNode<T> left, TreeNode<T> right) { super(); this.data = data; this.left = left; this.right = right; } public ThreadNode(T data, TreeNode<T> left, TreeNode<T> right, boolean isLeftThread, boolean isRightLeft) { super(); this.data = data; this.left = left; this.right = right; this.isLeftThread = isLeftThread; this.isRightLeft = isRightLeft; } }

线索化实质上是将二叉链表中的空指针指向相应结点的前驱或后继地址,而前驱和后继的信息只有遍历时才能得到,因此线索化的过程其实是在遍历过程中修改空指针的过程。

中序遍历线索化

//这个结点用来保存当前结点,来做下一个结点的前驱 public ThreadNode<T> pre=null; //使用中序遍历进行线索化 public void inThread(ThreadNode<T> root) { if(root!=null) { //线索化左子树 inThread(root.left); //如果左子树为空,则设置前驱结点 if(root.left==null) { root.isLeftThread=true; root.left=pre; } //设置后继结点 if(pre!=null&&pre.right==null) { pre.isRightLeft=true; pre.right=root; } pre=root; //线索化右子树 inThread(root.right); } }

中序遍历线索二叉树

public void inThreadOrder(ThreadNode<T> root) { //如果根结点不为null if (root != null) { //查找最后一个左孩子 while(root!=null&&!root.isLeftThread) { root=root.left; } while(root!=null) { //打印数据 System.out.print(root.data+" "); //如果当前结点的右孩子是线索的话,让当前结点等于根节点 if(root.isRightLeft) { root=root.right; }else { root=root.right; while(root!=null&&!root.isLeftThread) { root=root.left; } } } } }

树、森林和二叉树的转换

在前面讲树的存储结构时,我们知道树的孩子兄弟表示法可以将一棵树用二叉链表进行存储,借助二叉链表,树和二叉树可以相互进行转换。树的孩子兄弟链表从物理结构上与二叉树的二叉链表是完全相同的,只是逻辑含义不同。

树转换为二叉树

转换步骤

(1)树中所有相邻兄弟之间加一条线 (2)对树中的每个结点,只保留其与第一个孩子结点之间的连线,删去其与其他孩子之间的连线 (3)以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明

森林转换为二叉树

森林是若干棵树的集合。森林同样也可以转换为二叉树。

转换步骤

(1)将森林中的每棵树转换成相应的二叉树 (2)第一棵二叉树不懂,从第二棵二叉树开始,依次把后一棵的二叉树的根结点作为前一棵二叉树根结点的右孩子,当所有二叉树连在一起后,所得到的二叉树就是由森林转换得到的二叉树。

二叉树还原为树

转换步骤

(1)若某结点是其双亲的左孩子,则把该结点的右孩子、右孩子的右孩子……都与该结点的双亲结点用线连起来。 (2)删掉原二叉树中所有双亲结点与右孩子结点的连线 (3)层次调整,使之结构分明

二叉树还原为森林

怎么判断一棵二叉树是转换为树还是森林呢?只需要看这棵二叉树的根结点有没有右孩子,有就转换为森林,没有就转换为树。

转换步骤

(1)从根结点开始,若右孩子存在,则把与右孩子结点的连线删除,再查看分离后的二叉树,若右孩子存在,则删除连线……,直到所有右孩子连线都删除,得到分离的二叉树。 (2)再将每棵分离后的二叉树转换为树即可。

树与森林的遍历

树的遍历方法主要有以下两种:

(1)先序遍历:若树非空,则遍历方式为:访问根结点,然后从左到右,依次先序遍历每一课子树。

(2)后序遍历:若树非空,则遍历方式为:从左到右,依次后序遍历根结点的每一棵子树,然后访问根结点。

森林的遍历方法主要有以下两种:

(1)先序遍历:若森林非空,则遍历方式为:访问森林第一棵树的根结点,先序遍历第一棵树的根结点的子树,先序遍历剩余的树构成的森林。

(2)中序遍历:若森林非空,则遍历方式为:中序遍历森林中第一棵树的根结点的子树,访问第一棵树的根结点,中序遍历剩余的树构成的森林。

(3)后序遍历:若森林非空,则遍历方式为:后序遍历森林中第一棵树的根结点的子树,然后后序遍历剩余的树构成的森林,最后访问第一棵树的根结点。

转载请注明原文地址: https://www.6miu.com/read-10375.html

最新回复(0)