在使用javascript实现基本的数据结构中,练习了好几周,对基本的数据结构如 栈、队列、链表、集合、哈希表、树、图等内容进行了总结并且写了笔记和代码。

在 github中可以看到  点击查看,可以关注一下我哈。

树的基本术语

二叉树节点的存储结构

创建一个二叉搜索树

二叉树的先序、中序、后续遍历算法

二叉树的非递归先序、中序、后续遍历算法。

文章对树了解的不多的人有点不友好,这里简单介绍(从书上抄下来)那些基本的一点概念吧。

看下面这个示意图

树的基本术语:

结点:A、B、C等都是结点,结点不仅包含数据元素,而且包含指向子树的分支。例如,A结点不仅包含数据元素A、还包含3个指向子树的指针。

结点的度:结点拥有的子树个数或者分支的个数,例如A结点有3棵子树,所以A结点的度为3.

树的度:树中各结点度的最大值。如例子中结点度最大为3(A、D结点)。最小为0(F、G、I、J、K、L、M),所以树的度为3。

叶子节点:又叫做终端节点,指度为0的节点,F、G、I、J、K、L、M节点都是叶子节点。

孩子:结点的子树的根,如A节点的孩子为B、C、D。

双亲:与孩子的定义对应,如B C D结点的双亲都是A。

兄弟:同一个双亲的孩子之间互为兄弟。如B、C、D互为兄弟,因为它们都是A节点的孩子。

祖先:从根到某节点的路径上的所有结点,都是这个节点的祖先。如K的祖先是A B E,因为从A到K的路径为 A---B---E--K。

子孙: 以某节点为根的子树中的所有结点,都是该结点的子孙。如D的子孙为H I J M。

层次:从根开始,根为第一层,根的孩子为第二层,根的孩子的孩子为第三层,以此类推。

树的高度(或者深度):树中结点的最大层次,如例子中的树共有4层,所以高度为4.

理解了上面的树一些基本一些的概念后,我们来看一下什么是二叉树。

1)每个结点最多只有两棵子树,即二叉树中的节点的度只能为0、1、2

2) 子树有左右之分,不能颠倒。

以下二叉树的5中基本状态

构造一个二叉树的节点存储结构

我们会发现,二叉树中的存储结构一个是值,一个是左边有一个,右边有一个。他们分别叫左孩子/左子树  右孩子/右子树。

所以我们会很容易的写出来一个节点的构造函数。

1 // 树的结构
2 class BTNode {
3   constructor() {
4     this.key = key;
5     this.lchild = null;
6     this.rchild = null;
7   }
8 }

实现一个二叉搜索树 / 二叉排序树

看一下定义

二叉排序树或者是空树,或者是满足以下性质的二叉树。

1) 若它的左子树不空,则左子树上的所有关键字的值均小于根关键字的值。

2)若它的右子树不空,则右子树上所有关键字的值均大于根关键字的值。

3)左右子树又是一棵二叉排序树。

举个例子

假如要插入一堆数字,数字为 20 10 5 15 13 18 17 30

那么用代码如何实现呢?

 1 let BST = (function () {
 2
 3   let ROOT = Symbol();
 4
 5   // 节点结构
 6   let BTNode = class {
 7     constructor(key) {
 8       this.key = key;
 9       this.lchild = null;
10       this.rchild = null;
11     }
12   }
13
14   // 递归插入节点
15   let recursionInsert = function (root, node) {
16     if (node.key < root.key) {
17       if (root.lchild) {
18         recursionInsert(root.lchild, node);
19       } else {
20         root.lchild = node;
21       }
22     } else {
23       if (root.rchild) {
24         recursionInsert(root.rchild, node);
25       } else {
26         root.rchild = node;
27       }
28     }
29   }
30
31   // 二叉搜索树类
32   return class {
33     constructor() {
34       this[ROOT] = null;
35     }
36
37     // 插入
38     insert(key) {
39       let node = new BTNode(key);
40       let root = this[ROOT];
41       if (!root) {
42         this[ROOT] = node;
43         return;
44       }
45       // 递归插入
46       recursionInsert(root, node);
47     }
48   }
49
50 })();
51
52
53 let bst = new BST();
54
55
56 bst.insert(20);
57 bst.insert(10);
58 bst.insert(5);
59 bst.insert(15);
60 bst.insert(13);
61 bst.insert(18);
62 bst.insert(17);
63 bst.insert(30);
64
65 console.log(bst);

在浏览器中一层一层的展开看看是否和我们的一样。

二叉树的遍历算法

二叉树的遍历算法,二叉树的遍历就是按照某种规则将二叉树中的所有数据都访问一遍。

二叉树的遍历方式有先序遍历、中序遍历、后续遍历和层次遍历,很多算法都是基于这几种遍历方式衍生出来的。

递归方式的具体的代码实现

  1 let BST = (function () {
  2
  3   let ROOT = Symbol();
  4
  5   // 节点结构
  6   let BTNode = class {
  7     constructor(key) {
  8       this.key = key;
  9       this.lchild = null;
 10       this.rchild = null;
 11     }
 12   }
 13
 14   // 递归插入节点
 15   let recursionInsert = function (root, node) {
 16     if (node.key < root.key) {
 17       if (root.lchild) {
 18         recursionInsert(root.lchild, node);
 19       } else {
 20         root.lchild = node;
 21       }
 22     } else {
 23       if (root.rchild) {
 24         recursionInsert(root.rchild, node);
 25       } else {
 26         root.rchild = node;
 27       }
 28     }
 29   };
 30
 31   // 用于中序遍历二叉树的方法
 32   let inorderTraversal = function (root, arr) {
 33     if (!root) return;
 34     inorderTraversal(root.lchild, arr);
 35     arr.push(root.key);
 36     inorderTraversal(root.rchild, arr);
 37   };
 38
 39   // 用于先序遍历的递归函数
 40   let preOrderTraversal = function (root, arr) {
 41     if (!root) return;
 42     arr.push(root.key);
 43     preOrderTraversal(root.lchild, arr);
 44     preOrderTraversal(root.rchild, arr);
 45   };
 46
 47   // 用于后续遍历的递归函数
 48   let lastOrderTraversal = function (root, arr) {
 49     if (!root) return;
 50     lastOrderTraversal(root.lchild, arr);
 51     lastOrderTraversal(root.rchild, arr);
 52     arr.push(root.key);
 53   };
 54
 55   // 二叉搜索树类
 56   return class {
 57     constructor() {
 58       this[ROOT] = null;
 59     }
 60
 61     // 插入
 62     insert(key) {
 63       let node = new BTNode(key);
 64       let root = this[ROOT];
 65       if (!root) {
 66         this[ROOT] = node;
 67         return;
 68       }
 69       // 递归插入
 70       recursionInsert(root, node);
 71     }
 72
 73
 74     // 中序遍历二叉树
 75     inorderTraversal() {
 76       let arr = [];
 77       inorderTraversal(this[ROOT], arr);
 78       return arr;
 79     }
 80
 81     // 先序遍历二叉树
 82     preOrderTraversal() {
 83       let arr = [];
 84       preOrderTraversal(this[ROOT], arr);
 85       return arr;
 86     }
 87
 88     // 后续遍历
 89     lastOrderTraversal() {
 90       let arr = [];
 91       lastOrderTraversal(this[ROOT], arr);
 92       return arr;
 93     }
 94   }
 95
 96 })();
 97
 98
 99 let bst = new BST();
100
101 bst.insert(20);
102 bst.insert(15);
103 bst.insert(7);
104 bst.insert(40);
105 bst.insert(30);
106 bst.insert(45);
107 bst.insert(50);
108
109
110 console.log(bst);
111
112
113 let a = bst.inorderTraversal();
114 let b = bst.preOrderTraversal();
115 let c = bst.lastOrderTraversal();
116
117 console.log(a);
118 console.log(b);
119 console.log(c);

广度优先遍历

// 广度优先遍历
breadthRirstSearch() {// 初始化用于广度优先遍历的队列let queue = new Queue();console.log('根节点', this[ROOT]);let arr = [];let root = this[ROOT];if (!root) return;queue.enqueue(root);while (queue.size()) {let queueFirst = queue.dequeue();arr.push(queueFirst.key);queueFirst.lchild && queue.enqueue(queueFirst.lchild);queueFirst.rchild && queue.enqueue(queueFirst.rchild);}return arr;
}

二叉树遍历算法的改进

上面介绍的二傻叔的深度优先遍历算法都是用递归函数实现的,这是很低效的,原因就在于系统帮你调用了一个栈并做了诸如保护现场和恢复现场等复杂的操作,才使得遍历可以用非常简洁的代码实现。

有的人可能会想到,关于二叉树深度优先遍历算法的非递归实现和递归实现,一个是用户自己定义栈,一个是系统栈,为什么用户自己定义的栈要比系统栈执行高效?
一个较为通俗的解释是:递归函数所申请的系统栈,是一个所有递归函数都通用的栈,对于二叉树深度优先遍历算法,系统栈除了记录访问过的节点信息之外,还有其他信息需要记录,以实现函数的递归调用,用户自己定义的栈仅保存了遍历所需的节点信息,是对遍历算法的一个针对性的设计,对于遍历算法来说,显然要比递归函数通用的系统栈更高,也就是一般情况下,专业的要比通用的要好一些。

然而在实际应用中不是这样的,如尾递归在很多机器上都会被优化为循环,因此递归函数不一定就比非递归函数执行效率低。

栈数据结构,满足后进先出的规则用来辅佐我们遍历

// 栈结构 用来辅助非递归遍历
class Stack {constructor() {this.items = [];}push(data) {this.items.push(data);}pop() {return this.items.pop();}peek() {return this.items[this.items.length - 1];}size() {return this.items.length;}
}

非递归的先序遍历

preOrderTraversal() {console.log('先序遍历');let root = this[ROOT];// 初始化辅助遍历存储的栈let stack = new Stack();let arr = []; // 用于存储先序遍历的顺序
stack.push(root);// 如果栈不为空 则一直走while (stack.size()) {let stackTop = stack.pop();// 访问栈顶元素
        arr.push(stackTop.key);stackTop.rchild && stack.push(stackTop.rchild);stackTop.lchild && stack.push(stackTop.lchild);}return arr;
}

非递归的中序排序

// 中序遍历二叉树
inorderTraversal() {// 初始化用于辅助排序的栈let stack = new Stack;let p = null; // 用于指向当前遍历到的元素let arr = []; // 用户记录排序的顺序p = this[ROOT];while (stack.size() || p !== null) {while (p !== null) {stack.push(p);p = p.lchild;}// 如果栈不为空 出栈if (stack.size()) {p = stack.pop();arr.push(p.key);p = p.rchild;}}return arr;
}

非递归的后序排序

// 中序遍历二叉树
inorderTraversal() {// 初始化用于辅助排序的栈let stack = new Stack;let p = null; // 用于指向当前遍历到的元素let arr = []; // 用户记录排序的顺序p = this[ROOT];while (stack.size() || p !== null) {while (p !== null) {stack.push(p);p = p.lchild;}// 如果栈不为空 出栈if (stack.size()) {p = stack.pop();arr.push(p.key);p = p.rchild;}}return arr;
}

想一下,如果我们的插入顺序第一个数非常大,然后后面的数字都是越来越小的会有什么问题产生呢?

下一篇文章讲述这种问题的一个解决方案,平衡二叉树。

转载于:https://www.cnblogs.com/z937741304/p/11544054.html

javascript实现二叉搜索树相关推荐

  1. JavaScript中的二叉搜索树删除节点代码

    JavaScript中的二叉搜索树删除节点代码 提示:只有删除的节点的操作哦~ 二叉搜索树的删除节点操作: 删除二叉搜索树节点会出现的情况 1.删除的是叶子节点,即其左右均为空节点 2. 删除的是只有 ...

  2. js遍历树节点下的所有子节点_「Javascript数据结构」× 二叉搜索树

    目录 树的相关术语 二叉树和二叉搜索树 二叉树的遍历 二叉搜索树的搜索 二叉搜索树的移除 「 树的相关术语 」 一个树结构包含一些列存在父子关系的节点.每个节点都有一个父节点(除了顶部的第一个节点)以 ...

  3. LeetCode(783)——二叉搜索树结点最小距离(JavaScript)

    给定一个二叉搜索树的根结点 root, 返回树中任意两节点的差的最小值. 示例1: 输入: root = [4,2,6,1,3,null,null] 输出: 1 解释: 注意,root是树结点对象(T ...

  4. LeetCode(530)——二叉搜索树的最小绝对差(JavaScript)

    给定一个所有节点为非负值的二叉搜索树,求树中任意两节点的差的绝对值的最小值. 示例: 输入: 输出: 1 解释: 最小绝对差为1,其中 2 和 1 的差的绝对值为 1(或者 2 和 3). 注意: 树 ...

  5. ★LeetCode(538)——把二叉搜索树转换为累加树(JavaScript)

    给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和. 示例1: 输入: 二叉搜索树: ...

  6. LeetCode(235)——二叉搜索树的最近公共祖先(JavaScript)

    给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先. 百度百科中最近公共祖先的定义为:"对于有根树 T 的两个结点 p.q,最近公共祖先表示为一个结点 x,满足 x 是 p.q 的祖 ...

  7. ★LeetCode(669)——修剪二叉搜索树(JavaScript)

    给定一个二叉搜索树,同时给定最小边界L 和最大边界 R.通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) .你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根 ...

  8. ★LeetCode(108)——将有序数组转换为二叉搜索树(JavaScript)

    将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1. 示例: 给定有序数组: [-10,-3,0, ...

  9. LeetCode(938)——二叉搜索树的范围和(JavaScript)

    给定二叉搜索树的根结点 root,返回 L 和 R(含)之间的所有结点的值的和. 二叉搜索树保证具有唯一的值. 示例1: 输入:root = [10,5,15,3,7,null,18], L = 7, ...

最新文章

  1. ansible自动化运维(一)——基本架构简介
  2. 当钢铁直男去应聘...... | 每日趣闻
  3. 主nginx linux,Linux-实现双主模型的nginx的高可用
  4. mysql开启远程访问
  5. hello cnblogs(从wordpress、收费空间到博客园)
  6. mysql concat 日期_mysql group_concat时间用法
  7. 高中关于人工智能方面的课题_2019山东人工智能教育专项课题指南选题
  8. 中下游大学毕业如何在大城市和各种985大学生厮杀?
  9. python如何下载安装tensorflow_TensorFlow下载与安装
  10. 假如我结婚以后父母把房子过户到我一人名下,属于夫妻共同财产嘛?
  11. windows系统bat批处理 windows 关机,重启,锁定,休眠,注销
  12. 搜索鸿蒙系统,鸿蒙系统更进一步,华为上线搜索服务,HMS有望替代谷歌GMS
  13. 最近200篇文章汇总
  14. openwrt开机自启动脚本编写
  15. 获得代理ippython_Python自动获取代理IP
  16. 纯真IP/ZXinc_IPv6数据库镜像及MySQL脚本更新同步更新 for Python 3.x
  17. 公司法人代表变更需要多少钱?
  18. 计算机辅助设计 王茹,《计算机辅助设计》王茹.pdf
  19. STM32 NEC红外遥控器解码
  20. WHUT第九周训练整理

热门文章

  1. mysql 自动补齐 表名,列名 方法, 重启mysql方法
  2. spring集成kafka
  3. windows下的php的memcache扩展的安装及memcache最新下载地址
  4. 我已经把servlet-api.jar加到classpath中了,可还是无法编译servlet
  5. 我用的archlinux+slim+openbox+tint2+feh+thunar+gnome-terminal+gvim+fcitx
  6. 【数据挖掘笔记七】高级模式挖掘
  7. centos下排查vsftpd出现put零字节问题的记录
  8. Hadoop系列三:Hadoop之Pig篇
  9. 设备驱动程序INF文件
  10. java的知识点27——lambda推导