数据结构与算法Javascript描述-二叉树

299 阅读3分钟

二叉树

概念

树是一种非线性的数据结构,以分层的方式存储数据。常见的树结构有家挺关系图谱,公司组织结构。

二叉树

二叉树是一种特殊的树,它的子节点不超过两个。

二叉查找树

二叉查找树是一种特殊的二叉树,它把相对较小的数据保存在左节点,把较大的数据保存在右节点(我们假设数据不重复)。

下面我们主要研究二叉查找树的相关代码

二叉树创建及实例方法

// 节点对象
class Node {
    constructor(data) {
        this.right = null;
        this.left = null;
        this.data = data;
    }
}

// 二叉树类
class BST {
    constructor() {
        this.root = null;
    }

    insert(data) {
        const node = new Node(data);

        if (this.root === null) {
            this.root = node;
        } else {
            let curNode = this.root;

            while(1) {
                if (data < curNode.data) {
                    if (curNode.left) {
                        curNode = curNode.left;
                    } else {
                        curNode.left = node;
                        break;
                    }
                } else {
                    if (curNode.right) {
                        curNode = curNode.right;
                    } else {
                        curNode.right = node;
                        break;
                    }
                }
            }
        }
    }

    // 利用查找二叉树的特点,递归左子树的左节点即是最小节点
    getMin() {
        let curNode = this.root;

        while(curNode.left !== null) {
            curNode = curNode.left;
        }

        return curNode.data;
    }

    // 利用查找二叉树的特点,递归右子树的右节点即是最大节点
    getMax() {
        let curNode = this.root;

        while(curNode.right !== null) {
            curNode = curNode.right;
        }

        return curNode.data;
    }

    find(data) {
        let curNode = this.root;

        while(curNode) {
            if (curNode.data === data) {
                return curNode;
            }

            if (curNode.data > data) {
                curNode = curNode.left;
            } else {
                curNode = curNode.right;
            }
        }

        return null;
    }
}

我们为 BST 加上了4个实例方法,insert 用于给二叉树添加节点,getMin 用于查找最小节点数据,getMax 用于查找最大节点数据,find 用于查找对应数据的节点。

二叉树节点的删除

对于二叉树节点的删除,根据待删除节点的节点关系,我们分为几个不同的情况

  1. 待删除节点为叶子节点,直接删除节点即可

  2. 待删除节点只含有左(右)子树,将链接指向左(右)子树即可

  3. 待删除节点拥有左右子树,将节点数据替换为子子树中的最大值(或者右子树的最小值)(这样不会破坏二叉树结构),同时删除右子树对应的叶子节点。

// ...

remove(data) {
    this.root = this.removeNode(this.root, data);
}

removeNode(node, data) {
    if (node === null) {
        return null;
    }

    if (data === node.data) {
        // 对应1 叶子节点
        if (node.left === null && node.right === null) {
            return null;
        }

        // 对应2 只有左子树
        if (node.right === null) {
            return node.left;
        }

        // 对应2 只有右子树
        if (node.left === null) {
            return node.right;
        }

        // 对应3 同时拥有左右子树

        // 查找最大左子树
        let leftMaxNode = node.left;
        while(leftMaxNode.right) {
            leftMaxNode = leftMaxNode.right;
        }

        // 替换数据
        const temp = node.data;
        node.data = leftMaxNode.data;
        leftMaxNode.data = temp;

        // 删除对应最大值节点
        node.right = this.removeNode(node.right, temp);

        return node;
    } else {
        // 递归查找数据
        if (data > node.data) {
            node.right = this.removeNode(node.right, data);
        } 

        if (data < node.data) {
            node.left = this.removeNode(node.left, data);
        }

        return node;
    }
}

搜索二叉树(遍历)

二叉树的遍历分为深度优先遍历和广度优先遍历

  • 深度优先遍历:优先递归访问到叶子节点

  • 广度优先遍历:层次遍历,优先访问同一层节点,一层一层访问

深度优先遍历

深度优先遍历也可细分为

  • 前序遍历:访问顺序为 根节点 -> 左子树 -> 右子树

  • 中序遍历:访问顺序为 左子树 -> 根节点 -> 右子树

  • 后序遍历:访问顺序为 左子树 -> 右子树 -> 根节点

相关代码如下

// ···

// 前序
preOrder(node) {
    if (node) {
        console.log(node.data);
        this.preOrder(node.left);
        this.preOrder(node.right);
    }
}

// 中序
inOrder(node) {
    if (node) {
        this.inOrder(node.left);
        console.log(node.data);
        this.inOrder(node.right);
    }
}

// 后序
postOrder(node) {
    if (node) {
        this.postOrder(node.left);
        this.postOrder(node.right);
        console.log(node.data);
    }
}

广度优先遍历

widthOrder(nodes) {
    const nextNodes = [];

    nodes.forEach(node => {
        console.log(node.data);

        if (node.left) nextNodes.push(node.left);
        if (node.right) nextNodes.push(node.right);
    });

    if (nextNodes.length) {
        this.widthOrder(nextNodes);
    }
}

二叉树序列确定唯一树

我们可以根据其中两种遍历顺序确定二叉树

  • 前序 + 中序 ->

  • 后序 + 中序 ->

  • 前序 + 后序 -> 无法确定唯一树

// ···

// 前序 + 中序 -> 树
buildTreeBasePre(preOrder, inOrder) {
    if (preOrder.length < 1) {
        return null;
    }
    
    const idx = inOrder.indexOf(preOrder[0]);
    const leftInOrder = inOrder.slice(0, idx);
    const rightInOrder = inOrder.slice(idx + 1);
    const leftPreOrder = preOrder.slice(1, leftInOrder.length + 1);
    const rightPreOrder = preOrder.slice(leftInOrder.length + 1);

    const node = new Node(preOrder[0]);

    if (leftInOrder.length) {
        node.left = this.buildTreeBasePre(leftPreOrder, leftInOrder);
    }

    if (rightInOrder.length) {
        node.right = this.buildTreeBasePre(rightPreOrder, rightInOrder);
    }

    return node;
}

// 后序 + 中序 -> 树
buildTreeBasePost(inOrder, postOrder) {
    const len = inOrder.length;

    if (len.length < 1) {
        return null;
    }
    
    const idx = inOrder.indexOf(postOrder[len - 1]);
    const leftInOrder = inOrder.slice(0, idx);
    const rightInOrder = inOrder.slice(idx + 1);
    const leftPostOrder = postOrder.slice(0, leftInOrder.length);
    const rightPostOrder = postOrder.slice(leftInOrder.length, -1);

    const node = new Node(postOrder[len - 1]);

    if (leftInOrder.length) {
        node.left = this.buildTreeBasePost(leftInOrder, leftPostOrder);
    }

    if (rightInOrder.length) {
        node.right = this.buildTreeBasePost(rightInOrder, rightPostOrder);
    }

    return node;
}

计数二叉树

有时候当数据有重复时,我们可以为二叉树加上计数功能来统计数据的重复次数

class Node {
    constructor(data) {
        this.right = null;
        this.left = null;
        this.data = data;
        // 添加count
        this.count = 1;
    }
}

// ···

// 更新数据统计
update(data) {
    const result = this.find(data);

    result.count++;
}

完整代码

class Node {
    constructor(data) {
        this.right = null;
        this.left = null;
        this.data = data;
        // 计数统计
        this.count = 1;
    }
}

class BST {
    constructor() {
        this.root = null;
    }

    // 插入节点
    insert(data) {
        const node = new Node(data);

        if (this.root === null) {
            this.root = node;
        } else {
            let curNode = this.root;

            while(1) {
                if (data <= curNode.data) {
                    if (curNode.left) {
                        curNode = curNode.left;
                    } else {
                        curNode.left = node;
                        break;
                    }
                } else {
                    if (curNode.right) {
                        curNode = curNode.right;
                    } else {
                        curNode.right = node;
                        break;
                    }
                }
            }
        }
    }

    // 前序遍历
    preOrder(node) {
        if (node) {
            console.log(node.data);
            this.preOrder(node.left);
            this.preOrder(node.right);
        }
    }
    
    // 中序遍历
    inOrder(node) {
        if (node) {
            this.inOrder(node.left);
            console.log(node.data);
            this.inOrder(node.right);
        }
    }

    // 后序遍历
    postOrder(node) {
        if (node) {
            this.postOrder(node.left);
            this.postOrder(node.right);
            console.log(node.data);
        }
    }

    // 广度优先遍历
    widthOrder(nodes) {
        const nextNodes = [];

        nodes.forEach(node => {
            console.log(node.data);

            if (node.left) nextNodes.push(node.left);
            if (node.right) nextNodes.push(node.right);
        });

        if (nextNodes.length) {
            this.widthOrder(nextNodes);
        }
    }

    // 前序 + 中序 -> 树
    buildTreeBasePre(preOrder, inOrder) {
        if (preOrder.length < 1) {
            return null;
        }
        
        const idx = inOrder.indexOf(preOrder[0]);
        const leftInOrder = inOrder.slice(0, idx);
        const rightInOrder = inOrder.slice(idx + 1);
        const leftPreOrder = preOrder.slice(1, leftInOrder.length + 1);
        const rightPreOrder = preOrder.slice(leftInOrder.length + 1);

        const node = new Node(preOrder[0]);

        if (leftInOrder.length) {
            node.left = this.buildTreeBasePre(leftPreOrder, leftInOrder);
        }

        if (rightInOrder.length) {
            node.right = this.buildTreeBasePre(rightPreOrder, rightInOrder);
        }

        return node;
    }

    // 后序 + 中序 -> 树
    buildTreeBasePost(inOrder, postOrder) {
        const len = inOrder.length;

        if (len.length < 1) {
            return null;
        }
        
        const idx = inOrder.indexOf(postOrder[len - 1]);
        const leftInOrder = inOrder.slice(0, idx);
        const rightInOrder = inOrder.slice(idx + 1);
        const leftPostOrder = postOrder.slice(0, leftInOrder.length);
        const rightPostOrder = postOrder.slice(leftInOrder.length, -1);

        const node = new Node(postOrder[len - 1]);

        if (leftInOrder.length) {
            node.left = this.buildTreeBasePost(leftInOrder, leftPostOrder);
        }

        if (rightInOrder.length) {
            node.right = this.buildTreeBasePost(rightInOrder, rightPostOrder);
        }

        return node;
    }

    // 查找最小值
    getMin() {
        let curNode = this.root;

        while(curNode.left !== null) {
            curNode = curNode.left;
        }

        return curNode.data;
    }

    // 查找最大值
    getMax() {
        let curNode = this.root;

        while(curNode.right !== null) {
            curNode = curNode.right;
        }

        return curNode.data;
    }

    // 查找值
    find(data) {
        let curNode = this.root;

        while(curNode) {
            if (curNode.data === data) {
                return curNode;
            }

            if (curNode.data > data) {
                curNode = curNode.left;
            } else {
                curNode = curNode.right;
            }
        }

        return null;
    }

    // 更新计数统计
    update(data) {
        const result = this.find(data);

        result.count++;
    }

    // 删除节点
    remove(data) {
        this.root = this.removeNode(this.root, data);
    }

    // 删除节点函数
    removeNode(node, data) {
        if (node === null) {
            return null;
        }

        if (data === node.data) {
            // 对应1 叶子节点
            if (node.left === null && node.right === null) {
                return null;
            }

            // 对应2 只有左子树
            if (node.right === null) {
                return node.left;
            }

            // 对应2 只有右子树
            if (node.left === null) {
                return node.right;
            }

            // 对应3 同时拥有左右子树

            // 查找最大左子树
            let leftMaxNode = node.left;
            while(leftMaxNode.right) {
                leftMaxNode = leftMaxNode.right;
            }

            // 替换数据
            const temp = node.data;
            node.data = leftMaxNode.data;
            leftMaxNode.data = temp;

            // 删除对应最大值节点
            node.right = this.removeNode(node.right, temp);

            return node;
        } else {
            // 递归查找数据
            if (data > node.data) {
                node.right = this.removeNode(node.right, data);
            } 

            if (data < node.data) {
                node.left = this.removeNode(node.left, data);
            }

            return node;
        }
    }
}

欢迎到前端学习打卡群一起学习~516913974