小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
前两节介绍了栈、队列以及链表,不同于它们,树是非线性的。因为树决定了其存储的数据直接有明确的层级关系,因此对于维护具有层级特性的数据,树是一个天然良好的选择。
树具有以下特点:
- 除了根节点以外,所有的节点都有一个父节点;
- 每一个节点都可以有若干子节点,如果没有子节点,则称此节点为叶子节点;
- 一个节点所拥有的叶子节点的个数,称之为该节点的度,因此叶子节点的度为 0;
- 所有节点中,最大的度为整棵树的度;
- 树的最大层次称为树的深度。
二叉树是最基本的树,它的结构最简单,每个节点至多包含两个子节点。根据二叉树可以延伸出二叉搜索树、平衡二叉搜索树、红黑树等。因此,以下针对二叉树搜索树来讨论。
二叉搜索树的特点有:
- 左子树上所有结点的值均小于或等于它的根结点的值;
- 右子树上所有结点的值均大于或等于它的根结点的值;
- 左、右子树也分别为二叉搜索树。
构造节点类
class Node() {
constructor(data) {
this.left = null
this.right = null
this.value = data
}
}
insertNode:根据父节点,插入一个子节点
insertNode(root, newNode) {
// 根据待插入节点的值的大小,递归调用 insertNode
if(newNode.value < root.value) {
!root.left ? root.left = newNode : this.insertNode(root.left, newNode)
} else {
!root.right ? root.right = newNode : this.insertNode(root.right, newNode)
}
}
insert:插入一个新节点
insert(value) {
let newNode = new Node(value)
if(!this.root) {
this.root = newNode
} else {
this.insertNode(this.root, newNode)
}
}
insertNode 方法先判断目标父节点和插入节点的值,如果插入节点的值更小,则放在父节点的左边,递归调用 this.insertNode(root.left, newNode) ;如果插入节点的值更大,以此类推即可。而 insert 只是多了一步构造 Node 节点实例,然后区分有无父节点的情况,调用 this.insertNode 即可。
removeNode:根据一个父节点,移除一个子节点
removeNode(root, value) {
if(!root) {
return null
}
if(value < root.value) {
root.left = this.removeNode(root.left, value)
return root
} else if(value > root.value) {
root.right = this.removeNode(root.right, value)
return root
} else {
// root.value === value
if(!root.left && !root.right) { // 当前root无左右子节点
root = null
return root
}
if(root.left && !root.right) { // 当前root只有左节点
root = root.left
return root
} else if(root.right) { // 当前root只有右节点
root = root.right
return root
}
// 左右节点都有
let minRight = this.findMinNode(root.right)
root.value = minRight.value
root.right = this.removeNode(root.right, minRight.value)
return root
}
}
// 找最小的节点
findMinNode(root) {
if(!root.left) {
return root
} else {
return this.findMinNode(root.left)
}
}
searchNode:根据一个父节点,查找子节点
searchNode(root, value) {
if(!root) {
return null
}
if(value < root.value) {
return this.searchNode(root.left, value)
} else if(value > root.value) {
return this.searchNode(root.right, value)
}
return root
}
preOrder:前序遍历
preOrder(root) {
if(root) {
console.log(root.value)
this.preOrder(root.left)
this.preOrder(root.right)
}
}
inOrder:中序遍历
inOrder(root) {
if (root) {
this.inOrder(root.left)
console.log(root.value)
this.inOrder(root.right)
}
}
postOrder:后序遍历
postOrder(root) {
if (root) {
this.postOrder(root.left)
this.postOrder(root.right)
console.log(root.value)
}
}
上述前、中、后序遍历的区别其实就在于 console.log(root.value) 方法执行的位置。
最后说一句
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。