Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >《学习JavaScript数据结构与算法》-- 7.树(笔记)

《学习JavaScript数据结构与算法》-- 7.树(笔记)

作者头像
爱学习的程序媛
发布于 2022-04-07 08:18:18
发布于 2022-04-07 08:18:18
40700
代码可运行
举报
文章被收录于专栏:学习/读书笔记学习/读书笔记
运行总次数:0
代码可运行

树是一种分层数据的抽象模型。一个树结构包含一系列存在父子关系的节点,每个节点都有一个父节点(除了根节点)以及0个或多个子节点。位于树顶部的节点叫作根节点,它没有父节点。

树中的每个元素都叫作节点(或键),节点分为内部节点和外部节点。至少有一个子节点的节点称为内部节点,没有子节点的节点称为外部节点或叶节点。

一个节点可以有祖先和后代。一个节点(除了根节点)的祖先包括父节点、祖父节点、曾祖父节点等,一个节点的后代包含子节点、孙子节点、曾孙节点等。子树由节点和它的后代构成。

节点的一个属性是深度,节点的深度取决于它的祖先节点的数量。树的高度取决于所有节点深度的最大值。一棵树也可以被分解成层级,根节点在第0层,它的子节点在第1层,以此类推。

7.1 二叉树和二叉搜索树

二叉树中的节点最多只能有两个子节点,一个是左侧子节点,另一个是右侧子节点。

二叉搜索树(BST),是二叉树的一种,只允许在左侧子节点存储比父节点小的值,在右侧子节点存储比父节点大的值。

我们通过两个指针(引用)来表示节点之间的关系,一个指向左侧子节点,另一个指向右侧子节点。

7.1.1 创建二叉搜索树

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 创建二叉搜索树类
class BinarySearchTree {
    constructor(compareFn = defaultCompare) {
        this.compareFn = compareFn;
        this.root = undefined;
    }
}
// 创建节点类
class Node {
    constructor(key) {
        this.key = key;//节点值
        this.left = undefined;//左侧子节点引用
        this.right = undefined;//右侧子节点引用
    }
    toString() {
        return `${this.key}`;
    }
}

7.1.2 向二叉搜索树中插入一个节点(键)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
insert(key) {
    if (this.root == null) {
        this.root = new Node(key);
    } else {
        this.insertNode(this.root, key);
    }
}
insertNode(node, key) {
    if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
        if (node.left == null) {
            node.left = new Node(key);
        } else {
            this.insertNode(node.left, key);
        }
    } else if (node.right == null) {
        node.right = new Node(key);
    } else {
        this.insertNode(node.right, key);
    }
}

通过二叉搜索树类BinarySearchTree生成实例二叉搜索树binarySearchTree后,向二叉搜索树binarySearchTree中依次添加节点(键):11、7、15、5、3、9、8、10、13、12、14、20、18、25,二叉搜索树binarySearchTree的结构如图:

向二叉搜索树binarySearchTree中再插入一个节点(键)6,二叉搜索树binarySearchTree的结构如图:

7.2 树的遍历

遍历一颗树是指访问树的每个节点并对它们进行某种操作的过程。

7.2.1 中序遍历

中序遍历是一种以上行顺序访问BST所有节点的遍历方式,也就是从最小到最大的顺序访问所有节点。中序遍历的一种应用就是对树进行排序操作。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
inOrderTraverse(callback) {
    this.inOrderTraverseNode(this.root, callback);
}
inOrderTraverseNode(node, callback) {
    if (node != null) {
        this.inOrderTraverseNode(node.left, callback);
        callback(node.key);
        this.inOrderTraverseNode(node.right, callback);
    }
}

7.2.2 先序遍历

先序遍历是以优先于后代节点的顺序访问每个节点的。先序遍历的一种应用是打印一个结构化的文档。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
preOrderTraverse(callback) {
    this.preOrderTraverseNode(this.root, callback);
}
preOrderTraverseNode(node, callback) {
    if (node != null) {
        callback(node.key);
        this.preOrderTraverseNode(node.left, callback);
        this.preOrderTraverseNode(node.right, callback);
    }
}

7.2.3 后序遍历

后序遍历是先访问节点的后代节点,再访问节点本身。后序遍历的一种应用是计算一个目录及子目录中所有文件所占空间的大小。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
postOrderTraverse(callback) {
    this.postOrderTraverseNode(this.root, callback);
}
postOrderTraverseNode(node, callback) {
    if (node != null) {
        this.postOrderTraverseNode(node.left, callback);
        this.postOrderTraverseNode(node.right, callback);
        callback(node.key);
    }
}

7.3 搜索树中的值

7.3.1 搜索树中的最小值

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
min() {
    return this.minNode(this.root);
}
minNode(node) {
    let current = node;
    while (current != null && current.left != null) {
        current = current.left;
    }
    return current;
}

7.3.2 搜索树中的最大值

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
max() {
    return this.maxNode(this.root);
}
maxNode(node) {
    let current = node;
    while (current != null && current.right != null) {
        current = current.right;
    }
    return current;
}

7.3.3 搜索树中特定的值

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
search(key) {
    return this.searchNode(this.root, key);
}
searchNode(node, key) {
    if (node == null) {
        return false;
    }
    if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
        return this.searchNode(node.left, key);
    }
    if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
        return this.searchNode(node.right, key);
    }
    return true;
}

7.4 移除一个节点

7.4.1 移除一个叶节点

7.4.2 移除有一个左侧或右侧节点的节点

7.4.3 移除有两个子节点的节点

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
remove(key) {
    this.root = this.removeNode(this.root, key);
}
removeNode(node, key) {
    if (node == null) {
        return undefined;
    }
    if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
        node.left = this.removeNode(node.left, key);
        return node;
    }
    if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
        node.right = this.removeNode(node.right, key);
        return node;
    }
    // 移除一个叶节点
    if (node.left == null && node.right == null) {
        node = undefined;
        return node;
    }
    // 移除有一个左侧或右侧节点的节点
    if (node.left == null) {
        node = node.right;
        return node;
    }
    if (node.right == null) {
        node = node.left;
        return node;
    }
    // 移除有两个子节点的节点
    const aux = this.minNode(node.right);
    node.key = aux.key;
    node.right = this.removeNode(node.right, aux.key);
    return node;
}

7.5 自平衡树

BST存在一个问题:取决于添加的节点树,树的一条边可能会非常深。也就是说,树的一条分支会有很多层,而其他的分支却只有几层。

这会在需要在某条边上添加、移除和搜索某个节点时引起一些性能问题。为了解决这个问题,可以使用自平衡二叉搜索树(Adelson-Velskii-Landi,AVL树)。添加或移除节点时,AVL树会尝试保持自平衡,任意一个节点的左子树和右子树高度最多相差1。

在AVL树中插入或移除节点和BST完全相同,只是AVL需要检验树的平衡因子,如果需要,会将其逻辑应用于树的自平衡。

在AVL树中,需要对每个节点计算右子树高度(hr)和左子树高度(hl)之间的差值,该值(hr - hl)应为0、1或-1。如果结果不是这三个值之一,就需要平衡该AVL树,这就是平衡因子的概念。

7.5.1 创建自平衡树

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 声明一些用来作为计数器的常量
const BalanceFactor = {
    UNBALANCED_RIGHT: 1,
    SLIGHTLY_UNBALANCED_RIGHT: 2,
    BALANCED: 3,
    SLIGHTLY_UNBALANCED_LEFT: 4,
    UNBALANCED_LEFT: 5
};
// AVLTree类 继承 BinarySearchTree类
class AVLTree extends BinarySearchTree {
    constructor(compareFn = defaultCompare) {
        super(compareFn);
        this.compareFn = compareFn;
        this.root = null;
    }
}

7.5.2 计算一个节点高度

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
getNodeHeight(node) {
    if (node == null) {
        return -1;
    }
    return Math.max(this.getNodeHeight(node.left), this.getNodeHeight(node.right)) + 1;
}

7.5.3 计算一个节点的平衡因子

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
getBalanceFactor(node) {
    const heightDifference = this.getNodeHeight(node.left) - this.getNodeHeight(node.right);
    switch (heightDifference) {
        case -2:
            return BalanceFactor.UNBALANCED_RIGHT;
        case -1:
            return BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT;
        case 1:
            return BalanceFactor.SLIGHTLY_UNBALANCED_LEFT;
        case 2:
            return BalanceFactor.UNBALANCED_LEFT;
        default:
            return BalanceFactor.BALANCED;
    }
}

7.5.4 平衡操作--AVL旋转

向AVL树插入节点时,可以执行单旋转或双旋转两种平衡操作,分别对应四种场景。

a. 左-左(LL):向右的单旋转

这种情况出现于节点的左侧子节点的高度大于右侧子节点的高度,并且左侧子节点也是平衡或左侧较重的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
rotationLL(node) {
    const tmp = node.left;
    node.left = tmp.right;
    tmp.right = node;
    return tmp;
}

b. 右-右(RR):向左的单旋转

这种情况出现于节点的右侧子节点的高度大于左侧子节点的高度,并且右侧子节点也是平衡或右侧较重的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
rotationRR(node) {
    const tmp = node.right;
    node.right = tmp.left;
    tmp.left = node;
    return tmp;
 }

c. 左-右(LR):向右的双旋转

这种情况出现于左侧子节点的高度大于右侧子节点的高度,并且左侧子节点右侧较重。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
rotationLR(node) {
    node.left = this.rotationRR(node.left);
    return this.rotationLL(node);
}

d. 右-左(RL):向左的双旋转

这种情况出现于右侧子节点的高度大于左侧子节点的高度,并且右侧子节点左侧较重。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
rotationRL(node) {
    node.right = this.rotationLL(node.right);
    return this.rotationRR(node);
}

7.5.5 向AVL树插入节点

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
insert(key) {
    this.root = this.insertNode(this.root, key);
}
insertNode(node, key) {
    if (node == null) {
        return new Node(key);
    }
    if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
        node.left = this.insertNode(node.left, key);
    } else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
        node.right = this.insertNode(node.right, key);
    } else {
        return node;
    }
    const balanceFactor = this.getBalanceFactor(node);
    if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
        if (this.compareFn(key, node.left.key) === Compare.LESS_THAN) {
            node = this.rotationLL(node);
        } else {
            return this.rotationLR(node);
          }
    }
    if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
        if (this.compareFn(key, node.right.key) === Compare.BIGGER_THAN) {
            node = this.rotationRR(node);
        } else {
            return this.rotationRL(node);
        }
    }
    return node;
}

7.5.6 从AVL树中移除节点

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
removeNode(node, key) {
    node = super.removeNode(node, key);
    if (node == null) {
        return node;
    }
    const balanceFactor = this.getBalanceFactor(node);
    if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
        if (
            this.getBalanceFactor(node.left) === BalanceFactor.BALANCED ||
            this.getBalanceFactor(node.left) === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT
        ) {
            return this.rotationLL(node);
        }
        if (this.getBalanceFactor(node.left) === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT) {
            return this.rotationLR(node.left);
        }
    }
    if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
        if (
            this.getBalanceFactor(node.right) === BalanceFactor.BALANCED ||
            this.getBalanceFactor(node.right) === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT
        ) {
            return this.rotationRR(node);
        }
        if (this.getBalanceFactor(node.right) === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT) {
            return this.rotationRL(node.right);
        }
    }
    return node;
}

详细代码:

https://github.com/chenxiaohuan117/learning-javasrcipt-note/tree/main/%E3%80%8A%E5%AD%A6%E4%B9%A0JavaScript%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E3%80%8B(%E7%AC%AC3%E7%89%88)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱学习的程序媛 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
力扣 (LeetCode)-对称二叉树,树|刷题打卡
每天学习编程,让你离梦想更新一步,感谢不负每一份热爱编程的程序员,不论知识点多么奇葩,和我一起,让那一颗四处流荡的心定下来,一直走下去,加油,2021加油!欢迎关注加我vx:xiaoda0423,欢迎点赞、收藏和评论
达达前端
2021/03/22
4190
力扣 (LeetCode)-对称二叉树,树|刷题打卡
TypeScript实现二叉搜索树
树是一种非顺序数据结构,它用于存储需要快速查找的数据。现实生活中也有许多用到树的例子,比如:家谱、公司的组织架构图等。
神奇的程序员
2022/04/10
5040
TypeScript实现二叉搜索树
怒肝 JavaScript 数据结构 — 树与二叉树
到本篇为止我们已经学习了大多数的顺序数据结构,而第一个非顺序数据结构是散列表。本篇我们学习第二种非顺序数据结构 —— 树,是一个相对复杂的数据结构。
杨成功
2022/09/22
3720
怒肝 JavaScript 数据结构 — 树与二叉树
《javascript数据结构和算法》读书笔记(6):树
在明代世系表这棵树中,所有的皇帝都被称为节点。朱元璋称为根节点。后代是皇帝的节点,称为内部节点。没有子元素的节点比如明思宗朱由检称为外部节点或叶节点。朱棣及其后代节点称为朱元璋的子树。
一粒小麦
2019/07/18
6350
《javascript数据结构和算法》读书笔记(6):树
数据结构:一文看懂二叉搜索树 (JavaScript)
查询节点过程是,比较元素值是否相等,相等则返回,不相等则判断大小情况,迭代查询左、右子树,直到找到相等的元素,或子节点为空,返回节点不存在。
微芒不朽
2022/09/13
5060
寻找二叉树的下一个节点
已知一个包含父节点引用的二叉树和其中的一个节点,如何找出这个节点中序遍历序列的下一个节点?
神奇的程序员
2022/04/10
2740
寻找二叉树的下一个节点
TypeScript实现AVL树与红黑树
二叉搜索树存在一个问题: 当往树中插入的数据一大部分大于某个节点或小于某个节点,这样就会导致树的一条边非常深。为了解决这个问题就出现了自平衡树这种解决方案。
神奇的程序员
2022/04/10
5470
TypeScript实现AVL树与红黑树
【算法】273-每周一练 之 数据结构与算法(Tree)
这些都是数据结构与算法,一部分方法是团队其他成员实现的,一部分我自己做的,有什么其他实现方法或错误,欢迎各位大佬指点,感谢。
pingan8787
2019/07/25
3620
【算法】273-每周一练 之 数据结构与算法(Tree)
用js来实现那些数据结构13(树01-二叉搜索树的实现)
  前一篇文章我们学会了第一个非顺序数据结构hashMap,那么这一篇我们来学学树,包括树的概念和一些相关的术语以及二叉搜索树的实现。唉?为什么不是树的实现,不是二叉树的实现。偏偏是二叉搜索树的实现?
zaking
2018/05/02
1.4K0
用js来实现那些数据结构13(树01-二叉搜索树的实现)
为实习准备的数据结构(5)-- 图解AVL树(平衡二叉搜索树)
二叉搜索树一定程度上可以提高搜索效率,但是当原序列有序,例如序列A = {1,2,3,4,5,6},构造二叉搜索树如图。依据此序列构造的二叉搜索树为右斜树,同时二叉树退化成单链表,搜索效率降低为O(n)。
看、未来
2021/03/17
3490
javascript进阶必备的二叉树知识
每当放完小长假,我都会习惯性的反思和复盘一下自己的技术,尤其是端午节。为什么我会写二叉树的文章呢?其实这涉及到程序员的一个成长性的问题。对于0-3年的前端程序员来说,可能很少有机会涉及到数据结构和算法的工作中,除非去大厂或者做架构相关的工作。但是很多工作2-3年的前端工程师,业务工作已经相对熟悉了,各种技术或多或少也都使用过,那么在这个阶段,对于每个有追求的程序员,是不是应该突破一下自己的技术瓶颈,去研究一些更深层次的知识呢?没错,这个阶段我们最应该了解的就是数据结构,算法,设计模式相关的知识,设计模式和算法笔者在之前的文章中已经系统的总结过了,感兴趣的可以学习了解一下。
徐小夕
2020/06/30
5470
实现二叉搜索树
前言 ---- 二叉搜索树是二叉树的一种 每个节点的左子节点一定比自身小 每个节点的右子节点一定比自身大 图片 实现思路和代码 实现二叉搜索树类 定义内部节点类 包含以下属性 key节点值 left指向左子节点 right指向右子节点 定义root属性表示根节点 function BinarySearchTree() { this.root = null function Node(key) { this.key = key this.left = null th
peng_tianyu
2022/12/15
2540
实现二叉搜索树
【化解数据结构】详解树结构,并实现二叉搜索树
根据上面的图,我们大致知道了树是一个怎样的数据结构,虽然对于实现它还一头雾水,现在我们先来了解一下关于树的相关术语
小丞同学
2021/11/29
2990
【化解数据结构】详解树结构,并实现二叉搜索树
实现一个二叉搜索树(JavaScript 版)
二叉树在计算机科学中应用很广泛,学习它有助于让我们写出高效的插入、删除、搜索节点算法。二叉树的节点定义:一个节点最多只有两个节点,分别为左侧节点、右侧节点。
五月君
2020/03/03
1.5K0
前端中常见数据结构小结
数据结构在开发中是一种编程思想的提炼,无关于用何种语言开发或者是哪种端开发。下列将笔者涉猎到的与前端相关的数据结构案例作如下总结:
牧云云
2018/09/19
4591
二叉树的意义(P1)
二叉树是广泛用于表示层次关系的通用数据结构。他们擅长组织文件系统、在编译器中解析树以及捕获语义网络中的连接等任务。它们的分支结构可以有效地存储和检索数据,使它们成为各种应用程序中的宝贵工具。
IT千锋教育
2023/07/04
3650
二叉树的意义(P1)
怒肝 JavaScript 数据结构 — 树的遍历
上一篇我们介绍了树的概念,什么是二叉树与二叉搜索树,并实现了一个二叉搜索树的类,然后完成了节点插入的功能。
杨成功
2022/09/22
4990
怒肝 JavaScript 数据结构 — 树的遍历
Golang实现一个可存放重复元素的二叉搜索树,结合Morris算法
今天学习的时候看到一道题目是问在二叉搜索树如何存放重复值,借此机会顺便复习了一下二叉搜索树。二叉搜索树的中序遍历是有序的,它的左子树的所有节点的值都是小于它的,它的右子树的所有节点的值都是大于它的。在学习二叉树的遍历的时候,有一个大名鼎鼎的Morris算法,使用双指针就可以实现二叉树的前中后序遍历,并且时间复杂度是O(N),空间复杂度是O(1),于是我使用Golang实现一个可存放重复元素的二叉搜索树,并且结合Morris算法进行查找。
dddyge
2024/03/06
2160
js中的二叉树以及二叉搜索树的实现及应用
二叉树中的节点最多只能有2个子节点,一个是左侧子节点,一个是右侧子节点,这样定义的好处是有利于我们写出更高效的插入,查找,删除节点的算法。
徐小夕
2019/08/07
2K0
js中的二叉树以及二叉搜索树的实现及应用
数据结构之AVL树
我们先来回忆一下二分搜索树所存在的一个问题:当我们按顺序往二分搜索树添加元素时,那么二分搜索树可能就会退化成链表。例如,现在有这样一颗二分搜索树:
端碗吹水
2021/02/02
5030
相关推荐
力扣 (LeetCode)-对称二叉树,树|刷题打卡
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验