二叉树不是树的特殊情况,完全是两个概念。因为二叉树子树有左右之分而树没有,并且不可颠倒顺序
2^(i-1)
2^(k) - 1
n = m + 1
空二叉树,只有一个根节点,只有一个根节点和左子树,只有一个根节点和右子树,一个根节点和左右两个子树
2^(k) - 1
个节点的二叉树称为满二叉树[log n] + 1
P82(1) p84(2);常见的存储方式是顺序存储-----数组(不推荐)和链式存储------链表(推荐),一般我们使用的是用链表来存储二叉树,每个节点由三部分组成(二叉链表):左子节点的引用,右子节点的引用,自己本身的数据
遍历
先序遍历
访问根节点
先序遍历其左子树
先序遍历其右子树
中序遍历
中序遍历其左子树
访问根节点
中序遍历其右子树
后序遍历
层次遍历
// 先序遍历 BinarySearchTree.prototype.preOrderTraversal = (node, handle) => { if (node === null) return console.log('此二叉树为空') handle(node) if (node.leftChild) this.preOrderTraversal(node.leftChild, handle) if (node.rightChild) this.preOrderTraversal(node.rightChild, handle) } // 中序遍历 BinarySearchTree.prototype.midOrderTraversal = (node, handle) => { if (node === null) return console.log('此二叉树为空') if (node.leftChild) this.midOrderTraversal(node.leftChild, handle) handle(node) if (node.rightChild) this.midOrderTraversal(node.rightChild, handle) } // 后序遍历 BinarySearchTree.prototype.postOrderTraversal = (node, handle) => { if (node === null) return console.log('此二叉树为空') if (node.leftChild) this.postOrderTraversal(node.leftChild, handle) if (node.rightChild) this.postOrderTraversal(node.rightChild, handle) handle(node) }
function BinarySearchTree() { // 1. 相关属性和方法 /** * 根节点 */ this.root = null /** * @name: Node * @msg: 生成节点函数 * @param {*} key 键值 * @param {*} value 值 * @return {*} */ function Node(key, value) { this.key = key this.value = value /** * 左子节点 */ this.leftChild = null /** * 右子节点 */ this.rightChild = null } // 插入节点 BinarySearchTree.prototype.insert = (key, value) => { const newNode = new Node(key, value) const insertNode = (newNode, oldNode) => { const {key, leftChild, rightChild} = oldNode if (newNode.key < key) { if (leftChild === null) { oldNode.leftChild = newNode } else { insertNode(newNode, oldNode.leftChild) } } else { if (rightChild === null) { oldNode.rightChild = newNode } else { insertNode(newNode, oldNode.rightChild) } } } if (this.root === null) { this.root = newNode } else { insertNode(newNode, this.root) } } // 获得节点和其父节点 BinarySearchTree.prototype.getNode = key => { let parentNode = this.root const search = (key, node) => { if (node === null) return null if (node.key === key) return node parentNode = node if (key > node.key) return search(key, node.rightChild) if (key <= node.key) return search(key, node.leftChild) } return { currentNode: search(key, this.root), parentNode, } } //查询二叉树是否有某个节点,这个函数多余,getNode函数可以替代这个函数,TODO: 尝试用while循环来做 BinarySearchTree.prototype.hasNode = key => { const search = (key, node) => { if (node === null) return false if (node.key === key) return true if (key > node.key) return search(key, node.rightChild) if (key <= node.key) return search(key, node.leftChild) } return search(key, this.root) } // 修改节点数据 BinarySearchTree.prototype.updateNode = (key, value) => { const node = this.getNode(key) if (node === null) return false node.value = value return true } // 寻找被删除节点的后继 BinarySearchTree.prototype.getSuccessor = (delNode) => { let successor = delNode.rightChild let successorParent = delNode while (successor.leftChild) { successorParent = successor successor = successor.leftChild } if (successor !== delNode.rightChild) { successorParent.leftChild = successor.rightChild successor.rightChild = delNode.rightChild } return successor } //删除节点 BinarySearchTree.prototype.remove = key => { if (!this.hasNode(key)) return false /** * currentNode:当前被删除节点 * parentNode:当前被删除节点的父节点 */ const {currentNode, parentNode} = this.getNode(key) // 1. currentNode 是根节点,并且根节点没有左右子节点 if (currentNode === this.root && !currentNode.leftChild && !currentNode.rightChild) { this.root = null return true } // 2. leftChild === null rightChild === null 则表明删除的是叶子结点 if (!currentNode.leftChild && !currentNode.rightChild) { // 判断是currentNode 是 parentNode 的左子节点还是右子节点 if (currentNode === parentNode.leftChild) { parentNode.leftChild = null } else { parentNode.rightChild = null } } // 3. currentNode 具有左子节点,但没有右子节点 else if (currentNode.leftChild && !currentNode.rightChild) { if (currentNode === this.root) { // 删除的节点是根节点,并且根节点只有左子节点 this.root = currentNode.leftChild } else if (currentNode === parentNode.leftChild) { // 删除的节点是其父节点的左子节点 parentNode.leftChild = currentNode.leftChild } else { // 删除的节点是其父节点的右子节点 parentNode.rightChild = currentNode.leftChild } } // 4. currentNode 具有右子节点,但没有左子节点 else if (!currentNode.leftChild && currentNode.rightChild) { if (currentNode === this.root) { // 删除的节点是根节点,并且根节点只有右子节点 this.root = currentNode.rightChild } else if (currentNode === parentNode.leftChild) { // 删除的节点是其父节点的左子节点 parentNode.leftChild = currentNode.rightChild } else { // 删除的节点是其父节点的右子节点 parentNode.rightChild = currentNode.rightChild } } // 5. currentNode 具有左右节点(**) else { // 获取当前被删除节点的后继节点 const successor = this.getSuccessor(currentNode) // 如果当前被删除节点是根节点 if (currentNode === this.root) { this.root = successor } // 如果当前被删除节点是 其父节点的左子节点 else if (currentNode === parentNode.leftChild) { parentNode.leftChild = successor } // 如果当前被删除节点是 其父节点的右子节点 else { parentNode.rightChild = successor } successor.leftChild = currentNode.leftChild } return true } // 遍历节点:先序遍历,中序遍历,后序遍历 // 先序遍历 BinarySearchTree.prototype.preOrderTraversal = (node, handle) => { if (node === null) return console.log('此二叉树为空') handle(node) if (node.leftChild) this.preOrderTraversal(node.leftChild, handle) if (node.rightChild) this.preOrderTraversal(node.rightChild, handle) } // 中序遍历 BinarySearchTree.prototype.midOrderTraversal = (node, handle) => { if (node === null) return console.log('此二叉树为空') if (node.leftChild) this.midOrderTraversal(node.leftChild, handle) handle(node) if (node.rightChild) this.midOrderTraversal(node.rightChild, handle) } // // 后序遍历 BinarySearchTree.prototype.postOrderTraversal = (node, handle) => { if (node === null) return console.log('此二叉树为空') if (node.leftChild) this.postOrderTraversal(node.leftChild, handle) if (node.rightChild) this.postOrderTraversal(node.rightChild, handle) handle(node) } // 找最大值,最小值 BinarySearchTree.prototype.getMax = () => { if (this.root === null) return null let currentNode = this.root while (currentNode.rightChild) { currentNode = currentNode.rightChild } return currentNode } BinarySearchTree.prototype.getMin = () => { if (this.root === null) return null let currentNode = this.root while (currentNode.leftChild) { currentNode = currentNode.leftChild } return currentNode } } // test const tree = new BinarySearchTree() tree.insert(10, 10) tree.insert(11, 11) tree.insert(3, 3) tree.insert(5, 5) tree.insert(12, 12) tree.insert(16, 16) tree.insert(2, 2) tree.insert(4, 4) tree.insert(13, 13) tree.insert(9, 9) tree.insert(18, 18) tree.insert(1, 1) // console.log(tree) const {currentNode} = tree.getNode(12) console.log(tree.getSuccessor(currentNode))
为什么会出现红黑树?
先看一下如下这颗二叉搜索树
左边有7个节点而右边只有一个节点,如果不看右边那一个节点,这颗二叉树基本上等同于就是一个链表了,查找效率变成了O(n)。我们知道二叉搜索树对于删除查找插入效率比较高,但是如果出现上图这种情况,二叉搜索树的优势根本就没有体现出来,所以需要对上图二叉搜索树作出改进
所以我们需要对上图的二叉搜索树作出改进,使得左右两颗子树的节点分布均匀,像这种左子树的子孙结点尽可能的等于右子树的子孙节点的二叉树称为平衡二叉树树。
红黑树就是一种平衡二叉树
一颗红黑树需要满足如下规则:
红黑树的平很规则就是:从根节点到叶子节点的最长路径不会超过最短路径的2倍长
如下图,这就是一颗红黑树,符合了如上规则
那么一颗红黑树在插入、删除节点是如何保持平衡的呢?
红黑树通常用的是变色和旋转两种操作
一般插入的节点取红色更好(比较容易调整),如果插入节点是黑色的话很容易就会不符合规则5
旋转
下面讲讲红黑树插入节点时如何进行变换
先做如下规定:插入的节点是红色,插入的节点为N,其父节点为P,其爷节点为G,其父亲的兄弟节点为U;有如下五种情况:
节点的带权路径长度:从根节点到该节点之间的路径长度与该节点的权的乘积
树的带权路径长度:树中所有叶子结点的带权路径长度之和
如何构造一颗哈夫曼树(哈夫曼算法)
在兄弟之间加一连线,对每个节点,除了其左孩子外,去除其与其与孩子之间的关系,以树的根节点为轴心,将整棵树顺时针转45度