1. 程式人生 > >自學演算法之判斷一個二叉樹是否平衡/搜尋/完全二叉樹

自學演算法之判斷一個二叉樹是否平衡/搜尋/完全二叉樹

話不多說,在面試中遇到過,一臉矇蔽,被虐出翔…以下所述,僅僅是手撕程式碼時候使用,若是需要線上程式設計,可以根據該思路編寫對應AC程式碼。

  • 如何判斷一個二叉樹是否平衡?要解決這個問題,首先要知道什麼是平衡二叉樹。

    1. 平衡二叉樹定義如下:

      • 首先,是一個二叉樹,且每個節點的左右子樹的高度差的絕對值不超過1。
      • 其次,空樹是平衡的。
    2. 試想要實現該程式碼,需要怎麼記錄其中的高度差、當前節點是否平衡、以及怎麼統計每個節點的左右子樹的高度,話不多說,解決思路如下:

      • 高度差和左右子樹高度,其實我們只需要記錄一個,除此之外,還需記錄當前節點是否平衡,那麼總共有兩個變數,資料結構如下:

        	static
        class returnDate { // 是否平衡 public boolean isBlance; // 樹高度 public int h; public returnDate(boolean isBlance, int h) { this.isBlance = isBlance; this.h = h; } }
      • 確定返回資料結構後,我們可以使用深度遍歷,判斷每個節點是否平衡。

        1. 使用前序遍歷,遍歷二叉樹所有節點。
        	public static returnDate process(TreeNode head) {
        		// 空=平衡
        		if(head ==
        null) { return new returnDate(true, 0); } returnDate leftDate = process(head.left); // 判斷左子樹是否平衡 if(!leftDate.isBlance) { return new returnDate(false, 0); } returnDate rightDate = process(head.right); // 判斷右子樹是否平衡 if(!rightDate.isBlance) { return new returnDate(false, 0); } // 高度差===判斷平衡
        if(Math.abs(leftDate.h - rightDate.h) > 1) { return new returnDate(false, 0); } // 當前結點平衡,返回高度 // 當前左右子樹最高加上當前節點 return new returnDate(true, Math.max(leftDate.h, rightDate.h) + 1); }
  • 平衡二叉樹已經解決,那麼我再看怎麼判斷一個二叉樹是BST樹

    1. BST,即binary search tree二叉搜尋樹,其性質如下:
      • 若任意結點的左子樹不空,則左子樹上的所有節點均小於其根節點。
      • 若任意結點的右子樹不空,則右子樹上的所有節點均大於其根節點。
      • 任意節點的左、右子樹皆為BST樹。
      • 沒有值相等的數,即數值不重複。
    2. 其實,有很多種方法解決該問題,筆者只提供其中一種。
      • 解決思路:如果某二叉樹的中序遍歷是升序的,則該樹是BST樹。
      • 中序遍歷有遞迴和非遞迴兩種實現方法,在此筆者選擇非遞迴實現。
    	public static boolean isBST(TreeNode head) {
    		if(head == null) return true;
    		Stack<TreeNode> stack = new Stack<>();
    		TreeNode cur = head;
    		// 記錄狀態
    		boolean is_bst = true;
    		// 前一個節點
    		int pre = Integer.MIN_VALUE;
    		while(!stack.isEmpty() || cur != null) {
    			if(cur != null) {
    				stack.push(cur);
    				cur = cur.left;
    			} else {
    				cur = stack.pop();
    				// 迭代
    				if(is_bst) {
    					// 初始化前一個節點
    					pre = cur.val;
    					is_bst = false;
    				} else if(pre > cur.val) { 
    					return false;
    				} else {
    					// 記錄前一個節點
    					pre = cur.val;
    				}
    				cur = cur.right;
    			}
    		}
    		return true;
    	}
    
  • 最後一個判斷平衡二叉樹邏輯比較複雜,萬事開頭難,先從簡單走起。

    1. 完全二叉樹的定義:

      • 對於深度為K的,N個節點 二叉樹,當且僅當其每一個節點都與深度為K的滿二叉樹中編號從1到N的結點。

      • 其實,看了其定義,筆者也看不懂。

      • 簡單點,要畫一個完全二叉樹,必須先畫根節點,再畫左結點,最後畫右結點,畫的時候順序必須嚴格一致。

      在這裡插入圖片描述

    2. 解決思路:

      • 先確定遍歷方式:筆者使用層序遍歷
      • 明確違反完全二叉樹條件:
        1. 如果一個節點,有右子結點,沒有左子結點,則一定不是完全二叉樹。
        2. 如果一個節點左右子節點不全,即有左沒右或兩個都沒有,若出現該狀態,則接下來的節點都必須是葉子節點。
      	/**
      	 * 判斷一個樹是否是完全二叉樹
      	 * 思路, 按層遍歷
      	 * Q1,如果一個節點有右結點,而沒有左結點,一定不是。
      	 * Q2,如果一個節點左右不全, 即有左沒右,或左右都沒有, 該節點之後出現的結點,必須是葉子結點。
      	 * @author [email protected]
      	 *
      	 */
      	public static boolean isCBT(TreeNode head) {
      		if(head == null) return true;
      		Queue<TreeNode> queue = new LinkedList<>();
      		queue.offer(head);
      		// 左右孩子
      		TreeNode left = null;
      		TreeNode right = null;
      		// 開啟狀Q2
      		boolean flag = false;
      		while(!queue.isEmpty()) {
      			TreeNode current = queue.poll();
      			left = current.left;
      			right = current.right;
      			// 開啟狀態後, 節點不會存在左右子節點
      			if(flag && (left != null || right != null) ||
      					(left == null && right != null)) {
      				return false;
      			}
      			// 層序遍歷, 入隊邏輯
      			if(left != null) {
      				queue.offer(left);
      			}
      			if(right != null) {
      				queue.offer(right);
      			}
      			// 遇到Q2, 開啟狀態, 即以後的每一個節點都是葉子節點
      			if(left == null || right == null) {
      				flag = true;
      			}
      		}
      		return true;
      	}
      
  • 最後,只要記住每種二叉樹的性質,程式碼很好寫,僅是效率的差別而已。