预备工作
先写几个函数,能够生成树状结构的数据
生成随机数据
// 支持string、number、boolean和object类型随机数据
const randomData = {
get string() {
return Math.random().toString(36).slice(2, 6)
},
get number() {
return (Math.random() * 10000) | 0
},
get boolean() {
return !((Math.random() * 2) | 0)
},
get object() {
return {
id: this.number,
name: this.string,
}
},
}
用 randomData[类型] 调用即可。
生成随机多叉树
function randomTree(n, type = 'string') {
let root = {}, arr = []
for (let i = 0; i < n; i++) {
const node = { element: randomData[type], children: [] }
const parent = arr[(Math.random() * arr.length) | 0]
parent ? parent.children.push(node) : (root = node)
arr.push(node)
}
return root
}
生成随机二叉树
function randomBinaryTree(n, type = 'string') {
let root = {}, arr = []
for (let i = 0; i < n; i++) {
const node = { element: randomData[type], left: null, right: null }
let parent = arr[(Math.random() * arr.length) | 0]
if (parent) {
const child = (Math.random() * 2) | 0 ? 'left' : 'right'
if (parent[child]) while ((parent = parent[child])[child]);
parent[child] = node
} else root = node
arr.push(node)
}
return root
}
多叉树的遍历
节点结构
function Node(element) {
this.element = element
this.children = []
}
深度优先遍历
递归算法
递归实现的思路比较简单,就是先取根节点,然后对其子节点递归遍历即可:
function depthFirstTraversalRecursion(root, arr = []) {
if (!root) return arr
const { element, children } = root
arr.push(element)
children.forEach((child) => depthFirstTraversal(child, arr))
return arr
}
非递归算法
非递归要用到栈这种数据结构,每个出栈的节点,要对其子节点按照后进先出的原则入栈:
function depthFirstTraversal(root, arr = []) {
if (!root) return arr
let stack = [root], current
while ((current = stack.pop())) {
const { element, children } = current
arr.push(element)
for (let i = children.length - 1; i >= 0; i--) stack.push(children[i])
}
return arr
}
广度优先遍历
也就是层序遍历,这里要用到队列这种数据结构,先进先出
function breadthFirstTraversal(root, arr = []) {
if (!root) return
let queue = [root], current
while ((current = queue.shift())) {
const { element, children } = current
arr.push(element)
queue.push(...children)
}
return arr
}
结果验证
使用上面写好的随机函数 randomTree(6) 生成 6 个节点的树:
{
element: 'w8bs',
children: [
{
element: 'dbdd',
children: [ { element: 'yt4s', children: [] } ]
},
{
element: 'tgi8',
children: [ { element: 'gtf3', children: [] } ]
},
{ element: 'ixli', children: [] }
]
}
运行上面的代码,输出结果如下,与预期一致:
深度优先遍历(递归) [ 'w8bs', 'dbdd', 'yt4s', 'tgi8', 'gtf3', 'ixli' ]
深度优先遍历 [ 'w8bs', 'dbdd', 'yt4s', 'tgi8', 'gtf3', 'ixli' ]
广度优先遍历 [ 'w8bs', 'dbdd', 'tgi8', 'ixli', 'yt4s', 'gtf3' ]
二叉树的遍历
节点结构
function Node(element) {
this.element = element
this.left = null
this.right = null
}
前/中/后序遍历
递归算法
递归算法比较直观,前中后序遍历只要调整一下顺序即可:
function traversal(root, type, arr = []) {
if (!root) return arr
const { element, left, right } = root
switch (type) {
case 'preorder':
arr.push(element)
traversal(left, type, arr)
traversal(right, type, arr)
break
case 'inorder':
traversal(left, type, arr)
arr.push(element)
traversal(right, type, arr)
break
case 'postorder':
traversal(left, type, arr)
traversal(right, type, arr)
arr.push(element)
break
}
return arr
}
非递归算法
同样是用到了栈结构,操作元素多次出入栈,需要增加一个辅助属性 _visited 来区分是否曾经进过栈或访问过,每个节点,只有被第二次访问的时候才进行打印:
function traversal(root, type, arr = []) {
if (!root) return arr
let stack = [root], current
while ((current = stack.pop())) {
const { element, left, right, _visited } = current
if (!_visited) {
current._visited = true
switch (type) {
case 'preorder':
if (right) stack.push(right)
if (left) stack.push(left)
stack.push(current)
break
case 'inorder':
if (right) stack.push(right)
stack.push(current)
if (left) stack.push(left)
break
case 'postorder':
stack.push(current)
if (right) stack.push(right)
if (left) stack.push(left)
break
}
} else {
arr.push(element)
delete current._visited
}
}
return arr
}
层序遍历
与多叉树的类似,也是使用了队列这种数据结构:
function levelOrderTraversal(root, arr = []) {
if (!root) return arr
let queue = [root], current
while ((current = queue.shift())) {
const { element, left, right } = current
arr.push(element)
if (left) queue.push(left)
if (right) queue.push(right)
}
return arr
}
结果验证
使用上面写好的随机函数 randomBinaryTree(6) 生成 6 个节点的二叉树:
{
element: 'a8kk',
left: {
element: '7n9z',
left: {
element: 'k0pb',
left: { element: 'hxk0', left: null, right: null },
right: null
},
right: null
},
right: {
element: 'da0r',
left: null,
right: { element: 'mgr1', left: null, right: null }
}
}
分别调用上面的递归与非递归遍历算法,得到的结果与预期一致:
前序遍历(递归) [ 'a8kk', '7n9z', 'k0pb', 'hxk0', 'da0r', 'mgr1' ]
中序遍历(递归) [ 'hxk0', 'k0pb', '7n9z', 'a8kk', 'da0r', 'mgr1' ]
后续遍历(递归) [ 'hxk0', 'k0pb', '7n9z', 'mgr1', 'da0r', 'a8kk' ]
前序遍历 [ 'a8kk', '7n9z', 'k0pb', 'hxk0', 'da0r', 'mgr1' ]
中序遍历 [ 'hxk0', 'k0pb', '7n9z', 'a8kk', 'da0r', 'mgr1' ]
后序遍历 [ 'hxk0', 'k0pb', '7n9z', 'mgr1', 'da0r', 'a8kk' ]