1. 程式人生 > >二叉樹系列(1)——基本概念和遍歷

二叉樹系列(1)——基本概念和遍歷

二叉樹是一種非常重要的資料結構,其在查詢、排序等領域有著非常重要的應用。本文簡單介紹一些關於二叉樹的基本概念,並給出幾種二叉樹的基本遍歷方法。全文程式碼為Java語言實現。

二叉樹的基本概念

1. 二叉樹的定義

定義: 二叉樹是n(n >= 0)個節點所構成的集合,n=0為空樹,n>0是非空樹。

  • 有且僅有一個根節點
  • 二叉樹的度為小於等於2,即一個父節點最多隻會有兩個子節點
  • 二叉樹天生具有遞迴的特性
  • 二叉樹的子樹有左右之分,其次序不能顛倒
2. 二叉樹的性質
  • 二叉樹的第i層上最多有 2^(i-1) 個節點
  • 深度為k的二叉樹最多有 2^k - 1 個節點

二叉樹的兩種特殊形態:

  • 滿二叉樹

image

特點: 深度為k且含有2^k - 1 個節點的樹

  • 完全二叉樹

image

特性:具有n個節點的完全二叉樹的深度為 |log2N| + 1

二叉樹的構造與遍歷

1. 二叉樹的構造

在Java中,一個節點可以用一個類來描述,其三個成員變數分別為:資料域(value)、指向左子樹節點的引用(left)、指向右子樹節點的引用(right)

構造節點的程式碼實現:

/**
 * 二叉樹節點
 */
private static class TreeNode {
    // 資料域
    private String value;
    // 指向左子樹的引用
    private TreeNode left;
// 指向右子樹的引用 private TreeNode right; public TreeNode(String value, TreeNode left, TreeNode right) { this.value = value; this.left = left; this.right = right; } public String getValue() { return value; } public void setValue(String value) {
this.value = value; } public TreeNode getLeft() { return left; } public void setLeft(TreeNode left) { this.left = left; } public TreeNode getRight() { return right; } public void setRight(TreeNode right) { this.right = right; } }
2. 二叉樹的遍歷

二叉樹遍歷的三種方式如下:

(1) 先序遍歷:根節點->左子樹->右子樹

下面是兩種實現方式:遞迴實現和非遞迴實現

/**
 * 遞迴實現 先序遍歷:根節點->左子樹->右子樹
 * 1. 訪問根節點
 * 2. 先序遍歷左子樹
 * 3. 先序遍歷右子樹
 */
private void preOrderR(TreeNode node) {
    if (node != null) {
        // 1. 訪問根節點
        System.out.print(node.getValue() + " ");
        // 2. 先序遍歷左子樹
        preOrderR(node.getLeft());
        // 3. 先序遍歷右子樹
        preOrderR(node.getRight());
    }
}

/**
 * 非遞迴實現 先序遍歷
 */
private void preOrder() {
    Stack<TreeNode> stack = new Stack<TreeNode>();
    stack.push(root);
    TreeNode current = null;
    while (!stack.isEmpty()) {
        current = stack.pop();
        System.out.print(current.getValue() + " ");
        if (current.getRight() != null) {
            stack.push(current.getRight());
        }
        if (current.getLeft() != null) {
            stack.push(current.getLeft());
        }
    }
    System.out.println();
}

(2) 中序遍歷:左子樹->根節點->右子樹

下面是兩種實現方式:遞迴實現和非遞迴實現

/**
 * 遞迴實現 中序遍歷:左子樹->根節點->右子樹
 * 1. 中序遍歷左子樹
 * 2. 訪問根節點
 * 3. 中序遍歷右子樹
 */
private void inOrderR(TreeNode node) {
    if (node != null) {
        // 1. 中序遍歷左子樹
        inOrderR(node.getLeft());
        // 2. 訪問根節點
        System.out.print(node.getValue() + " ");
        // 3. 中序遍歷右子樹
        inOrderR(node.getRight());
    }
}

/**
 * 非遞迴實現 中序遍歷
 */
private void inOrder() {
    Stack<TreeNode> stack = new Stack<TreeNode>();
    TreeNode current = root;
    while (current != null || !stack.isEmpty()) {
        while (current != null) {
            stack.push(current);
            current = current.getLeft();
        }
        if (!stack.isEmpty()) {
            current = stack.pop();
            System.out.print(current.getValue() + " ");
            current = current.getRight();
        }
    }
    System.out.println();
}

(3) 後續遍歷:左子樹->右子樹->根節點

下面是兩種實現方式:遞迴實現和非遞迴實現

/**
 * 遞迴實現 後序遍歷:左子樹->右子樹->根節點
 * 1. 後續遍歷左子樹
 * 2. 後續遍歷右子樹
 * 3. 訪問根節點
 */
private void lastOrderR(TreeNode node) {
    if (node != null) {
        // 1. 後序遍歷左子樹
        lastOrderR(node.getLeft());
        // 2. 後序遍歷右子樹
        lastOrderR(node.getRight());
        // 3. 訪問根節點
        System.out.print(node.getValue() + " ");
    }
}

/**
 * 非遞迴實現 後續遍歷
 */
private void lastOrder() {
    TreeNode rNode = null;
    TreeNode current = root;
    Stack<TreeNode> stack = new Stack<TreeNode>();
    while (current != null || !stack.isEmpty()) {
        while (current != null) {
            stack.push(current);
            current = current.getLeft();
        }
        current = stack.pop();
        while (current != null && (current.getRight()) == null || current.getRight() == rNode) {
            System.out.print(current.getValue() + " ");
            rNode = current;
            if (stack.isEmpty()) {
                System.out.println();
                return;
            }
            current = stack.pop();
        }
        stack.push(current);
        current = current.getRight();
    }
}

(4) 驗證結果

下面是任意給出的一棵二叉樹:

image

驗證上面的三種遍歷方式:

public class ConstructBinaryTree {
    // 根節點
    private TreeNode root;

    public ConstructBinaryTree(TreeNode root) {
        this.root = root;
    }

    public TreeNode getRoot() {
        return root;
    }

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

    public static void main(String[] args) {
        // 第三層節點
        TreeNode l3_1 = new TreeNode("F", null, null);
        TreeNode l3_2 = new TreeNode("G", null, null);
        TreeNode r3 = new TreeNode("H", null, null);
        // 第二層節點
        TreeNode l2 = new TreeNode("E", l3_2, r3);
        TreeNode r2 = new TreeNode("D", l3_1, null);
        // 第一層節點
        TreeNode l1 = new TreeNode("B", null, r2);
        TreeNode r1 = new TreeNode("C", l2, null);
        TreeNode root = new TreeNode("A", l1, r1);

        ConstructBinaryTree cbt = new ConstructBinaryTree(root);

        System.out.println("遞迴 先序遍歷:");
        cbt.preOrderR(cbt.getRoot());
        System.out.println("\n非遞迴 先序遍歷:");
        cbt.preOrder();

        System.out.println("\n遞迴 中序遍歷:");
        cbt.inOrderR(cbt.getRoot());
        System.out.println("\n非遞迴 中序遍歷:");
        cbt.inOrder();

        System.out.println("\n遞迴 後序遍歷:");
        cbt.lastOrderR(cbt.getRoot());
        System.out.println("\n非遞迴 後序遍歷:");
        cbt.lastOrder();
    }
}

結果如下:
遞迴 先序遍歷:
A B D F C E G H 
非遞迴 先序遍歷:
A B D F C E G H 

遞迴 中序遍歷:
B F D A G E H C 
非遞迴 中序遍歷:
B F D A G E H C 

遞迴 後序遍歷:
F D B G H E C A 
非遞迴 後序遍歷:
F D B G H E C A