二叉樹的相關的各種題目

  二叉樹是每個結點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”(left subtree)和“右子樹”。一棵深度爲k,且有(2^k)-1個結點的二叉樹,稱爲滿二叉樹。最少結點爲k,爲斜二叉樹。

  瞭解了什麼是二叉樹後,自然要懂得它的三種遍歷方法。前序(先序):中 -> 左 -> 右、中序:左 -> 中 -> 右、後序:左 -> 右 -> 中。(中是父節點,左是左節點,右是右節點)

遞歸版的三種遍歷方式

先序

public static void PreOrder(TreeNode root) {
	if(root == null) return;  //判斷根結點是不是爲空
	System.out.println(root.val);   //直接輸出根結點
	PreOrder(root.left);     //循環遍歷左節點
	PreOrder(root.right);   //循環遍歷右節點
}

中序

public static void InOrder(TreeNode root) {
	if(root == null) return;  //判斷根結點是不是爲空
	InOrder(root.left);     //循環遍歷左節點
	System.out.println(root.val);   //直接輸出根結點
	InOrder(root.right);   //循環遍歷右節點
}

後序

public static void PostOrder(TreeNode root) {
	if(root == null) return;  //判斷根結點是不是爲空
	PostOrder(root.left);     //循環遍歷左節點
	PostOrder(root.right);   //循環遍歷右節點
	System.out.println(root.val);   //直接輸出根結點
}

非遞歸版的三種遍歷方式

先序

public static void PreOrder(TreeNode root) {
	Stack <TreeNode> stack = new Stack <TreeNode>();
	if(root==null) return;
	while(!stack.empty() || root!=null){
		while(root!=null){
			stack.push(root);
			System.out.println(root.val);  
			root = root.left;  //指向左節點
		}
		root=root.pop();  //取出棧頂元素
		root =root.right;  //遍歷右孩子
	}
}

中序

public static void InOrder(TreeNode root) {
	Stack <TreeNode> stack = new Stack <TreeNode>();
	if(root==null) return;
	while(!stack.empty() || root!=null){
		while(root!=null){
			stack.push(root);
			root = root.left;  //指向左節點
		}
		System.out.println(root.val); 
		root=root.pop();  //取出棧頂元素
		root =root.right;  //遍歷右孩子
	}
}

後序

public static void PostOrder(TreeNode root) {
	Stack <TreeNode> stack = new Stack <TreeNode>();
	if(root==null) return;
	while(!stack.empty() || root!=null){
		while(root!=null){
			stack.push(root);
			root = root.left;  //指向左節點
		}
		root=root.peek().right;  //peek是指向棧頂元素,  查找元素右節點
		if(root==null){
			root=root.pop();
			System.out.println(root.val); 
			root=null;  //這一步很重要,輸出左節點之後,棧頂元素變爲了根節點,但是不能直接輸出
		}
	}
}

  對於非遞歸版的遍歷方法,需要大家新建棵樹去每一步的認真走一下流程,這樣才能真正的理解,不至於死記硬背。我寫的非遞歸版的都是利用棧這種數據結構,它和js中數組一樣,這樣的話大家理解起來也比較容易。

深度優先遍歷

  深度優先遍歷,也是利用棧來保存數據進行操作的。

//二叉樹深度優先
		 *				    1
         *                 /  \
         *               2     3
         *              /  \   /\
         *             4  5   6  7
         * 結果是:1,2,4,5,3,6,7
     
  public static void  getDFS(TreeNode root){
	if(root == null) { return; }
	Stack <TreeNode> stack = new Stack <TreeNode>();
	stack.push(root);
	while(!stack.empty()){
		TreeNode node = stack.pop();	
		System.out.println(node.val); 
		if(node.right !=null) stack.push(node.right);
		if(node.left !=null) stack.push(node.left);
	}
  }

廣度優先遍歷

  上面的遍歷都是運用棧來保存節點,而廣度優先是利用隊列來保存節點。二者的差別可自行百度

  廣度優先是值先打印根節點,然後依次打印左節點和右節點(是指同一層次中的所有節點)

//二叉樹廣度優先
		 *				    1
         *                 /  \
         *               2     3
         *              /  \   /\
         *             4  5   6  7
         * 結果是:1,2,3,4,5,6,7

//代碼實現
public static void getBFS(TreeNode root) {
	if(root == null) { return; }
	Queue<TreeNode> queue = new LinkedList<TreeNode>();
	queue.offer(root);
	while(!queue.isEmpty() || root!=null){
		TreeNode node = queue.poll();
		System.out.println(node.val);
		if(node.left!=null){   //隊列是先進先出,每次出列的時候都把自己的左、右節點添加到隊列中
			queue.offer(node.left);
		}
		if(node.right!=null){
			queue.offer(node.right);
		}
	}
}    

二叉搜索樹的第k個結點

  二叉搜索樹的特點是根節點大於左孩子,小於右孩子。

	 *				    4
     *                 / \
     *               2    6
     *              / \  / \
     *             1   3 5  7
     * 結果是:1234567
     * 
     * 主要是利用中序遍歷,在彈出棧的時候記錄count是否和 k 相等。
     * 
 public static void KthNode(TreeNode root,int k){
	Stack<TreeNode> stack = new Stack<TreeNode>();
	int count = 0;
	while(!stack.empty()||root!=null){
		while(root!=null){
			stack.push(root);
			root = root.left;
		}
		count++;  //計數
		if(count==k) return root;  //進行比較
		root = stack.pop();
		root = root.right;
	}
 }

二叉樹中第k層結點個數

  使用遞歸來實現,首先需要了解的問題是:

  1. 根結點是第0層,所以當 k<0 時返回 0
  2. k=0 時,只有一個根結點,應該返回 1
  3. k>0 時,遞歸求 k-1 層左右結點個數相加
public int K_nodes(int k){
	if(k<0) return 0;
	return kNodes(root,k);
}
private int kNodes(TreeNode root,int k){
	if(root==null) return 0;
	if(k==0) return 1;  //只有根結點
	return kNodes(root.left,k-1)+kNodes(root.right,k-1); //遞歸計數
}

打印二叉樹中和等於k的路徑

  滿足的和爲k的路徑是:最後一個結點要爲葉子結點並且和等於k。

  在這我們會利用棧來保存滿足的路徑,最後遍歷棧打印輸出路徑。

 *				    4
 *                 / \
 *               2    5
 *              / \  
 *             1   3
 * 若k=9 滿足的兩條路徑是:4, 2, 34, 5 這兩條路徑
 *  
 public class sumK(TreeNode root,int k){
	Stack<TreeNode> stack = new Stack<TreeNode>();
	findPath(stack,root,k);
	void findPath(Stack<TreeNode> stack,TreeNode root,int k){
		if(root==null) return;
		stack.push(root);
		k-=root.val;  // k 減去當前結點值
		if(k==0 && root.left==null && root.right==null){  //判斷是否滿足條件
			for(TreeNode node:stack){   //把滿足的路徑打印出
				System.out.print(node.val);
			}
			System.out.println();
		}
		findPath(srack,root.left,k);  //遍歷左節點
		findPath(stack,root.right,k);  //遍歷右節點
		stack.pop();  //如果當前遍歷節點爲葉子節點,並且k!=0 把當前節點從棧頂彈出
	}
 }

二叉樹中全部的路徑

//這題是LeetCode上的題目,可以去看看,自己做做
class Solution {
   public List<String> binaryTreePaths(TreeNode root) {
        Stack<String> stack = new Stack<String>();
        if(root==null) return stack;
        String str="";
        AllPaths(stack,root,str);
        return stack;
    }
    void AllPaths(Stack<String> stack,TreeNode root,String str){
        if(root==null){return;}
        str+=root.val;
        if(root.left==null && root.right==null){
            stack.push(str);
            return;
        }
        if(root.left!=null) AllPaths(stack,root.left,str+"->");
        if(root.right!=null) AllPaths(stack,root.right,str+"->");
    }
}
// ["1->2->3","1->5"]

額外的js簡單編程題:

實現解析瀏覽器中url參數
/*最後 結果
{ user: ‘anonymous’,
id: [ 123, 456 ], // 重複出現的 key 要組裝成數組
city: ‘北京’,
enabled: true, // 未指定值得 key 約定爲 true
}
*/
url = ‘http://www.domain.com/?user=anonymous&id=123&id=456&city=“beijing”&enabled’;

function parseParam(url) {
    var obj={};
    var data=url.split('?')[1];  //若沒有指定url,可以使用window.location.search
    var arr=data.split('&');  //分割後,保存到數組中
    for(let i=0;i<arr.length;i++){
        let key = arr[i].split('=')[0];
        let val = arr[i].split('=')[1];
        if(obj[key]){  //存在的情況下
            let res=[];   //定義一個數組,因爲如果之前存在的話要保存爲數組形式
            res=res.concat(obj[key]);  //把之前的值連接到數組中
            res.push(val);  //新解析的值添加進去 ["123","456"]
            obj[key]=res;   //別忘了添加到obj中
        }else if(!val){
            obj[key]=true;  //如果沒有值的,默認爲true
        }else{
            obj[key]=val;  //剩餘情況
        }
    }
   return obj;
}

  斐波那契數列我想大家肯定都瞭解,之前寫的時候可能會直接用遞歸和數組保存,這兩種呢會消耗大量的內存空間,因爲它保存了重複計算的值。

  上次面試的時候被問到如何進行優化數列,下面就來講講優化的方法。

function fibo(n){
	if(n<=0) return 0;
    var a=1,b=1;  //利用變量來保存結果
    if(n===1||n===2){
        return 1;
    }else{
        for(let i=3;i<=n;i++){
           let c=a;  
           a=b;     //每次計算後都把上次的值覆蓋掉,大大節約了空間
           b=c+b;  //結果值
        }
        return b;  
   }
}

  以上是本篇博客爲大家分享的知識點,每天進步一點點。希望和大家一起進步?

  也歡迎大家指出有誤的地方,或者留下更好的解決方法?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章