一、二叉树基础

高度、深度:是从零开始算的
:和深度一样,只是从一开始算
完全二叉树:最后一层只允许缺右子节点的二叉树,如果不缺则是满二叉树
存储方式

  1. 链式存储法(常用)
  2. 数组。假设i为数组下标,根节点存在i = 1的位置,那么左子节点存储在下标 2 i 的位置,右子节点存储在 2 i + 1。如果是完全二叉树,那么该方式最节约空间。如果不是,则会浪费比较多的数组存储空间。

遍历
前序遍历、中序遍历、后续遍历
以中序遍历为例:

function inOrder(point) {
    if (point === null) {
        return
    }
    inOrder(point.left);
    console.log(point.value);
    inOrder(point.right)
}

时间复杂度:O(n)

二、二叉查找树

树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。二叉查找树支持快速查找、插入、删除操作
下面代码实现了二叉查找树的相关功能:

class Node {
    constructor(value) {
        this.value = value;
        this.left = null;
        this.right = null;
    }
}

class SearchTree {
    constructor() {
        this.root = null
    }
    // 插入
    insert(num) {
        if (this.root === null) {
            this.root = new Node(num)
            return
        }
        let current = this.root
        while (true) {
            if (num <= current.value) {
                if (current.left === null) {
                    current.left = new Node(num)
                    return
                }
                current = current.left
            } else {
                if (current.right === null) {
                    current.right = new Node(num)
                    return
                }
                current = current.right
            }
        }
    }
    // 中序遍历
    print(current) {
        if (current === null) {
            return
        }
        this.print(current.left)
        console.log(current.value)
        this.print(current.right)
    }
    // 查找,支持查找多个
    find(num) {
        let result = []
        let current = this.root
        while (current !== null) {
            if (num <= current.value) {
                if (num === current.value) {
                    result.push(current)
                }
                current = current.left
            } else {
                current = current.right
            }
        }
        return result.length > 1 ? result : (result[0] || null)
    }
    remove(num) {
        let p = this.root;
        let pp = null; // pp记录的是p的父节点  
        while (p !== null && p.value != num) {
            pp = p;
            p = num > p.value ? p.right : p.left
        }
        if (p === null) { // 没找到
            return false
        }

        // 要删除的节点有两个子节点
        if (p.left != null && p.right != null) { // 查找右子树中最小节点
            let minP = p.right;
            let minPP = p; // minPP表示minP的父节点
            while (minP.left != null) {
                minPP = minP;
                minP = minP.left;
            }
            p.value = minP.data; // 将minP的数据替换到p中
            p = minP; // 下面就变成了删除minP了
            pp = minPP;
        }
        // 删除节点是叶子节点或者仅有一个子节点 
        let child = null;
        if (p.left !== null) child = p.left
        else if (p.right !== null) child = p.right
        if (pp === null) tree = child; // 删除的是根节点  
        else if (pp.left.value === p.value) pp.left = child;
        else pp.right = child;
        return true
    }
}


function test() {
    const searchTree = new SearchTree();
    searchTree.insert(4);
    searchTree.insert(1);
    searchTree.insert(2);
    searchTree.insert(5);
    console.log('before remove');
    searchTree.print(searchTree.root); //1 2 4 5
    searchTree.remove(1);
    console.log('after remove');
    searchTree.print(searchTree.root); //1 2 4 5
    console.log('search 1', searchTree.find(1))
    console.log('search 5', searchTree.find(5))
}

test()

中序遍历二叉查找树,可以输出有序的数据序列,时间复杂度是 O(n)
通过遍历左右子树可以快速查找到最小值和最大值
插入、删除和查找,时间复杂度其实都跟树的高度成正比,也就是 O(height)
完全二叉树的高度小于等于 log2n,极度不平衡的二叉查找树高度可以是n
平衡二叉查找树:任意一个节点的左右子树的高度相差不能大于 1 的二叉查找树。它的优势:让整棵树的高度相对来说低一些,插入、删除、查找等操作的效率高一些。使用最多的是红黑树

三、堆

概念:堆是一个完全二叉树;堆中每一个节点的值都必须大于等于(大顶堆)或小于等于(小顶堆)其子树中每个节点的值。
用数组实现:i的子节点为2i和2i+1,父节点为i/2向下取整
插入操作:往数组尾部插入,为了保证堆的特性,要将该数据依次与父节点对比,如果大于父节点则交换(假设是大顶堆),部分代码逻辑为:

while (i/2 >= 1 && a[i] > a[Math.floor(i/2)]) { 
    swap(a, i, i/2);      
    i = Math.floor(i/2);    
}

这部分循环次数最多为树的高度,所以时间复杂度为O(logn)
删除操作:将数组最后一个节点赋值给要删除的节点,最后一个节点删除,再将该节点与子节点依次对比交换,时间复杂度也是O(logn)
应用:堆排序


toln
49 声望1 粉丝

前端开发@深圳