1. 程式人生 > 實用技巧 >資料結構與演算法(十一):二叉樹

資料結構與演算法(十一):二叉樹

一、什麼是二叉樹

1.概述

首先,需要了解樹這種資料結構的定義:

樹:是一類重要的非線性資料結構,是以分支關係定義的層次結構。每個結點有零個或多個子結點;沒有父結點的結點稱為根結點;每一個非根結點有且只有一個父結點;除了根結點外,每個子結點可以分為多個不相交的子樹

樹的結構類似現實中的樹,一個父節點有若干子節點,而一個子節點又有若干子節點,以此類推。

2.名詞解釋

名稱 含義
根節點 樹的頂端結點
父節點 若一個節點含有子節點,則這個節點稱為其子節點的父節點
子節點 具有相同父節點的節點
兄弟節點 彼此都擁有同一個父節點的節點
葉子節點 即沒有子節點的節點
節點的權 即節點值
路節點的度 一個節點含有的子樹的個數
樹的度 一棵樹中,最大的節點的度稱為樹的度
深度 根結點到這個結點所經歷的邊的個數
層數 該節點的深度+1
高度 結點到葉子結點的最長路徑所經歷的邊的個數
樹高度 即根節點的高度
森林 由m(m>=0)棵互不相交的樹的集合稱為森林

3.二叉樹

二叉樹就是每個節點最多隻有兩顆子樹的樹:

對於二叉樹有:

  • 滿二叉樹:所有的子節點都在最後一層,且節點總數與層數有節點總數=2^n-1

  • 完全二叉樹:從根節點到倒數第二層都符合滿二叉樹,但是最後一層節點不完全充填,葉子結點都靠左對齊

二、二叉樹的遍歷

二叉樹遍歷分為三種:

  • 前序遍歷: 先輸出父節點,再遍歷左子樹和右子樹
  • 中序遍歷: 先遍歷左子樹,再輸出父節點,再遍歷右子樹
  • 後序遍歷: 先遍歷左子樹,再遍歷右子樹,最後輸出父節點

可見,根據父節點輸出順序即可以判斷是哪一種遍歷。

1.簡單程式碼實現

先建立節點類:

/**
 * @Author:黃成興
 * @Date:2020-07-11 17:30
 * @Description:二叉樹
 */
public class BinaryTreeNode {

    private int nodeNum;

    /**
     * 右子節點
     */
    private BinaryTreeNode right;

    /**
     * 左子節點
     */
    private BinaryTreeNode left;

    public BinaryTreeNode(int nodeNum) {
        this.nodeNum = nodeNum;
    }

    @Override
    public String toString() {
        return "BinaryTreeNode{" +
                "nodeNum=" + nodeNum +
                '}';
    }

    public int getNodeNum() {
        return nodeNum;
    }

    public void setNodeNum(int nodeNum) {
        this.nodeNum = nodeNum;
    }

    public BinaryTreeNode getRight() {
        return right;
    }

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

    public BinaryTreeNode getLeft() {
        return left;
    }

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

實現遍歷方法:

/**
 * @Author:黃成興
 * @Date:2020-07-11 17:44
 * @Description:二叉樹
 */
public class BinaryTree {

    private BinaryTreeNode root;

    public BinaryTree(BinaryTreeNode root) {
        if (root == null) {
            throw new RuntimeException("根節點不允許為空!");
        }
        this.root = root;
    }

    public void preOrder(){
        preOrder(root);
    }
    /**
     * 前序遍歷
     */
    public void preOrder(BinaryTreeNode node){
        //列印節點
        System.out.println(node);
        //向左子樹前序遍歷
        if (node.getLeft() != null) {
            preOrder(node.getLeft());
        }
        //向右子樹前序遍歷
        if (node.getRight() != null) {
            preOrder(node.getRight());
        }
    }

    public void inOrder(){
        inOrder(root);
    }
    /**
     * 中序遍歷
     */
    public void inOrder(BinaryTreeNode node){
        //向左子樹中序遍歷
        if (node.getLeft() != null) {
            inOrder(node.getLeft());
        }
        //列印節點
        System.out.println(node);
        //向右子樹中序遍歷
        if (node.getRight() != null) {
            inOrder(node.getRight());
        }
    }

    public void postOrder(){
        postOrder(root);
    }
    /**
     * 後序遍歷
     */
    public void postOrder(BinaryTreeNode node){
        //向左子樹中序遍歷
        if (node.getLeft() != null) {
            postOrder(node.getLeft());
        }
        //向右子樹中序遍歷
        if (node.getRight() != null) {
            postOrder(node.getRight());
        }
        //列印節點
        System.out.println(node);
    }
}

2.測試

對含有7個簡單的滿二叉樹進行遍歷的結果:

前序遍歷:
BinaryTreeNode{nodeNum=1}
BinaryTreeNode{nodeNum=2}
BinaryTreeNode{nodeNum=4}
BinaryTreeNode{nodeNum=5}
BinaryTreeNode{nodeNum=3}
BinaryTreeNode{nodeNum=6}
BinaryTreeNode{nodeNum=7}
中序遍歷:
BinaryTreeNode{nodeNum=4}
BinaryTreeNode{nodeNum=2}
BinaryTreeNode{nodeNum=5}
BinaryTreeNode{nodeNum=1}
BinaryTreeNode{nodeNum=6}
BinaryTreeNode{nodeNum=3}
BinaryTreeNode{nodeNum=7}
後序遍歷:
BinaryTreeNode{nodeNum=4}
BinaryTreeNode{nodeNum=5}
BinaryTreeNode{nodeNum=2}
BinaryTreeNode{nodeNum=6}
BinaryTreeNode{nodeNum=7}
BinaryTreeNode{nodeNum=3}
BinaryTreeNode{nodeNum=1}

三、二叉樹的查詢

大體邏輯同遍歷,這裡就不在贅述了,直接放程式碼:

/**
 * 前序查詢
 * @param num
 * @param node
 * @return
 */
public BinaryTreeNode preSearch(int num,BinaryTreeNode node){
    BinaryTreeNode result = null;

    //判斷當前節點是否為查詢節點
    if (node.getNodeNum() == num) {
        result = node;
    }
    //判斷左節點是否為空,不為空就前序查詢節點
    if (node.getLeft() != null) {
        result = preSearch(num, node.getLeft());
    }
    //如果左樹找到就返回
    if (result != null){
        return result;
    }
    //否則就判斷並遞迴前序查詢右樹
    if (node.getRight() != null) {
        result = preSearch(num, node.getRight());
    }
    return result;
}

/**
 * 中序查詢
 * @param num
 * @param node
 * @return
 */
public BinaryTreeNode inSearch(int num,BinaryTreeNode node){
    BinaryTreeNode result = null;

    //判斷左節點是否為空,不為空就中序查詢節點
    if (node.getLeft() != null) {
        result = inSearch(num, node.getLeft());
    }
    //如果左樹找到就返回
    if (result != null){
        return result;
    }
    //如果左樹未找到就判斷當前節點是不是
    if (node.getNodeNum() == num) {
        result = node;
    }
    //否則就判斷並遞迴前序查詢右樹
    if (node.getRight() != null) {
        result = inSearch(num, node.getRight());
    }
    return result;
}

/**
 * 後序查詢
 * @param num
 * @param node
 * @return
 */
public BinaryTreeNode postSearch(int num,BinaryTreeNode node){
    BinaryTreeNode result = null;

    //判斷左節點是否為空,不為空就後序查詢節點
    if (node.getLeft() != null) {
        result = postSearch(num, node.getLeft());
    }
    //如果左樹找到就返回
    if (result != null){
        return result;
    }

    //否則就判斷並遞迴後序查詢右樹
    if (node.getRight() != null) {
        result = postSearch(num, node.getRight());
    }
    //判斷右樹是否找到
    if (result != null){
        return result;
    }

    //如果右樹仍未找到就判斷當前節點是不是
    if (node.getNodeNum() == num) {
        result = node;
    }
    return result;
}

四、二叉樹的刪除

對於二叉樹的刪除,有以下邏輯:

  • 由於樹的節點和節點之間的聯絡是單向的,對於要刪除的節點,需要找到他的父節點進行刪除
  • 從根節點開始遍歷節點,判斷節點的左右子節點是否為目標節點
  • 如果是就刪除並返回
  • 否則就持續向右或左遞迴,直到找到目標節點,或者將樹遍歷完為止
/**
 * 刪除節點
 * @param num
 * @param node
 * @return
 */
public void delete(int num, BinaryTreeNode node) {
    //判斷刪除的是否為根節點
    if (root.getNodeNum() == num) {
        throw new RuntimeException("不允許刪除根節點!");
    }
    //如果子節點就是要刪除的節點
    if (node.getLeft() != null && node.getLeft().getNodeNum() == num) {
        node.setLeft(null);
        return;
    }
    if (node.getRight() != null && node.getRight().getNodeNum() == num) {
        node.setRight(null);
        return;
    }
    //否則就往左樹或右樹遍歷直到找到或遍歷完為止
    if (node.getLeft() != null) {
        delete(num, node.getLeft());
    }
    if (node.getRight() != null) {
        delete(num,node.getRight());
    }
}