前端演算法 - 二叉樹

JsRicardo發表於2019-12-09

二維拓撲結構(圖)

  • 二叉樹 多叉樹 都是基於拓撲結構生成的
// 拓撲結構
function Node (value) {
    this.value = value
    this.neighbor = []
}

var node1 = new Node(1)
var node2 = new Node(2)
var node3 = new Node(3)
var node4 = new Node(4)

node1.neighbor.push(node2, node3)
node2.neighbor.push(node1, node4)
node3.neighbor.push(node1, node4)
node4.neighbor.push(nod2, node3)
複製程式碼

樹形結構(有向無環圖)

  • 樹是圖的一種
  • 樹有一個根節點
  • 樹沒有環形結構(迴路)
  • 度: 樹的最多叉的節點有多少叉,就有多少度
  • 深度: 樹最深有多少層,就是多少深度

二叉樹

  • 樹的度最多為二的樹
  • 滿二叉樹

    1. 所有的葉子節點都在最底層
    2. 每個非葉子節點都有兩個子節點
  • 完全二叉樹

國內定義

  1. 葉子節點都在最後一層或者倒數第二層
  2. 葉子節點都向左聚攏

國際定義

  1. 葉子節點都在最後一層或者倒數第二層
  2. 如果有葉子節點,就必須有兩個葉子節點

二叉樹的遍歷

  • 前序遍歷(前根次序遍歷)先列印當前的,再列印左邊的,再列印右邊的
  • 中序遍歷(中根次序遍歷)先列印左邊的,先列印當前的,再列印右邊的
  • 後序遍歷(後根次序遍歷)先列印左邊的,再列印右邊的,先列印當前的
// 構建二叉樹
function Node(val) {
    this.value = val
    this.left = null
    this.right = null
}

var a = new Node('a')
var b = new Node('b')
var c = new Node('c')
var d = new Node('d')
var e = new Node('e')
var f = new Node('f')
var g = new Node('g')

a.left = c
a.right = b
b.left = e
b.right = f
c.left = g
複製程式碼
  • 二叉樹演算法
// 前序遍歷
function qianxu (node) {
    if (node === null) return
    console.log(node.val)
    qianxu(node.left)
    qianxu(node.right)
}

// 中序遍歷
function zhongxu (node) {
    if (node === null) return
    qianxu(node.left)
    console.log(node.val)
    qianxu(node.right)
}

// 後序遍歷
function houxu (node) {
    if (node === null) return
    qianxu(node.left)
    qianxu(node.right)
    console.log(node.val)
}
複製程式碼

中序遍歷 根節點的左邊是左子樹;

前序遍歷 第一個是根節點;

後序遍歷 最後一個是根節點

用中序遍歷確認左右子樹,前後序遍歷確認根節點

// 根據前序後序 還原二叉樹
var qian = ['a', 'b', 'e', 'f', 'c', 'g', ]
var zhong = ['b', 'e', 'f', 'a', 'c', 'g', ]
var num = 0
function huanyuan(qian, zhong) {
    if (qian == null || zhong == null || qian.length == 0 || zhong.length == 0 || qian.length != zhong.length) return null;
    var root = new Node(qian[0]) // 建立二叉樹 根據前序找到根節點
    var zhongIdx = zhong.indexOf(root.val) // 在中序中找到根節點位置
    var qLeft = qian.slice(1, zhongIdx + 1) // 前序左子樹
    var qRight = qian.slice(zhongIdx + 1, qian.length) // 前序右子樹
    var zLeft = zhong.slice(0, zhongIdx) // 中序左子樹
    var zRight = zhong.slice(zhongIdx + 1, zhong.length) // 中序右子樹
    root.right = huanyuan(qLeft, zLeft)
    root.left = huanyuan(qRight, zRight)
    return root
}
var root = huanyuan(qian, zhong)
複製程式碼

二叉樹的搜尋

  • 樹的搜尋 圖的搜尋 爬蟲邏輯 搜尋引擎爬蟲邏輯
  • 深度優先搜尋:更適合探索未知 往深處搜尋
  • 廣度優先搜尋:更適合搜尋局域 一層一層搜尋

對於二叉樹來說,深度優先搜尋和前序遍歷的順序是一樣的 廣度優先搜尋和中序遍歷的順序是一樣的

// 深度優先搜尋
/**
 *實現一顆二叉樹 
 */
// 搜尋 f 在不在這個二叉樹中
function deepSearch (node, target) {
    if (node == null) return false
    if (node.value == target) return true
    // 向更深層遞迴搜尋
    var left = deepSearch(node.left, target)
    var right = deepSearch(node.right, target)
    return left || right
}

/**
 * 二叉樹的廣度優先 每一層搜尋完了再向下一層搜尋
 */
function widthSearch(nodeList, target) {
    if (nodeList == null || nodeList.length == 0) return false
    var childList = []
    for (var i = 0; i < nodeList.length; i++) {
        if (nodeList[i] != null && nodeList[i].value == target) {
            return true
        } else {
            childList.push(nodeList[i].left)
            childList.push(nodeList[i].right)
        }
    }
    return widthSearch(childList, target)
}

/**
 * 二叉樹比較
 * 左子樹和右子樹互換 不算 同一棵樹的情況
 */
 function compareTree(tree1, tree2){
    if (tree1 == tree2) return true // 同一棵樹
    if (tree1.value == tree2.value) {
        var left = compareTree(tree1.left, tree2.left)
        var right = compareTree(tree1.right, tree2.right)
        return left && right
    }
    return false
}

/**
 * 二叉樹比較
 * 左子樹和右子樹互換 算 同一棵樹的情況
 */
 function compareTree(tree1, tree2){
    if (tree1 == tree2) return true
    if (tree1.value == tree2.value) {
        return compareTree(tree1.left, tree2.left) && compareTree(tree1.right, tree2.right)
        || compareTree(tree1.left, tree2.right) && compareTree(tree1.right, tree2.left)
    }
    return false
}
複製程式碼

二叉樹diff演算法

知道樹結構新增了什麼,刪除了什麼,修改了什麼

/**
 * 二叉樹的diff演算法
 */
function diffList(tree1, tree2, diffLists = []) {
    if (tree1 == tree2) return diffLists
    // 修改 刪除 新增 三種情況
    if (tree1.value != tree2.value) { // 修改
        diffLists.push({type: 'update', origin: tree1, now: tree2})
        // 修改需要繼續向下遞迴,有可能子節點沒有修改
        diffList(tree1.left, tree2.left)
        diffList(tree1.right, tree2.right)
    } else if (tree1 != null && tree2 == null) { // 刪除
        diffLists.push({type: 'delete', origin: tree1, now: null})
    } else if (tree1 == null && tree2 != null) { // 新增
        diffLists.push({type: 'add', origin: null, now: tree2})
    } else { // 相同則遍歷子節點
        diffList(tree1.left, tree2.left)
        diffList(tree1.right, tree2.right)
    }
    return diffLists
}
複製程式碼

相關文章