一起学习数据结构与算法:二叉搜索树

286 阅读5分钟

// 节点类
class Node {
    // 创建节点,传入值可以是Node实例,或者数值,或者类节点对象
    constructor (data = null, left = null, right = null) {
        if (Node.isNode(data))  [this.data, this.left, this.right] = [data.data, data.left, data.right]
        else [this.data, this.left, this.right] = [data, left, right]
    }
    // 判断是否是满节点:左右子节点都存在
    static isFullNode (node) {
        if (!Node.isNode(node)) return false
        return !!(node.left && node.right)
    }
    // 判断是否是叶子节点:左右子节点都不存在
    static isLeafNode (node) {
        if (!Node.isNode(node)) return false
        return !node.left && !node.right
    }
    // 判断是否是节点类型:是Node的实例
    static isNode (node) {
        return !!(node && Object.isPrototypeOf.call(Node.prototype, node))
    }
    // 获取节点的数据
    getData () {
        return this.data
    }
}

// 树类: 二叉搜索树
class Tree {
    // 初始化树,可传入多个Node,或者数值,或者类节点对象
    constructor (...data) {
        if (!data || !data.length)  this.root = null
        if (new.target && new.target.name === 'Tree') this.insert(...data)
    }
    // 插入数据,可传入多个Node,或者数值,或者类节点对象。
    // 规则:比父节点小的放左边,比父节点大的放右边
    insert (...data) {
        while (data.length) {
            /* eslint-disable no-continue*/
            const nodeCopy = data.shift()
            if (!nodeCopy) continue
            const node = new Node(nodeCopy)
            if (!this.root) {
                this.root = node
                continue
            }
            let currentNode = this.root
            while (currentNode) {
                if (node.data < currentNode.data) {
                    if (!currentNode.left) {
                        currentNode.left = node
                        break
                    }
                    currentNode = currentNode.left
                } else {
                    if (!currentNode.right) {
                        currentNode.right = node
                        break
                    }
                    currentNode = currentNode.right
                }
            }
        }
    }
   // 前序遍历:根左右
    preOrder (node = this.root, result = []) {
        if (!node) return []
        result.push(node.data)
        this.preOrder(node.left, result)
        this.preOrder(node.right, result)
        return result
    }
    // 中序遍历:左根右
    midOrder (node = this.root, result = []) {
        if (!node) return []
        this.midOrder(node.left, result)
        result.push(node.data)
        this.midOrder(node.right, result)
        return result
    }
    // 后续遍历:左右根
    laterOrder (node = this.root, result = []) {
        if (!node) return []
        this.laterOrder(node.left, result)
        this.laterOrder(node.right, result)
        result.push(node.data)
        return result
    }
    // 获取节点:类似二分查找
    getNode (data, node = this.root) {
        if (!node) return null
        if (data === node.data) return node
        if (data < node.data) return this.getNode(data, node.left)
        if (data > node.data) return this.getNode(data, node.right)
    }
    // 按照层数遍历树,从左往右: [[第一层的节点数据], [第二层的节点数据], ...]
    // 关键: 准备一个队列childNodes来存储当前节点的左右子节点,
    //        此队列中的节点即为下次需要遍历的节点。
    //        每一次childNodes遍历完成后,即为一层节点遍历完成,
    //        将下一次需要遍历的数据parentNodes从childNodes拷贝出来,并清空childNodes, 
    //        将结果塞入result。
    /**
     * @param {*} node 遍历开始的节点
     */
    printWithLine (node = this.root) {
        if (!node) return []
        const result = []                       // 最终的结果
        let parentNodes = [node]                // 记录当前遍历的节点
        let childNodes = []                     // 记录下一次需要遍历的节点队列
        let currentNode = parentNodes.shift()   // 当前正在遍历的节点
        let tempNodeData = []                   // 每一层遍历的数据
        while (currentNode) {
            tempNodeData.push(currentNode.data)
            if (currentNode.left) childNodes.push(currentNode.left)
            if (currentNode.right) childNodes.push(currentNode.right)
            if (parentNodes.length === 0) {
                parentNodes = childNodes
                childNodes = []
                result.push(tempNodeData)
                tempNodeData = []
            }
            currentNode = parentNodes.shift()
        }
        return result
    }
    // 不分层遍历树,从左往右: [第一层的节点数据,第二层的节点数据, ...]
    // 关键: 和分层相比,不需要单独记录下一层的节点数据
    //        只需要准备一个队列来存储需要编辑的节点即可
    /**
     * @param {*} node 遍历开始的节点
     */
    printWithoutLine (node = this.root) {
        if (!node) return []
        const result = []                       // 最终的结果
        const childNodes = [node]               // 需要遍历的节点队列
        let currentNode = childNodes.shift()
        while (currentNode) {
            result.push(currentNode.data)
            if (currentNode.left) childNodes.push(currentNode.left)
            if (currentNode.right) childNodes.push(currentNode.right)
            currentNode = childNodes.shift()
        }
        return result
    }
    // 按照之字分层型遍历树:[[根], [奇数层从左往右], [偶数层从右往左]]
    // 关键: 准备两个栈,奇数行栈和偶数行栈
    //        如果当前是偶数行:子节点从左往右存,因为是栈,后进先出,取得时候就是从左往右取了
    //        如果当前是奇数行:子节点从右王左存
    /**
     * @param {*} node 遍历开始的节点
     */
    printWithZ (node = this.root) {
        if (!node) return []
        const evenLine = []     // 奇数行栈
        const oddLine = []      // 偶数行栈
        const result = []       // 最终结果
        let temp = []           // 遍历奇数行或者偶数行后每一行的节点数据
        oddLine.push(node)
        while (oddLine.length > 0 || evenLine.length > 0) {
            let currentNode = null
            while (oddLine.length > 0) {
                currentNode = oddLine.pop()
                temp.push(currentNode.data)
                if (currentNode.left) evenLine.push(currentNode.left)
                if (currentNode.right) evenLine.push(currentNode.right)
            }
            if (temp.length > 0) {
                result.push(temp)
                temp = []
            }
            while (evenLine.length > 0) {
                currentNode = evenLine.pop()
                temp.push(currentNode.data)
                if (currentNode.right) oddLine.push(currentNode.right)
                if (currentNode.left) oddLine.push(currentNode.left)
            }
            if (temp.length > 0) {
                result.push(temp)
                temp = []
            }
        }
        return result
    }
    // 获取树的最大深度
    getMaxDepth (node = this.root) {
        if (!node) return 0
        return Math.max(this.getMaxDepth(node.left) + 1, this.getMaxDepth(node.right) + 1)
    }
    // 获取树的最小深度
    getMinDepth (node = this.root) {
        if (!node) return 0
        return Math.min(this.getMaxDepth(node.left) + 1, this.getMaxDepth(node.right) + 1)
    }
    // 得到镜像树:递归交换左右节点
    mirror (node = this.root) {
        if (!node) return []
        const nodeCopy = Object.assign(node)
        const nodeLeft = nodeCopy.left
        nodeCopy.left = node.right
        nodeCopy.right = nodeLeft
        this.mirror(nodeCopy.left)
        this.mirror(nodeCopy.right)
        return new Tree(nodeCopy)
    }
    // 判断一颗树是否是对称数: 镜像树和本身相同
    isSymmetric (nodeLeft = this.root, nodeRight = this.root) {
        if (!nodeLeft || !nodeRight) return true
        if (nodeLeft.left !== nodeRight.right) return false
        if (nodeLeft.right !== nodeRight.left) return false
        return this.isSymmetric(nodeLeft.left, nodeRight.right) && this.isSymmetric(nodeLeft.right, nodeRight.left)
    }
    // 判断一颗树是否是平衡术:左右子数的深度差不超过1
    // 关键: 递归遍历左右子树是否平衡
    isBlanced (node = this.root) {
        const leftDepth = node && node.left ? this.getMaxDepth(node.left) : 0
        const rightDepth = node && node.right ? this.getMaxDepth(node.right) : 0
        if (Math.abs(leftDepth - rightDepth) > 1) return false
        return (leftDepth > 0 ? this.isBlanced(node.left) !== false : true) && (rightDepth > 0 ? this.isBlanced(node.right) !== false : true)
    }
    // 获取从根节点到当前节点的路径,包含当前节点
    // 关键: 准备路径栈,每次遍历一个节点,当前节点入栈
    //        再递归遍历左子树,如果有,返回路径。
    //        如果没有,递归遍历右子树,如果有返回路径
    //        如果都没有,当前节点出栈
    getNodeRouter (data, node = this.root, prePath = []) {
        if (!node) return []
        const path = prePath // 路径栈
        path.push(node.data)
        if (data === node.data) return path
        let flag = false
        if (node.left) flag = !!(this.getNodeRouter(data, node.left, path).indexOf(data) > -1)
        if (!flag && node.right) flag = !!(this.getNodeRouter(data, node.right, path).indexOf(data) > -1)
        if (!flag) path.pop()
        return path
    }
    // 获取和为当前数值的路径,调用getNodeRouter,求和。
    // 关键: 准备路径栈,获取当前节点的路径,求和看是否等于传入值,相等则返回路径
    //        如果不等,再递归遍历左子树。如果找到则返回路径
    //        如果递归左子树也没找到,最后递归遍历右子树,如果找到则返回路径
    getNodeRouterOfSum (sum, node = this.root) {
        let path = []    // 路径栈
        let nodeSum = 0  // 节点总和
        path = this.getNodeRouter(node.data)
        path.forEach(item => {
            nodeSum += item
        })
        if (nodeSum === sum) return path
        if (node.left) path = this.getNodeRouterOfSum(sum, node.left)
        if (path.length) return path
        if (node.right) path = this.getNodeRouterOfSum(sum, node.right)
        if (path.length) return path
        return []
    }
    // 比较节点
    compareNode (nodeSource = null, nodeCompared = null) {
        if (!nodeCompared) return true
        if (!nodeSource) return false
        if (nodeSource.data !== nodeCompared.data) return false
        return this.compareNode(nodeSource.left, nodeCompared.left) && this.compareNode(nodeSource.right, nodeCompared.right)
    }
    // 判断是否为子树
    isChildTreeOf ({ root: nodeSource = null }, nodeCompared = this.root || null) {
        let result = false
        if (nodeSource && nodeCompared) {
            if (nodeSource.data === nodeCompared.data) {
                result = this.compareNode(nodeSource, nodeCompared)
            }
            if (!result) this.isChildTreeOf(nodeSource.left, nodeCompared)
            if (!result) this.isChildTreeOf(nodeSource.right, nodeCompared)
        }
        return result
    }
}

测试
const tree = new Tree(8)
tree.insert(new Node(6))
tree.insert(10)tree.insert(5, 7, 9, 12, 4)
console.log('preOrder', tree.preOrder())
console.log('midOrder', tree.midOrder())
console.log('laterOrder', tree.laterOrder())
console.log('printWithLine', tree.printWithLine())
console.log('printWithoutLine', tree.printWithoutLine())
console.log('printWithZ', tree.printWithZ())
console.log('isSymmetric', tree.isSymmetric())
console.log('getNode', tree.getNode(4))
console.log('getMaxDepth', tree.getMaxDepth())
console.log('getMinDepth', tree.getMinDepth())
console.log('isBlanced', tree.isBlanced())
console.log('getNodeRouter', tree.getNodeRouter(7))
console.log('getNodeRouterOfSum', tree.getNodeRouterOfSum(14))

const childTree = new Tree(8, 6, 10, 5)
console.log('childTree-isChildTreeOf-tree', childTree.isChildTreeOf(tree))

const mirrorTree = tree.mirror()
console.log('printWithLine-mirrorTree-of-tree', mirrorTree.printWithLine())

测试结果: