目录
一、二叉树
1.1 为什么需要树这种数据结构?
(1)数组存储方式的分析
优点:通过下标方式访问元素,速度快。对于有序数组,还可使用二分查找提高检索速度。
缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低 。
(2)链式存储方式的分析
优点:在一定程度上对数组存储方式有优化(比如:插入一个数值节点,只需要将插入节点,链接到链表中即可,删除效率也很好)。
缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历) 【示意图】
(3)树存储方式的分析
能提高数据存储,读取的效率, 比如利用 二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入,删除,修改的速度。
1.2 树示意图
1.3 二叉树的概念
-
树有很多种,每个节点最多只能有两个子节点的一种形式称为二叉树。
-
二叉树的子节点分为左节点和右节点
-
示意图
4.如果该二叉树的所有叶子节点都在最后一层,并且结点总数= , n 为层数,则我们称为满二叉树。
5.如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二
层的叶子节点在右边连续,我们称为完全二叉树.
1.4 二叉树的遍历
分为三种:前序,中序和后序。
前序遍历: 先输出父节点,再遍历左子树和右子树
中序遍历: 先遍历左子树,再输出父节点,再遍历右子树
后序遍历: 先遍历左子树,再遍历右子树,最后输出父节点
小结: 看输出父节点的顺序,刚开始就输出父节点的就是前序,中间输出父节点的就是中序,最后输出父节点的就是后序!!!
遍历思路分析
创建二叉树。
注意:不管是哪种情况的遍历,程序始终是从根节点开始往下走的......
(1)前序遍历。
①输出当前节点。②如果当前节点的左子节点不为空,则递归前序遍历。③如果当前节点的右子节点不为空,则递归前序遍历。
(2)中序遍历。
①如果当前节点的左子节点不为空,则递归中序遍历。②输出当前节点。③如果当前节点的右子节点不为空,则递归中序遍历。
(3)后序遍历。
①如果当前节点的左子节点不为空,则递归后序遍历。②如果当前节点的右子节点不为空,则递归后序遍历。③输出当前节点。
将遍历的核心代码放到结点中了,在BinaryTree类中来判断根结点是否为空。代码如下:
package com.zengwen.tree;
public class BinaryTreeDemo{
public static void main(String[] args) {
//创建二叉树
BinaryTree binaryTree = new BinaryTree();
//创建结点
StudentNode node1 = new StudentNode(1, "罗志祥渣男1号");
StudentNode node2 = new StudentNode(2, "罗志祥渣男2号");
StudentNode node3 = new StudentNode(3, "罗志祥渣男3号");
StudentNode node4 = new StudentNode(4, "罗志祥渣男4号");
StudentNode node5 = new StudentNode(5, "罗志祥渣男5号");
binaryTree.setRoot(node1);//设置根结点
node1.setLeft(node2);
node1.setRight(node3);
node3.setLeft(node5);
node3.setRight(node4);
binaryTree.preOrder();//前序遍历
binaryTree.midOrder();//中序遍历
binaryTree.postOrder();//后序遍历
}
}
class BinaryTree{
//定义一个根结点
private StudentNode root;
//初始化根结点
public void setRoot(StudentNode root){
this.root = root;
}
//前序遍历
public void preOrder(){
//判断根结点是否为空
if (root == null){
System.out.println("该二叉树为空~");
return;
}
root.preOrder();
}
//中序遍历
public void midOrder(){
//判断根结点是否为空
if (root == null){
System.out.println("该二叉树为空~");
return;
}
root.midOrder();
}
//后序遍历
public void postOrder(){
//判断根结点是否为空
if (root == null){
System.out.println("该二叉树为空~");
return;
}
root.postOrder();
}
}
class StudentNode{
private int stuNo;
private String stuName;
private StudentNode left;
private StudentNode right;
public StudentNode(int stuNo, String stuName) {
this.stuNo = stuNo;
this.stuName = stuName;
}
public int getStuNo() {
return stuNo;
}
public void setStuNo(int stuNo) {
this.stuNo = stuNo;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public StudentNode getLeft() {
return left;
}
public void setLeft(StudentNode left) {
this.left = left;
}
public StudentNode getRight() {
return right;
}
public void setRight(StudentNode right) {
this.right = right;
}
@Override
public String toString() {
return "StudentNode{" +
"stuNo=" + stuNo +
", stuName='" + stuName + '\'' +
'}';
}
//前序遍历
public void preOrder(){
//打印父结点
System.out.println(this);
//判断左子结点是否为空,不为空,则继续前序遍历
if (this.left != null){
this.left.preOrder();
}
//判断右子结点是否为空,不为空,则继续前序遍历
if (this.right != null){
this.right.preOrder();
}
}
//中序遍历
public void midOrder(){
//判断左子结点是否为空,不为空,则继续中序遍历
if (this.left != null){
this.left.midOrder();
}
//打印父结点
System.out.println(this);
//判断右子结点是否为空,不为空,则继续中序遍历
if (this.right != null){
this.right.midOrder();
}
}
//后序遍历
public void postOrder(){
//判断左子结点是否为空,不为空,则继续后序遍历
if (this.left != null){
this.left.postOrder();
}
//判断右子结点是否为空,不为空,则继续后序遍历
if (this.right != null){
this.right.postOrder();
}
//打印父结点
System.out.println(this);
}
}
本人对上述代码进行二次创作,将遍历的核心代码直接全部放到了BinaryTree里面,不过调用遍历代码需要将根结点作为参数传入。
代码如下:
package com.zengwen.tree;
public class BinaryTreeDemo{
public static void main(String[] args) {
//创建二叉树
BinaryTree binaryTree = new BinaryTree();
//创建学生结点
StudentNode node1 = new StudentNode(1, "罗志祥渣男1号");
StudentNode node2 = new StudentNode(2, "罗志祥渣男2号");
StudentNode node3 = new StudentNode(3, "罗志祥渣男3号");
StudentNode node4 = new StudentNode(4, "罗志祥渣男4号");
StudentNode node5 = new StudentNode(5, "罗志祥渣男5号");
node1.left = node2;
node1.right = node3;
node3.left = node5;
node3.right = node4;
//前序遍历
binaryTree.preOrder(node1);
binaryTree.midOrder(node1);
binaryTree.postOrder(node1);
}
}
//二叉树
class BinaryTree{
//后序遍历
public void postOrder(StudentNode root){
//判断根结点是否为空
if (root == null){
System.out.println("该二叉树为空~~");
return;
}
//判断左子结点
if (root.left != null){
//继续后序遍历
postOrder(root.left);
}
//判断右子结点
if (root.right != null){
//继续后序遍历
postOrder(root.right);
}
//打印当前结点
System.out.println(root);
}
//中序遍历
public void midOrder(StudentNode root){
//判断根结点是否为空
if (root == null){
System.out.println("该二叉树为空~~");
return;
}
//判断左子结点
if (root.left != null){
//继续中序遍历
midOrder(root.left);
}
//打印根结点
System.out.println(root);
//判断右子结点
if (root.right != null){
//继续中序遍历
midOrder(root.right);
}
}
//前序遍历
public void preOrder(StudentNode root){
//判断根结点是否为空
if (root == null){
System.out.println("该二叉树为空~~");
return;
}
//打印根结点
System.out.println(root);
//判断左子结点
if (root.left != null){
//继续前序遍历
preOrder(root.left);
}
if (root.right != null){
//继续前序遍历
preOrder(root.right);
}
}
}
//学生结点
class StudentNode{
public int stuNo;//学号
public String stuName;//姓名
public StudentNode left;//指向左子结点
public StudentNode right;//指向右子结点
public StudentNode(int stuNo,String stuName){
this.stuNo = stuNo;
this.stuName = stuName;
}
@Override
public String toString() {
return "StudentNode{" +
"stuNo=" + stuNo +
", stuName='" + stuName + '\'' +
'}';
}
}
不过个人感觉还是第一种好.......
1.5 使用(前)(中)(后)序的方式查询指定节点
思路分析:
(1) 前序查找思路:
首先 判断当前结点的stuNo是否等于要查找的。如果是相等的,则返回当前结点,
否则 判断当前结点的左子结点是否为空,不为空,则左递归前序查找,如果左递归前序查找找到了结点,则返回,
否则 继续判断当前结点的右子结点是否为空,不为空,则右递归前序查找。如果右递归前序查找找到了结点,则返回,
否则 返回null。
(2) 中序查找思路:
首先 判断当前结点的左子结点是否为空,不为空,则左递归前序查找,如果左递归中序查找找到了结点,则返回
否则 和当前结点的stuNo比较,如果是相等的,则返回当前结点。
否则 继续判断当前结点的右子结点是否为空,不为空,则右递归中序查找,如果右递归中序查找找到了结点,则返回
否则 返回null。
(3) 后序查找思路:
首先 判断当前结点的左子结点是否为空,不为空,则左递归后序查找。如果左递归后序查找找到了结点,则返回,
否则 继续判断当前结点的右子结点是否为空,不为空,则右递归后序查找。如果右递归后序查找找到了结点,则返回,
否则 和当前结点的stuNo比较,如果是相等的,则返回当前结点,
否则 返回null。
package com.zengwen.tree;
public class BinaryTreeDemo{
public static void main(String[] args) {
//创建二叉树
BinaryTree binaryTree = new BinaryTree();
//创建结点
StudentNode node1 = new StudentNode(1, "罗志祥渣男1号");
StudentNode node2 = new StudentNode(2, "罗志祥渣男2号");
StudentNode node3 = new StudentNode(3, "罗志祥渣男3号");
StudentNode node4 = new StudentNode(4, "罗志祥渣男4号");
StudentNode node5 = new StudentNode(5, "罗志祥渣男5号");
binaryTree.setRoot(node1);//设置根结点
node1.setLeft(node2);
node1.setRight(node3);
node3.setLeft(node5);
node3.setRight(node4);
//前序遍历查找
System.out.println(binaryTree.preOrderSearch(5));
//中序遍历查找
System.out.println(binaryTree.midOrderSearch(5));
//后序遍历查找
System.out.println(binaryTree.postOrderSearch(5));
}
}
class BinaryTree{
//定义一个根结点
private StudentNode root;
//初始化根结点
public void setRoot(StudentNode root){
this.root = root;
}
//前序遍历查找
public StudentNode preOrderSearch(int stuNo){
if (root != null) return root.preOrderSerach(stuNo);
return null;
}
//中序遍历查找
public StudentNode midOrderSearch(int stuNo){
if (root!=null) return root.midOrderSearch(stuNo);
return null;
}
//后序遍历查找
public StudentNode postOrderSearch(int stuNo){
if(root != null) return root.postOrderSearch(stuNo);
return null;
}
}
class StudentNode{
private int stuNo;
private String stuName;
private StudentNode left;
private StudentNode right;
public StudentNode(int stuNo, String stuName) {
this.stuNo = stuNo;
this.stuName = stuName;
}
public int getStuNo() {
return stuNo;
}
public void setStuNo(int stuNo) {
this.stuNo = stuNo;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public StudentNode getLeft() {
return left;
}
public void setLeft(StudentNode left) {
this.left = left;
}
public StudentNode getRight() {
return right;
}
public void setRight(StudentNode right) {
this.right = right;
}
@Override
public String toString() {
return "StudentNode{" +
"stuNo=" + stuNo +
", stuName='" + stuName + '\'' +
'}';
}
//前序遍历查找
public StudentNode preOrderSerach(int stuNo){
// 首先 判断当前结点的stuNo是否等于要查找的。如果是相等的,则返回当前结点,
if (this.stuNo == stuNo) return this;
StudentNode resNode = null;
// 否则 判断当前结点的左子结点是否为空,不为空,则左递归前序查找,如果左递归前序查找找到了结点,则返回,
if (this.left != null) resNode = this.left.preOrderSerach(stuNo);
if (resNode != null) return resNode;
// 否则 继续判断当前结点的右子结点是否为空,不为空,则右递归前序查找。如果右递归前序查找找到了结点,则返回,
if (this.right != null) resNode = this.right.preOrderSerach(stuNo);
return resNode;
}
//中序遍历查找
public StudentNode midOrderSearch(int stuNo){
StudentNode resNode = null;
// 首先 判断当前结点的左子结点是否为空,不为空,则左递归前序查找,如果左递归中序查找找到了结点,则返回
if (this.left != null) resNode = this.left.midOrderSearch(stuNo);
if (resNode != null) return resNode;
// 否则 和当前结点的stuNo比较,如果是相等的,则返回当前结点。
if (this.stuNo == stuNo) return this;
// 否则 继续判断当前结点的右子结点是否为空,不为空,则右递归中序查找,如果右递归中序查找找到了结点,则返回
// 否则 返回null。
if (this.right != null) resNode = this.right.midOrderSearch(stuNo);
return resNode;
}
//后序遍历查找
public StudentNode postOrderSearch(int stuNo){
StudentNode resNode = null;
// 首先 判断当前结点的左子结点是否为空,不为空,则左递归后序查找。如果左递归后序查找找到了结点,则返回,
if (this.left != null) resNode = this.left.postOrderSearch(stuNo);
if (resNode != null) return resNode;
// 否则 继续判断当前结点的右子结点是否为空,不为空,则右递归后序查找。如果右递归后序查找找到了结点,则返回,
if (this.right != null) resNode = this.right.postOrderSearch(stuNo);
if (resNode != null) return resNode;
// 否则 和当前结点的stuNo比较,如果是相等的,则返回当前结点,
if (this.stuNo == stuNo) return this;
// 否则 返回null。
return null;
}
}
1.6 二叉树删除结点
我们看下需求:
(1)如果删除的结点是叶子结点,则删除该结点。
(2)如果删除的结点是非叶子结点,则删除该子树。(因为目前的二叉树没有什么规则,不能说删除哪个结点,就把其左右子树给提上来)
(3)测试删除罗志祥渣男5号叶子结点和罗志祥渣男3号子树。
思路分析:
➡因为二叉树是单向的,比如不能通过3号找到1号。所以我们是判断当前结点的子结点是否需要删除结点,而不能去判断当前这个结点是不是需要删除结点。
1.如果树是空树root,提示信息,判断根节点是否是删除结点,等价则将root=null。否则进行下面的思路。
2.如果当前结点的左子结点不为空,并且左子结点就是要删除的结点,就将this.left=null;并且就返回。
3.否则 如果当前结点的右子结点不为空,并且右子结点就是要删除的结点,就将this.right=null,并且就返回。
4.否则 向左子树进行递归删除。
5.否则 向右子树进行递归删除。
package com.zengwen.tree;
public class BinaryTreeDemo{
public static void main(String[] args) {
//创建二叉树
BinaryTree binaryTree = new BinaryTree();
//创建结点
StudentNode node1 = new StudentNode(1, "罗志祥渣男1号");
StudentNode node2 = new StudentNode(2, "罗志祥渣男2号");
StudentNode node3 = new StudentNode(3, "罗志祥渣男3号");
StudentNode node4 = new StudentNode(4, "罗志祥渣男4号");
StudentNode node5 = new StudentNode(5, "罗志祥渣男5号");
binaryTree.setRoot(node1);//设置根结点
node1.setLeft(node2);
node1.setRight(node3);
node3.setLeft(node5);
node3.setRight(node4);
System.out.println("删除罗志祥5号前的前序遍历~~");
binaryTree.preOrder();
binaryTree.delStudentNode(5);
System.out.println("删除罗志祥5号后的前序遍历~~");
binaryTree.preOrder();
}
}
class BinaryTree{
//定义一个根结点
private StudentNode root;
//初始化根结点
public void setRoot(StudentNode root){
this.root = root;
}
//前序遍历
public void preOrder(){
//判断根结点是否为空
if (root == null){
System.out.println("该二叉树为空~");
return;
}
root.preOrder();
}
//删除结点
public void delStudentNode(int stuNo){
if (root == null) System.out.println("空树不能删除~~");
if (root.getStuNo() == stuNo) {
root = null;
}else {
root.delStudentNode(stuNo);
}
}
}
class StudentNode{
private int stuNo;
private String stuName;
private StudentNode left;
private StudentNode right;
public StudentNode(int stuNo, String stuName) {
this.stuNo = stuNo;
this.stuName = stuName;
}
public int getStuNo() {
return stuNo;
}
public void setStuNo(int stuNo) {
this.stuNo = stuNo;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public StudentNode getLeft() {
return left;
}
public void setLeft(StudentNode left) {
this.left = left;
}
public StudentNode getRight() {
return right;
}
public void setRight(StudentNode right) {
this.right = right;
}
@Override
public String toString() {
return "StudentNode{" +
"stuNo=" + stuNo +
", stuName='" + stuName + '\'' +
'}';
}
//前序遍历
public void preOrder(){
//打印父结点
System.out.println(this);
//判断左子结点是否为空,不为空,则继续前序遍历
if (this.left != null){
this.left.preOrder();
}
//判断右子结点是否为空,不为空,则继续前序遍历
if (this.right != null){
this.right.preOrder();
}
}
//删除结点
//(1)如果删除的结点是叶子结点,则删除该结点。
//(2)如果删除的结点是非叶子结点,则删除该子树。
public void delStudentNode(int stuNo){
//如果当前结点的左子结点不为空,并且左子结点就是要删除的结点,就将this.left=null;并且就返回。
if (this.left != null && this.left.stuNo == stuNo){
this.left = null;
return;
}
//如果当前结点的右子结点不为空,并且右子结点就是要删除的结点,就将this.right=null,并且就返回。
if (this.right != null && this.right.stuNo == stuNo){
this.right = null;
return;
}
//如果2、3步完成没有删除结点,那么我们就需要向左子树进行递归删除。
if (this.left != null) this.left.delStudentNode(stuNo);
//如果4步没有删除结点,那么我们就需要向右子树进行递归删除。
if (this.right != null) this.right.delStudentNode(stuNo);
}
}
二、顺序存储二叉树
2.1 顺序存储二叉树的概念
从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组
二叉树的顺序存储就是用一组连续的存储单元存放二又树中的结点元素,一般按照二叉树结点自上向下、自左向右的顺序存储。
2.2 顺序存储二叉树的特点:
- 顺序二叉树通常只考虑完全二叉树
- 编号为n的元素的左子节点为 2 * n + 1
- 编号为n的元素的右子节点为 2 * n + 2
- 编号为n的元素的父节点为 (n-1) / 2
需求: 给你一个数组 {1,2,3,4,5,6,7},要求以二叉树前序遍历的方式进行遍历。 前序遍历的结果应当为 1,2,4,5,3,6,7
package com.zengwen.tree;
public class ArrayBinaryTreeDemo {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6, 7};
ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(arr);
arrayBinaryTree.preOrder();
}
}
//编写一个ArrayBinaryTree,实现顺序存储二叉树遍历
class ArrayBinaryTree {
private int[] arr;//存储数据结点的数组
public ArrayBinaryTree(int[] arr) {
this.arr = arr;
}
//编写一个方法,完成顺序存储二叉树的一个前序遍历
/*
顺序存储二叉树的特点:
Ⅰ 顺序存储二叉树通常只考虑完全二叉树
Ⅱ 编号为n的元素的左子结点为2n+1
Ⅲ 编号为n的元素的右子结点为2n+2
Ⅳ 编号为n的元素的父结点为(n-1)/2
*/
//重载preOrder
public void preOrder(){
this.preOrder(0);
}
//重载midOrder
public void midOrder(){
this.midOrder(0);
}
//重载postOrder
public void postOrder(){
this.postOrder(0);
}
/**
* 前序遍历
* @param n n表示索引,即数组下标
*/
public void preOrder(int n) {
//如果数组为空或者arr.lenth=0(无数据)
if (arr == null || arr.length == 0) {
System.out.println("数组空,不能按照二叉树的前序遍历");
return;
}
//输出当前这个元素
System.out.println(arr[n]);
//向左递归遍历
//不越界的情况下
if ((2 * n + 1) < arr.length) {
preOrder(2 * n + 1);
}
//向右递归遍历
//不越界的情况下
if ((2 * n + 2) < arr.length) {
preOrder(2 * n + 2);
}
}
/**
* 中序遍历
* @param n n表示索引,即数组下标
*/
public void midOrder(int n) {
//如果数组为空或者arr.lenth=0(无数据)
if (arr == null || arr.length == 0) {
System.out.println("数组空,不能按照二叉树的前序遍历");
return;
}
//向左递归遍历
//不越界的情况下
if ((2 * n + 1) < arr.length) {
midOrder(2 * n + 1);
}
//输出当前这个元素
System.out.println(arr[n]);
//向右递归遍历
//不越界的情况下
if ((2 * n + 2) < arr.length) {
midOrder(2 * n + 2);
}
}
/**
* 后序遍历
* @param n n表示索引,即数组下标
*/
public void postOrder(int n) {
//如果数组为空或者arr.lenth=0(无数据)
if (arr == null || arr.length == 0) {
System.out.println("数组空,不能按照二叉树的前序遍历");
return;
}
//向左递归遍历
//不越界的情况下
if ((2 * n + 1) < arr.length) {
postOrder(2 * n + 1);
}
//向右递归遍历
//不越界的情况下
if ((2 * n + 2) < arr.length) {
postOrder(2 * n + 2);
}
//输出当前这个元素
System.out.println(arr[n]);
}
}
三、线索化二叉树
3.1 线索二叉树基本介绍
(1) n 个结点的二叉链表中含有 n+1 【公式 2n-(n-1)=n+1】 个空指针域。利用二叉链表中的空指针域,存放指向该结点在某种遍历次序下的前驱和后继结点的指针(这种附加的指针称为"线索")
(2)这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
(3) 一个结点的前一个结点,称为前驱结点
(4) 一个结点的后一个结点,称为后继结点
3.2 二叉树实现线索化
将下面的二叉树,进行中序线索二叉树。
上述二叉树中序遍历的结果为 {8, 3, 10, 1, 14, 6}
❶ 首先看8,按照中序遍历的结果,8前面没有前驱结点,不用动8的左指针,8后继结点是3,于是8的右指针指向3。
❷ 然后看3,因为3的左右指针已经被使用,不能动。
❸ 然后看10,按照中序遍历的结果,10前驱结点是3,于是10的左指针指向3,10后继结点是1,于是10的右指针指向1。
❹ 然后看1,因为1的左右指针已经被使用,不能动。
❺ 然后看14, 按照中序遍历的结果,14的前驱结点是1,于是14的左指针指向1,14后继结点是6,于是14的右指针指向6。
❻最后看6,按照中序遍历的结果,6的前驱结点是14,但6右指针已经用了,不用管,6没有后继结点。
在上述步骤中,
当线索化二叉树后,Node 节点的 属性 left 和 right ,有如下情况:
(1)left 指向的是左子树,也可能是指向的前驱节点. 比如 ① 节点 left 指向的左子树, 而 ⑩ 节点的 left 指向的
就是前驱节点.
(2)right 指向的是右子树,也可能是指向后继节点,比如 ① 节点 right 指向的是右子树,而⑩ 节点的 right 指向
的是后继节点
代码实现思路:
package com.zengwen.tree;
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
Student root = new Student(1, "tom");
Student node2 = new Student(3, "jack");
Student node3 = new Student(6, "smith");
Student node4 = new Student(8, "mary");
Student node5 = new Student(10, "king");
Student node6 = new Student(14, "kimi");
root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
//测试线索化
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
threadedBinaryTree.setRoot(root);
System.out.println("中序线索化前:10左边是:"+node5.getLeft());//测试10的左边是不是3
System.out.println("中序线索化前:10右边是:"+node5.getLeft());//测试10的右边是不是1
threadedBinaryTree.threadedNodes();
System.out.println("中序线索化后:10前驱结点是:"+node5.getLeft());//测试10的左边是不是3
System.out.println("中序线索化后:10后继结点是:"+node5.getRight());//测试10的右边是不是1
}
}
//实现了线索化功能的二叉树
class ThreadedBinaryTree{
//定义一个根结点
private Student root;
//为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
//在递归进行线索化时,pre总是保留前一个结点
private Student pre;
//初始化根结点
public void setRoot(Student root){
this.root = root;
}
//重载threadedNodes
public void threadedNodes(){
this.threadedNodes(root);
}
//编写对二叉树进行中序线索化的方法
/**
* @param node 当前需要线索化的结点
*/
public void threadedNodes(Student node){
//如果node==null,不能线索化
if (node == null) return;
//中序线索化步骤
//一、先线索化左子树
threadedNodes(node.getLeft());
//二、再线索化当前结点[有难度]
//先处理当前结点的前驱结点
if (node.getLeft() == null){
//让当前结点的左指针指向前驱结点
node.setLeft(pre);
//修改当前结点的左指针类型,指向前驱结点
node.setLeftType(1);
}
//处理后继结点
if (pre != null && pre.getRight() == null){
//让前驱结点的右指针指向当前结点
pre.setRight(node);
//修改前驱结点的右指针类型
pre.setRightType(1);
}
//每处理一个结点后,让当前结点是下一个结点的前驱结点
pre = node;
//三、最后线索化右子树
threadedNodes(node.getRight());
}
}
class Student{
private int stuNo;
private String stuName;
private Student left;
private Student right;
private int leftType;//0表示指向的是左子树,如果是1则表示指向前驱结点
private int rightType;//0表示指向的是右子树,如果是1则表示指向后继结点
//getters and setters ...
//Constructor...
}
3.3 遍历线索二叉树
说明:对前面的中序线索化的二叉树, 进行遍历
分析:因为线索化后,各个结点指向有变化,因此原来的遍历方式不能使用,这时需要使用新的方式遍历线索化二叉树,各个节点可以通过线型方式遍历,因此无需使用递归方式,这样也提高了遍历的效率。 遍历的次序应当和中序遍历保持一致。
强行使用之前中序遍历的代码,会直接溢出。
package com.zengwen.tree;
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
Student root = new Student(1, "tom");
Student node2 = new Student(3, "jack");
Student node3 = new Student(6, "smith");
Student node4 = new Student(8, "mary");
Student node5 = new Student(10, "king");
Student node6 = new Student(14, "kimi");
root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
//测试线索化
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
threadedBinaryTree.setRoot(root);
System.out.println("中序线索化前:10左边是:"+node5.getLeft());//测试10的左边是不是3
System.out.println("中序线索化前:10右边是:"+node5.getLeft());//测试10的右边是不是1
threadedBinaryTree.threadedNodes();
System.out.println("中序线索化后:10前驱结点是:"+node5.getLeft());//测试10的左边是不是3
System.out.println("中序线索化后:10后继结点是:"+node5.getRight());//测试10的右边是不是1
System.out.println("线索化遍历结果:");
threadedBinaryTree.threadedList();
}
}
//实现了线索化功能的二叉树
class ThreadedBinaryTree{
//定义一个根结点
private Student root;
//为了实现线索化,需要创建要给指向当前结点的前驱结点的指针
//在递归进行线索化时,pre总是保留前一个结点
private Student pre;
//初始化根结点
public void setRoot(Student root){
this.root = root;
}
//遍历线索化二叉树的方法
public void threadedList(){
//定义一个变量,存储当前遍历的结点,从root开始
Student node = root;
while (node != null){
//循环的找到leftType==1的结点,第一个找到的就是8结点
while (node.getLeftType() == 0) node = node.getLeft();
//退出循环则找到有效的节点
System.out.println(node);
//如果当前结点的右指针指向的值后继结点就一直输出
while (node.getRightType() == 1) System.out.println(node=node.getRight());
//替换这个遍历的结点
node = node.getRight();
}
}
//重载threadedNodes
public void threadedNodes(){
this.threadedNodes(root);
}
//编写对二叉树进行中序线索化的方法
/**
* @param node 当前需要线索化的结点
*/
public void threadedNodes(Student node){
//如果node==null,不能线索化
if (node == null) return;
//中序线索化步骤
//一、先线索化左子树
threadedNodes(node.getLeft());
//二、再线索化当前结点[有难度]
//先处理当前结点的前驱结点
if (node.getLeft() == null){
//让当前结点的左指针指向前驱结点
node.setLeft(pre);
//修改当前结点的左指针类型,指向前驱结点
node.setLeftType(1);
}
//处理后继结点
if (pre != null && pre.getRight() == null){
//让前驱结点的右指针指向当前结点
pre.setRight(node);
//修改前驱结点的右指针类型
pre.setRightType(1);
}
//每处理一个结点后,让当前结点是下一个结点的前驱结点
pre = node;
//三、最后线索化右子树
threadedNodes(node.getRight());
}
}
class Student{
private int stuNo;
private String stuName;
private Student left;
private Student right;
private int leftType;//0表示指向的是左子树,如果是1则表示指向前驱结点
private int rightType;//0表示指向的是右子树,如果是1则表示指向后继结点
...
}