二叉樹(Java實現)


一、常見用語

1、邏輯結構:描述數據之間邏輯上的相關關系。分為線性結構(如,字符串),和非線性結構(如,樹,圖)。

2、物理結構:描述數據的存儲結構,分為順序結構(如,數組)和鏈式結構。

3、結點的度:一個節點子樹(即分支)個數。

4、葉結點:也稱為終端節點,指度為0的節點。

5、樹的深度(高度):樹中結點層次的最大值。

6、有序樹:子樹有先后次序之分(我的理解就是左右節點次序不可以顛倒)。

7、同構:將節點適當重命名,即可得兩顆完全相同的樹

8、孩子節點:一個節點的直接后繼節點。

9、雙親節點:一個節點的直接前驅節點。

二、二叉樹中的概念

1、二叉樹:滿足以下兩個條件的樹稱為二叉樹

①節點的度不可以超過2

②節點的孩子節點次序不可顛倒

2、滿二叉樹:每層得節點數都是滿的,即2i-1

3、完全二叉樹:節點1~n分別對應於滿二叉樹的節點1~n

4、完全二叉樹的性質:

 (1)若節點序號為i(i>1),則其雙親節點序號為i/2。(這里是整除)

 (2)若節點序號為i(i>=1),則其左子節點序號為2i。

 (3)若節點序號為i (i>=1),則其右子節點序號為2i+1。

三、二叉樹的操作

 1、二叉樹節點的存儲結構

public class TreeNode {
    String data;
    TreeNode LChild;
    TreeNode RChild;
    TreeNode(String data) {
        this.data = data;
    }
    public String toString() {
        return data;
    }
}

 2、創建二叉樹

使用前序遍歷創建二叉樹是比較合適,按照邏輯,總要先創建根節點在創建左右子樹,總不能還沒有創建根節點就把root.LChild傳遞出去吧。

private static String[] tree = {"A","B",".",".","C","D",".",".","."};
private static int i = 0;
//先序創建二叉樹是比較合適的
TreeNode inOrderCreateBTree() {
    TreeNode bt = null;
    String s = tree[i++];
    if(s == ".") {
        return bt;
    }else {
        bt = new TreeNode(s);
        bt.LChild = inOrderCreateBTree();
        bt.RChild = inOrderCreateBTree();
        return bt;
    }
}

 可以用非遞歸的方式建樹,但是難度還是挺大的。

3、先序遍歷

遞歸方式:

//遞歸法前序遍歷二叉樹
void preOrderPrintBTree(TreeNode bt) {
    if(bt == null) {
        System.out.print("." + " ");
    }else {
        System.out.print(bt + " ");
        preOrderPrintBTree(bt.LChild);
        preOrderPrintBTree(bt.RChild);
    }
}

 基於棧的非遞歸方式:

跟着思路寫就好:事實上這就是一個代碼的模板,三種遍歷的代碼在結構上都是差不多的。

①指針移到最左子孫節點,邊移變打印,邊入棧(入棧是為了保存雙親節點,以便訪問右子樹)。

while(p != null) {
    System.out.print(p + " ");
    stack.push(p);
    p = p.LChild;
    if(p == null) {
        System.out.print("." + " ");
    }
}

 ②棧不空,就出棧,p指針指向右子樹。

if(!stack.isEmpty()) {
    p = stack.pop();
    p = p.RChild;
    if(p == null) {
        System.out.print("." + " ");
    }
}

 完整代碼:

//基於棧的非遞歸法先序遍歷二叉樹
void preOrderPrintBTree1(TreeNode bt) {
    if(bt == null) {
        System.out.println("null tree");
    }
    Stack<TreeNode> stack = new Stack<TreeNode>();
    TreeNode p = bt;
    while(!stack.isEmpty() || p != null) {
        while(p != null) {
            System.out.print(p + " ");
            stack.push(p);
            p = p.LChild;
            if(p == null) {
                System.out.print("." + " ");
            }
        }
        if(!stack.isEmpty()) {
            p = stack.pop();
            p = p.RChild;
            if(p == null) {
                System.out.print("." + " ");
            }
        }
    }
}

 4、中序遍歷

 遞歸法:

void inOrderPrint(TreeNode bt) {
    if(bt == null) {
        System.out.print("." + " ");
    } else {
        inOrderPrint(bt.LChild);
        System.out.print(bt + " ");
        inOrderPrint(bt.RChild);
    }
}

 非遞歸法:

前序遍歷和中序遍歷是查不多的,前者先打印后入棧,后者是先入棧后打印。

①p指針移到最左端,邊移變入棧。

while(p != null) {
    stack.push(p);
    p = p.LChild;
}

 ②邊出棧邊打印,p指針指向右子樹

if(!stack.isEmpty()) {
    p = stack.pop();
    System.out.print(p + " ");
}

 完整代碼:

void inOrderPrint1(TreeNode bt) {
    if(bt == null) {
        System.out.println(".");
    }else {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode p = bt;
        while(!stack.isEmpty() || p != null) {
            while(p != null) {
                stack.push(p);
                p = p.LChild;
            }
            if(!stack.isEmpty()) {
                p = stack.pop();
                System.out.print(p + " ");
            }
        }
    }
}

 5、后續遍歷

遞歸:

void postOrderPrint(TreeNode bt) {
    if(bt == null) {
        System.out.print("." + " ");
    }else {
        postOrderPrint(bt.LChild);
        postOrderPrint(bt.RChild);
        System.out.print(bt + " ");
    }
}

非遞歸:后續遍歷的難點就在於要知道前一次訪問的節點是左孩子還是右孩子,所以要設置一個前置指針pre。

 ①pcur指針移到最左端,邊移邊入棧

 ②pcur的有孩子被為null或者被訪問過,則訪問pcur,否則pcur要繼續入棧,pcur指向其右孩子。

完整代碼:

void postOrderPrint1(TreeNode bt) {
    if(bt == null) {
        System.out.print("." + " ");
    }else {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode pcur = bt;
        TreeNode pre = null;
        while(pcur != null) {
            stack.push(pcur);
            pcur = pcur.LChild;
        }
        while(!stack.isEmpty()) {
            pcur = stack.pop();
            
            if(pcur.RChild == null || pcur.RChild == pre) {
                System.out.print(pcur + " ");
                pre = pcur;
            }else {
                stack.push(pcur);
                pcur = pcur.RChild;
                while(pcur != null) {
                    stack.push(pcur);
                    pcur = pcur.LChild;
                }
            }
        }
    }
}

 6、二叉樹的其他操作

(1)打印葉子節點

//前序遍歷打印葉子節點
public static void preprintLeaves(TreeNode bt) {
    if(bt == null) {
        
    }else {
        if(bt.LChild == null && bt.RChild == null) {
            System.out.print(bt + " ");
        }
        preprintLeaves(bt.LChild);
        preprintLeaves(bt.RChild);
    }
}

 (2)求樹的深度

//先序遍歷求二叉樹的深度
public static int preTreeDepth(TreeNode bt, int h) {
    if(bt != null) {
        if(h > depth) depth = h;
        preTreeDepth(bt.LChild,h+1);
        preTreeDepth(bt.RChild,h+1);
    }
    return depth;
}
//后續遍歷求二叉樹深度
public static int postTreeDepth(TreeNode bt) {
    int hl = 0;
    int hr = 0;
    int max = 0;
    if(bt == null) {
        return 0;
    }else {
        hl = postTreeDepth(bt.LChild);
        hr = postTreeDepth(bt.RChild);
        max = Math.max(hr, hl);
        return max + 1;
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM