参考: 数组,链表,二叉树,这些是为了解决什么问题而出现的呢?
二叉树与 JavaScript
3 分钟理解完全二叉树、平衡二叉树、二叉查找树
树的作用:
数组的查找性能为O(1)
,插入删除为O(n)
链表的查找性能为O(n)
,插入删除为O(1)
树是数组与链表之间一个折中的办法:查找性能为O(logn)
,插入删除性能为O(logn)
二叉树的作用:
二叉树的前序遍历可以用来显示目录结构等;中序遍历可以实现表达式树,在编译器底层很有用;后序遍历可以用来实现计算目录内的文件及其信息等。
熟练使用二叉树在很多时候可以提升程序的运行效率,减少代码量,使程序更易读。
二叉树不仅是一种数据结构,也是一种编程思想。学好二叉树是程序员进阶的一个必然进程。
二叉树基本概念:
完全二叉树:所有叶子节点都在最后一层或者倒数第二层,且最后一层的叶子节点再左边连续,倒数第二层的叶子节点在右边连续。
(从上到下,从左到右数是没有中断的。一种数据比较有规律的树)
满二叉树:每一层都是满的,节点数为2^n-1 (n为层数)
满二叉树中的数字规律:
1.每一层的个数是2^(n-1)个,总节点数为 2^n-1 n为层数 (2^0 + 2^1 +2^2 =2^3-1)
2.一个节点的位置为i
,左节点位置为i*2+1
,右节点位置为i*2+1
二叉树的基本操作:
二叉树的构建:(数组转二叉树)
问题来源:101. 对称二叉树
[1,2,2,3,4,4,3]
转为二叉树的结构为:
1
/ \
2 2
/ \ / \
3 4 4 3
[1,2,2,null,3,null,3]
转为二叉树的结构为
1
/ \
2 2
\ \
3 3
利用完全二叉树叶子节点与根节点之间的数据关系:
父节点的序号为i
时(从0开始),左节点为i*2+1
,右节点为i*2+2
(完全二叉树可能最后一个父节点上没有右节点)
class TreeNode {
constructor (val) {
this.val = val
this.left = this.right = undefined
}
setLeft (left) {
this.left= left
return this // 为了链式调用
}
setRight (right) {
this.right = right
return this // 为了链式调用
}
}
class BinaryTree {
constructor () {
this.root = null
}
setRoot (node) {
this.root = node
}
getRoot () {
return this.root
}
arrayToBtree (arr) { // 从左到右广度遍历的数组,转二叉树(针对完全二叉树)
let nodeList = arr.map(item => new TreeNode(item))
this.setRoot(nodeList[0])
let i = 0
// 给存在右节点的节点添加两个节点
for (; i * 2 + 2 < arr.length; i++) {
let node = nodeList[i]
// left:i*2+1 right:i*2+2
node.setLeft(nodeList[i * 2 + 1]).setRight(nodeList[i * 2 + 2])
}
// 最后一个节点可能为左节点
if (i * 2 + 1 < arr.length) {
nodeList[i].setLeft(nodeList[i * 2 + 1])
}
}
}
let tree = new BinaryTree()
tree.arrayToBtree([0, 1, 2, 3, 4, 5, 6])
console.log(tree.getRoot())
遍历:
遍历的序是以根节点的位置为基准的
前序遍历:根左右
中序遍历:左根右
后序遍历:左右根
遍历是以一个子树为单位进行递归的遍历
所以上面这个二叉树的中序遍历流程为:
子树1(由于是子树所以继续递归)
节点1.1、节点1.2、节点1.3 3,1,4
子树1遍历完毕,遍历与1平级的2
节点2 0
2遍历完毕,遍历与之平级的子树3
节点3.1、3.2、3.3 5,2,6
前序:0, 1, 3, 4, 2, 5, 6
中序:3, 1, 4, 0, 5, 2, 6
后续: 3, 4, 1, 5, 6, 2, 0
// 前序遍历 (根左右)
preorderShow (result = []) {
result.push(this.val)
this.left&& this.left.preorder(result) // 如果有左节点,那么进行递归
this.right && this.right.preorder(result)
return result
}
// 中序遍历 (左根右)
inorderShow (result = []) {
this.left&& this.left.inorder(result)
result.push(this.val)
this.right && this.right.inorder(result)
return result
}
// 后续遍历 (左右根)
postorderShow (result = []) {
this.left&& this.left.postorder(result)
this.right && this.right.postorder(result)
result.push(this.val)
return result
}
查找:
// 前序查找
preorderSearch (val) {
if (this.val === val) { // 根节点找到
return this
}
let target = null
target = this.left&& this.left.preorderSearch(val) // 左子树可能找到,可能没找到
if (target !== undefined) {
return target
} else { // 左子树没找到找右子树
return this.right && this.right.preorderSearch(val)
}
}
// 中序查找
inorderSearch (val) {
let target = null
target = this.left&& this.left.inorderSearch(val) // 左子树可能找到,可能没找到
if (target !== undefined) {
return target
} else {
if (this.val === val) { // 左子树没找到找根节点
return this
} else { // 根节点没找到找右子树
return this.right && this.right.inorderSearch(val)
}
}
}
// 后续查找
postOrderSearch (val) {
let target = null
target = this.left&& this.left.postOrderSearch(val) // 左子树可能找到,可能没找到
if (target !== undefined) {
return target
} else {
target = this.right && this.right.postOrderSearch(val) // 左子树没找到找右子树
if (target !== undefined) {
return target
} else if (this.val === val) { // 右子树没找到找根节点
return this
}
}
}
删除:
删除一个节点就是将父节点对它的指向设置为undefined
所以在寻找节点并删除的遍历过程中,遍历父节点,判断父节点中是否有子节点需要删除
边界值:根节点没有父节点
只有在树中可以判断一个节点是否为根节点,所以对根节点的删除在树中处理。
BinaryTree:
delete (val) {
if (this.root === val) {
this.root = null
} else {
this.root.delete(val)
}
}
TreeNode:
delete (val) {
if (this.left&& this.left.val === val) {
this.left= undefined
return
}
if (this.right && this.right.val === val) {
this.right = undefined
return
}
this.left&& this.left.delete(val)
this.right && this.right.delete(val)
}
leetcode二叉树的对称:
根节点:判断左右两个子树是否对称
非根节点:对比两个子树是否对称,一层一层的递归判,判断根节点是否相等,再判断A1.left
,与A2.right
的子树是否对称。
当两棵树的根节点值不相等即可判断为不对称,终止递归
初步思路伪代码:
function isMirror(A1,A2) {
if(A1.val!== A2.val) { return false }
......
return isMirror(A1.left,A2.right) && isMirror(A1.right,A2.left)
}
改写前序遍历:
之前的前序遍历是写在TreeNode
中,递归的终止条件是.left
或者.right
为空
class TreeNode {
.
.
.
preorderShow (result = []) {
result.push(this.val)
this.left && this.left.preorderShow(result)
this.right && this.right.preorderShow(result)
return result
}
}
改写之后,TreeNode
自身没有遍历方法,而是将TreeNode
作为参数,此时的递归终止条件为传入的treeNode
为空
function preorderShow (treeNode, result = []) {
if (!treeNode) { return result }
result.push(treeNode.val)
preorderShow(treeNode.left, result)
preorderShow(treeNode.right, result)
return result
}
所以根据之前的思路以及第2种遍历方法的思想:判断两棵树相等的递归终止条件为两棵树都为空
判断两棵树不等的递归终止条件有有一颗为空,或都不为空但是值不相等
所以题解:
function isMirror (A1, A2) {
if (A1 == null && A2 == null) { return true }
if ((!A1 && A2) || (A1 && !A2) || A1.val !== A2.val) { return false }
return isMirror(A1.left, A2.right) && isMirror(A1.right, A2.left)
}
var isSymmetric = function (root) {
if(root == null) return true
return isMirror(root.left, root.right)
}
官方java题解:
以上涉及到的所有代码:
class TreeNode {
constructor (val) {
this.val = val
this.left = this.right = undefined
}
setLeft (left) {
this.left = left
return this // 为了链式调用
}
setRight (right) {
this.right = right
return this // 为了链式调用
}
// 前序遍历 (根左右)
preorderShow (result = []) {
result.push(this.val)
this.left && this.left.preorderShow(result)
this.right && this.right.preorderShow(result)
return result
}
// 中序遍历 (左根右)
inorderShow (result = []) {
this.left && this.left.inorderShow(result)
result.push(this.val)
this.right && this.right.inorderShow(result)
return result
}
// 后续遍历 (左右根)
postorderShow (result = []) {
this.left && this.left.postorderShow(result)
this.right && this.right.postorderShow(result)
result.push(this.val)
return result
}
// 前序查找
preorderSearch (val) {
if (this.val === val) { // 根节点找到
return this
}
let target = null
target = this.left && this.left.preorderSearch(val) // 左子树可能找到,可能没找到
if (target !== undefined) {
return target
} else { // 左子树没找到找右子树
return this.right && this.right.preorderSearch(val)
}
}
// 中序查找
inorderSearch (val) {
let target = null
target = this.left && this.left.inorderSearch(val) // 左子树可能找到,可能没找到
if (target !== undefined) {
return target
} else {
if (this.val === val) { // 左子树没找到找根节点
return this
} else { // 根节点没找到找右子树
return this.right && this.right.inorderSearch(val)
}
}
}
// 后续查找
postOrderSearch (val) {
let target = null
target = this.left && this.left.postOrderSearch(val) // 左子树可能找到,可能没找到
if (target !== undefined) {
return target
} else {
target = this.right && this.right.postOrderSearch(val) // 左子树没找到找右子树
if (target !== undefined) {
return target
} else if (this.val === val) { // 右子树没找到找根节点
return this
}
}
}
delete (val) {
if (this.left && this.left.val === val) {
this.left = undefined
return
}
if (this.right && this.right.val === val) {
this.right = undefined
return
}
this.left && this.left.delete(val)
this.right && this.right.delete(val)
}
}
class BinaryTree {
constructor () {
this.root = null
}
setRoot (node) {
this.root = node
}
getRoot () {
return this.root
}
arrayToBtree (arr) { // 从左到右广度遍历的数组,转二叉树(针对完全二叉树)
let nodeList = arr.map(item => new TreeNode(item))
this.setRoot(nodeList[0])
let i = 0
// 给存在右节点的节点添加两个节点
for (; i * 2 + 2 < arr.length; i++) {
let node = nodeList[i]
// left:i*2+1 right:i*2+2
node.setLeft(nodeList[i * 2 + 1]).setRight(nodeList[i * 2 + 2])
}
// 最后一个节点可能为左节点
if (i * 2 + 1 < arr.length) {
nodeList[i].setLeft(nodeList[i * 2 + 1])
}
}
delete (val) {
if (this.root === val) {
this.root = null
} else {
this.root.delete(val)
}
}
}
let tree = new BinaryTree()
tree.arrayToBtree([0, 1, 2, 3, 4, 5, 6])
console.log(tree.getRoot())
let root = tree.getRoot()
let preorder = root.preorderShow()
console.log(preorder) // [ 0, 1, 3, 4, 2, 5, 6 ]
let inorder = root.inorderShow()
console.log(inorder) // [ 3, 1, 4, 0, 5, 2, 6 ]
let postorder = root.postorderShow()
console.log(postorder) // [ 3, 4, 1, 5, 6, 2, 0 ]
console.log(root.preorderSearch(3))
console.log(root.preorderSearch(2))
console.log(root.inorderSearch(6))
console.log(root.inorderSearch(1))
console.log(root.postOrderSearch(5))
console.log(root.postOrderSearch(1))
console.log(root.postOrderSearch(8)) // undefined
tree.delete(2)
console.log(root.preorderShow()) // [ 0, 1, 3, 4 ]
// 前序遍历
function preorderShow (treeNode, result = []) {
if (!treeNode) { return result }
result.push(treeNode.val)
preorderShow(treeNode.left, result)
preorderShow(treeNode.right, result)
return result
}
console.log(preorderShow(root, [])) // [ 0, 1, 3, 4 ]
// 二叉树的对称
function isMirror (A1, A2) {
if (A1 == null && A2 == null) { return true }
if ((!A1 && A2) || (A1 && !A2) || A1.val !== A2.val) { return false }
return isMirror(A1.left, A2.right) && isMirror(A1.right, A2.left)
}
function isSymmetric (root) {
if (root == null) return true
return isMirror(root.left, root.right)
}
console.log(isSymmetric(root)) // false
二叉树进阶
红黑树、线段树、搜索树、B+树、语法树、哈夫曼树