數據結構之線索二叉樹

1.順序存儲二叉樹的概念

  • 基本說明
    從數據存儲來看,數組存儲方式和樹
    的存儲方式可以相互轉換,即數組可
    以轉換成樹,樹也可以轉換成數組,
    看右面的示意圖。
  • 要求:
    右圖的二叉樹的結點,要求以數組�的方式來存放 arr : [1, 2, 3, 4, 5, 6, 6]
    要求在遍歷數組 arr時,仍然可以以�前序遍歷,中序遍歷和後序遍歷的�方式完成結點的遍歷

  • 順序存儲二叉樹的特點:

    ​ 順序二叉樹通常只考慮完全二叉樹
    ​ 第n個元素的左子節點爲 2 * n + 1
    ​ 第n個元素的右子節點爲 2 * n + 2
    ​ 第n個元素的父節點爲 (n-1) / 2

    ​ n : 表示二叉樹中的第幾個元素(按0開始編號�如圖所示)

  • 代碼實現

    package cn.smallmartial.tree;
    
    /**
     * @Author smallmartial
     * @Date 2019/6/16
     * @Email [email protected]
     */
    public class ArrBinaryTreeDemo {
    
        public static void main(String[] args) {
            int[] arr = {1,2,3,4,5,6,7};
            //創建
            ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr);
            System.out.println("前序遍歷:");
            arrBinaryTree.preOrder();//1,2,,4,5,3,6,7
        }
    
    }
    
    class ArrBinaryTree{
        private int[] arr;
    
        public ArrBinaryTree(int[] arr) {
            this.arr = arr;
        }
    
        public void preOrder(){
            this.preOrder(0);
        }
        /**
         * 前序遍歷
         *
         * @param index
         */
        public void preOrder(int index){
           if (arr == null || arr.length ==0){
               System.out.println("數組爲空,不能按照二叉樹的前序遍歷");
           }
           //輸出改元素
            System.out.print(arr[index]+" ");
            //向左遞歸
           if (index * 2 + 1 < arr.length ){
               preOrder(2*index +1);
           }
            //向右遞歸
            if (index * 2 + 2 < arr.length ){
                preOrder(2*index +2);
            }
    
        }
    }
    

2.線索化二叉樹

2.1線索二叉樹基本介紹

  • n個結點的二叉鏈表中含有n+1 【公式 2n-(n-1)=n+1】 個空指針域。利用二叉鏈表中的空指針域,存放指向該結點在某種遍歷次序下的前驅和後繼結點的指針(這種附加的指針稱爲"線索")

  • 這種加上了線索的二叉鏈表稱爲線索鏈表,相應的二叉樹稱爲線索二叉樹(Threaded BinaryTree)。根據線索性質的不同,線索二叉樹可分爲前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種

  • 一個結點的前一個結點,稱爲前驅結點

  • 一個結點的後一個結點,稱爲後繼結點

2.2線索二叉樹應用案例

應用案例說明:將下面的二叉樹,進行中序線索二叉樹。中序遍歷的數列爲 {8, 3, 10, 1, 14, 6}

2.3代碼實現

package cn.smallmartial.tree.threadedbinarytree;

/**
 * @Author smallmartial
 * @Date 2019/6/16
 * @Email [email protected]
 */


public class ThreadedBinaryTreeDemo {

    public static void main(String[] args) {
        //測試一把中序線索二叉樹的功能
        HeroNode root = new HeroNode(1, "tom");
        HeroNode node2 = new HeroNode(3, "jack");
        HeroNode node3 = new HeroNode(6, "smith");
        HeroNode node4 = new HeroNode(8, "mary");
        HeroNode node5 = new HeroNode(10, "king");
        HeroNode node6 = new HeroNode(14, "dim");

        //二叉樹,後面我們要遞歸創建, 現在簡單處理使用手動創建
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);

        //測試中序線索化
        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        threadedBinaryTree.setRoot(root);
        threadedBinaryTree.threadedNodes();

        //測試: 以10號節點測試
        HeroNode leftNode = node5.getLeft();
        HeroNode rightNode = node5.getRight();
        System.out.println("10號結點的前驅結點是 ="  + leftNode); //3
        System.out.println("10號結點的後繼結點是="  + rightNode); //1

        //當線索化二叉樹後,能在使用原來的遍歷方法
        //threadedBinaryTree.infixOrder();
        System.out.println("使用線索化的方式遍歷 線索化二叉樹");
        threadedBinaryTree.threadedList(); // 8, 3, 10, 1, 14, 6

    }

}




//定義ThreadedBinaryTree 實現了線索化功能的二叉樹
class ThreadedBinaryTree {
    private HeroNode root;

    //爲了實現線索化,需要創建要給指向當前結點的前驅結點的指針
    //在遞歸進行線索化時,pre 總是保留前一個結點
    private HeroNode pre = null;

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

    //重載一把threadedNodes方法
    public void threadedNodes() {
        this.threadedNodes(root);
    }

    //遍歷線索化二叉樹的方法
    public void threadedList() {
        //定義一個變量,存儲當前遍歷的結點,從root開始
        HeroNode 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();

        }
    }

    //編寫對二叉樹進行中序線索化的方法
    /**
     *
     * @param node 就是當前需要線索化的結點
     */
    public void threadedNodes(HeroNode node) {

        //如果node==null, 不能線索化
        if(node == null) {
            return;
        }

        //(一)先線索化左子樹
        threadedNodes(node.getLeft());
        //(二)線索化當前結點[有難度]

        //處理當前結點的前驅結點
        //以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;

        //(三)在線索化右子樹
        threadedNodes(node.getRight());


    }

    //刪除結點
    public void delNode(int no) {
        if(root != null) {
            //如果只有一個root結點, 這裏立即判斷root是不是就是要刪除結點
            if(root.getNo() == no) {
                root = null;
            } else {
                //遞歸刪除
                root.delNode(no);
            }
        }else{
            System.out.println("空樹,不能刪除~");
        }
    }
    //前序遍歷
    public void preOrder() {
        if(this.root != null) {
            this.root.preOrder();
        }else {
            System.out.println("二叉樹爲空,無法遍歷");
        }
    }

    //中序遍歷
    public void infixOrder() {
        if(this.root != null) {
            this.root.infixOrder();
        }else {
            System.out.println("二叉樹爲空,無法遍歷");
        }
    }
    //後序遍歷
    public void postOrder() {
        if(this.root != null) {
            this.root.postOrder();
        }else {
            System.out.println("二叉樹爲空,無法遍歷");
        }
    }

    //前序遍歷
    public HeroNode preOrderSearch(int no) {
        if(root != null) {
            return root.preOrderSearch(no);
        } else {
            return null;
        }
    }
    //中序遍歷
    public HeroNode infixOrderSearch(int no) {
        if(root != null) {
            return root.infixOrderSearch(no);
        }else {
            return null;
        }
    }
    //後序遍歷
    public HeroNode postOrderSearch(int no) {
        if(root != null) {
            return this.root.postOrderSearch(no);
        }else {
            return null;
        }
    }
}

//先創建HeroNode 結點
class HeroNode {
    private int no;
    private String name;
    private HeroNode left; //默認null
    private HeroNode right; //默認null
    //說明
    //1. 如果leftType == 0 表示指向的是左子樹, 如果 1 則表示指向前驅結點
    //2. 如果rightType == 0 表示指向是右子樹, 如果 1表示指向後繼結點
    private int leftType;
    private int rightType;



    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;
    }
    public HeroNode(int no, String name) {
        this.no = no;
        this.name = name;
    }
    public int getNo() {
        return no;
    }
    public void setNo(int no) {
        this.no = no;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public HeroNode getLeft() {
        return left;
    }
    public void setLeft(HeroNode left) {
        this.left = left;
    }
    public HeroNode getRight() {
        return right;
    }
    public void setRight(HeroNode right) {
        this.right = right;
    }
    @Override
    public String toString() {
        return "HeroNode [no=" + no + ", name=" + name + "]";
    }

    //遞歸刪除結點
    //1.如果刪除的節點是葉子節點,則刪除該節點
    //2.如果刪除的節點是非葉子節點,則刪除該子樹
    public void delNode(int no) {

        //思路
        /*
         *  1. 因爲我們的二叉樹是單向的,所以我們是判斷當前結點的子結點是否需要刪除結點,而不能去判斷當前這個結點是不是需要刪除結點.
            2. 如果當前結點的左子結點不爲空,並且左子結點 就是要刪除結點,就將this.left = null; 並且就返回(結束遞歸刪除)
            3. 如果當前結點的右子結點不爲空,並且右子結點 就是要刪除結點,就將this.right= null ;並且就返回(結束遞歸刪除)
            4. 如果第2和第3步沒有刪除結點,那麼我們就需要向左子樹進行遞歸刪除
            5.  如果第4步也沒有刪除結點,則應當向右子樹進行遞歸刪除.

         */
        //2. 如果當前結點的左子結點不爲空,並且左子結點 就是要刪除結點,就將this.left = null; 並且就返回(結束遞歸刪除)
        if(this.left != null && this.left.no == no) {
            this.left = null;
            return;
        }
        //3.如果當前結點的右子結點不爲空,並且右子結點 就是要刪除結點,就將this.right= null ;並且就返回(結束遞歸刪除)
        if(this.right != null && this.right.no == no) {
            this.right = null;
            return;
        }
        //4.我們就需要向左子樹進行遞歸刪除
        if(this.left != null) {
            this.left.delNode(no);
        }
        //5.則應當向右子樹進行遞歸刪除
        if(this.right != null) {
            this.right.delNode(no);
        }
    }

    //編寫前序遍歷的方法
    public void preOrder() {
        System.out.println(this); //先輸出父結點
        //遞歸向左子樹前序遍歷
        if(this.left != null) {
            this.left.preOrder();
        }
        //遞歸向右子樹前序遍歷
        if(this.right != null) {
            this.right.preOrder();
        }
    }
    //中序遍歷
    public void infixOrder() {

        //遞歸向左子樹中序遍歷
        if(this.left != null) {
            this.left.infixOrder();
        }
        //輸出父結點
        System.out.println(this);
        //遞歸向右子樹中序遍歷
        if(this.right != null) {
            this.right.infixOrder();
        }
    }
    //後序遍歷
    public void postOrder() {
        if(this.left != null) {
            this.left.postOrder();
        }
        if(this.right != null) {
            this.right.postOrder();
        }
        System.out.println(this);
    }

    //前序遍歷查找
    /**
     *
     * @param no 查找no
     * @return 如果找到就返回該Node ,如果沒有找到返回 null
     */
    public HeroNode preOrderSearch(int no) {
        System.out.println("進入前序遍歷");
        //比較當前結點是不是
        if(this.no == no) {
            return this;
        }
        //1.則判斷當前結點的左子節點是否爲空,如果不爲空,則遞歸前序查找
        //2.如果左遞歸前序查找,找到結點,則返回
        HeroNode resNode = null;
        if(this.left != null) {
            resNode = this.left.preOrderSearch(no);
        }
        if(resNode != null) {//說明我們左子樹找到
            return resNode;
        }
        //1.左遞歸前序查找,找到結點,則返回,否繼續判斷,
        //2.當前的結點的右子節點是否爲空,如果不空,則繼續向右遞歸前序查找
        if(this.right != null) {
            resNode = this.right.preOrderSearch(no);
        }
        return resNode;
    }

    //中序遍歷查找
    public HeroNode infixOrderSearch(int no) {
        //判斷當前結點的左子節點是否爲空,如果不爲空,則遞歸中序查找
        HeroNode resNode = null;
        if(this.left != null) {
            resNode = this.left.infixOrderSearch(no);
        }
        if(resNode != null) {
            return resNode;
        }
        System.out.println("進入中序查找");
        //如果找到,則返回,如果沒有找到,就和當前結點比較,如果是則返回當前結點
        if(this.no == no) {
            return this;
        }
        //否則繼續進行右遞歸的中序查找
        if(this.right != null) {
            resNode = this.right.infixOrderSearch(no);
        }
        return resNode;

    }

    //後序遍歷查找
    public HeroNode postOrderSearch(int no) {

        //判斷當前結點的左子節點是否爲空,如果不爲空,則遞歸後序查找
        HeroNode resNode = null;
        if(this.left != null) {
            resNode = this.left.postOrderSearch(no);
        }
        if(resNode != null) {//說明在左子樹找到
            return resNode;
        }

        //如果左子樹沒有找到,則向右子樹遞歸進行後序遍歷查找
        if(this.right != null) {
            resNode = this.right.postOrderSearch(no);
        }
        if(resNode != null) {
            return resNode;
        }
        System.out.println("進入後序查找");
        //如果左右子樹都沒有找到,就比較當前結點是不是
        if(this.no == no) {
            return this;
        }
        return resNode;
    }

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章