二叉樹是每個結點最多有兩個子樹的樹結構。通常子樹被稱作“左子樹”(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
* 結果是:1,2,3,4,5,6,7
*
* 主要是利用中序遍歷,在彈出棧的時候記錄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層結點個數
使用遞歸來實現,首先需要了解的問題是:
- 根結點是第0層,所以當 k<0 時返回 0
- k=0 時,只有一個根結點,應該返回 1
- 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, 3 和 4, 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;
}
}
以上是本篇博客爲大家分享的知識點,每天進步一點點。希望和大家一起進步?
也歡迎大家指出有誤的地方,或者留下更好的解決方法?