二叉樹的各類操做(遞歸和非遞歸遍歷,樹深度,結點個數等等)

二叉樹創建

先給出結點結構:java

static class Node {
    public int val;
    public Node left;
    public Node right;

    public Node(int val) {
        this.val = val;
    }
}
複製代碼

兩種創建方式:bash

  • 能夠根據二叉樹根節點和左右子結點的下標關係遞歸創建二叉樹,層次輸入二叉樹結點;
  • 也可使用輸入流前序創建二叉樹(注意空樹要輸入-1);

一、根據下標關係

// given a arr to build
static Node createTree(int arr[], int i) {
    if (i >= arr.length || arr[i] == -1)
        return null;
    Node root = new Node(arr[i]);
    root.left = createTree(arr, 2 * i + 1);
    root.right = createTree(arr, 2 * i + 2);
    return root;
}
複製代碼

大體過程以下:post

二、前序輸入(cin)創建

// cin method	
static Node buildTree(Scanner cin) {
    Node root = null;
    int data = cin.nextInt();
    if (data != -1) {
        root = new Node(data);
        root.left = buildTree(cin);
        root.right = buildTree(cin);
    }
    return root;
}
複製代碼

過程以下:ui

前序遍歷

一、遞歸前序

static void preOrder(Node T) {
    if (T == null)
        return;
    System.out.print(T.val + " ");
    preOrder(T.left);
    preOrder(T.right);
}
複製代碼

二、非遞歸前序

前序遍歷順序爲: 根結點->左子樹->右子樹,因此對於正在訪問的根結點,能夠直接訪問,訪問完以後,按照相同的方式訪問左子樹,再訪問右子樹,過程以下 :this

  • 若是當前節點p不爲空,訪問結點p,並將結點p入棧,並繼續訪問左子樹(直到左子樹爲空);
  • 不然將棧頂元素出棧,並訪問棧頂的元素的右子樹;
  • 直到棧爲空且p爲空,循環結束。

代碼:spa

static void iterativePre(Node root) {
    Stack<Node> s = new Stack<>();
    Node p = root;
    while (!s.empty() || p != null) {
        if (p != null) {//也能夠寫一個while循環,直到左子樹爲空
            s.push(p);
            System.out.print(p.val + " ");
            p = p.left;
        } else {
            p = s.pop();
            p = p.right;
        }
    }
}
複製代碼

也能夠將上面的一直訪問到左子樹爲空寫成一個while循環:3d

static void iterativePre2(Node root) {
    Stack<Node> s = new Stack<>();
    Node p = root;
    while (!s.empty() || p != null) {
        while (p != null) { // while循環,直到左子樹爲空
            s.push(p);
            System.out.print(p.val + " ");
            p = p.left;
        }
        p = s.pop();
        p = p.right;
    }
}
複製代碼

還有另一種寫法是: code

  • 先把根節點入棧,而後每次出棧一個元素,先訪問這個元素,而後若是它的右子樹存在,就入棧,若是它的左子樹存在也入棧;
  • 爲何要先入右子樹呢,由於,前序遍歷是中->左->右,而棧能夠逆序,因此先右再左;

這個方法在後續遍歷的雙棧法中有體現,那個只是這個稍微的修改。cdn

static void iterativePre3(Node root) {
    if (root == null)
        return;
    Node p = root;
    Stack<Node> stack = new Stack<>();
    stack.add(p);
    while (!stack.isEmpty()) {
        p = stack.pop();
        System.out.print(p.val + " ");
        if (p.right != null)// 先右再左便可
            stack.push(p.right);
        if (p.left != null)
            stack.push(p.left);
    }
}
複製代碼

中序遍歷

一、遞歸中序

static void inOrder(Node T) {
    if (T == null)
        return;
    inOrder(T.left);
    System.out.print(T.val + " ");
    inOrder(T.right);
}
複製代碼

二、非遞歸中序

中序遍歷 : 左子樹->根->右子樹,過程以下:blog

  • 當前節點不空!= null,壓入棧中(和前序遍歷不一樣的是,不須要打印),當前節點向左;
  • 當前節點爲空== null,從棧中拿出一個而且打印(在這裏打印) ,當前節點向右;

直到棧爲空且p爲空,循環結束。

/**
* 1)、當前節點不空(!=null),壓入棧中(和前序遍歷不一樣的是,不須要打印),當前節點向左;
* 2)、當前節點爲空(==null),從棧中拿出一個而且打印(在這裏打印) ,當前節點向右;
*/
static void iterativeIn(Node root) {
    if (root == null)
        return;
    Stack<Node> s = new Stack<>();
    Node p = root;
    while (!s.empty() || p != null) {
        if (p != null) {
            s.push(p);
            p = p.left;
        } else {
            p = s.pop();
            System.out.print(p.val + " "); //在這裏打印
            p = p.right;
        }
    }
}
複製代碼

同理,那個一直訪問左孩子那裏也能夠改爲whlie:

static void iterativeIn2(Node root) {
    if (root == null)
        return;
    Stack<Node> s = new Stack<>();
    Node p = root;
    while (!s.empty() || p != null) {
        while (p != null) { //這裏改爲while
            s.push(p);
            p = p.left;
        }
        p = s.pop();
        System.out.print(p.val + " "); //在這裏打印
        p = p.right;
    }
}
複製代碼

後序遍歷

一、遞歸後序

static void postOrder(Node T) {
    if (T == null)
        return;
    postOrder(T.left);
    postOrder(T.right);
    System.out.print(T.val + " ");
}
複製代碼

二、非遞歸後序

1)、雙棧法

這個其實就是非遞歸前序(iterativePre3)的稍微一點改進。

  • 首先,前序遍歷入棧(iterativePre3)的順序是先 右 再左
  • 這時,咱們能夠作到反過來先 左 再右,這樣遍歷的順序能夠作到 "中右左",然後續遍歷是 "左右中",正好是前面那個的相反,因此咱們再使用一個棧反轉保存便可

代碼:

/**
* 非遞歸後續1(雙棧法解決非遞歸後續)
* 後續遍歷是要實現&emsp;&emsp;&emsp;左->右->中
* 這個方法和前序遍歷的第二種方法&emsp;只是多了一個棧而已
* 由於&emsp;前序遍歷是  中->左->右&emsp;&emsp;壓棧順序是 右->左
* 這樣,咱們就很容易實現&emsp;中->右->左遍歷&emsp;&emsp;壓棧順序是&emsp;左->右
* 然後續遍歷是要實現  左->右->中,
* 咱們把上面的&emsp;&emsp;中右左&emsp;壓入到另外一個棧中&emsp;就實現了&emsp;左右中
*/
static void iterativePos(Node root) {
    Stack<Node> s = new Stack<>(), s2 = new Stack<>();
    Node p;
    s.push(root);
    while (!s.empty()) {
        p = s.pop();
        s2.push(p);
        if (p.left != null) s.push(p.left); //這裏是先左再右  (非遞歸前序是先右再左)
        if (p.right != null) s.push(p.right);
    }
    while (!s2.empty())
        System.out.print(s2.pop().val + " ");
}
複製代碼

2)、設置pre結點

過程以下:

  • 對於任一結點p,先將其入棧;
  • 能夠訪問的狀況: ①若p不存在左孩子和右孩子,則能夠直接訪問它。②或者p存在左孩子或者右孩子,可是左孩子和右孩子都已經被訪問過了,則也能夠直接訪問該結點;
  • 若非上述兩種狀況,則將右孩子和左孩子依次入棧。這樣能夠保證每次取棧頂元素時,左孩子在右孩子前面被訪問,根結點在左孩子和右孩子訪問以後被訪問;

代碼:

/*** 非遞歸後續2(設置pre結點) */
static void iterativePos2(Node root) {
    Node cur, pre = null;
    Stack<Node> s = new Stack<>();
    s.push(root);
    while (!s.empty()) { 
        cur = s.peek();
        // 兩種能夠訪問的狀況
        if ((cur.left == null && cur.right == null) ||
            ((pre != null) && (pre == cur.left || pre == cur.right))) {
            System.out.print(cur.val + " ");
            s.pop();
            pre = cur;
        } else {
            if (cur.right != null) s.push(cur.right);
            if (cur.left != null) s.push(cur.left);
        }
    }
}
複製代碼

層次遍歷

很簡單。利用隊列BFS便可,每次訪問完p,若左右孩子存在,則入隊,直至隊空;

static void levelOrder(Node root) {
    if (root == null)
        return;
    Queue<Node> queue = new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        Node now = queue.poll();
        System.out.print(now.val + " ");
        if (now.left != null) queue.add(now.left);
        if (now.right != null) queue.add(now.right);
    }
}
複製代碼

尋找樹中有沒有值爲x的結點

遞歸條件有兩個,一個是爲空表明沒找到,找到了的話直接返回,不然遞歸查找左右子樹。

//查找某個值爲x的結點
static Node search(Node T, int x) {
    if (T == null)
        return null;
    if (T.val == x)
        return T;
    else {
        if (search(T.left, x) == null)
            return search(T.right, x);
        else
            return search(T.left, x);
    }
}
複製代碼

統計樹中結點的個數

樹中結點的個數等於根節點(1) + 左子樹結點個數 + 右子樹的個數,遞歸求解便可。

source-java
//統計結點個數
static int count(Node T) {
    if (T == null)
        return 0;
    else
        return count(T.left) + count(T.right) + 1;
}
複製代碼

計算樹的高度

也是遞歸求解,左右子樹的高度中的比較高的加上根節點就是樹的高度。

source-java
//計算二叉樹的深度
static int depth(Node T) {
    if (T == null)
        return 0;
    return Math.max(depth(T.left), depth(T.right)) + 1;
}
複製代碼

判斷兩棵樹是否是相等

也是遞歸求解,兩棵樹相等,既要根節點的值相等,並且左右子樹也要相等。

source-java
//判斷兩棵樹是否是相等
static boolean is_SameTree(Node T1, Node T2) {
    if (T1 == null && T2 == null)
        return true;
    else {
        return T1 != null && T2 != null && T1.val == T2.val
            && is_SameTree(T1.left, T2.left) && is_SameTree(T1.right, T2.right);
    }
}
複製代碼

歡迎關注公衆號:老男孩的成長之路,精選乾貨每週按期奉上!

相關文章
相關標籤/搜索