遍歷二叉樹

在二叉樹的一些應用中,常常要求在樹中查找具有某些特徵的結點,或者對樹中全部結點逐一進行某種處理。這就提出了一個遍歷二叉樹的問題,即如果按某條搜索路徑巡訪樹中的每個結點,使得每個結點均被訪問一次,而且僅被訪問一次。“訪問”的含義很廣,可以是對結點作各種處理,如輸出結點的信息等。遍歷對於線性結構來說是一個很容易解決的問題,而對二叉樹則不然,因爲二叉樹是一種非線性結構。

回顧二叉樹的定義,我們知道二叉樹可以看成是由三個部分組成的:一個根結點,根的左子樹和根的右子樹。因此如果能夠遍歷這三部分,則可以遍歷整棵二叉樹。如果用L、D、R分別表示遍歷左子樹、訪問根結點、遍歷右子樹。那麼對於二叉樹的遍歷次序就可以有6中方案,即L、D、R的排列組合,那麼如果限制對左子樹的遍歷要先於對右子樹的遍歷,這就剩下3中情況,分別是:

  • (1)訪問根,遍歷左子樹,遍歷右子樹(DLR)
  • (2)遍歷左子樹,訪問根,遍歷右子樹(LDR)
  • (3)遍歷左子樹,遍歷右子樹,訪問根(LRD)

根據對根訪問的不同順序,分別成DLR爲先根(序)遍歷,LDR爲中根(序)遍歷,LRD爲後根(序)遍歷。

需要注意的是,這裏的先序遍歷、中序遍歷和後序遍歷是遞歸定義的,即在左右子樹中也是按相應的規律進行遍歷。


這裏寫圖片描述

例如,如上圖所示的二叉樹表示下述表達式:

4 + (6 - 8 + 2 * 2)*2

若先序遍歷此二叉樹,按訪問結點的先後次序將結點排列起來,可得二叉樹的先序序列爲:

+ 4 * + - 6 8 * 2 2 2   (式1)

類似的,中序遍歷此二叉樹,可得此二叉樹的中序序列爲:

4 + 6 - 8 + 2 * 2 * 2   (式2)

後序遍歷此二叉樹,可得此二叉樹的後序序列爲:

4 6 8 - 2 2 * + 2 * +    (式3)

從表達式上看,式1、2、3恰好是表達式的前綴表示(波蘭式)、中綴表示和後綴表示(逆波蘭式)。


我們可以很簡單地寫出遍歷二叉樹的遞歸算法,同時,根據遞歸時工作棧的狀態變化情況,我們也可以很簡單地寫出遍歷二叉樹的非遞歸算法。

每一個遍歷的非遞歸算法都有兩種思路,如果藉助訪問標記的話,我們可以根據遞歸的特點,手動地用一個棧來保存每一個結點,舉例來說,先序遍歷時先訪問根,再訪問左子樹,接着訪問右子樹,即DLR,那麼我們對於每一個結點,都按照RLD的順序入棧,如果一個結點的左右子樹已經入過棧,那麼該結點視爲已訪問過的,下一次再遇到的話直接從棧中彈出,並且訪問即可;而如果一個結點是未訪問過的,則先將其彈出,再按照RLD的順序,將其右孩子、左孩子入棧,接着自己再入棧。重複這個過程直到棧爲空。這種方法實現和理解起來都比較簡單。

另一種思路不需要藉助訪問標記,但是也要藉助一個棧來實現,這種方式理解起來比上一種方法稍微有點複雜。


那麼下述代碼,實現了二叉樹的遞歸與非遞歸的遍歷方法。並且,每一種非遞歸方法都採用至少兩種思路實現。

另外,我們是通過二叉樹的順序存儲結構(即數組)來創建二叉樹,與此同時,也實現了二叉樹的層序遍歷。

import java.util.HashMap;
import java.util.Map;

import com.gavin.datastructure.queue.ArrayQueue;
import com.gavin.datastructure.stack.ArrayStack;

/**
 * 實現二叉樹的構建以及各種遍歷
 * 
 * @author Gavin
 *
 * @param <T>
 */
public class BinaryTree<T> {

    /**
     * 從順序存儲結構,即數組中構建二叉樹
     * 
     * @param array
     *            數組
     * @return 返回二叉樹根結點
     */
    public TripleLinkedNode<T> createTreeFromArray(T[] array) {
        if (array == null || array.length == 0) {
            return null;
        }
        Map<Integer, TripleLinkedNode<T>> nodeMap = new HashMap<>();
        for (int i = 0; i < array.length; i++) {
            // 如果值爲空,說明當前結點不存在
            if (array[i] == null) {
                continue;
            }
            TripleLinkedNode<T> currentNode = null;
            if (nodeMap.containsKey(i)) {
                // 當前結點已存在
                currentNode = nodeMap.get(i);
            } else {
                // 當前結點不存在
                currentNode = new TripleLinkedNode<T>(array[i]);
                // 加入
                nodeMap.put(i, currentNode);
            }
            // 設置當前結點的左孩子
            if (2 * i + 1 < array.length && array[2 * i + 1] != null) {
                TripleLinkedNode<T> leftNode = new TripleLinkedNode<T>(array[2 * i + 1]);
                currentNode.setLeft(leftNode);
                nodeMap.put(2 * i + 1, leftNode);
            }
            // 設置當前結點的右孩子
            if (2 * i + 2 < array.length && array[2 * i + 2] != null) {
                TripleLinkedNode<T> rightNode = new TripleLinkedNode<T>(array[2 * i + 2]);
                currentNode.setRight(rightNode);
                nodeMap.put(2 * i + 2, rightNode);
            }
        }
        // 返回根結點root
        return nodeMap.get(0);

    }

    /**
     * 訪問一個結點
     * 
     * @param node
     */
    public void visit(TripleLinkedNode<T> node) {
        System.out.print(node.getData() + " ");
    }

    /**
     * 將訪問標記復位爲false
     * 
     * @param root
     */
    public void resetVisited(TripleLinkedNode<?> root) {
        if (root == null) {
            return;
        }
        root.setVisited(false);
        resetVisited(root.getLeft());
        resetVisited(root.getRight());
    }

    /**
     * 遞歸實現前序遍歷
     * 
     * @param root
     */
    public void preOrderTraverse(TripleLinkedNode<T> root) {
        if (root == null) {
            return;
        }
        visit(root);
        preOrderTraverse(root.getLeft());
        preOrderTraverse(root.getRight());
    }

    /**
     * 遞歸實現後續遍歷
     * 
     * @param root
     */
    public void postOrderTraverse(TripleLinkedNode<T> root) {
        if (root == null) {
            return;
        }
        postOrderTraverse(root.getLeft());
        postOrderTraverse(root.getRight());
        visit(root);
    }

    /**
     * 遞歸實現中序遍歷
     * 
     * @param root
     */
    public void inOrderTraverse(TripleLinkedNode<T> root) {
        if (root == null) {
            return;
        }
        inOrderTraverse(root.getLeft());
        visit(root);
        inOrderTraverse(root.getRight());
    }

    /**
     * 層序遍歷
     * 
     * @param root
     */
    public void levelOrderTraverse(TripleLinkedNode<T> root) {
        // 要使用一個隊列
        ArrayQueue<TripleLinkedNode<T>> queue = new ArrayQueue<>();
        queue.enqueue(root);
        while (!queue.isEmpty()) {
            TripleLinkedNode<T> currentNode = queue.dequeue();
            // 訪問隊頭結點
            visit(currentNode);
            // 左孩子入隊
            if (currentNode.hasLeft()) {
                queue.enqueue(currentNode.getLeft());
            }
            // 右孩子入隊
            if (currentNode.hasRight()) {
                queue.enqueue(currentNode.getRight());
            }
        }
    }

    /**
     * 使用訪問標記的非遞歸前序遍歷
     * 
     * @param root
     */
    public void preOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        stack.push(node);
        while (!stack.isEmpty()) {
            node = stack.peek();
            if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {
                // 已被訪問過或者是葉子結點,則直接訪問
                visit(node);
                stack.pop();
                continue;
            }
            // 先彈出
            stack.pop();
            if (node.hasRight()) {
                stack.push(node.getRight());
            }
            if (node.hasLeft()) {
                stack.push(node.getLeft());
            }
            // 再加入
            node.setVisited(true);
            stack.push(node);
        }
    }

    /**
     * 不使用訪問標記的非遞歸實現前序遍歷
     * 
     * @param root
     */
    public void preOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {
        // 要用到一個棧
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        while (node != null) {
            // 向左走到底
            while (node != null) {
                visit(node); // 訪問根結點
                // 將該根結點的右孩子入棧
                if (node.hasRight()) {
                    stack.push(node.getRight());
                }
                node = node.getLeft();
            }
            // 右子樹根退棧遍歷右子樹
            if (!stack.isEmpty()) {
                node = stack.pop();
            }
        }
    }

    /**
     * 不使用訪問標記的非遞歸實現前序遍歷2:簡單好理解
     * 
     * @param root
     */
    public void preOrderTraverseNoRecursionWithoutVisited2(TripleLinkedNode<T> root) {
        // 要用到一個棧
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        stack.push(node);
        while (!stack.isEmpty()) {
            node = stack.pop();
            visit(node);
            if (node.hasRight()) {
                stack.push(node.getRight());
            }
            if (node.hasLeft()) {
                stack.push(node.getLeft());
            }
        }
    }

    /**
     * 使用訪問標記的非遞歸中序遍歷
     * 
     * @param root
     */
    public void inOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        stack.push(node);
        while (!stack.isEmpty()) {
            node = stack.peek();
            if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {
                visit(node);
                stack.pop();
                continue;
            }
            // 先彈出
            stack.pop();
            if (node.hasRight()) {
                stack.push(node.getRight());
            }
            // 再加入
            node.setVisited(true);
            stack.push(node);
            if (node.hasLeft()) {
                stack.push(node.getLeft());
            }
        }
    }

    /**
     * 不使用訪問標記的非遞歸實現中序遍歷
     * 
     * @param root
     */
    public void inOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {
        // 要用到一個棧
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        while (node != null || !stack.isEmpty()) {
            // 向左走到盡頭
            while (node != null) {
                stack.push(node);
                node = node.getLeft();
            }
            if (!stack.isEmpty()) {
                // 取出根結點並訪問
                node = stack.pop();
                visit(node);
                // 轉向根的右子樹進行遍歷
                node = node.getRight();
            }
        }
    }

    /**
     * 使用訪問標記的非遞歸後序遍歷
     * 
     * @param root
     */
    public void postOrderTraverseNoRecursionWithVisited(TripleLinkedNode<T> root) {
        // 要用到一個棧
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        stack.push(node);
        while (!stack.isEmpty()) {
            node = stack.peek();
            // 如果曾經已經訪問過其孩子,這裏直接訪問即可。或者是葉子結點的話,這裏也直接訪問
            if (node.isVisited() || (!node.hasLeft() && !node.hasRight())) {
                // 葉子結點
                visit(node);
                stack.pop();
                continue;
            }
            // 先加入右孩子
            if (node.hasRight()) {
                stack.push(node.getRight());
            }
            // 再加入左孩子
            if (node.hasLeft()) {
                stack.push(node.getLeft());
            }
            // 其子結點加入棧,這裏設置其訪問標記爲true
            node.setVisited(true);
        }
    }

    /**
     * 不使用訪問標記的非遞歸後序遍歷
     * 
     * @param root
     */
    public void postOrderTraverseNoRecursionWithoutVisited(TripleLinkedNode<T> root) {
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        while (node != null || !stack.isEmpty()) {
            // 先左後右不斷深入
            while (node != null) {
                // 將根結點入棧
                stack.push(node);
                if (node.hasLeft()) {
                    node = node.getLeft();
                } else {
                    node = node.getRight();
                }
            }
            if (!stack.isEmpty()) {
                // 取出棧頂元素訪問
                node = stack.pop();
                visit(node);
            }
            // 滿足條件時,說明棧頂根結點右子樹已經訪問過,應出棧訪問之
            while (!stack.isEmpty() && stack.peek().getRight() == node) {
                node = stack.pop();
                visit(node);
            }
            // 轉向棧頂根結點的右子樹繼續後續遍歷
            if (!stack.isEmpty()) {
                node = stack.peek().getRight();
            } else {
                node = null;
            }
        }
    }

    /**
     * 不使用訪問標記的非遞歸後序遍歷的第二種解法:使用兩個棧,簡單也好理解
     * 
     * @param root
     */
    public void postOrderTraverseNoRecursionWithoutVisited2(TripleLinkedNode<T> root) {
        ArrayStack<TripleLinkedNode<T>> stack = new ArrayStack<>();
        ArrayStack<TripleLinkedNode<T>> output = new ArrayStack<>();
        TripleLinkedNode<T> node = root;
        stack.push(node);
        while (!stack.isEmpty()) {
            node = stack.pop();
            output.push(node);
            if (node.hasLeft()) {
                stack.push(node.getLeft());
            }
            if (node.hasRight()) {
                stack.push(node.getRight());
            }
        }
        while (!output.isEmpty()) {
            visit(output.pop());
        }
    }
}

下面代碼對當前的二叉樹遍歷進行測試:

import static org.junit.Assert.*;

import org.junit.Test;

public class BinaryTreeTest {

    @Test
    public void testCreateTreeFromArray() {
        BinaryTree<String> bt = new BinaryTree<>();
        String[] array = { "+", "4", "*", null, null, "+", "2", null, null, null, null, "-", "*", null, null, null,
                null, null, null, null, null, null, null, "6", "8", "2", "2", null, null, null, null };
        TripleLinkedNode<String> root = bt.createTreeFromArray(array);
        assertEquals(11, root.getSize());
        assertEquals(5, root.getHeight());
        // 遍歷
        System.out.println("遞歸先序遍歷:");
        bt.preOrderTraverse(root);
        System.out.println();
        System.out.println("使用訪問標記的非遞歸先序遍歷:");
        bt.preOrderTraverseNoRecursionWithVisited(root);
        System.out.println();
        System.out.println("不使用訪問標記的非遞歸先序遍歷1:");
        bt.preOrderTraverseNoRecursionWithoutVisited(root);
        // 標記復位
        bt.resetVisited(root);
        System.out.println();
        System.out.println("不使用訪問標記的非遞歸先序遍歷2:");
        bt.preOrderTraverseNoRecursionWithoutVisited2(root);

        System.out.println();
        System.out.println();
        System.out.println("遞歸中序遍歷:");
        bt.inOrderTraverse(root);
        System.out.println();
        System.out.println("使用訪問標記的非遞歸中序遍歷:");
        bt.inOrderTraverseNoRecursionWithVisited(root);
        System.out.println();
        System.out.println("不使用訪問標記的非遞歸中序遍歷:");
        bt.inOrderTraverseNoRecursionWithoutVisited(root);
        //標記復位
        bt.resetVisited(root);


        System.out.println();
        System.out.println();
        System.out.println("遞歸後序遍歷:");
        bt.postOrderTraverse(root);
        System.out.println();
        System.out.println("使用訪問標記的非遞歸後序遍歷:");
        bt.postOrderTraverseNoRecursionWithVisited(root);
        System.out.println();
        System.out.println("不使用訪問標記的非遞歸後序遍歷1:");
        bt.postOrderTraverseNoRecursionWithoutVisited(root);
        bt.resetVisited(root);
        System.out.println();
        System.out.println("不使用訪問標記的非遞歸後序遍歷2:");
        bt.postOrderTraverseNoRecursionWithoutVisited2(root);

        System.out.println();
        System.out.println();
        System.out.println("層序遍歷:");
        bt.levelOrderTraverse(root);
    }

}

輸出如下:

遞歸先序遍歷:
+ 4 * + - 6 8 * 2 2 2 
使用訪問標記的非遞歸先序遍歷:
+ 4 * + - 6 8 * 2 2 2 
不使用訪問標記的非遞歸先序遍歷1:
+ 4 * + - 6 8 * 2 2 2 
不使用訪問標記的非遞歸先序遍歷2:
+ 4 * + - 6 8 * 2 2 2 

遞歸中序遍歷:
4 + 6 - 8 + 2 * 2 * 2 
使用訪問標記的非遞歸中序遍歷:
4 + 6 - 8 + 2 * 2 * 2 
不使用訪問標記的非遞歸中序遍歷:
4 + 6 - 8 + 2 * 2 * 2 

遞歸後序遍歷:
4 6 8 - 2 2 * + 2 * + 
使用訪問標記的非遞歸後序遍歷:
4 6 8 - 2 2 * + 2 * + 
不使用訪問標記的非遞歸後序遍歷1:
4 6 8 - 2 2 * + 2 * + 
不使用訪問標記的非遞歸後序遍歷2:
4 6 8 - 2 2 * + 2 * + 

層序遍歷:
+ 4 * + 2 - * 6 8 2 2

可見,每個算法的結果都完全正確。

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