树的深度广度优先遍历,看这篇就够了

352 阅读3分钟

树的深度优先遍历(Depth-First Search, DFS)和广度优先遍历(Breadth-First Search, BFS)是两种常见的树遍历算法。

将一个数组转换成一棵树是一个常见的任务,特别是在处理层次数据结构时。假设数组中的每个元素是一个对象,包含 idparentId 属性,其中 parentId 表示该节点的父节点的 id。根节点的 parentId 通常为 null 或者特定的值(例如 -1)。

下面是一个示例数组:

const array = [
    { id: 1, parentId: null, name: 'A' },
    { id: 2, parentId: 1, name: 'B' },
    { id: 3, parentId: 1, name: 'C' },
    { id: 4, parentId: 2, name: 'D' },
    { id: 5, parentId: 2, name: 'E' },
    { id: 6, parentId: 3, name: 'F' }
];

目标是将这个数组转换成一棵树结构,例如:

const tree = [
    {
        id: 1,
        parentId: null,
        name: 'A',
        children: [
            {
                id: 2,
                parentId: 1,
                name: 'B',
                children: [
                    { id: 4, parentId: 2, name: 'D', children: [] },
                    { id: 5, parentId: 2, name: 'E', children: [] }
                ]
            },
            {
                id: 3,
                parentId: 1,
                name: 'C',
                children: [
                    { id: 6, parentId: 3, name: 'F', children: [] }
                ]
            }
        ]
    }
];

下面是实现这一转换的JavaScript代码:

function arrayToTree(array) {
    const map = new Map();
    const tree = [];

    // 先将所有节点放入 map 中,方便查找
    array.forEach(item => {
        map.set(item.id, { ...item, children: [] });
    });

    // 再次遍历数组,构建树结构
    array.forEach(item => {
        const node = map.get(item.id);
        if (item.parentId === null) {
            tree.push(node);
        } else {
            const parent = map.get(item.parentId);
            if (parent) {
                parent.children.push(node);
            }
        }
    });

    return tree;
}

const array = [
    { id: 1, parentId: null, name: 'A' },
    { id: 2, parentId: 1, name: 'B' },
    { id: 3, parentId: 1, name: 'C' },
    { id: 4, parentId: 2, name: 'D' },
    { id: 5, parentId: 2, name: 'E' },
    { id: 6, parentId: 3, name: 'F' }
];

const tree = arrayToTree(array);
console.log(JSON.stringify(tree, null, 2));

解释

  1. 创建 Map:首先,我们将数组中的每个节点放入一个 Map 中,键为节点的 id,值为节点对象,并且初始化 children 数组为空。
  2. 构建树结构:再次遍历数组,对于每个节点:
    • 如果 parentIdnull,则该节点是根节点,将其添加到 tree 数组中。
    • 否则,找到其父节点,并将该节点添加到父节点的 children 数组中。

输出

运行上述代码后,tree 变量将包含转换后的树结构,输出结果如下:

[
  {
    "id": 1,
    "parentId": null,
    "name": "A",
    "children": [
      {
        "id": 2,
        "parentId": 1,
        "name": "B",
        "children": [
          {
            "id": 4,
            "parentId": 2,
            "name": "D",
            "children": []
          },
          {
            "id": 5,
            "parentId": 2,
            "name": "E",
            "children": []
          }
        ]
      },
      {
        "id": 3,
        "parentId": 1,
        "name": "C",
        "children": [
          {
            "id": 6,
            "parentId": 3,
            "name": "F",
            "children": []
          }
        ]
      }
    ]
  }
]

深度优先遍历(DFS)

深度优先遍历有三种常见的遍历顺序:前序遍历、中序遍历和后序遍历。这里以二叉树为例进行说明。

1. 前序遍历(Pre-order Traversal)

  • 访问根节点
  • 递归地前序遍历左子树
  • 递归地前序遍历右子树
function preOrderTraversal(node) {
    if (node === null) {
        return;
    }
    console.log(node.value); // 访问根节点
    preOrderTraversal(node.left); // 遍历左子树
    preOrderTraversal(node.right); // 遍历右子树
}

2. 中序遍历(In-order Traversal)

  • 递归地中序遍历左子树
  • 访问根节点
  • 递归地中序遍历右子树
function inOrderTraversal(node) {
    if (node === null) {
        return;
    }
    inOrderTraversal(node.left); // 遍历左子树
    console.log(node.value); // 访问根节点
    inOrderTraversal(node.right); // 遍历右子树
}

3. 后序遍历(Post-order Traversal)

  • 递归地后序遍历左子树
  • 递归地后序遍历右子树
  • 访问根节点
function postOrderTraversal(node) {
    if (node === null) {
        return;
    }
    postOrderTraversal(node.left); // 遍历左子树
    postOrderTraversal(node.right); // 遍历右子树
    console.log(node.value); // 访问根节点
}

广度优先遍历(BFS)

广度优先遍历通常使用队列来实现,从根节点开始,逐层访问所有节点。

function breadthFirstTraversal(root) {
    if (root === null) {
        return;
    }
    const queue = [root];
    while (queue.length > 0) {
        const node = queue.shift();
        console.log(node.value); // 访问当前节点
        if (node.left !== null) {
            queue.push(node.left); // 将左子节点加入队列
        }
        if (node.right !== null) {
            queue.push(node.right); // 将右子节点加入队列
        }
    }
}

示例树结构

假设我们有一个如下的二叉树结构:

    1
   / \
  2   3
 / \
4   5

对应的JavaScript对象表示如下:

const tree = {
    value: 1,
    left: {
        value: 2,
        left: { value: 4, left: null, right: null },
        right: { value: 5, left: null, right: null }
    },
    right: { value: 3, left: null, right: null }
};

测试遍历

preOrderTraversal(tree); // 输出: 1, 2, 4, 5, 3
inOrderTraversal(tree);  // 输出: 4, 2, 5, 1, 3
postOrderTraversal(tree); // 输出: 4, 5, 2, 3, 1
breadthFirstTraversal(tree); // 输出: 1, 2, 3, 4, 5

以上是树的深度优先遍历和广度优先遍历的实现方法。希望这些示例对你有所帮助。如果有任何疑问或需要进一步的解释,请随时提问。

PS:学会了记得,点赞,评论,收藏,分享