树(数据结构)

140 阅读4分钟

exports.tree = {
    val: 1,
    children: [{
            val: 2,
            children: [{
                    val: 4,
                    children: []
                },
                {
                    val: 5,
                    children: []
                }
            ]
        },
        {
            val: 3,
            children: [{
                    val: 6,
                    children: []
                },
                {
                    val: 7,
                    children: []
                }
            ]
        }
    ]
}

深度优先遍历

实现思路
1.访问根节点
2.对根节点的children挨个进行深度优先遍历

// 树深度优先遍历
const dfs = (root) => {
    if (!root) return;
    console.log(root.val);
    root.children.forEach(dfs);
}
// 二叉树
const dfs = (root) => {
    if (!root) return;
    console.log(root.val);
    dfs(root.left);
    dfs(root.right);
}

广度优先遍历

实现思路
1.新建一个队列
2.把队头出队并访问
3.把队头children挨个入队
4.重复步骤2、3,直到队列为空

// 树广度优先遍历
const bfs = (root) => {
    if (!root) return;
    const q = [root];
    while (q.length) {
        const n = q.shift();
        console.log(n.val);
        n.children.forEach(child => {
            q.push(child)
        })
    }
}
// 二叉树
const bfs = (root) => {
    if (!root) return;
    const q = [root];
    while (q.length) {
        const n = q.shift();
        console.log(n.val);
        if(n.left) q.push(n.left);
        if(n.right) q.push(n.right);
    }
}

二叉树

exports.bt = {
    val: 1,
    left: {
    	// 二级
        val: 2,
        left: {
            val: 4,
            left: null,
            right: null
        },
        right: {
            val: 5,
            left: null,
            right: null
        },
    },
    right: {
    	// 二级
        val: 3,
        left: {
            val: 6,
            left: null,
            right: null
        },
        right: {
            val: 7,
            left: null,
            right: null
        }
    }
}

先序广度优先遍历

实现思路
1.访问根节点
2.对根节点的子树进行先序遍历
3.对根节点的子树进行先序遍历

// 先序广度优先遍历(递归版)
const preorder = (root) => {
    if (!root) return;
    console.log(root.val);
    preorder(root.left);
    preorder(root.right);
}
preorder(bt)  // 1->2->4->5->3->6->7
// 先序广度优先遍历(非递归版)
const preorder = (root) => {
    if (!root) return;
    const stack = [root];
    while(stack.length) {
        const n = stack.pop();
        // 后续遍历可以引用(逆序:左->右->根):根->左->右
        // 具体实现是:根->右->左
        console.log(n.val);
        if(n.right) stack.push(n.right);
        if(n.left) stack.push(n.left);
    }
}
preorder(bt)  // 1->2->4->5->3->6->7

中序广度优先遍历

实现思路
1.对根节点的子树进行中序遍历
2.访问根节点
3.对根节点的子树进行中序遍历

// 中序广度优先遍历(递归版)
const inorder = (root) => {
    if (!root) return;
    inorder(root.left);
    console.log(root.val);
    inorder(root.right);
}
inorder(bt)  // 4->2->5->1->6->3->7
// 中序广度优先遍历(非递归版)
const inorder = (root) => {
    if (!root) return;
    const stack = [];
    let p = root;
    while (stack.length || p) {
        while (p) {
            stack.push(p);
            p = p.left;
        }
        const n = stack.pop();
        console.log(n.val);
        p = n.right;
    }
}
inorder(bt)  // 4->2->5->1->6->3->7

后序广度优先遍历

实现思路
1.对根节点的子树进行中序遍历
2.对根节点的子树进行中序遍历
3.访问根节点

// 后序广度优先遍历(递归版)
const inorder = (root) => {
    if (!root) return;
    inorder(root.left);
    console.log(root.val);
    inorder(root.right);
}
inorder(bt)  // 4->5->2->6->7->3->1
// 后序广度优先遍历(非递归版)
const postorder = (root) => {
    if (!root) return;
    const outputStack = [];
    const stack = [root];
    while (stack.length) {
        const n = stack.pop();
        // 根-左->右
        outputStack.push(n);
        if (n.left) stack.push(n.left);
        if (n.right) stack.push(n.right);
    }
    // 逆序
    while (outputStack.length) {
        const n = outputStack.pop();
        console.log(n.val);
    }
}
postorder(bt)  // 4->5->2->6->7->3->1

翻转二叉树

const invertTree = function (root) {
    // 定义递归边界
    if (!root) {
        return root;
    }
    // 递归交换右孩子的子结点
    let right = invertTree(root.right);
    // 递归交换左孩子的子结点
    let left = invertTree(root.left);
    // 交换当前遍历到的两个左右孩子结点
    root.left = right;
    root.right = left;
    return root;
};

二叉搜索树(Binary Search Tree)简称 BST

查找数据域为某一特定值的结点

function search(root, val) {
    // 若 root 为空,查找失败,直接返回
    if(!root) {
        return 
    }
    // 找到目标结点,输出结点对象
    if(root.val === val) {
        console.log('目标结点是:', root)
    } else if(root.val > val) {
        // 当前结点数据域大于n,向左查找
        search(root.left, val)
    } else {
        // 当前结点数据域小于n,向右查找
        search(root.right, val)
    }
}

插入新结点

function insertIntoBST(root, val) {
    // 若 root 为空,说明当前是一个可以插入的空位
    if(!root) { 
        // 用一个值为n的结点占据这个空位
        root = new TreeNode(val)
        return root
    }
    
    if(root.val > val) {
        // 当前结点数据域大于n,向左查找
        root.left = insertIntoBST(root.left, val)
    } else {
        // 当前结点数据域小于n,向右查找
        root.right = insertIntoBST(root.right, val)
    }

    // 返回插入后二叉搜索树的根结点
    return root
}

删除指定结点

function deleteNode(root, key) {
    // 如果没找到目标结点,则直接返回
    if (!root) {
        return root
    }
    // 定位到目标结点,开始分情况处理删除动作
    if (root.val === key) {
        // 若是叶子结点,则不需要想太多,直接删除
        if (!root.left && !root.right) {
            root = null
        } else if (root.left) {
            // 寻找左子树里值最大的结点
            const maxLeft = findMax(root.left)
            // 用这个 maxLeft 覆盖掉需要删除的当前结点  
            root.val = maxLeft.val
            // 覆盖动作会消耗掉原有的 maxLeft 结点
            root.left = deleteNode(root.left, maxLeft.val)
        } else {
            // 寻找右子树里值最小的结点
            const minRight = findMin(root.right)
            // 用这个 minRight 覆盖掉需要删除的当前结点  
            root.val = minRight.val
            // 覆盖动作会消耗掉原有的 minRight 结点
            root.right = deleteNode(root.right, minRight.val)
        }
    } else if (root.val > key) {
        // 若当前结点的值比 key 大,则在左子树中继续寻找目标结点
        root.left = deleteNode(root.left, key)
    } else {
        // 若当前结点的值比 key 小,则在右子树中继续寻找目标结点
        root.right = deleteNode(root.right, key)
    }
    return root
}
// 寻找左子树最大值
function findMax(root) {
    while(root.right) {
        root = root.right
    }
    return root 
}
// 寻找右子树的最小值
function findMin(root) {
    while(root.left) {
        root = root.left
    }
    return root
}

应用

翻转二叉树

const invertTree = (root) => {
  if (!root) {
    return;
  }

  const left = invertTree(root.left);
  const right = invertTree(root.right);
  root.left = right;
  root.right = left;
  return root;
}