1. 程式人生 > >【java 資料結構】還不會二叉樹?一篇搞定二叉樹

【java 資料結構】還不會二叉樹?一篇搞定二叉樹

二叉樹是我們常見的資料結構之一,在學習二叉樹之前我們需要知道什麼是樹,什麼是二叉樹,本篇主要講述了二叉樹,以及二叉樹的遍歷。

你能get到的知識點?

1、樹的介紹
2、二叉樹的介紹
3、二叉樹遍歷的四種方法
4、牛客題目:反轉二叉樹

目錄

  • 你能get到的知識點?
  • 一、知識預備
    • 1、樹
    • 2、樹的相關術語介紹
    • 1、二叉樹
    • 2、二叉樹型別
  • 二、二叉樹實操(我沒有說髒話)
    • 1、定義二叉樹的結點
    • 2、遍歷二叉樹(四種方法)
  • 三、小試牛刀
    • leetcode題目:反轉二叉樹

一、知識預備

1、樹

樹(Tree)是n(n>=0)個結點的有限集。

資料結構中的樹可以看作一個倒立的樹,他的‘根’在上面,他的'葉子'在下面。

graph TD 4-->2 4-->7 2-->1 2-->3 7-->6 7-->9

2、樹的相關術語介紹

  • 1、樹的結點(node):包含一個數據元素及若干指向子樹的分支;
  • 2、孩子結點(child node):結點的子樹的根稱為該結點的孩子,對於結點4來說,結點2和結點7就是結點4的孩子結點;
  • 3、雙親結點:B 結點是A 結點的孩子,則A結點是B 結點的雙親;
  • 4、兄弟結點:同一雙親的孩子結點; 堂兄結點:同一層上結點;
  • 5、祖先結點: 從根到該結點的所經分支上的所有結點
  • 6、子孫結點:以某結點為根的子樹中任一結點都稱為該結點的子孫
  • 7、結點層:根結點的層定義為1;根的孩子為第二層結點,依此類推;
  • 8、樹的深度:樹中最大的結點層,該樹的深度為三,因為他只有三層。
  • 9、結點的度:結點子樹的個數
  • 10、樹的度: 樹中最大的結點度。
  • 11、葉子結點:也叫終端結點,是度為 0 的結點,例如結點1、2、6、9,都是葉子結點;
  • 12、分枝結點:度不為0的結點;
  • 13、有序樹:子樹有序的樹,如:家族樹;
  • 14、無序樹:不考慮子樹的順序;

1、二叉樹

二叉樹(Binary Tree)是每個結點最多有兩個子樹的樹結構。所以二叉樹也是一種特殊的樹。

通常我們將二叉樹的子樹被稱作“左子樹”(left subtree)和“右子樹”(right subtree)。

二叉樹常被用於實現二叉查詢樹和二叉堆。

由此可以看出,一棵二叉樹,他的每個節點最多隻有兩個結點,也就是結點的度小於等於二,即取0、1、2。

2、二叉樹型別

  1. 滿二叉樹:除了葉結點外每一個結點都有左右子葉且葉子結點都處在最底層的二叉樹。

  2. 完全二叉樹:若設二叉樹的高度為h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第h層有葉子結點,並且葉子結點都是從左到右依次排布,這就是完全二叉樹。
    簡單來說:如果二叉樹中除去最後一層節點為滿二叉樹,且最後一層的結點依次從左到右分佈,則此二叉樹被稱為完全二叉樹。

  3. 平衡二叉樹:平衡二叉樹又被稱為AVL樹(區別於AVL演算法),它是一棵二叉排序樹,且具有以下性質:它是一棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,並且左右兩個子樹都是一棵平衡二叉樹。

二、二叉樹實操(我沒有說髒話)

1、定義二叉樹的結點

定義二叉樹每一個節點的結構,他擁有左右子葉,並且本身擁有一個值val,定義一個建構函式,多個結點組合在一起就是一個二叉樹。

    /**
     * Definition for binary tree
     */
    public static class TreeNode {
      //定義該結點值
      int val;
      //定義左結點
      TreeNode left;
      //定義右結點
      TreeNode right;

	  //定義一個建構函式
      TreeNode(int x) { val = x; }
    }

例圖:以下將以該例圖進行解說

graph TD 4-->2 4-->7 2-->1 2-->3 7-->6 7-->9

2、遍歷二叉樹(四種方法)

遍歷二叉樹主要有四種方法:①:前序遍歷 ②:中序遍歷 ③:後序遍歷 ④:層序遍歷

需要事先說明的就是前三種遍歷,就是根節點的訪問順序不同,但是訪問左右節點的順序仍然是先訪問左結點,再訪問右結點。

①:前序遍歷

1、訪問根節點;
2、訪問當前節點的左子樹;
3、訪問當前節點的右子樹;
就是先從根節點出發,先訪問根節點,然後訪問根結點的左子樹,若該左子樹的根節點上存在左子樹,則訪問其左子樹,否則,訪問其右子樹,依次類推。

以上圖為例,

  1. 先找到根節點,讀取4,
  2. 該結點還有左子樹,訪問其左子樹的根節點,讀取2,
  3. 結點2,還有左子樹,讀取1,
  4. 結點1沒有左子樹也沒有右子樹,返回上一層,訪問結點2的右子樹,讀取3,
  5. 這時候應該訪問3的左右子樹,但是沒有,返回上一層,此時結點2的左右子樹都已經讀取完,返回上一層,讀取結點4的右子樹,讀取7,
  6. 訪問結點7的左子樹,讀取6,
  7. 結點6沒有左右子樹,返回上一層,訪問結點7的右子樹,讀取9,
  8. 結點9沒有左右子樹,這時候該二叉樹已經遍歷完成。

所以訪問到的順序為:4 2 1 3 7 6 9

②:中序遍歷

1、訪問當前節點的左子樹;
2、訪問根節點;
3、訪問當前節點的右子樹;
遍歷思想與前序差不多,只不過將讀取根節點放在讀取左結點之後、右結點之前

③:後序遍歷

1、訪問當前節點的左子樹;
2、訪問當前節點的右子樹;
3、訪問根節點;
遍歷思想與前序差不多,只不過將讀取根節點放在讀取左結點之後、右結點之後

④:層序遍歷

按照二叉樹的層級結構從左至右依次遍歷結點
演算法思路:定義一個佇列,從樹的根結點開始,依次將其左孩子和右孩子入隊。而後每次佇列中一個結點出隊,都將其左孩子和右孩子入隊,直到樹中所有結點都出隊,出隊結點的先後順序就是層次遍歷的最終結果。

  1. 根節點4入隊,
  2. 根節點4出隊,訪問結點4的左右結點(2,7),依次入隊,
  3. 結點2出隊,訪問結點2的左右結點(1,3),依次入隊,
  4. 結點1出隊,無子結點,無需入隊,
  5. 結點3出隊,無子結點,無需入隊,
  6. 結點6出隊,無子結點,無需入隊,
  7. 結點9出隊,無子結點,無需入隊,
  8. 佇列為空,遍歷完成。

最後訪問順序為:4 2 7 1 3 6 9

程式碼實現:

    /**
     * 先序遍歷(遞迴)
     * @param node
     */
    public void previous(TreeNode node) {
        if (node == null) {
            return;
        }
        System.out.print(node.val+"\t");
        this.previous(node.left);
        this.previous(node.right);
    }
    /**
     * 中序遍歷(遞迴)
     * @param node
     */
    public void middle(TreeNode node) {
        if (node == null) {
            return;
        }
        this.middle(node.left);
        System.out.print(node.val+"\t");
        this.middle(node.right);
    }
    /**
     * 後序遍歷(遞迴)
     * @param node
     */
    public void next(TreeNode node) {
        if (node == null) {
            return;
        }
        this.next(node.left);
        this.next(node.right);
        System.out.print(node.val+"\t");
    }
	    
	 /**
     * 遍歷二叉樹
     * 層序遍歷(非遞迴)
     * @param node
     */
    public void bfs(TreeNode node){
        if (node == null) {
            return;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(node);
        while (!queue.isEmpty()){
            TreeNode current = queue.poll();
            System.out.print(current.val + "\t");
            //如果當前節點的左節點不為空入隊
            if(current.left != null)
            {
                queue.offer(current.left);
            }
            //如果當前節點的右節點不為空,把右節點入隊
            if(current.right != null)
            {
                queue.offer(current.right);
            }
        }
    }
遍歷結果:
1、前序遍歷:4	2	1	3	7	6	9	
2、中序遍歷:1	2	3	4	6	7	9	
3、後序遍歷:1	3	2	6	9	7	4	
4、層序遍歷:4	2	7	1	3	6	9	

在這裡附上前三種方法的非遞迴方法,感興趣的小夥伴可以研究研究。
附:非遞迴方法
主要實現是依靠棧來實現

    /**
     * 先序遍歷非遞迴
     * @param node
     */
    public void previous1(TreeNode node) {
        if (node == null) {
            return;
        }
        Stack<TreeNode> queue = new Stack<>();
        queue.add(node);
        while (!queue.isEmpty()) {
            TreeNode current = queue.pop();
            while(current!=null) {
                System.out.print(current.val + "\t");
                if (current.right!=null){
                    queue.push(current.right);
                }
                current = current.left;
            }
        }
    } 


	/**
     * 中序遍歷(非遞迴)
     * @param node
     */
    public void middle1(TreeNode node) {
        Stack<TreeNode> stack = new Stack<>();
        while (!stack.isEmpty() || node !=null) {
            while (node != null){
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            System.out.print(node.val + "\t");
            node = node.right;
        }
    }

	/**
     * 後序遍歷(非遞迴)
     * @param node
     */
    public void next1(TreeNode node) {
        Stack<TreeNode> stack = new Stack<>();
        Stack<Integer> stack1 = new Stack<>();
        while (!stack.isEmpty() || node !=null) {
            while (node != null){
                stack.push(node);
                stack1.push(0);
                node = node.left;
            }
            while (!stack.isEmpty() && stack1.peek() == 1) {
                stack1.pop();
                System.out.print(stack.pop().val + "\t");
            }
            if (!stack.isEmpty()) {
                stack1.pop();
                stack1.push(1);
                node = stack.peek();
                node = node.right;
            }
        }
    }

三、小試牛刀

leetcode題目:反轉二叉樹

原來的二叉樹:

graph TD 4-->2 4-->7 2-->1 2-->3 7-->6 7-->9

經過演算法,需要轉換為:

graph TD 4-->7 4-->2 2-->3 2-->1 7-->9 7-->6

解法:
二叉樹的遍歷有四種方法,那麼,該題解法也至少有四種,如果讀懂了上面的遍歷演算法,那麼這道題簡直輕而易舉。

主要思路:就是遍歷某一結點時,也就是在原來輸出該節點的操作換成將其結點的左右結點交換位置。

/**
     * 反轉二叉樹
     * 前序反轉
     * @param node
     */
    public void invertTree_previous(TreeNode node){
        if (node == null){
            return;
        }
        TreeNode node1 = node.left;
        node.left = node.right;
        node.right = node1;
        this.invertTree_previous(node.left);
        this.invertTree_previous(node.right);
    }

    /**
     * 反轉二叉樹
     * 中序反轉
     * @param node
     */
    public void invertTree_middle(TreeNode node){
        if (node == null){
            return;
        }
        this.invertTree_middle(node.left);
        TreeNode node1 = node.left;
        node.left = node.right;
        node.right = node1;
        this.invertTree_middle(node.left);
    }

    /**
     * 反轉二叉樹
     * 後序序反轉
     * @param node
     */
    public void invertTree_next(TreeNode node){
        if (node == null){
            return;
        }
        this.invertTree_next(node.left);
        this.invertTree_next(node.right);
        TreeNode node1 = node.left;
        node.left = node.right;
        node.right = node1;
    }


    /**
     * 反轉二叉樹
     * 層序反轉
     * @param node
     */
    public void invertTree_bfs(TreeNode node){
        if (node == null) {
            return;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(node);
        while (!queue.isEmpty()){
            TreeNode current = queue.poll();
            TreeNode node1 = current.left;
            current.left = current.right;
            current.right = node1;
            //如果當前節點的左節點不為空入隊
            if(current.left != null)
            {
                queue.offer(current.left);
            }
            //如果當前節點的右節點不為空,把右節點入隊
            if(current.right != null)
            {
                queue.offer(current.right);
            }
        }
    }

答案:

1、轉換前
前序遍歷:4	2	1	3	7	6	9	
中序遍歷:1	2	3	4	6	7	9	
後序遍歷:1	3	2	6	9	7	4	
層次遍歷:4	2	7	1	3	6	9	

2、轉換後
前序遍歷:4	7	9	6	2	3	1	
中序遍歷:9	7	6	4	3	2	1	
後序遍歷:9	6	7	3	1	2	4	
層次遍歷:4	7	2	9	6	3	1	

原始碼獲取:關注公眾號:博奧思園,回覆:資料結構二叉樹

你的支援是我前進的最大動力

參考:
1、 Java資料結構和演算法(十)——二叉樹
2、二叉樹的四種遍歷演算法
3、Java實現二叉樹的前序、中序、後序、層序遍歷(非遞迴方法)