十三、树
1、二叉树
5)顺序存储二叉树
从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组。
顺序存储二叉树的特点:
1、顺序二叉树通常只考虑完全二叉树。
2、第n个元素的左子节点下标为 2 * n + 1
3、第n个元素的右子节点下标为 2 * n + 2
4、第n个元素的父节点为 (n - 1) / 2
5、n:表示二叉树中的第几个元素(从0开始编号)
代码实现
public class ArrBinaryTree {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7};
//创建一个ArrBinaryTree
ArrBinary arrBinary = new ArrBinary(arr);
System.out.print("前序存储:");
arrBinary.preOrder();//1,2,4,5,3,6,7
System.out.println();
System.out.print("中序存储:");
arrBinary.infixOrder();//4,2,5,1,6,3,7
System.out.println();
System.out.print("后序存储:");
arrBinary.postOrder();//4,5,2,6,7,3,1
}
}
//编写一个arrbinary,实现顺序存储二叉树遍历
class ArrBinary{
private int[] arr;//存储数据节点的数组
public ArrBinary(int[] arr){
this.arr = arr;
}
//重载一下preOrder让代码变得更好看点
public void preOrder(){
this.preOrder(0);
}
//重载一下infixOrder让代码变得更好看点
public void infixOrder(){
this.infixOrder(0);
}
//重载一下postOrder让代码变得更好看点
public void postOrder(){
this.postOrder(0);
}
/**
* @Author: Cui
* @Description: 编写方法,顺序存储前序遍历的二叉树
* @DateTime: 10:10
* @Params: n :数组下标
* @Return
*/
public void preOrder(int n){
//如果数组为空,或者arr.length = 0
if(arr == null || arr.length == 0){
System.out.println("数组为空。");
}
//输出当前的数组元素
System.out.print(arr[n] + " ");
//向左递归遍历
if((n * 2 + 1) < arr.length){//防止下标越界
preOrder(2 * n + 1);
}
//向右递归
if((n * 2 + 2) < arr.length){//防止下标越界
preOrder(2 * n + 2);
}
}
/**
* @Author: Cui
* @Description: 编写方法,顺序存储中序遍历的二叉树
* @DateTime: 10:10
* @Params: n :数组下标
* @Return
*/
public void infixOrder(int n){
//如果数组为空,或者arr.length = 0
if(arr == null || arr.length == 0){
System.out.println("数组为空。");
}
//向左递归遍历
if((n * 2 + 1) < arr.length){//防止下标越界
infixOrder(2 * n + 1);
}
//输出当前的数组元素
System.out.print(arr[n] + " ");
//向右递归
if((n * 2 + 2) < arr.length){//防止下标越界
infixOrder(2 * n + 2);
}
}
/**
* @Author: Cui
* @Description: 编写方法,顺序存储后序遍历的二叉树
* @DateTime: 10:10
* @Params: n :数组下标
* @Return
*/
public void postOrder(int n){
//如果数组为空,或者arr.length = 0
if(arr == null || arr.length == 0){
System.out.println("数组为空。");
}
//向左递归遍历
if((n * 2 + 1) < arr.length){//防止下标越界
postOrder(2 * n + 1);
}
//向右递归
if((n * 2 + 2) < arr.length){//防止下标越界
postOrder(2 * n + 2);
}
//输出当前的数组元素
System.out.print(arr[n] + " ");
}
}
运行结果
前序存储:1 2 4 5 3 6 7
中序存储:4 2 5 1 6 3 7
后序存储:4 5 2 6 7 3 1
6)线索化二叉树
对于一个普通的二叉树节点,通常是分为三部分的,如下所示:
假设,我们现在要存储这样一个二叉树:
可以看出 6 8 10 14 这四个节点的左右指针没有完全的利用上,如果我们希望充分的利用各个节点的左右指针,让各个节点可以指向自己的前后节点,怎么办呢?我们就需要线索二叉树了。
基本概述
n 个节点的二叉链表中含有 n + 1 个空指针域。利用二叉链表中的空指针域,存放指向节点在某种遍历次序下的前驱和后继节点的指针(这种指针称为“线索”)。
这种加上了线索的二叉链表被称为线索链表,也分为三种:前序线索二叉树、中序线索二叉树、后序线索二叉树三种。
一个节点的前一个节点,称为前驱节点。
一个节点的后一个节点,称为后继节点。
实现思路
我们以中序遍历为例,从中序遍历的结果可以看出,8 没有前驱,8 的后继为 3,10 的前驱为 3,10 的后继为 1,14 的前驱为1,14 的后继为6 ,6没有后继。于是,空指针就要指向他们分别的前后节点。
代码实现
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) {
//测试
//先创建一个二叉树
ThreadedTree threadedTree = new ThreadedTree();
//创建需要的节点
Node root = new Node(1,"1");
Node node2 = new Node(3,"3");
Node node3 = new Node(6,"6");
Node node4 = new Node(8,"8");
Node node5 = new Node(10,"10");
Node node6 = new Node(14,"14");
root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
//测试中序线索化二叉树
threadedTree.setRoot(root);
threadedTree.threadedNodes();
//测试:以10号的节点为例,测试,看看它的前驱和后继都是谁
Node leftNode = node5.getLeft();
Node rightNode = node5.getRight();
System.out.println("前驱节点为:" + leftNode.toString());//3
System.out.println("后继节点为:" + rightNode.toString());//1
}
}
//编写线索化二叉树的方法
class ThreadedTree {
private Node root;//根节点
//定义一个指针,指向当前节点的前驱节点
private Node pre = null;
public void setRoot(Node root) {
this.root = root;
}
//重载一下
public void threadedNodes(){
this.threadedNodes(root);
}
//线索化节点
public void threadedNodes(Node node){
//如果node == null,不能线索化
if(node == null){
return;
}
//先线索化左子树
threadedNodes(node.getLeft());
//再线索化当前节点(难)
//处理当前节点的左子节点
if(node.getLeft() == null){
//让当前节点的左指针指向前驱节点
node.setLeft(pre);
//修改当前节点的左指针类型
node.setLeftType(1);//将左指针类型设置为 1
}
//处理当前节点的右子节点
if(pre != null && pre.getRight() == null){
pre.setRight(node);//让前驱节点的右指针,指向当前节点
pre.setRightType(1);//将右指针类型设置为 1
}
//!!! 每处理一个节点后,让当前节点是下一个节点的前驱节点。
pre = node;
//最后线索化右子树
threadedNodes(node.getRight());
}
}
//先创建HeroNode节点
class Node {
private String name;
private int no;
private Node left;
private Node right;
private int leftType;//leftType 为0表示指向左子树,为1表示指向前驱节点
private int rightType;//rightType 为0表示指向右子树,为1表示指向后继节点
public Node(int no, String name) {
this.no = no;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
//输出方法
@Override
public String toString() {
return "Node{" +
"name='" + name + '\'' +
", no=" + no +
'}';
}
public int getLeftType() {
return leftType;
}
public void setLeftType(int leftType) {
this.leftType = leftType;
}
public int getRightType() {
return rightType;
}
public void setRightType(int rightType) {
this.rightType = rightType;
}
}
运行结果
前驱节点为:Node{name='3', no=3}
后继节点为:Node{name='1', no=1}
这里只写了中序线索二叉树,有兴趣的可以自己按照这个思路写前序和后序线索二叉树。
遍历线索化二叉树
对于线索化二叉树,原先的遍历方式已经无法使用了。因为以前的遍历方式是以 null 为判断方式的。但是现在,除了最后一个节点,其他节点已经不是为空了。我们需要新的方式遍历线索化二叉树。
各个节点可以通过线性方式遍历,因此不需要用递归的方式,这样也提高了遍历的效率。遍历的次序应当和中序遍历保持一致。
思路分析
我们只需要查找到第一个线索,也就是 leftType != 0 时,我们才算是找到了线索的第一个值,接下来,只需要顺着线索,向下查找它的后续节点,然后再将节点移向下一个满足 leftType != 0 的节点,最终,直到 node == null 时为止。
代码实现
主函数中代码:
//遍历线索化二叉树
threadedTree.threadedList();//8,3,10,1,14,6
遍历方法体:
//遍历线索化二叉树的方法
public void threadedList(){
//定义一个变量,用来指向当前遍历的节点,从root开始。
Node node = root;
while(node != null){
//先循环找到leftType = 1的节点,那么就找到了第一个节点8
//后面随着遍历而变化,因为leftType = 1的时候,说明该节点就是按照线索化处理后的有效节点。
while(node.getLeftType() == 0){
node = node.getLeft();//如果没找到,就向后传递。
}
//找到了第一个节点——8。打印该节点
System.out.println(node);
//如果当前节点的右指针指向的是后继节点,就一直输出
while(node.getRightType() == 1){
//获取到当前节点的后继节点
node = node.getRight();
System.out.println(node);
}
//替换这个节点
node = node.getRight();
}
}
运行结果
Node{name='8', no=8}
Node{name='3', no=3}
Node{name='10', no=10}
Node{name='1', no=1}
Node{name='14', no=14}
Node{name='6', no=6}