1. 程式人生 > 其它 >資料結構08 線索二叉樹

資料結構08 線索二叉樹

上一篇總結了二叉樹,這一篇要總結的是線索二叉樹,我想從以下幾個方面進行總結。

1、什麼是線索二叉樹?

2、為什麼要建立線索二叉樹?

3、如何將二叉樹線索化?

4、線索二叉樹的常見操作及實現思路?

5、演算法實現程式碼?

1、什麼是線索二叉樹

線索二叉樹:

按照某種方式對二叉樹進行遍歷,可以把二叉樹中所有節點排序為一個線性序列,在該序列中,除第一個節點外每個節點有且僅有一個直接前驅節點;除最後一個節點外每一個節點有且僅有一個直接後繼節點;

在N個節點的二叉樹中,每個節點有2個指標,所以一共有2N個指標,除了根節點以外,每一個節點都有一個指標從它的父節點指向它,所以一共使用了N-1個指標,所以剩下2N-(N-1)也就是N+1個空指標;

如果能利用這些空指標域來存放指向該節點的直接前驅或是直接後繼的指標,則可由此資訊直接找到在該遍歷次序下的前驅節點或後繼節點,從而比遞迴遍歷提高了遍歷速度,節省了建立系統遞迴棧所使用的儲存空間;

這些被重新利用起來的空指標就被稱為線索(Thread),加上了線索的二叉樹就是線索二叉樹;如圖:

2、為什麼要建立線索二叉樹

有了二叉樹不就足夠了嗎?那為什麼還要弄個線索二叉樹出來呢?

在原來的二叉連結串列中,查詢節點的左,右孩子可以直接實現,可是如果要找該節點的前驅和後繼節點呢?這就變得非常困難,所以為了實現這個常見的需求,我們要在每個節點中增加兩個指標域來存放遍歷時得到的前驅和後繼節點,這樣就可以通過該指標直接或間接訪問其前驅和後繼節點。

3、如何將二叉樹線索化

按某種次序遍歷二叉樹,在遍歷過程中用線索取代空指標即可。

下面是線索二叉樹和線索二叉連結串列的示意圖,它可以幫助我們更好地理解線索二叉樹。

4、線索二叉樹的常見操作及實現思路

4-1、二叉樹線索化

實現思路:按某種次序遍歷二叉樹,在遍歷過程中用線索取代空指標即可。

4-2、遍歷

實現思路:以中序遍歷為例,首先找到中序遍歷的開始節點,然後利用線索依次查詢後繼節點即可。

5、實現程式碼

程式碼:

Node.java

public class Node {
    private int data;
    private Node left;
    private boolean leftIsThread; // 左孩子是否為線索
    private Node right;
    private boolean rightIsThread; // 右孩子是否為線索

    public Node(int data) {
        this.data = data;
        this.left = null;
        this.leftIsThread = false;
        this.right = null;
        this.rightIsThread = false;
    }

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public boolean isLeftIsThread() {
        return leftIsThread;
    }

    public void setLeftIsThread(boolean leftIsThread) {
        this.leftIsThread = leftIsThread;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

    public boolean isRightIsThread() {
        return rightIsThread;
    }

    public void setRightIsThread(boolean rightIsThread) {
        this.rightIsThread = rightIsThread;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Node) {
            Node temp = (Node) obj;
            if (temp.getData() == this.data) {
                return true;
            }
        }
        return false;
    }

    @Override
    public int hashCode() {
        return super.hashCode() + this.data;
    }
}

ThreadTree.java

public class ThreadTree {
    private Node root; // 根節點
    private int size; // 大小
    private Node pre = null; // 線索化的時候儲存前驅

    public ThreadTree() {
        this.root = null;
        this.size = 0;
        this.pre = null;
    }

    public ThreadTree(int[] data) {
        this.pre = null;
        this.size = data.length;
        this.root = createTree(data, 1); // 建立二叉樹
    }

    /**
     * 建立二叉樹
     *
     * @param data
     * @param index
     * @return
     */
    public Node createTree(int[] data, int index) {
        if (index > data.length) {
            return null;
        }
        Node node = new Node(data[index - 1]);
        Node left = createTree(data, 2 * index);
        Node right = createTree(data, 2 * index + 1);
        node.setLeft(left);
        node.setRight(right);
        return node;
    }

    /**
     * 將以root為根節點的二叉樹線索化
     */
    public void inThread(Node root) {
        if (root != null) {
            inThread(root.getLeft()); // 線索化左孩子
            if (null == root.getLeft()) { // 左孩子為空
                root.setLeftIsThread(true); // 將左孩子設定為線索
                root.setLeft(pre);
            }
            if (pre != null && null == pre.getRight()) { // 右孩子為空
                pre.setRightIsThread(true);
                pre.setRight(root);
            }
            pre = root;
            inThread(root.getRight()); // 線索化右孩子
        }
    }

    /**
     * 中序遍歷線索二叉樹
     */
    public void inThreadList(Node root) {
        if (root != null) {
            while (root != null && !root.isLeftIsThread()) {
                // 如果左孩子不是線索
                root = root.getLeft();
            }
            do {
                System.out.print(root.getData() + " ");
                if (root.isRightIsThread()) {
                    // 如果右孩子是線索
                    root = root.getRight();
                } else {
                    // 有右孩子
                    root = root.getRight();
                    while (root != null && !root.isLeftIsThread()) {
                        root = root.getLeft();
                    }
                }
            } while (root != null);
        }
    }

    /**
     * 中序遞迴遍歷
     */
    public void inList(Node root) {
        if (root != null) {
            inList(root.getLeft());
            System.out.print(root.getData() + " ");
            inList(root.getRight());
        }
    }

    public Node getRoot() {
        return root;
    }

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

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }
}

ThreadTreeTest.java

public class ThreadTreeTest {
    public static void main(String[] args) {
        int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

        ThreadTree threadTree = new ThreadTree(data); // 建立普通二叉樹
        System.out.println("中序遞迴遍歷二叉樹");
        threadTree.inList(threadTree.getRoot()); // 中序遞迴遍歷二叉樹
        System.out.println();

        threadTree.inThread(threadTree.getRoot()); // 採用中序遍歷將二叉樹線索化
        System.out.println("中序遍歷線索化二叉樹");
        threadTree.inThreadList(threadTree.getRoot()); // 中序遍歷線索化二叉樹
    }
}

執行結果:

6、總結

由於它充分利用了空指標域的空間(等於節省了空間),又保證了建立時的一次遍歷就可以終生受用前驅、後繼的資訊(這意味著節省了時間),所以在實際問題中,如果所使用的二叉樹需要經常遍歷或查詢節點時需要某種遍歷中的前驅和後繼,那麼採用線索二叉連結串列的儲存結構就是不錯的選擇。