媒介
能够有一部分人没有读过我上一篇写的二叉堆,所以这里把二叉树的基本概念复制过来了,假如读过的人能够疏忽前面针对二叉树基本概念的引见,别的假如对链表数据构造不清楚的最好先看一下本人之前写的js数据构造-链表
二叉树
二叉树(Binary Tree)是一种树形构造,它的特点是每一个节点最多只要两个分支节点,一棵二叉树平常由根节点,分支节点,恭弘=叶 恭弘子节点组成。而每一个分支节点也经常被称作为一棵子树。
- 根节点:二叉树最顶层的节点
- 分支节点:除了根节点之外且具有恭弘=叶 恭弘子节点
- 恭弘=叶 恭弘子节点:除了本身,没有其他子节点
经常使用术语
在二叉树中,我们经常还会用父节点和子节点来形貌,比方图中2为6和3的父节点,反之6和3是2子节点
二叉树的三个性子
在二叉树的第i层上,最多有2^i-1个节点
- i=1时,只要一个根节点,2^(i-1) = 2^0 = 1
深度为k的二叉树最多有2^k-1个节点
- i=2时,2^k-1 = 2^2 – 1 = 3个节点
- 对任何一棵二叉树T,假如总结点数为n0,度为2(子树数目为2)的节点数为n2,则n0=n2+1
树和二叉树的三个主要差异
- 树的节点个数最少为1,而二叉树的节点个数能够为0
- 树中节点的最大度数(节点数目)没有限定,而二叉树的节点的最大度数为2
- 树的节点没有摆布之分,而二叉树的节点有摆布之分
二叉树分类
二叉树分为完全二叉树(complete binary tree)和满二叉树(full binary tree)
- 满二叉树:一棵深度为k且有2^k – 1个节点的二叉树称为满二叉树
- 完全二叉树:完全二叉树是指末了一层左侧是满的,右侧能够满也能够不满,然后其他层都是满的二叉树称为完全二叉树(满二叉树也是一种完全二叉树)
二叉搜刮树
二叉搜刮树满足以下的几个性子:
- 若恣意节点的左子树不空,则左子树上一切节点的值均小于它的根节点的值;
- 若恣意节点的右子树不空,则右子树上一切节点的值均大于它的根节点的值;
- 恣意节点的左、右子树也须要满足左侧小右侧大的性子
我们来举个例子来深切明白以下
一组数据:12,4,18,1,8,16,20
由下图能够看出,左侧的图满足了二叉树的性子,它的每一个左子节点都小于父节点,右子节点大于其父节点,同时左子树的节点都小于根节点,右子树的节点都大于根节点
二叉搜刮树主要的几个操纵:
- 查找(search)
- 插进去(insert)
- 遍历(transverse)
二叉树搜刮树的链式存储构造
经由历程下图,能够晓得二叉搜刮树的节点平常包括4个域,数据元素,离别指向其左,右节点的指针和一个指向父节点的指针所组成,平常把这类存储构造称为三叉链表。
用代码初始化一个二叉搜刮树的结点:
- 一个指向父亲节点的指针 parent
- 一个指向左节点的指针 left
- 一个指向右节点的指针 right
- 一个数据元素,内里能够是一个key和value
class BinaryTreeNode {
constructor(key, value){
this.parent = null;
this.left = null;
this.right = null;
this.key = key;
this.value = value;
}
}
接着我们再用代码去初始化一个二叉搜刮树
- 在二叉搜刮树中我们会保护一个root指针,这个就相当于链表中的head指针,在没有任何节点插进去的时刻它指向空,在有节点插进去今后它指向根节点。
class BinarySearchTree {
constructor() {
this.root = null;
}
}
建立节点
static createNode(key, value) {
return new BinarySearchTree(key, value);
}
插进去操纵
看下面这张图,13是我们要插进去的节点,它插进去的具体步骤:
- 跟根节点12做比较,比12大,所以我们肯定了,这个节点是往右子树插进去的
- 而根节点的右侧已有节点,那末跟这个节点18做比较,效果小于18所以往18的左节点找位置
- 而18的左节点也已有节点了,所以继承跟这个节点做比较,效果小于16
- 恰好16的左节点是空的(left=null),所以13这个节点就插进去到了16的左节点
经由历程上面的形貌,我们来看看代码是怎样写的
- 定义两个指针,离别是p和tail,最初都指向root,p是用来指向要插进去的位置的父节点的指针,而tail是用来查找插进去位置的,所以末了它会指向null,用上图举个例子,p末了指向了6这个节点,而tail末了指向了null(tail为null则申明已找到了要插进去的位置)
- 轮回,tail依据我们上面剖析的一步一步往下找位置插进去,假如比当前节点小就往左找,大则往右找,一直到tail找到一个空位置也就是null
- 假如当前的root为null,则申明当前构造中并没有节点,所以插进去的第一个节点直接为跟节点,即this.root = node
- 将插进去后的节点的parent指针指向父节点
insert(node){
let p = this.root;
let tail = this.root;
// 轮回遍历,去找到对应的位置
while(tail) {
p = tail;
// 要插进去的节点key比当前节点小
if (node.key < tail.key){
tail = tail.left;
}
// 要插进去的节点key比当前节点大
else {
tail = tail.right;
}
}
// 没有根节点,则直接作为根节点插进去
if(!p) {
this.root = node;
return;
}
// p是末了一个节点,也就是我们要插进去的位置的父节点
// 比父节点大则往右侧插进去
if(p.key < node.key){
p.right = node;
}
// 比父节点小则往左侧插进去
else {
p.left = node;
}
// 指向父节点
node.parent = p;
}
查找
查找就很简朴了,实在和插进去差多,都是去别叫摆布节点的大小,然后往下找
- 假如root = null, 则二叉树中没有任何节点,直接return,或许报个错什么的。
- 轮回查找
search(key) {
let p = this.root;
if(!p) {
return;
}
while(p && p.key !== key){
if(p.key<key){
p = p.right;
}else{
p = p.left;
}
}
return p;
}
遍历
- 中序遍历(inorder):先遍历左节点,再遍历本身,末了遍历右节点,输出的恰好是有序的列表
- 前序遍历(preorder):先本身,再遍历左节点,末了遍历右节点
- 后序遍历(postorder):先左节点,再右节点,末了本身
最经常使用的平常是中序遍历,由于中序遍历能够获得一个已排好序的列表,这也是为何会用二叉搜刮树排序的缘由
依据上面临中序遍历的诠释,那末代码就变的很简朴,就是一个递归的历程,递归住手的条件就是节点为null
- 先遍历左节点–>yield* this._transverse(node.left)
- 遍历本身 –> yield* node
- 遍历左节点 –> yield* this._transverse(node.right)
transverse() {
return this._transverse(this.root);
}
*_transverse(node){
if(!node){
return;
}
yield* this._transverse(node.left);
yield node;
yield* this._transverse(node.right)
}
看上面这张图,我们简化的来看一下,先接见左节点4,再本身12,然后右节点18,如许输出的就恰好是一个4,12,18
补充:这个处所用了generater,所以返回的一个迭代器。能够经由历程下面这类体式格局获得一个有序的数组,这里的条件就当是已有插进去的节点了
const tree = new BinaryTree();
//...中心省略插进去历程
// 如许就返回了一个有序的数组
var arr = [...tree.transverse()].map(item=>item.key);
完全代码
class BinaryTreeNode {
constructor(key, value) {
// 指向父节点
this.p = null;
// 左节点
this.left = null;
// 右节点
this.right = null;
// 键
this.key = key;
// 值
this.value = value;
}
}
class BinaryTree {
constructor() {
this.root = null;
}
static createNode(key, value) {
return new BinaryTreeNode(key, value);
}
search(key) {
let p = this.root;
if (!p) {
return;
}
while (p && p.key !== key) {
if (p.key < key) {
p = p.right;
} else {
p = p.left;
}
}
return p;
}
insert(node) {
// 尾指针的父节点指针
let p = this.root;
// 尾指针
let tail = this.root;
while (tail) {
p = tail;
if (node.key < tail.key) {
tail = tail.left;
} else {
tail = tail.right;
}
}
if (!p) {
this.root = node;
return;
}
// 插进去
if (p.key < node.key) {
p.right = node;
} else {
p.left = node;
}
node.p = p;
}
transverse() {
return this.__transverse(this.root);
}
*__transverse(node) {
if (!node) {
return;
}
yield* this.__transverse(node.left);
yield node;
yield* this.__transverse(node.right);
}
}
总结
二叉查找树就讲完了哈,实在这个和链表很像的,照样操纵那末几个指针,既然叫查找树了,它主要照样用来左一些搜刮,另有就是排序了,别的补充一下,二叉查找树里找最大值和最小值也很轻易是否是,假如你大抵读懂了的话。
这篇文章我写的觉得有点乱诶,由于总觉得那里引见的不到位,让一些基本差的人会看不懂,假如有不懂或许文章那里写错了,迎接批评留言哈
后续
后续写什么呢,这个题目我也在想,排序算法,react第三方的一些模仿完成?,做个小顺序组件库?照样别的,容我再想几个小时,由于能够,有发起的朋友们也能够留言说一下哈。
末了末了,最主要的请给个赞,请粉一个呢,感谢啦