算法10 顺序存储二叉树+线索化二叉树

255 阅读6分钟

1.顺序存储二叉树

从存储数据来看,数组存储方式和树的存储方式可以相互转换。

要求

  1. 要求以二叉树的方式来存放数组:arr [1,2,3,4,5,6,7]
  2. 要求在遍历数组arr时,仍然可以以前序遍历、中序遍历和后序遍历的方式完成节点的遍历

顺序存储二叉树的特点
①. 顺序二叉树通常只考虑完全二叉树
②. 第n个元素的左子节点为2n+1
③. 第n个元素的右子节点为2
n+2
④. 第n个元素的父节点为(n-1)/2
⑤. n表示二叉树中的第几个元素(按0开始编号)

代码实现

以数组方式存储二叉树

public class ArrayBinaryTree {
    private int[] arr;//存储数据节点的数组

    public ArrayBinaryTree(int[] arr) {
        this.arr = arr;
    }

    public void preOrder() {
        preOrder(0);
    }

    //完成顺序存储二叉树的前序遍历
    //index表示数组的下标
    public void preOrder(int index){
        if (arr==null||arr.length==0){
            System.out.println("数组为空,不能按照二叉树的前序遍历");
            return;
        }
        //输出当前数组元素
        System.out.println(arr[index]);
        //向左递归遍历
        if (index*2+1<arr.length){
            preOrder(2*index+1);
        }
        //向右递归遍历
        if (index*2+2<arr.length){
            preOrder(2*index+2);
        }
    }

    //中序
    public void infixOrder(int index){
        if (arr==null||arr.length==0){
            System.out.println("数组为空,不能按照二叉树的前序遍历");
            return;
        }
        //向左递归遍历
        if (index*2+1<arr.length){
            infixOrder(2*index+1);
        }
        //输出当前数组元素
        System.out.println(arr[index]);
        //向右递归遍历
        if (index*2+2<arr.length){
            infixOrder(2*index+2);
        }
    }

    //后序
    public void postOrder(int index){
        if (arr==null||arr.length==0){
            System.out.println("数组为空,不能按照二叉树的前序遍历");
            return;
        }
        //向左递归遍历
        if (index*2+1<arr.length){
            postOrder(2*index+1);
        }
        //向右递归遍历
        if (index*2+2<arr.length){
            postOrder(2*index+2);
        }
        //输出当前数组元素
        System.out.println(arr[index]);
    }

}

测试代码

public class ArrBinaryTreeDemo {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7};
        ArrayBinaryTree tree = new ArrayBinaryTree(arr);
        tree.preOrder();// 1 2 4 5 3 6 7
        tree.infixOrder(0);//4 2 5 1 6 3 7
        tree.postOrder(0);//4 5 2 6 7 3 1
    }
}

2. 线索化二叉树

概念

  • 1)n个节点的二叉链表中含有n+1【公式2n-(n-1)=n+1】个空指针域。利用二叉链表中的空指针域,存放指向结点在牟宗遍历次序下的前驱和后驱节点的指针(这种附加的指针称为“线索”)
  • 2)这种加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree)。根据线索性质的不同,线索二叉树可以分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种
  • 3)一个节点的前一个节点,称为前驱节点
  • 4)一个节点的后一个节点,称为后继节点
  • 5)下图中,中序遍历,3的前驱节点是8,后继节点是10

将数列{1,3,6,8,10,14}构建成一颗二叉树,含有n+1=7个空指针域

问题分析:

  • 对上面的二叉树进行中序遍历,数列为{8,3,10,11,14,6}
  • 但是6,8,10,14这几个节点的左右指针,并没有完全利用上
  • 如果我们希望充分利用各个节点上的左右指针,让各个节点可以指向自己的前后节点该怎么办呢
  • 这个时候我们的解决方案是:==线索化二叉树==

说明

当线索化二叉树后,Node节点的属性left和right,有如下情况

  • 1)left指向的是左子树,也可能是指向的前驱节点,比如①节点left指向的左子树,而10节点的left指向的就是前驱节点
  • 2)right指向的是右子树,也可能是指向后继节点,比如①节点right指向的是右子树,而10节点的right指向的是后继节点

代码实现

线索化代码实现

节点THeroNode

/**
 * @author DSH
 * @date 2020/5/13
 * @description  线索化二叉树  二叉树节点
 */
public class THeroNode {

    private int no;
    private String name;
    private THeroNode left;
    private THeroNode right;
    //说明
    //1 如果leftType == 0 表示指向的是左子树,如果为1则表示指向前驱节点
    //2 如果rightType == 0 表示指向的是右子树,如果为1表示指向的是后继节点
    private int leftType = 0;
    private int rightType = 0;

    public THeroNode(int hNo, String hName){
        this.no = hNo;
        this.name = hName;
    }
    
    //省略get set代码
    ...setxxx();
    ...getxxx();
    
}

线索化二叉树TBinaryTree

public class TBinaryTree {
    private THeroNode root;
    //为了实现线索化排序,需要创建要给指向当前节点的前驱节点的指针
    //在递归进行线索化时,pre 总是保留前一个接地那
    private THeroNode pre = null;

    public void setRoot(THeroNode root) {
        this.root = root;
    }

    public void threadedNodes(){
        this.threadedNodes(root);
    }

    //编写对二叉树进行中序线索化的方法
    /**
     *
     * @param node 当前需要线索化的node
     */
    public void threadedNodes(THeroNode node){
        if (node==null){//空 不能线索化
            return;
        }
        //中序
        // (1) 先线索化左子树
        threadedNodes(node.getLeft());

        // (2) 再线索化当前节点
        //先处理当前节点的前驱节点
        //以8节点为例  8.left =null 8.leftType = 1
        if (node.getLeft()==null){
            //让当前节点的左指针指向前驱节点
            node.setLeft(pre);
            //修改当前节点的左指针的类型,指向前驱节点
            node.setLeftType(1);
        }
        //处理后继节点
        if (pre!=null&&pre.getRight()==null){
            //让前驱节点的右指针指向当前节点
            pre.setRight(node);
            //修改
            pre.setRightType(1);
        }
        //每处理一个节点后,让当前节点是下一个节点的前驱节点
        pre = node;

        // (3) 最后线索化右子树
        threadedNodes(node.getRight());

    }
    
    
    
}

测试代码

/**
 * @author DSH
 * @date 2020/5/13
 * @description 中序线索化二叉树
 */
public class ThreadedBinaryTreeDemo {
    public static void main(String[] args) {
        THeroNode root = new THeroNode(1, "tom");
        THeroNode node2 = new THeroNode(3, "jack");
        THeroNode node3 = new THeroNode(6, "smith");
        THeroNode node4 = new THeroNode(8, "mary");
        THeroNode node5 = new THeroNode(10, "king");
        THeroNode node6 = new THeroNode(14, "stephen");

        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

        TBinaryTree tBinaryTree = new TBinaryTree();
        tBinaryTree.setRoot(root);

        //线索化二叉树
        tBinaryTree.threadedNodes();
        //以10号节点测试,测试前驱后继节点
        System.out.println(node5.getLeft());//3
        System.out.println(node5.getRight());//1

    }
}

遍历

TBinary添加遍历方法

    //中序遍历线索化二叉树
    public void listThreadedBinaryTree(){
        //定义一个遍历,存储当前遍历的节点 从root开始
        THeroNode node = root;
        while (node!=null){
            //循环找到leftType == 1的节点 ,第一个找的的就是8节点
            //后面随着遍历而变化,当leftType == 1时,说明该节点是按照线索化处理后的有效节点
            while (node.getLeftType()==0){
                node = node.getLeft();
            }
            //打印当前节点
            System.out.println(node);
            //如果当前节点的右指针指向的是后继节点,就一直输出
            while (node.getRightType()==1){
                //获取到当前节点的后继节点
                node = node.getRight();
                System.out.println(node);
            }
            //替换遍历的节点
            node = node.getRight();
        }
    }

测试代码

    //线索化之后  不能使用原来的遍历方式
    System.out.println("使用线索化方式遍历线索化二叉树");
    tBinaryTree.listThreadedBinaryTree();