【資料結構與演算法】二叉樹

gonghr發表於2021-08-18

二叉樹節點結構

class Node<V>{ 
    V value; 
	Node left; 
	Node right; 
}

二叉樹的遍歷(遞迴)

先序遍歷

順序:根左右

    public static void preOrderRecur(Node head) {
		if (head == null) {
			return;
		}
		System.out.print(head.value + " ");
		preOrderRecur(head.left);
		preOrderRecur(head.right);
	}

中序遍歷

順序:左根右

    public static void inOrderRecur(Node head) {
		if (head == null) {
			return;
		}
		inOrderRecur(head.left);
		System.out.print(head.value + " ");
		inOrderRecur(head.right);
	}

後序遍歷

順序:左右根

    public static void posOrderRecur(Node head) {
		if (head == null) {
			return;
		}
		posOrderRecur(head.left);
		posOrderRecur(head.right);
		System.out.print(head.value + " ");
	}

二叉樹的遍歷(非遞迴)

先序遍歷

順序:根左右

先把根節點壓入棧中,每次

  • 從棧中彈出一個節點cur

  • 處理節點cur

  • 先壓入cur的右節點,再壓入cur的左節點(如果有的話)

只要棧不為空,周而復始

    public static void preOrder(Node head) {
        if (head != null) {
            Stack<Node> stack = new Stack<>();
            stack.push(head);
            while (!stack.isEmpty()) {
                head = stack.pop();
                System.out.print(head.value + " ");
                if (head.right != null)
                    stack.push(head.right);
                if (head.left != null)
                    stack.push(head.left);
            }
            System.out.println();
        }

中序遍歷

順序:左根右

  • 每棵子樹整棵樹左邊界進棧

  • 依次彈出的過程中處理節點

  • 對彈出節點右樹做同樣操作

周而復始

    public static void inOrder(Node head) {
        if (head != null) {
            Stack<Node> stack = new Stack<>();
            while (!stack.isEmpty() || head != null) {  //剛開始stack為空,head不為null
                if (head != null) {
                    stack.push(head);
                    head = head.left;
                } else {
                    head = stack.pop();
                    System.out.print(head.value + " ");
                    head = head.right;
                }
            }
            System.out.println();
        }
    }

後序遍歷

順序:左右根

反過來就是根右左,準備兩個棧。

先把根節點壓入棧1中,每次

  • 從棧1中彈出一個節點cur

  • 把節點cur壓入棧2

  • 先在棧1中壓入cur的右節點,再壓入cur的左節點(如果有的話)

只要棧1不為空,周而復始

最後依次彈出棧2中的節點,其順序就是後序遍歷的順序

    public static void postOrder(Node head) {
        if (head != null) {
            Stack<Node> stack1 = new Stack<>();
            Stack<Node> stack2 = new Stack<>();
            stack1.push(head);
            while (!stack1.isEmpty()){
                head = stack1.pop();
                stack2.push(head);
                if(head.left!=null){
                    stack1.push(head.left);
                }
                if(head.right!=null){
                    stack1.push(head.right);
                }
            }
            while (!stack2.isEmpty()){
                head = stack2.pop();
                System.out.print(head.value+" ");
            }
            System.out.println();
        }
    }

二叉樹層序(寬度)遍歷

先把根節點放入佇列中,每次

  • 彈出節點cur

  • 處理節點cur

  • 先把cur的左節點放入佇列,再把cur的右節點放入佇列(如果存在的話)

周而復始,直到佇列為空

    public static void leverOrder(Node head) {
        if (head != null) {
            Queue<Node> queue = new LinkedList<>();
            queue.add(head);
            while (!queue.isEmpty()) {
                head = queue.poll();
                System.out.print(head.value + " ");
                if (head.left != null) {
                    queue.add(head.left);
                }
                if (head.right != null) {
                    queue.add(head.right);
                }
            }
            System.out.println();
        }
    }
    public static void leverOrder(Node head) {
        if (head == null) return;
        Queue<Node> queue = new LinkedList<>();
        queue.add(head);
        while (!queue.isEmpty()) {
            int n = queue.size();
            for (int i = 0; i < n; i++) {  //一次處理一層節點
                head = queue.poll();
                System.out.printf(head.value + " ");
                if (head.left != null) queue.add(head.left);
                if (head.right != null) queue.add(head.right);
            }
        }
        System.out.println();
    }

二叉樹應用題

二叉樹深度

遞迴

class Solution {
    public int maxDepth(TreeNode root) {
        if(root==null) return 0;
        return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
    }
}

層序遍歷bfs

    public int maxDepth(TreeNode root) {
        if(root==null) return 0;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        int ans = 0;
        while(!queue.isEmpty()){
            ans++;       //每次處理一層,處理完ans加一
            int n = queue.size();
            for(int i = 0;i < n; i++){
                root = queue.poll();
                if(root.left!=null)  queue.add(root.left);
                if(root.right!=null) queue.add(root.right);
            }
        }
        return ans;
    }

求二叉樹最大寬度

層序遍歷的過程中使用HashMap記錄節點層數

    public static void maxWidth(Node head) {
        if (head != null) {
            Queue<Node> queue = new LinkedList<>();
            queue.add(head);
            HashMap<Node, Integer> levelMap = new HashMap<>();
            levelMap.put(head, 1);
            int curLevel = 1;    //當前層數
            int curLevelNodes = 0;    //當前層數的節點數
            int max = Integer.MIN_VALUE;
            while (!queue.isEmpty()) {
                Node cur = queue.poll();
                int curNodeLevel = levelMap.get(cur);  //獲取當前節點層數
                if (curNodeLevel == curLevel) {   //當前節點層數等於當前層數,節點數加一
                    curLevelNodes++;
                } else {
                    max = Math.max(max, curLevelNodes);  //否則max取較大值
                    curLevel++;                   //當前層數加一
                    curLevelNodes = 1;            //重置當前層數的節點數為1
                }
                if (cur.left != null) {
                    levelMap.put(cur.left, curNodeLevel + 1);
                    queue.add(cur.left);
                }
                if (cur.right != null) {
                    levelMap.put(cur.right, curNodeLevel + 1);
                    queue.add(cur.right);
                }
            }
            System.out.println(max);
        }
    }

判斷是否為搜尋二叉樹

image

中序遍歷遞增就是搜尋二叉樹

遞迴方式

    public static int preValue = Integer.MIN_VALUE;
    public static boolean checkBST(Node head){
        if(head!=null){
            boolean isLeftBst = checkBST(head.left);
            if(!isLeftBst){
                return false;
            }
            if(head.value<=preValue){
                return false;
            }else{
                preValue = head.value;
            }
            return checkBST(head.right);
        }
        return true;
    }

非遞迴方式

    public static boolean checkBST(Node head) {
        if (head != null) {
            Stack<Node> stack = new Stack<>();
            while (!stack.isEmpty() || head != null) {  //剛開始stack為空,head不為null
                if (head != null) {
                    stack.push(head);
                    head = head.left;
                } else {
                    head = stack.pop();
                    if(head.value<=preValue){
                        return false;
                    }else {
                        preValue = head.value;
                    }
                    head = head.right;
                }
            }
        }
        return true;
    }

樹形DP處理

  • 左子樹是搜尋二叉樹

  • 右子樹是搜尋二叉樹

  • 根節點大於左子樹最大值

  • 根節點小於右子樹最小值

    public static boolean isBST(Node head){
        return process(head).isBST;
    }
    public static class ReturnType {
        public boolean isBST;
        public int max;
        public int min;

        public ReturnType(boolean isBST, int max, int min) {
            this.isBST = isBST;
            this.max = max;
            this.min = min;
        }
    }
    public static ReturnType process(Node x){
        if(x==null) {
            return null;
        }
        ReturnType leftData = process(x.left);  //獲取左子樹處理資訊
        ReturnType rightData = process(x.right);  //獲取右子樹處理資訊
        int min = x.value;
        int max = x.value;
        if(leftData!=null){                   //獲取當前樹的最大值最小值
            min = Math.min(min,leftData.min);
            max = Math.max(max,leftData.max);
        }
        if(rightData!=null){
            min = Math.min(min,rightData.min);
            max = Math.max(max,rightData.max);
        }
        boolean isBST = true;
        //左子樹存在並且(左子樹不是BST或者左子樹最大值大於x)
        if(leftData!=null&&(!leftData.isBST||leftData.max>=x.value)){
            isBST = false;
        }
        //右子樹存在並且(右子樹不是BST或者右子樹最小值小於x)
        if(rightData!=null&&(!rightData.isBST||x.value>=rightData.min)){
            isBST = false;
        }
        return new ReturnType(isBST,max,min);
    }

判斷是否是完全二叉樹

image

  • 層序遍歷

  • 任何一個節點有右孩子沒左孩子,則不是完全二叉樹(1)

  • 在(1)的前提下,遇到第一個左右不雙全節點,那其後面必須都是葉子節點,否則不是二叉樹

    public static boolean checkCBT(Node head) {
        if (head != null) {
            boolean leaf = false;  //是否遇到過左右不雙全節點
            Node l = null;
            Node r = null;
            Queue<Node> queue = new LinkedList<>();
            queue.add(head);
            while (!queue.isEmpty()) {
                head = queue.poll();
                l = head.left;
                r = head.right;
                if ((leaf && !(l == null && r == null))  //遇到第一個左右不雙全節點,那麼以後的節點都必須是葉子節點
                        ||
                        (l == null && r != null)) {  //任何一個節點有右孩子沒左孩子
                    return false;
                }
                if (l != null) {
                    queue.add(l);
                }
                if (r != null) {
                    queue.add(r);
                }
                if (l == null || r == null) {
                    leaf = true;
                }
            }
        }
        return true;
    }

判斷是否是平衡二叉樹

  • 左子樹是平衡的

  • 右子樹是平衡的

  • 左右子樹高度差的絕對值小於2

獲取左樹資訊和右樹資訊後,結合處理。屬於樹形DP

    public static boolean isBalancd(Node head) {
        return process(head).isBalanced;
    }

    public static class ReturnType {  //封裝了平衡狀態和高度
        public boolean isBalanced;
        public int height;

        public ReturnType(boolean isB, int hei) {
            isBalanced = isB;
            height = hei;
        }
    }

    public static ReturnType process(Node x) {
        if (x == null) {           //空樹是平衡的,高度為0
            return new ReturnType(true, 0);
        }
        ReturnType leftData = process(x.left);   //左樹
        ReturnType rightData = process(x.right); //右樹
        int height = Math.max(leftData.height, rightData.height) + 1;  //獲取左子樹和右子樹的最高高度+1
        boolean isBalanced = leftData.isBalanced && rightData.isBalanced &&  //如果左子樹平衡,右子樹平衡
                Math.abs(leftData.height - rightData.height) < 2;     //左右子樹的高度差的絕對值小於2
        return new ReturnType(isBalanced, height);    //返回新狀態
    }

判斷是否是滿二叉樹

如果一個二叉樹的層數為K,且結點總數是 (2^k) -1 ,則它就是滿二叉樹。

樹形DP問題

    public static boolean isFull(Node head) {
        if (head == null) {
            return true;
        }
        Info data = process(head);
        return data.nodes == ((1 << data.height) - 1);//是否層數為K,且結點總數是 (2^k) -1
    }

    public static class Info {  //封裝樹的高度和節點數
        public int height;
        public int nodes;

        public Info(int h, int n) {
            height = h;
            nodes = n;
        }
    }

    public static Info process(Node x) {
        if (x == null) {
            return new Info(0, 0);
        }
        Info leftData = process(x.left);    //獲取左子樹資訊
        Info rightData = process(x.right);  //獲取右子樹資訊
        int height = Math.max(leftData.height, rightData.height) + 1; //求新高度
        int nodes = leftData.nodes + rightData.nodes + 1;  //求總的節點數

        return new Info(height, nodes);
    }

在二叉樹中找到一個節點的後繼節點

現在有一種新的二叉樹節點型別如下:

public class Node { 
    public int value; 
	public Node left; 
	public Node right; 
	public Node parent; 
	public Node(int val) { 
	    value = val; 
	} 
}

該結構比普通二叉樹節點結構多了一個指向父節點的parent指標。
假設有一棵Node型別的節點組成的二叉樹,樹中每個節點的parent指標都正確地指向自己的父節點,頭節 點的parent指向null。 只給一個在二叉樹中的某個節點node,請實現返回node的後繼節點的函式。

在二叉樹的中序遍歷的序列中, node的下一個節點叫作node的後繼節點。

  • 一個節點有右子樹,那麼它的下一個節點就是它的右子樹中的最左子節點。例如b的後繼節點是h。

  • 一個節點沒有右子樹時分兩種情況:

    • 當前節點是它父節點的左子節點,那麼它的下一個節點就是它的父節點。 例如節點f的後繼節點是c,節點d的後繼節點是b。
      image
    • 當前節點是它父節點的右子節點,此時沿著指向父節點的指標一直向上遍歷,直到找到一個是它父節點的左子節點的節點,如果這個節點存在,那麼這個節點的父節點就是我們要找的下一個節點。如下圖所示: f的下一個節點是a。
      image
    public static class Node {
		public int value;
		public Node left;
		public Node right;
		public Node parent;

		public Node(int data) {
			this.value = data;
		}
	}

	public static Node getSuccessorNode(Node node) {
		if (node == null) {
			return node;
		}
		if (node.right != null) {
			return getLeftMost(node.right);
		} else {
			Node parent = node.parent;
			while (parent != null && parent.left != node) {
				node = parent;
				parent = node.parent;
			}
			return parent;
		}
	}

	public static Node getLeftMost(Node node) {
		if (node == null) {
			return node;
		}
		while (node.left != null) {
			node = node.left;
		}
		return node;
	}

摺紙問題

請把一段紙條豎著放在桌子上,然後從紙條的下邊向上方對摺1次,壓出摺痕後 展開。 此時摺痕是凹下去的,即摺痕突起的方向指向紙條的背面。 如果從紙條的下邊向上方連續對摺2次,壓出摺痕後展開,此時有三條摺痕,從 上到下依次是下摺痕、下摺痕和上摺痕。 給定一個輸入引數N,代表紙條都從下邊向上方連續對摺N次。 請從上到下列印所有摺痕的方向。
例如:
N=1時,列印: down
N=2時,列印: down down up

分析:
image

發現第n+1次的摺痕中凹摺痕一定在第n次摺痕的左邊,第n+1次摺痕中凸摺痕一定在第n次摺痕的右邊

形成一棵二叉樹

image

中序遍歷該二叉樹就可得到答案

    public static void printAllFolds(int N) {
		printProcess(1, N, true);
	}

	public static void printProcess(int i, int N, boolean down) {
		if (i > N) {
			return;
		}
		printProcess(i + 1, N, true);
		System.out.println(down ? "down " : "up ");
		printProcess(i + 1, N, false);
	}

	public static void main(String[] args) {
		int N = 1;
		printAllFolds(N);
	}

相關文章