源码地址
二叉树
二叉树是 n(n>=0) 个节点的有限集合,该集合或者为空集(空二叉树),或者一由一个根节点和两颗互不相交的、分别成为根节点的左子树和右子树的二叉树组成
特点
- 每个节点最多由两颗子树,所以二叉树不存在度大于2的节点
- 左子树根右子树是有顺序的,次序不能随意颠倒
- 即使树中某个节点只有一棵子树,也要区分是左子树还是右子树
形态
- 空二叉树
- 只有一个根节点
- 根节点只有左子树
- 根节点只有右子树
- 根节点既有左子树也有右子树
特殊的二叉树
- 斜树
- 左斜树
- 右斜树
- 满二叉树
- 如果所有的分支节点都有左子树根右子树,并且所有的叶子结点都在同一层
- 完全二叉树
- 对一个具有n个节点的二叉树进行层序编号,刚好编号为i的节点与同样深度的满二叉树中编号i的节点的位置完全相同
- 叶子节点只能出现在最下两层
- 最下层的叶子节点一定集中在左边
- 倒数二层如果有叶子结点,一定都在右边
- 如果节点度为1,则该节点只有左孩子,不存在右子树
- 同样节点数的二叉树,完全二叉树的深度最小
性质
- 第 i 层上至多有 2^i-1 个节点 ( i>=1 )
- 深度为 k 的二叉树最多有 2^k - 1 个节点 ( k>=1 )
- 终端节点 n0 = (度为2的节点数)n2 + 1
- 具有N个节点的完全二叉树的深度为 log2N + 1
- 具有N个节点的完全二叉树按层序编号,如果 i 为 1则节点为根节点 如果 2i > n 则节点 i 没有左孩子 如果 2i + 1 > n 则 i 没有右孩子
树的遍历
深度优先遍历
前序遍历
根节点 -> 左子树 -> 右子树
若二叉树为空,则空操作返回,否则先访问根节点,然后遍历左子树,在遍历右子树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const preOrderTraversal = function(treeNode, res=[]) { if(!treeNode) return [] res.push(treeNode.data) preOrderTraversal(treeNode.leftChild, res) preOrderTraversal(treeNode.rightChild, res) return res }
const preOrderTraversalByStack = function(treeNode) { if(!treeNode) return [] let stack = [] let node = treeNode let res = [] while (node !== null || stack.length > 0) { while (node !== null){ stack.push(node) res.push(node.data) node = node.leftChild } if(stack.length > 0) { node = stack.pop() node = node.rightChild } } return res }
|
中序遍历
左子树 -> 根节点 -> 右子树
若二叉树为空,则空操作返回,否则先访问左子树,然后遍历根节点,在遍历右子树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const inOrderTraversal = function(treeNode, res=[]) { if(!treeNode) return [] inOrderTraversal(treeNode.leftChild, res) res.push(treeNode.data) inOrderTraversal(treeNode.rightChild, res) return res }
const inOrderTraversalByStack = function(treeNode) { if(!treeNode) return [] let stack = [] let node = treeNode let res = [] while (node !== null || stack.length > 0) { while (node !== null){ stack.push(node) node = node.leftChild } if(stack.length > 0) { node = stack.pop() res.push(node.data) node = node.rightChild } } return res }
|
后序遍历
左子树 -> 右子树 -> 根节点
若二叉树为空,则空操作返回,否则先访问左子树,然后遍历右子树,在遍历根节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const postOrderTraversal = function(treeNode, res=[]) { if(!treeNode) return [] postOrderTraversal(treeNode.leftChild, res) postOrderTraversal(treeNode.rightChild, res) res.push(treeNode.data) return res }
const postOrderTraversalByStack = function(treeNode) { if(!treeNode) return [] let stack = [treeNode] let curr = treeNode let last = treeNode let res = [] while(stack.length > 0){ curr = stack[stack.length - 1] if(curr.leftChild != null && last != curr.leftChild && last != curr.rightChild){ stack.push(curr.leftChild) }else if(curr.rightChild != null && last != curr.rightChild){ stack.push(curr.rightChild) }else{ stack.pop() res.push(curr.data) last = curr } } return res }
|
广度优先遍历
层序遍历
第一层 -> 第二层 -> 第三层
若二叉树为空,则空操作返回,否则先访问树的第一层,然后从上而下组层遍历,从左往右
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const levelOrderTraversal = function(treeNode) { if(!treeNode) return [] const queue = new Queue() queue.enqueue(treeNode) let res = [] while (!queue.isEmpty()) { let node = queue.dequeue() res.push(node.data) if(node.leftChild != null) { queue.enqueue(node.leftChild) } if(node.rightChild != null) { queue.enqueue(node.rightChild) } } return res }
|
生成树
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const TreeNode = function(data) { this.data = data this.leftChild = null this.rightChild = null }
const createBinaryTree = function(list){ let node = null if(list == null || list.length == 0) return null let data = list.shift() if(data) { node = new TreeNode(data) node.leftChild = createBinaryTree(list, node.leftChild) node.rightChild = createBinaryTree(list, node.rightChild) } return node }
|
线索二叉树
指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就被称为线索二叉树
原理
把二叉树进行中序遍历之后,把所有空指针中rightChild指向它的后继节点,把leftChild指向前驱节点,这个过程称为线索化。需要通过 ltag 跟 rtag 的值 0/1 来区分是左右孩子还前驱后继
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let pre = null const ThreadedInOrderTraversal = function(treeNode, res=[]) { if(!treeNode) return [] ThreadedInOrderTraversal(treeNode.leftChild, res) if(!treeNode.leftChild){ treeNode.lTag = 1 treeNode.leftChild = pre } res.push(treeNode.data) if(pre && !pre.rightChild){ pre.rTag = 1 pre.rightChild = treeNode } pre = treeNode ThreadedInOrderTraversal(treeNode.rightChild, res) return res }
|
树转换为二叉树
- 加线。在所有兄弟节点之间加一条连线
- 去线。对树中对每个节点,只保留它与第一个孩子节点对连线,删除它与其他孩子子节点之间对连线
- 以树的根节点为轴心,顺时针旋转一定角度,使其结构分明
森林转换成二叉树
- 把每棵树都转化成二叉树
- 第一颗二叉树不动,从第二颗二叉树开始,依次把后一颗二叉树对根节点作为前一颗二叉树的根节点的右孩子,用线连起来
二叉树转换成树
- 加线。若某节点的左孩子存在,将左孩子的右孩子节点、右孩子的右孩子节点…,都连线
- 去掉原二叉树中所有节点与其右孩子节点的连线
- 旋转调整层次
二叉树转换成森林
- 从根节点开始,若右孩子存在,将右孩子节点的连线删除,再查看分离后的二叉树,若右孩子存在,则接着删除,直到都删除感觉
- 再将分离后的二叉树转换成树