数据结构与算法--树

235 阅读3分钟

什么是树

树是一种分层数据的抽象模型。树是二维数据数据结构,Linked List是特殊化的Tree,Tree是特殊化的Graph。前端工作中常见的树包括:DOM树、级联选择、树形控件...

级联选择组件

树形控件

JS中没有树,但是可以用Object和Array构建树

var tree = {
    "lable": "根节点",
    "value": 0,
    "hasChildren": true,
    "children": [
        {
            "lable": "父节点1",
            "value": 1,
            "hasChildren": true,
            "children": [
                {
                    "lable": "子节点11",
                    "value": 2,
                    "hasChildren": false
                }, {
                    "lable": "子节点12",
                    "value": 3,
                    "hasChildren": false
                }
            ]
        }, {
            "lable": "父节点2",
            "value": 4,
            "hasChildren": true,
            "children": [
                {
                    "lable": "子节点21",
                    "value": 5,
                    "hasChildren": false
                },
                {
                    "lable": "子节点22",
                    "value": 6,
                    "hasChildren": false
                }
            ]
        }
    ]
};

树的结构

function TreeNode(val){
	this.val = val;
    this.left = this.right = null;
}

树的常用操作:深度/广度优先遍历、先中后序遍历

深度与广度优先遍历

深度优先遍历:尽可能深的搜索树的分支。深度优先遍历算法:

  • 1、访问根节点
  • 2、对根节点的children挨个进行深度优先遍历
const dfs = (root) => {
    console.log(root.lable);
    root.hasChildren && root.children.forEach(dfs);
}

dfs(tree);

//输出结果
根节点
父节点1
子节点11
子节点12
父节点2
子节点21
子节点22

广度优先遍历:先访问离根节点最近的节点。广度优先遍历算法:

  • 1、新建一个队列,把根节点入队
  • 2、把队头出队并访问
  • 3、把队头的children挨个入队
  • 4、重复2、3步,直到队列为空
const bfs = (root) => {
    let queue = [root];

    while (queue.length) {
        const n = queue.shift();
        console.log(n.lable);
        if (n.hasChildren){
            n.children.forEach(child => queue.push(child))
        }
    }
}
bfs(tree);

//输出结果
根节点
父节点1
父节点2
子节点11
子节点12
子节点21
子节点22

先中后序遍历(二叉树)

二叉树中每个节点最多只能有两个子节点。遍历方法有:递归版和非递归版(先序遍历、中序遍历、后序遍历)

在JS中通常用Object来模拟二叉树

var binaryTree = {
    label: '根节点',
    value: 1,
    left: {
        label: '左节点',
        value: 2,
        left: {
            label: '左节点-左',
            value: 3,
            left: null,
            right: null,
        },
        right: {
            label: '左节点-右',
            value: 4,
            left: {
                label: '右节点-右-左',
                value: 5,
                left: null,
                right: null,
            },
            right: null,
        }
    }, 
    right: {
        label: '右节点',
        value: 6,
        left: null,
        right: {
            label: '右节点-右',
            value: 7,
            left: null,
            right: null,
        },
    }
}

先序遍历算法:

  • 1、访问节点
  • 2、对根节点的子树进行先序遍历
  • 3、对根节点的子树进行先序遍历
//递归版
const  preorder = (root)=> {
    if(!root){
        return;
    }
    console.log(root.value);
    preorder(root.left);
    preorder(root.right);
}
//非递归版
const preorder2 = (root)=> {
    if(!root){
        return;
    }
    let stack = [root];
    while (stack.length) {
        let n = stack.pop();
        console.log(n.value);
        if (n.right) {
            stack.push(n.right)
        } 
        if(n.left){ 
            stack.push(n.left)
        }
    }
}

preorder(binaryTree);

//输出结果
1
2
3
4
5
6
7

中序遍历算法:

  • 1、对根节点的子树进行中序遍历
  • 2、访问节点
  • 3、对根节点的子树进行中序遍历
//递归版
const inorder = (root)=> {
    if(!root){
        return;
    }
    inorder(root.left);
    console.log(root.value);
    inorder(root.right);
}
//非递归版
const inorder2 = (root)=> { 
    if(!root){ return; }
    let stack = [];
    let p = root;

    while (stack.length || p) {
        while (p) {
            stack.push(p);
            p = p.left;
        }
        const n = stack.pop();
        console.log(n.value);
        p = n.right;
    }
}

inorder(binaryTree);

//输出结果
3
2
5
4
1
6
7

后序遍历算法:

  • 1、对跟节点的子树进行后序遍历
  • 2、对根节点的子树进行后序遍历
  • 3、访问节点
//递归版
const  postorder = (root) => {
    if(!root){
        return;
    }
    postOrder(root.left);
    postOrder(root.right);
    console.log(root.value);
}

//非递归版
const postorder2 = (root) => {
    if (!root) {
        return;
    }
    let stack = [root];
    let outputStack = [];

    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 o = outputStack.pop();
        console.log(o.value);
    }
}
postorder(binaryTree);
//输出结果:
3
5
4
2
7
6
1

前端与树

1、遍历JSON的所有节点值

const json = {
    a: { b: { c: 1}},
    d: [1,2,3]
}

const dfs = (n, path) => {
    console.log(n, path);
    Object.keys(n).forEach((key)=> {
        dfs(n[key], path.concat(key))
    })
}

dfs(json, []);

2、渲染Antd的树组件

知识来源: coding.imooc.com/learn/list/…