树形结构:有向无环图 树是图的一种
树形结构有一个根节点
没有回路
根节点:A
叶子节点:下面没有其他节点
节点:既不是根节点,也不是叶子节点的 普通节点
树的度:树 中有最多叉的节点有多少个插叉,这棵树的度就为多少
树的深度:树最深有几层 深度就为几
树的度最多为2的树形结构
满二叉树:
- 所有叶子节点都在最底层
- 每个风叶子节点都有两个子节点
国内定义:
- 叶子节点都在最后一层或倒数第二层
- 叶子节点都向左聚拢
国际定义:
- 叶子节点都在最后一层或倒数第二层
- 如果有叶子节点,则必然有两个叶子节点
在二叉树中,每个节点都认为自己是根节点
子树:二叉树中,每一个节点或叶子节点,都是一棵子树的根节点
左子树、右子树:
传递二叉树要传根节点
前序遍历:先根次序遍历 前->左->右 前:当前
中序遍历:先根次序遍历 左->前->右
后序遍历:先根次序遍历 左->右->前
function Node (value) {
this.value = value
this.left = null
this.right = null
}
var a = new Node('a')
var b = new Node('b')
var c = new Node('c')
var d = new Node('d')
var e = new Node('e')
var f = new Node('f')
var g = new Node('g')
a.left = c
a.right = b
b.left = d
b.right = e
c.left = f
c.right = g
function getTree (node) {
if (node == null) return
console.log(node.value)
getTree(node.left)
getTree(node.right)
}
getTree(a)
function getTree (node) {
if (node == null) return
getTree(node.left)
console.log(node.value)
getTree(node.right)
}
getTree(a)
function getTree (node) {
if (node == null) return
getTree(node.left)
getTree(node.right)
console.log(node.value)
}
getTree(a)
准备一下,前序、中序、后续遍历数组,以及创建树节点函数
function changeToLowerCase (item) {
return Array.from(item).map(o => o.toLowerCase())
}
//懒得写小写数组了,转换了一下,可直接代入原始小写数组哦
var arr1 = changeToLowerCase('ACFGBDE')//arr1 (7) ['a', 'c', 'f', 'g', 'b', 'd', 'e']
var arr2 = changeToLowerCase('FCGADBE')//arr2 (7) ['f', 'c', 'g', 'a', 'd', 'b', 'e']
var arr3 = changeToLowerCase('FGCDEBA')//arr3 (7) ['f', 'g', 'c', 'd', 'e', 'b', 'a']
function Node (value) {
this.value = value
this.left = null
this.right = null
}
function restore (arr1, arr2) {
if (arr1 == null || arr2 == null || arr1.length == 0 || arr2.length == 0 || arr1.length !== arr2.length) return null
var root = new Node(arr1[0])//先确定根节点
var index = arr2.indexOf(root.value)//找到根节点在中序遍历数组中的位置索引
var arr1Left = arr1.slice(1, 1 + index)//前序遍历左子树
var arr1Right = arr1.slice(1 + index, arr1.length)//前序遍历右子树
var arr2Left = arr2.slice(0, index)//中序遍历左子树
var arr2Right = arr2.slice(index + 1, arr2.length)//中序遍历右子树
root.left = restore(arr1Left, arr2Left)//根据左子树的前序和中序 还原左子树 并赋值给root.left
root.right = restore(arr1Right, arr2Right)//根据右子树的前序和中序 还原右子树 并赋值给root.right
return root
}
console.log(restore(arr1, arr2))
function restore (arr2, arr3) {
if (arr3 == null || arr2 == null || arr3.length == 0 || arr2.length == 0 || arr3.length !== arr2.length) return null
var root = new Node(arr3[arr3.length - 1])//先确定根节点
var index = arr2.indexOf(root.value)//找到根节点在后序遍历数组中的位置索引
var arr2Left = arr2.slice(0, index)//中序遍历左子树
var arr2Right = arr2.slice(index + 1, arr2.length)//中序遍历右子树
var arr3Left = arr3.slice(0, index)//后序遍历左子树
var arr3Right = arr3.slice(index, arr3.length - 1)//后序遍历右子树
root.left = restore(arr2Left, arr3Left)//根据左子树的后序和中序 还原左子树 并赋值给root.left
root.right = restore(arr2Right, arr3Right)//根据右子树的后序和中序 还原右子树 并赋值给root.right
return root
}
console.log(restore(arr2, arr3))
树的搜索、图的搜索、爬虫的逻辑、搜索引擎的爬虫算法
//对于二叉树来说,深度优先搜索 和 前序遍历顺序是一样的
function deepSearch (root, target) {
if (root == null) return false
if (root.value == target) return true
var left = deepSearch(root.left, target)
var right = deepSearch(root.right, target)
return left || right
}
console.log(deepSearch(a, 'd'))//true
function breadthSearch (rootList, target) {
if (rootList == null || rootList.length == 0) return false
var childList = []//当前层所有节点的子节点,都在这个list中,这样传入下一层级时候,就可以遍历整个层级的集合
for (let i = 0; i < rootList.length; i++) {
console.log(rootList[i])
if (rootList[i] != null && rootList[i].value == target) {
return true
} else {
childList.push(rootList[i].left)
childList.push(rootList[i].right)
}
}
return breadthSearch(childList, target)
}
console.log(breadthSearch([a], 'c'))//true
有点问题,需要改进。如果传入target为“n”,则报错
后续待优化
var a1 = new Node('a')
var b1 = new Node('b')
var c1 = new Node('c')
var d1 = new Node('d')
var e1 = new Node('e')
var f1 = new Node('f')
var g1 = new Node('g')
a1.left = c1
a1.right = b1
b1.left = d1
b1.right = e1
c1.left = f1
c1.right = g1
function compare (root1, root2) {
if (root1 == root2) return true//是同一棵树
if (root1 == null || root2 == null) return false
if (root1.value != root2.value) return false//相同位置值不相等
var leftBool = compare(root1.left, root2.left)//判断左子树是否相等
var rightBool = compare(root1.right, root2.right)//判断右子树是否相等
return leftBool && rightBool
}
console.log(compare(a, a1))//true
遇到二叉树比较问题时,必须要确定,左右两棵子树如果交换位置,即左右互换,算不算同一棵二叉树
默认互换后不是同一棵
面试时问 确定规则
function compare (root1, root2) {
if (root1 == root2) return true//是同一棵树
if (root1 == null || root2 == null) return false
if (root1.value != root2.value) return false//相同位置值不相等
var leftBool = compare(root1.left, root2.left)//判断左子树是否相等
var rightBool = compare(root1.right, root2.right)//判断右子树是否相等
//左右可互换情况
var leftBool1 = compare(root1.left, root2.right)//判断左子树与右子树是否相等
var rightBool1 = compare(root1.right, root2.left)//判断右子树与左子树是否相等
return leftBool && rightBool || leftBool1 && rightBool1
}
console.log(compare(a, a1))//true
如图所示,比较以下两棵树
var a1 = new Node('a')
var c1 = new Node('c')
var e1 = new Node('e')
var f1 = new Node('f')
var g1 = new Node('g')
var x1 = new Node('x')
var z1 = new Node('z')
a1.left = c1
a1.right = x1
x1.right = e1
c1.left = f1
c1.right = g1
g1.right = z1
// 新增了什么 修改了什么 删除了什么
var diffList = []
function difftree (root1, root2, diffList) {
if (root1 == root2) return diffList
if (root1 == null && root2 != null) {//新增了节点
diffList.push({ type: '新增', origin: null, now: root2 })
} else if (root1 != null && root2 == null) {//删除了节点
diffList.push({ type: '删除', origin: root1, now: null })
} else if (root1.value != root2.value) {//相同的位置节点值不同,修改了节点
diffList.push({ type: '修改', origin: root1, now: root2 })//修改后也要走diffTree 因为修改当前节点 不代表所有子节点都变了
difftree(root1.left, root2.left, diffList)
difftree(root1.right, root2.right, diffList)
} else {
difftree(root1.left, root2.left, diffList)
difftree(root1.right, root2.right, diffList)
}
return diffList
}
console.log(difftree(a, a1, diffList))
白的也一样,那个方便看那个~
首先 这是一棵搜索树
其次 有排序的效果,左子树的节点都比当前节点小,右子树的节点都比当前节点大
//制作一个随机数组
var arr = []
for (let i = 0; i < 10000; i++) {
arr[i] = Math.floor(Math.random() * 10000)
}
var num = 0
function search (arr, target) {
for (let i = 0; i < arr.length; i++) {
num += 1
if (arr[i] == target) return true
}
return false
}
function Node (value) {
this.value = value
this.right = null
this.left = null
}
function addNode (root, num) {
if (root == null) return
if (root.value == num) return
if (root.value < num) {//目标值比当前节点大
if (root.right == null) root.right = new Node(num)//右侧为空 则创建新节点 将目标值放在该位置
else {
addNode(root.right, num)//右侧不为空 则向右侧进行递归
}
} else {//目标值比当前节点小
if (root.left == null) {
root.left = new Node(num)
}
else {
addNode(root.left, num)
}
}
}
function buildSearchTree (arr) {
if (arr == null || arr.length === 0) return null
var root = new Node(arr[0])
for (let i = 1; i < arr.length; i++) {
addNode(root, arr[i])
}
return root
}
var num2 = 0
function searchByTree (root, target) {
if (root == null) return false
num2 += 1
if (root.value == target) return true
if (root.value > target) {
return searchByTree(root.left, target)
} else {
return searchByTree(root.right, target)
}
}
console.log("普通for循环搜索", search(arr, 2344), num)
var root1 = buildSearchTree(arr)
console.log(root1)
console.log("二叉树搜索", searchByTree(root1, 2344), num2)
平衡二叉树
function Node (value) {
this.value = value
this.left = null
this.right = null
}
var a = new Node('a')
var b = new Node('b')
var c = new Node('c')
var d = new Node('d')
var e = new Node('e')
var f = new Node('f')
var g = new Node('g')
var h = new Node('F')
var j = new Node('J')
a.left = b
a.right = c
b.left = d
b.right = e
c.left = f
c.right = g
d.right = h
e.right = j
function getDeep (root) {
if (root == null) return 0
var leftDeep = getDeep(root.left)
var rightDeep = getDeep(root.right)
return Math.max(leftDeep, rightDeep) + 1//算出左右子树层深后再加当前一层
}
function isBalance (root) {
if (root == null) return true
var leftDeep = getDeep(root.left)
var rightDeep = getDeep(root.right)
if (Math.abs(leftDeep - rightDeep) > 1)//绝对值大于1 则不平衡
{
return false
} else {
return isBalance(root.left) && isBalance(root.right)
}
}
console.log(isBalance(a))//false
某一节点不平衡
如果左边浅,右边深,进行左单旋
左单旋时
- 旋转节点:当前不平衡的节点
- 新根:右子树的根节点
- 变化分支:旋转节点的右子树的左子树
- 不变分支:旋转节点的右子树的右子树
- 旋转节点:不平衡的节点为旋转节点(2)
- 新根:旋转之后称为根节点的节点(5)
- 变化分支:父级节点发生变化的分支(3)
- 不变分支:父级节点不发生变化的分支(6)
to do list
右单旋时
- 旋转节点:当前不平衡的节点
- 新根:左子树的根节点
- 变化分支:旋转节点的左子树的右子树
- 不变分支:旋转节点的左子树的左子树
to do list
function Node (value) {
this.value = value
this.left = null
this.right = null
}
var node2 = new Node('2')
var node5 = new Node('5')
var node3 = new Node('3')
var node6 = new Node('6')
node2.right = node5
node5.left = node3
node5.right = node6
function change (root) {
// 返回平衡之后的根节点
if (isBalance(root)) return root
if (root.left != null) root.left = change(root.left)
if (root.right != null) root.right = change(root.right)
var leftDeep = getDeep(root.left)
var rightDeep = getDeep(root.right)
if (Math.abs(leftDeep - rightDeep) < 2) {
return true
} else if (leftDeep > rightDeep) {//不平衡 左边深 右旋
return rightRotate(root)
} else {//不平衡 右边深 左旋
return leftRotate(root)
}
}
function leftRotate (root) {
// 找到新根
var newRoot = root.right
// 找到变化分支
var changeTree = root.right.left
// 当前旋转节点的右孩子为变化分支
root.right = changeTree
// 新根的左孩子为旋转节点
newRoot.left = root
// 返回新的根节点
return newRoot
}
function rightRotate (root) {
// 找到新根
var newRoot = root.left
// 找到变化分支
var changeTree = root.left.right
// 当前旋转节点的左孩子为变化分支
root.left = changeTree
// 新根的右孩子为旋转节点
newRoot.right = root
// 返回新的根节点
return newRoot
}
function getDeep (root) {
if (root == null) return 0
var leftDeep = getDeep(root.left)
var rightDeep = getDeep(root.right)
return Math.max(leftDeep, rightDeep) + 1//算出左右子树层深后再加当前一层
}
function isBalance (root) {
if (root == null) return true
var leftDeep = getDeep(root.left)
var rightDeep = getDeep(root.right)
if (Math.abs(leftDeep - rightDeep) > 1)//绝对值大于1 则不平衡
{
return false
} else {
return isBalance(root.left) && isBalance(root.right)
}
}
console.log("node2是否为平衡二叉树", isBalance(node2))
var newRoot = change(node2)
console.log("新的二叉树是", newRoot)
console.log("新的二叉树是否为平衡二叉树", isBalance(newRoot))
当要对某个节点进行左单旋时,如果变化是唯一的最深分支 ,那么我们要对新根进行右单旋 然后再左单旋 这样的旋转叫右左双旋
当要对某个节点进行右单旋时,如果变化是唯一的最深分支,那么我们要对新根进行左单旋 然后 进行右单旋,这样的旋转叫做左右双旋
function change (root) {
// 返回平衡之后的根节点
if (isBalance(root)) return root
if (root.left != null) root.left = change(root.left)
if (root.right != null) root.right = change(root.right)
var leftDeep = getDeep(root.left)
var rightDeep = getDeep(root.right)
if (Math.abs(leftDeep - rightDeep) < 2) {
return true
} else if (leftDeep > rightDeep) {//不平衡 左边深 右旋
var changeTreeDeep = getDeep(root.left.right) //获取变化分支深度
var noChangeTreeDeep = getDeep(root.left.left)//获取不变化分支深度
if (changeTreeDeep > noChangeTreeDeep) {
root.left = leftRotate(root.left)
}
return rightRotate(root)
} else {//不平衡 右边深 左旋
var changeTreeDeep = getDeep(root.right.left) //获取变化分支深度
var noChangeTreeDeep = getDeep(root.right.right)//获取不变化分支深度
if (changeTreeDeep > noChangeTreeDeep) {
root.right = rightRotate(root.right)
}
return leftRotate(root)
}
}
function Node (value) {
this.value = value
this.left = null
this.right = null
}
var node8 = new Node('8')
var node7 = new Node('7')
var node6 = new Node('6')
var node5 = new Node('5')
var node2 = new Node('2')
node8.left = node7
node7.left = node6
node6.left = node5
node5.left = node2
function change (root) {
// 返回平衡之后的根节点
if (isBalance(root)) return root
if (root.left != null) root.left = change(root.left)
if (root.right != null) root.right = change(root.right)
var leftDeep = getDeep(root.left)
var rightDeep = getDeep(root.right)
if (Math.abs(leftDeep - rightDeep) < 2) {
return true
} else if (leftDeep > rightDeep) {//不平衡 左边深 右旋
var changeTreeDeep = getDeep(root.left.right) //获取变化分支深度
var noChangeTreeDeep = getDeep(root.left.left)//获取不变化分支深度
if (changeTreeDeep > noChangeTreeDeep) {
root.left = leftRotate(root.left)
}
return rightRotate(root)
} else {//不平衡 右边深 左旋
var changeTreeDeep = getDeep(root.right.left) //获取变化分支深度
var noChangeTreeDeep = getDeep(root.right.right)//获取不变化分支深度
if (changeTreeDeep > noChangeTreeDeep) {
root.right = rightRotate(root.right)
}
return leftRotate(root)
}
}
function leftRotate (root) {
// 找到新根
var newRoot = root.right
// 找到变化分支
var changeTree = root.right.left
// 当前旋转节点的右孩子为变化分支
root.right = changeTree
// 新根的左孩子为旋转节点
newRoot.left = root
// 返回新的根节点
return newRoot
}
function rightRotate (root) {
// 找到新根
var newRoot = root.left
// 找到变化分支
var changeTree = root.left.right
// 当前旋转节点的左孩子为变化分支
root.left = changeTree
// 新根的右孩子为旋转节点
newRoot.right = root
// 返回新的根节点
return newRoot
}
function getDeep (root) {
if (root == null) return 0
var leftDeep = getDeep(root.left)
var rightDeep = getDeep(root.right)
return Math.max(leftDeep, rightDeep) + 1//算出左右子树层深后再加当前一层
}
function isBalance (root) {
if (root == null) return true
var leftDeep = getDeep(root.left)
var rightDeep = getDeep(root.right)
if (Math.abs(leftDeep - rightDeep) > 1)//绝对值大于1 则不平衡
{
return false
} else {
return isBalance(root.left) && isBalance(root.right)
}
}
console.log("node2是否为平衡二叉树", isBalance(node8))
var newRoot = change(node8)
console.log("新的二叉树是", newRoot)
console.log("新的二叉树是否为平衡二叉树", isBalance(newRoot))
这个时候其实二叉树还是不平衡
function change (root) {
// 返回平衡之后的根节点
if (isBalance(root)) return root
if (root.left != null) root.left = change(root.left)
if (root.right != null) root.right = change(root.right)
var leftDeep = getDeep(root.left)
var rightDeep = getDeep(root.right)
if (Math.abs(leftDeep - rightDeep) < 2) {
return root
} else if (leftDeep > rightDeep) {//不平衡 左边深 右旋
var changeTreeDeep = getDeep(root.left.right) //获取变化分支深度
var noChangeTreeDeep = getDeep(root.left.left)//获取不变化分支深度
if (changeTreeDeep > noChangeTreeDeep) {
root.left = leftRotate(root.left)
}
var newRoot = rightRotate(root)
newRoot.right = change(root.right)
newRoot = change(newRoot)//右侧分支对自己再做一次平衡
return newRoot
} else if (leftDeep < rightDeep) {//不平衡 右边深 左旋
var changeTreeDeep = getDeep(root.right.left) //获取变化分支深度
var noChangeTreeDeep = getDeep(root.right.right)//获取不变化分支深度
if (changeTreeDeep > noChangeTreeDeep) {
root.right = rightRotate(root.right)
}
var newRoot = leftRotate(root)
newRoot.left = change(newRoot.left)
newRoot = change(newRoot)
return newRoot
}
return root
}
现在变成二叉平衡排序树