二叉樹算法題


  • 二叉樹層次遍歷

//思路

特殊情況 ,根節點為null,深度為0,return 0
借助隊列,隊列儲存每一層的所有節點,
先保存數組長度,用於控制依次遍歷循環——遍歷每一個節點保存值,該節點的所有子結點從隊尾入隊,訪問過的節點從頭出隊
注意——需要先把width存下來,用於for循環的結束標志,因為for循環里面直接操作了queue,不能去都不敢動態獲取
隊列初始值為root,對每一層都要進行上述遍歷——while循環控制,隊列空代表葉節點這一層遍歷完成,此時遍歷結束,退出循環
每次循環開始前初始化一個curNodes儲存該層所有節點,每次循環結束,將curNodes壓入result

var levelOrder = function(root) {
  if (root === null) return []; //空樹
  var result = [],
    queue = [root];
  while (queue.length) {
    let width = queue.length; //需要先把width存下來,用於for循環,for循環里面直接操作了數組
    let curNodes = [];
    for (let i = 0; i < width; i++) {
      let node = queue.shift();
      curNodes.push(node.val);
      node.left ? queue.push(node.left) : "";
      node.right ? queue.push(node.right) : "";
    }
    result.push(curNodes);
  }
  return result;
};
  • 二叉樹反向層次遍歷

var levelOrderBottom = function(root) {
  if (root === null) return []; //空樹
  var result = [],
    queue = [root];
  while (queue.length) {
    let width = queue.length; //需要先把width存下來,用於for循環,for循環里面直接操作了數組
    let curNodes = [];
    for (let i = 0; i < width; i++) {
      let node = queue.shift();
      curNodes.push(node.val);
      node.left ? queue.push(node.left) : "";
      node.right ? queue.push(node.right) : "";
    }
    result.unshift(curNodes);//從頭部插入,先插入頂部的,后插入底部的額
  }
  return result;
};
  • 先序遍歷

//思路
和中序其他都相似,要先訪問根節點要后訪問左節點,所以在根節點入棧的時候加入result數組

特殊情況:根節點為空,返回空數組
利用棧來存放訪問過的根節點——定義一個指針指向要訪問的節點,先訪問根節點。將遍歷到的結點值存入result數組,結點存入棧中。
stack存放遍歷過的根,左節點,以便回溯訪問右節點
然后指針一直向下訪問左節點
當根節點沒有左節點時,將根節點退棧,然后指針指向右節點,訪問右節點
直到棧為空而且指針為空,所有節點遍歷完成,退出循環,返回結果

var preorderTraversal = function(root) {
  if (root === null) return []; //空樹
  var result = [],
    stack = []; //result儲存結果,stack存放遍歷過的根,左節點,以便回溯訪問右節點
  var p = root; //p指向當前遍歷的節點
  //當有未訪問的右節點(stack不空)||還有左節點沒有訪問(p不空)時進入循環
  while (stack.length != 0 || p != null) {
    //還有左節點沒有訪問(p不空)
    if (p != null) {
      result.push(p.val); //遍歷根節點
      stack.push(p); //把遍歷過的節點放入stack保管
      p = p.left; //訪問左節點
    }
    //左節點訪問完
    else {
      p = stack.pop().right; //棧頂節點退棧,訪問右節點
    }
  }
  return result;
}; 
  • 中序遍歷

//思路
和前序其他都相似,左節點要先訪問根節點要后訪問,所以在根節點出棧的時候加入result數組

特殊情況:根節點為空,返回空數組
利用棧來存放訪問過的根節點——定義一個指針指向要訪問的節點,先訪問根節點。,結點存入棧中。
stack存放遍歷過的根,左節點,以便回溯訪問右節點
然后指針一直向下訪問左節點
當根節點沒有左節點時,將根節點退棧,將遍歷到的結點值存入result數組,然后指針指向右節點,訪問右節點
直到棧為空而且指針為空,所有節點遍歷完成,退出循環,返回結果

var inorderTraversal = function(root) {
  if (root === null) return []; //空樹
  var result = [],
    stack = []; //result儲存結果,stack存放遍歷過的根,左節點,以便回溯訪問右節點
  var p = root; //p指向當前遍歷的節點
  //當有未訪問的右節點(stack不空)||還有左節點沒有訪問(p不空)時進入循環
  while (stack.length != 0 || p != null) {
    //還有左節點沒有訪問(p不空)
    if (p != null) {
      stack.push(p); //把遍歷過的節點放入stack保管
      p = p.left; //訪問左節點
    }
    //左節點訪問完
    else {
        let node = stack.pop();//棧頂節點退棧,
      result.push(node.val); //遍歷左節點-根節點
      p = node.right; //訪問右節點
    }
  }
  return result;
};
  • 后序遍歷

//思路
特殊情況:根節點為空,返回空數組
利用棧來存放訪問過的根節點——定義一個指針指向要訪問的節點,先訪問根節點。,結點存入棧中。
stack存放遍歷過的根,右節點,以便回溯訪問左節點
然后指針一直向下訪問右節點
當根節點沒有右節點時,將根節點退棧,將遍歷到的結點值存入result數組,然后指針指向右節點,訪問右節點
直到棧為空而且指針為空,所有節點遍歷完成,退出循環,返回結果

var postorderTraversal = function(root) {
  if (root === null) return []; //空樹
  var result = [],
    stack = []; //result儲存結果,stack存放遍歷過的根,左節點,以便回溯訪問右節點
  var p = root; //p指向當前遍歷的節點
  //當有未訪問的左節點(stack不空)||還有右節點沒有訪問(p不空)時進入循環
  while (stack.length != 0 || p != null) {
    //還有左節點沒有訪問(p不空)
    if (p != null) {
      stack.push(p); //把遍歷過的節點放入stack保管
      result.unshift(p.val); //從result數組頭部插入根節點-右節點-左節點
      //最后result順序為左節點-右節點-根節點
      p = p.right; //訪問右節點
    }
    //右節點訪問完
    else {
      p = stack.pop().left; //棧頂節點退棧,訪問左節點
    }
  }
  return result;
};
  • 重建二叉樹

//思路:二叉樹前序遍歷第一個點為根節點,中序遍歷順序為先左子樹然后根節點最后右子樹。
所以先通過前序遍歷找出根節點,然后將中序遍歷分為左右子樹兩組,最后對於每個子樹依次遞歸調用。
function reConstructBinaryTree(pre, vin) { if (pre.length === 0 || vin.length === 0) return null; // 前序/中序又一個為空,就返回空值 let root = new TreeNode(pre[0]); //新建節點,作為根節點 if (pre.length === 1) return root; //是葉節點直接返回root,不需要計算子樹 //不是葉節點,先遞歸得到左右節點 let rootIdx = vin.indexOf(pre[0]); //根節點在中序的位置 root.left = reConstructBinaryTree( // 遞歸調用得到左子樹的根節點 pre.slice(1, rootIdx + 1), vin.slice(0, rootIdx) ); root.right = reConstructBinaryTree( // 遞歸調用得到右子樹的根節點 pre.slice(rootIdx + 1), vin.slice(rootIdx + 1) ); return root; }
  • 二叉樹鏡像

//思路:先將根的左右節點互換,然后就是遞歸調用,對左右子樹進行分別處理

function Mirror(root)
{
    // write code here
    if(root==null) return null;
    //首先先將左右節點互換
    var  tmp = root.left;
    root.left=root.right;
    root.right=tmp;
    //遞歸
    Mirror(root.left);
    Mirror(root.right);

}
  • 平衡二叉樹

//在這里,由於我們是以遞歸的形式,所以,我們會先遍歷左節點,再遍歷右節點,最后再遍歷根節點(后序遍歷)

同時,我們判斷左右樹的高度差是否超過 1,如果是,則進行中斷,返回 false;否則,繼續遞歸。

最后,我們將遍歷的結果與 -1 進行比較,返回 true 或者 false

let root = {
  val: 3,
  left: { val: 9, left: null, right: null },
  right: {
    val: 20,
    left: { val: 3, left: null, right: null },
    right: {
      val: 7,
      left: null,
      right: { val: 2, left: null, right: null },
    },
  },
}

var isBalanced = function(root) {
  let ergodic = function(root) {
    if (!root) {
      return 0;
    }
    let left = ergodic(root.left);
    if (left === -1) {
      return -1;
    }
    let right = ergodic(root.right);
    if (right === -1) {
      return -1;
    }
    return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1;
  }
  return ergodic(root) != -1;
};

console.log(isBalanced(root));
  • 二叉樹深度

//題目:
輸入一棵二叉樹,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,最長路徑的長度為樹的深度。

//思路
特殊情況 ,根節點為null,深度為0,return 0
遞歸法,遞歸法獲取左右子樹的深度,
root的深度是左右子樹的深度最大值+1,返回深度

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */
function TreeDepth(pRoot) {
  // write code here
  if (!pRoot) return 0; //根節點為空,深度為0
  var left = TreeDepth(pRoot.left); //左深度等於左子樹深度+1
  var right = TreeDepth(pRoot.right); //右深度等於右子樹深度+1
  return 1 + Math.max(left, right); //該節點深度為左右深度中的max,返回
}
  • 二叉樹最大深度

//思路
我們判斷 root 是否還存在 left 和 right:如果不存在,那么樹到了最底層,終止遞歸;如果存在,那么 depth 深度 + 1,並且遍歷它的左樹和右樹。

同時,我們在樹存在 left 和 right 的時候,將 depth 和 longest 比較,把最深的樹給記錄下來。

返回最長深度 longest。

var maxDepth = function(root) {
  return root === null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
};
  • 二叉樹最小深度

//思路
假設我是一只蜘蛛,我在一顆大樹最底下(根節點),開始往上爬。

每經過 1 米(1 個 val 節點),我就留下一個分身。

當我爬到最頂的時候,我就進行最后標記,並告訴分身,前面涼涼了,開始報數!

於是從我為 1 開始,一直到根節點的長度,就是這個分支的高度。

const root = {
  val: 3,
  left: { val: 9, left: null, right: null },
  right: {
    val: 20,
    left: { val: 15, left: null, right: null },
    right: { val: 7, left: null, right: null },
  },
}
var minDepth = function(root) {
  if (!root) {
    return 0;
  }
  if (!root.left) {
    return minDepth(root.right) + 1;
  }
  if (!root.right) {
    return minDepth(root.left) + 1;
  }
  return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
};
minDepth(root);
  • 翻轉二叉樹

var invertTree = function(root) {
  if (!root) {
    return null;
  }
  return {
    val: root.val,
    left: invertTree(root.right) || null,
    right: invertTree(root.left) || null,
  }
}; 
  • 二叉樹下一個節點

//題目
給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點並且返回。注意,樹中的結點不僅包含左右子結點,同時包含指向父結點的指針。

//思路
特殊情況 空節點

情況一,有右子節點,下一個訪問右子樹中最左邊的第一個節點。通過while循環,尋找右子樹的左葉子節點可以找到。

情況二,沒有右子結點,有父節點,當前節點是父節點的左節點,下一個訪問父節點

情況三,沒有右子結點,有父節點,當前節點是父節點的右節點,此時父節點一下都已經訪問完,下一個訪問父節點的父節點中,滿足從左邊連接的第一個

情況二和情況三可以歸為一類——父節點存在時,設一個指針cur指向pnode父節點,

如果pnode是cur的左節點,返回;
如果pnode是cur的右節點,pnode向上指,
如果退出循環,查找到根節點都沒有返回,就表示沒有滿足條件的節點,返回null

function GetNext(pNode) {
  // write code here
  if (!pNode) return null; //空節點
  var cur = null;
  //有右節點,下一個訪問右子樹里的最左邊節點
  if (pNode.right) {
    cur = pNode.right;
    while (cur.left) {
      cur = cur.left;
    }
    return cur;
  }
  //沒有右節點,有父節點,
  while (pNode.next) {
    cur = pNode.next; //cur指向父節點
    //如果pnode是cur的左節點,下一個訪問的就是cur
    if (cur.left === pNode) return cur;
    //如果pnode不是cur的左節點,pnode指向父節點,向上繼續查找
    pNode = cur;
  }
  return null; //查找到根節點也沒有符合條件的節點
  • 最大二叉樹

//題目:
給定一個不含重復元素的整數數組。一個以此數組構建的最大二叉樹定義如下:
二叉樹的根是數組中的最大元素。
左子樹是通過數組中最大值左邊部分構造出的最大二叉樹。
右子樹是通過數組中最大值右邊部分構造出的最大二叉樹。
通過給定的數組構建最大二叉樹,並且輸出這個樹的根節點。

//思路
遞歸法實現構建二叉樹
遞歸出口,數組長度為0,return null,返回空節點
根節點為傳入數組的max值,
左節點為遞歸調用,傳入以max為分割點的左邊數組的返回值,
右節點為遞歸調用,傳入以max為分割點的右邊數組的返回值,
slice(left,right)分割數組,索引為left的元素划分進去,索引為right的元素不划分進去。

var constructMaximumBinaryTree = function(nums) {
  if (nums.length <= 0) return null;
  let max = Math.max(...nums);
  let i = nums.indexOf(max);
  let root = new TreeNode(max);
  root.left = constructMaximumBinaryTree(nums.slice(0, i));
  root.right = constructMaximumBinaryTree(nums.slice(i + 1));
  return root;
};
  • 序列化二叉樹

//題目描述
請實現兩個函數,分別用來序列化和反序列化二叉樹

//思路:

序列化,將節點值存入數組中,空節點則使用特殊標記存入數組中。
反序列化,從數組中獲取元素,為number類型則生成節點,為特殊標記,則為空節點

var arr=[];
function Serialize(pRoot)
{
    // write code here
    if(pRoot==null){
        arr.push('#')
        return;
    } 
    arr.push(pRoot.val);
    Serialize(pRoot.left)
    Serialize(pRoot.right)
}
function Deserialize(s)
{
    // write code here
    if(arr==null){
        return null;
    }

    if(arr.length<1){
        return null;
    }
    var root=null;
    var temp=arr.shift();
    if(typeof temp=='number'){
        root=new TreeNode(temp);
        root.left=Deserialize(arr);
        root.right=Deserialize(arr);
    }
    return root;
}
  • 二叉樹中和為某一值的路徑

//思路

因為要先訪問最長的路徑,所以考慮`
遞歸深度遍歷:對根節點調用遞歸函數
curPath保存當前路徑,sum保存當前和,每訪問一個節點,更新路徑和和,
更新之后,判斷是否滿足葉節點+和為指定值,如果滿足,壓入結果數組
注意:因為 curPath是一個引用類型,它的值一直在變化,所以壓入數組時需要先進行深拷貝,如果不拷貝會導致結果輸出為最后的curPath空數組。
左右節點存在時,對左右節點遞歸遍歷
當前節點和所有子節點遍歷完成,當前節點退出路徑


function FindPath(root, expectNumber) {
  // write code here
  if (!root) return [];
  var res = [], //所有滿足條件路徑
    curPath = [], //當前路徑
    p = root, //訪問指針
    sum = 0; //當前路徑和
  dfs(p, curPath, sum, res, (exp = expectNumber));
  return res;
}
function dfs(p, curPath, sum, res, exp) {
    if(!p&&curPath.length===0) return;//所有節點訪問完畢,退出遞歸
  curPath.push(p.val); //節點值加入路徑
  sum += p.val; //更新和
  if (!p.left && !p.right && sum === exp) {
    //棧頂節點是葉節點且路徑滿足條件
    res.push(curPath.slice(0)); //路徑加入結果
  }
  p.left ? dfs(p.left, curPath, sum, res, exp) : "";
  p.right ? dfs(p.right, curPath, sum, res, exp) : "";
  curPath.pop(); //節點和所有子樹訪問完畢,退出路徑
}
  • 求根到葉子節點數字之和

//題目:
給定一個二叉樹,它的每個結點都存放一個 0-9 的數字,每條從根到葉子節點的路徑都代表一個數字。
例如,從根到葉子節點路徑 1->2->3 代表數字 123。計算從根到葉子節點生成的所有數字之和。 //方法一:遞歸法 //思路 遞歸輸入當前節點和上一層的和,初始為root和0 如果節點空返回0 更新sum,是葉節點的話直接返回這個值 不是葉節點再加上左右節點的遞歸返回值 var sumNumbers = function(root) { return dfs(root,0); }; var dfs = function(p,sum) { if(!p) return 0; sum=sum*10+p.val; if(!p.left&&!p.right) return sum; return dfs(p.left,sum)+dfs(p.right,sum); };
  • 左葉子之和

const sumOfLeftLeaves = (root, left) => {
  if (!root) {
    return 0;
  }
  if (!root.left && !root.right && left) {
    return root.val;
  }
  return sumOfLeftLeaves(root.left, true) + sumOfLeftLeaves(root.right);
};
  • 二叉樹所有路徑

//思路:
使用遞歸方法來解決此問題。對於根節點,如果根節點為空,則返回空數組[],如果為葉子節點,則返回包含此節點的值的數組(這個數組只有一個元素)。
若不為空也不是葉子節點,則對左右子樹遞歸調用,將兩個結果數組拼接起來,最后使用 map 函數來對數組的每個元素進行字符串的操作。
/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } */ var binaryTreePaths = function(root) { if (root === null) return []; if (root.left === null && root.right === null) { return [root.val.toString()]; } var left = binaryTreePaths(root.left), right = binaryTreePaths(root.right); return left.concat(right).map(x => root.val + '->' + x); };
  • 樹的子結構

//題目描述
輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)

//思路:比較B是不是A的子樹,B是不是A的右子樹的子樹,B是不是A的左子樹的子樹。如果根元素相同,則開始判斷左子樹和右子樹

function isSubtree(pRoot1,pRoot2){
    if (pRoot2 == null) return true;//pRoot2為null,表示子樹已經遍歷完
    if (pRoot1 == null) return false;
    if(pRoot1.val==pRoot2.val){
        return isSubtree(pRoot1.left,pRoot2.left) && isSubtree(pRoot1.right,pRoot2.right);
    }else{
        return false;
    }
}

function HasSubtree(pRoot1, pRoot2)
{
    // write code here
    if(pRoot1==null||pRoot2==null) return false;   
    return isSubtree(pRoot1,pRoot2)||HasSubtree(pRoot1.left,pRoot2)||HasSubtree(pRoot1.right,pRoot2);
} 
  • 二叉搜索樹最近公共祖先

//思路:
首先確保p的值小於q,若不是,則互換。這樣有助於判斷root、p、q三者的位置。

三種情況

root最小,說明p和q的最近公共祖先一個在root的右邊,使用root.right遞歸調用
root在中間,說明最近公共祖先只能是root,返回root
root最大,說明p和q的最近公共祖先一個在root的左邊,使用root.left遞歸調用

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

var lowestCommonAncestor = function(root, p, q) {
  if (p.val > q.val) [p, q] = [q, p];                           // 讓p小於q,方便判斷
  if (root.val >= p.val && root.val <= q.val) {
    return root;
  } else if (root.val <= p.val && root.val <= q.val) {
    return lowestCommonAncestor(root.right, p, q);
  } else if (root.val >= p.val && root.val >= q.val) {
    return lowestCommonAncestor(root.left, p, q);
  }
};

 

  • 二叉搜索樹的后序遍歷

//思路
后續遍歷我們可以知道,最右邊的是根節點r。
2.通過根節點r我們可以判斷左子樹和右子樹。
3.判斷左子樹中的每個值是否小於r,右子樹的每個值是否大於r.
4.對左、右子樹遞歸判斷。

function VerifySquenceOfBST(sequence)
{
    // write code here
    if(sequence.length<=0) return;
    return test(sequence,0,sequence.length-1)
}
function test(sequence,start,end){
    if(start>=end) return true;
    var i=end-1;
    while(i>=start && sequence[i]>sequence[end]){
        i--;
    }
    for(var j=i;j>=start;j--){
        if(sequence[j]>sequence[end]){
            return false;
        }
    }
    return test(sequence,start,i)&&test(sequence,i+1,end-1)
}
  • 二叉搜索樹的第k個結點

//思路:二叉搜索樹,若任意節點的左子樹不空,則左子樹上所有節點的值均小於它的根節點的值;
若任意節點的右子樹不空,則右子樹上所有節點的值均大於它的根節點的值;任意節點的左、右子樹也分別為二叉查找樹;
所以采用中序遍歷的方法,遍歷后的結果就是從小到大順序的結果 function KthNode(pRoot, k) { // write code here var arr=[]; if(pRoot===null||k<1){ return null; } function midInorder(root){ if(root.left!==null){ midInorder(root.left); } arr.push(root); if(root.right!==null){ midInorder(root.right); } } midInorder(pRoot); return arr[k-1]; }
  • 將有序數組轉換為二叉搜索樹

var sortedArrayToBST = function(nums) {
  if (!nums.length) return null;
  let mid = Math.floor(nums.length / 2);
  let root = {
    val: nums[mid],
    left: sortedArrayToBST(nums.slice(0, mid)),
    right: sortedArrayToBST(nums.slice(mid + 1)),
  }
  return root;
}; 
  • 深度優先遍歷

該方法是以縱向的維度對dom樹進行遍歷,從一個dom節點開始,一直遍歷其子節點,直到它的所有子節點都被遍歷完畢之后在遍歷它的兄弟節點。
//
遞歸 function deepFirstSearch(node,nodeList) { if (node) { nodeList.push(node); var children = node.children; for (var i = 0; i < children.length; i++) //每次遞歸的時候將 需要遍歷的節點 和 節點所存儲的數組傳下去 deepFirstSearch(children[i],nodeList); } return nodeList; }

//deepFirstSearch接受兩個參數,第一個參數是需要遍歷的節點,第二個是節點所存儲的數組,並且返回遍歷完之后的數組,該數組的元素順序就是遍歷順序,調用方法:

  let root = document.getElementById('root')
  deepTraversal(root,nodeList=[])

//非遞歸
function deepFirstSearch(node) {
    var nodes = [];
    if (node != null) {
        var stack = [];
        stack.push(node);
        while (stack.length != 0) {
        var item = stack.pop();
        nodes.push(item);
        var children = item.children;
        for (var i = children.length - 1; i >= 0; i--)
            stack.push(children[i]);
        }
    }
    return nodes;
}
  • 廣度優先遍歷

該方法是以橫向的維度對dom樹進行遍歷,從該節點的第一個子節點開始,遍歷其所有的兄弟節點,
再遍歷第一個節點的子節點,完成該遍歷之后,暫時不深入,開始遍歷其兄弟節點的子節點。
//非遞歸版本 function breadthFirstSearch(node) { var nodes = []; if (node != null) { var queue = []; queue.unshift(node); while (queue.length != 0) { var item = queue.shift(); nodes.push(item); var children = item.children; for (var i = 0; i < children.length; i++) queue.push(children[i]); } } return nodes; }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM