Js实现二叉树
1.二叉树特性
- 一个二叉树第i层最多的节点数为2^(i-1),i>=1;
- 深度==层数;
- 深度为k的二叉树最多的节点数为2^k-1,k>=1;
- 对于任何非空的二叉树T,若n0表示叶节点的个数,n2表示度为2的非叶节点的个数,则有n0=n2+1.
2.二叉搜索树(BST)
- 二叉搜索树是一颗二叉树,可以是空;
-
如果不为空:
非空左子树的所有键值小于其根节点的键值;
非空右子树的所有键值大于其根节点的键值;
左右子树本身也是二叉搜索树;
2-1.向树中插入一个新的键
//向树中插入一个新的键
BinarySearchTree.prototype.insert=function(key){
//1.创建一个新节点
var newNode=new Node(key);
//2.判断当前的树是否是空树,如果是则让root指向newNode
if(this.root==null){
this.root=newNode;
}
//不是空树,则让根节点和newNode做比较
else{
this.insertNode(this.root,newNode);
}
}
BinarySearchTree.prototype.insertNode=function(node,newNode){
//如果新节点比要比较的节点小,则进入左子树继续比较
if(newNode.key<node.key){
//如果左子树的部分不存在,则直接让node.left指向newNode
if(node.left==null){
node.left=newNode;
}//如果左子树的部分存在,则让newNode和node.left的节点做比较
else{
this.insertNode(node.left,newNode);
}
}
//如果新节点比要比较的节点大,则进入右子树继续比较
else{
//如果右子树的部分不存在,则直接让node.right指向newNode
if(node.right==null){
node.right=newNode;
}//如果右子树的部分存在,则让newNode和node.right的节点做比较
else{
this.insertNode(node.right,newNode);
}
}
}
2-2.先序遍历
//先序遍历所有节点,先遍历根节点,然后遍历左子树,再遍历右子树
BinarySearchTree.prototype.preOrder=function(handler){
//遍历根节点
this.preOrderNode(this.root,handler);
}
BinarySearchTree.prototype.preOrderNode=function(node,handler){
if(node!=null){
//处理节点
handler(node.key);
//然后遍历左子树
this.preOrderNode(node.left,handler);
//再遍历右子树
this.preOrderNode(node.right,handler);
}
}
2-3.中序遍历
//中序遍历所有节点,先遍历左子树,再处理根节点,最后是右子树
BinarySearchTree.prototype.midOrder=function(handler){
this.midOrderNode(this.root,handler);
}
BinarySearchTree.prototype.midOrderNode=function(node,handler){
if(node!=null){
//先遍历左子树
this.midOrderNode(node.left,handler);
//再处理根节点
handler(node.key);
//最后是右子树
this.midOrderNode(node.right,handler);
}
}
2-4.后序遍历
//后序遍历所有节点,先遍历左子树,再遍历右子树,最后处理根节点
BinarySearchTree.prototype.postOrder=function(handler){
this.postOrderNode(this.root,handler);
}
BinarySearchTree.prototype.postOrderNode=function(node,handler){
if(node!=null){
//先遍历左子树
this.postOrderNode(node.left,handler);
//再遍历右子树
this.postOrderNode(node.right,handler);
//最后处理根节点
handler(node.key);
}
}
2-5.返回树中最小的值/键
//返回树中最小的值/键,因为二叉搜索树的特性,所以最小值是在BST的最左边的节点上
BinarySearchTree.prototype.minValue=function(){
var node=this.root;
//找BST的最左边的节点
while(node.left!=null){
node=node.left;
}
//返回最小值
return node.key;
}
2-6.返回树中最大的值/键
//返回树中最大的值/键,因为二叉搜索树的特性,所以最大值是在BST的最右边的节点上
BinarySearchTree.prototype.maxValue=function(){
var node=this.root;
//找BST的最右边的节点
while(node.right!=null){
node=node.right;
}
//返回最大值
return node.key;
}
2-7.在树中查一个键
//在树中查一个键,若存在,返回true,不存在,返回false
BinarySearchTree.prototype.search=function(key){
var node=this.root;
//让key和node.key作比较,小于的话,去左子树继续找,大于的话,去右子树找,等于的话,就返回true,一直找都没找到则返回false
while(node!=null){
if(key<node.key){
node=node.left;
}else if(key>node.key){
node=node.right;
}
else{
return true;
}
}
return false;
}
2-8.删除树中的节点
- 先找到要删除的节点,没找到就不删了;
-
找到了要删除的节点:
①要删除的节点是叶子结点或者是没有左右子树的根节点
要删除的节点是叶子结点或者是没有左右子树的根节点,是叶子节点的情 况:找到要删除节点的父节点。判断要删除的节点是父节点的左孩子还是右 孩子,对应的指向为null;是根节点的情况,让root指向null
②删除的是只有一个子节点的节点
有下面6种情况:
前四种情况是current不是根节点:
后两种情况是current是根节点:
③删除的是有两个子节点的节点
首先先说下删除两个节点的思路:
如图:如果要删除15这个节点,可以用14这个节点替换15,也可以用18替换15(满足树一直是BST),我们把14可以叫做15的前驱,把18叫做15的后继。对于
前驱
可以理解为BST中
小于并且最接近要删除节点
的节点,
后继
可以理解为BST中
大于且最接近要删除节点
的节点,以15为例,15作为要删除的节点,要找它的前驱就要在它的左子树最右端找(找到的是14),找后继就在它右子树的最左端找(找到的是18)。用前驱或者是后继去替换要删除的节点,都可以,我用的是后继替换。
求后继节点的方法:找到要删除的节点,看它的右子树,找右子树最左端的节点。这个节点就是后继节点。
代码如下:
//找后继的方法
BinarySearchTree.prototype.getSuccessor=function(delNode){
//1.定义变量,保存找到的后继
var successor=delNode;
var current=delNode.right;
var successorParent=delNode;
//循环查找
while(current!=null){
successorParent=successor;
successor=current;
current=current.left;
}
//如果后继不是要删除节点的right节点
//这个判断的原因是:如果找到的后继还有个右孩子(后继不可能有左孩子,如果有左孩子,那么找到的后继就该是左孩子了),让后继的父节点的left指向后继的右孩子(防止这个右孩子丢失)
if(successor!=delNode.right){
successorParent.left=successor.right;
successor.right=delNode.right;
}
return successor;
}
找到后继节点,就该替换了,接下来,分情况讨论:
①如果要删除的节点是根节点
②如果删除的节点不是根节点并且对于父节点来说是左孩子,而且它的后继就是他的right
③如果删除的节点不是根节点并且对于父节点来说是右孩子,而且它的后继就是他的right
④如果删除的节点不是根节点并且对于父节点来说是左孩子,且它的后继不是它的right
⑤如果删除的节点不是根节点并且对于父节点来说是右孩子,且它的后继不是它的right
代码实现:
//从树中移除某一项
BinarySearchTree.prototype.remove=function(key){
//1.寻找要删除的节点
var current=this.root;
var parent=null;
var isLeftChild=true;
while(key!=current.key){
parent=current;
if(key<current.key){
current=current.left;
isLeftChild=true;
}else{
current=current.right;
isLeftChild=false;
}
//如果当前的current==null,就说明没找到
if(current==null){
return false;
}
}
//2.找到要删除的节点,分情况讨论:
//2-1.要删除的节点是叶子结点或者是没有左右子树的根节点,是叶子节点的情况:找到要删除节点的父节点。判断要删除的节点是父节点的左孩子还是右孩子,对应的指向为null;是根节点的情况,让root指向null
if(current.left==null&¤t.right==null){
if(current==this.root){
this.root=null;
}
else if(isLeftChild){
parent.left=null;
}else{
parent.right==null;
}
}
//2-2.要删除的节点只有一个子节点,分6种情况
else if(current.right==null){
if(current==this.root){
this.root=current.left;
}
else if(isLeftChild){
parent.left=current.left;
}
else{
parent.right=current.left;
}
}else if(current.left==null){
if(current==this.root){
this.root=current.right;
}
else if(isLeftChild){
parent.left=current.right;
}else{
parent.right=current.right;
}
}
//2-3.删除的节点由两个子节点
else{
//获取删除节点的后继
var successor=this.getSuccessor(current);
//判断是否为根节点
if(current==this.root){
//如果是根节点的话,让root指向这个后继,让当前current(原根节点)的左子树充当后继的左子树
this.root=successor;
successor.left=current.left;
}//如果不是根节点的话,判断是左孩子还是右孩子
else if(isLeftChild){
//是左孩子的话,让parent.left直接指向后继,让要删除节点的左子树充当后继的左子树
parent.left=successor;
successor.left=current.left;
}else{
//是右孩子的话,让parent.right直接指向后继,让要删除节点的左子树充当后继的左子树
parent.right=successor;
successor.left=current.left;
}
}
}
3.平衡二叉树与非平衡二叉树
- 比较好的二叉平衡树数据是左右分布均匀的;
- 非平衡二叉树:在插入连续数据后,分布不均的二叉树;
- 对于一个平衡二叉树来说,插入或者查找等操作的效率是O(logN)
- 对于一个非平衡二叉树来说,插入或者查找等操作的效率是O(N)
-
常见的平衡树有:AVL树和红黑树
AVL树:是最早的一种平衡树,让每个节点多储存一个额外的数据;
红黑树:它的性能优于AVL树。
最后附上BST基本操作的代码:
//二叉搜索树
function BinarySearchTree(){
//节点
function Node(key){
this.key=key;
this.left=null;
this.right=null;
}
//属性
this.root=null;
//方法
//向树中插入一个新的键
BinarySearchTree.prototype.insert=function(key){
//1.创建一个新节点
var newNode=new Node(key);
//2.判断当前的树是否是空树,如果是则让root指向newNode
if(this.root==null){
this.root=newNode;
}
//不是空树,则让根节点和newNode做比较
else{
this.insertNode(this.root,newNode);
}
}
BinarySearchTree.prototype.insertNode=function(node,newNode){
//如果新节点比要比较的节点小,则进入左子树继续比较
if(newNode.key<node.key){
//如果左子树的部分不存在,则直接让node.left指向newNode
if(node.left==null){
node.left=newNode;
}//如果左子树的部分存在,则让newNode和node.left的节点做比较
else{
this.insertNode(node.left,newNode);
}
}
//如果新节点比要比较的节点大,则进入右子树继续比较
else{
//如果右子树的部分不存在,则直接让node.right指向newNode
if(node.right==null){
node.right=newNode;
}//如果右子树的部分存在,则让newNode和node.right的节点做比较
else{
this.insertNode(node.right,newNode);
}
}
}
//先序遍历所有节点,先遍历根节点,然后遍历左子树,再遍历右子树
BinarySearchTree.prototype.preOrder=function(handler){
//遍历根节点
this.preOrderNode(this.root,handler);
}
BinarySearchTree.prototype.preOrderNode=function(node,handler){
if(node!=null){
//处理节点
handler(node.key);
//然后遍历左子树
this.preOrderNode(node.left,handler);
//再遍历右子树
this.preOrderNode(node.right,handler);
}
}
//中序遍历所有节点,先遍历左子树,再处理根节点,最后是右子树
BinarySearchTree.prototype.midOrder=function(handler){
this.midOrderNode(this.root,handler);
}
BinarySearchTree.prototype.midOrderNode=function(node,handler){
if(node!=null){
//先遍历左子树
this.midOrderNode(node.left,handler);
//再处理根节点
handler(node.key);
//最后是右子树
this.midOrderNode(node.right,handler);
}
}
//后序遍历所有节点,先遍历左子树,再遍历右子树,最后处理根节点
BinarySearchTree.prototype.postOrder=function(handler){
this.postOrderNode(this.root,handler);
}
BinarySearchTree.prototype.postOrderNode=function(node,handler){
if(node!=null){
//先遍历左子树
this.postOrderNode(node.left,handler);
//再遍历右子树
this.postOrderNode(node.right,handler);
//最后处理根节点
handler(node.key);
}
}
//返回树中最小的值/键,因为二叉搜索树的特性,所以最小值是在BST的最左边的节点上
BinarySearchTree.prototype.minValue=function(){
var node=this.root;
//找BST的最左边的节点
while(node.left!=null){
node=node.left;
}
//返回最小值
return node.key;
}
//返回树中最大的值/键,因为二叉搜索树的特性,所以最大值是在BST的最右边的节点上
BinarySearchTree.prototype.maxValue=function(){
var node=this.root;
//找BST的最右边的节点
while(node.right!=null){
node=node.right;
}
//返回最大值
return node.key;
}
//在树中查一个键,若存在,返回true,不存在,返回false
BinarySearchTree.prototype.search=function(key){
var node=this.root;
//让key和node.key作比较,小于的话,去左子树继续找,大于的话,去右子树找,等于的话,就返回true,一直找都没找到则返回false
while(node!=null){
if(key<node.key){
node=node.left;
}else if(key>node.key){
node=node.right;
}
else{
return true;
}
}
return false;
}
//从树中移除某一项
BinarySearchTree.prototype.remove=function(key){
//1.寻找要删除的节点
var current=this.root;
var parent=null;
var isLeftChild=true;
while(key!=current.key){
parent=current;
if(key<current.key){
current=current.left;
isLeftChild=true;
}else{
current=current.right;
isLeftChild=false;
}
//如果当前的current==null,就说明没找到
if(current==null){
return false;
}
}
//2.找到要删除的节点,分情况讨论:
//2-1.要删除的节点是叶子结点或者是没有左右子树的根节点,是叶子节点的情况:找到要删除节点的父节点。判断要删除的节点是父节点的左孩子还是右孩子,对应的指向为null;是根节点的情况,让root指向null
if(current.left==null&¤t.right==null){
if(current==this.root){
this.root=null;
}
else if(isLeftChild){
parent.left=null;
}else{
parent.right==null;
}
}
//2-2.要删除的节点只有一个子节点,分6种情况
else if(current.right==null){
if(current==this.root){
this.root=current.left;
}
else if(isLeftChild){
parent.left=current.left;
}
else{
parent.right=current.left;
}
}else if(current.left==null){
if(current==this.root){
this.root=current.right;
}
else if(isLeftChild){
parent.left=current.right;
}else{
parent.right=current.right;
}
}
//2-3.删除的节点由两个子节点
else{
//获取删除节点的后继
var successor=this.getSuccessor(current);
//判断是否为根节点
if(current==this.root){
//如果是根节点的话,让root指向这个后继,让当前current(原根节点)的左子树充当后继的左子树
this.root=successor;
successor.left=current.left;
}//如果不是根节点的话,判断是左孩子还是右孩子
else if(isLeftChild){
//是左孩子的话,让parent.left直接指向后继,让要删除节点的左子树充当后继的左子树
parent.left=successor;
successor.left=current.left;
}else{
//是右孩子的话,让parent.right直接指向后继,让要删除节点的左子树充当后继的左子树
parent.right=successor;
successor.left=current.left;
}
}
}
//找后继的方法
BinarySearchTree.prototype.getSuccessor=function(delNode){
//1.定义变量,保存找到的后继
var successor=delNode;
var current=delNode.right;
var successorParent=delNode;
//循环查找
while(current!=null){
successorParent=successor;
successor=current;
current=current.left;
}
//如果后继不是要删除节点的right节点
//这个判断的原因是:如果找到的后继还有个右孩子(后继不可能有左孩子,如果有左孩子,那么找到的后继就该是左孩子了),让后继的父节点的left指向后继的右孩子(防止这个右孩子丢失)
if(successor!=delNode.right){
successorParent.left=successor.right;
successor.right=delNode.right;
}
return successor;
}
}
//使用二叉搜索树
var bst=new BinarySearchTree();
bst.insert(11);
bst.insert(7);
bst.insert(15);
bst.insert(5);
bst.insert(3);
bst.insert(9);
bst.insert(8);
bst.insert(10);
bst.insert(13);
bst.insert(12);
bst.insert(14);
bst.insert(20);
bst.insert(18);
bst.insert(25);
//先序遍历
var result='';
bst.preOrder(function(key){
result+=key+' ';
})
console.log(result);//11 7 5 3 9 8 10 15 13 12 14 20 18 25
//中序遍历
var res='';
bst.midOrder(function(key){
res+=key+' ';
})
console.log(res);//3 5 7 8 9 10 11 12 13 14 15 18 20 25
//后序遍历
var postRes='';
bst.postOrder(function(key){
postRes+=key+' ';
})
console.log(postRes);//3 5 8 10 9 7 12 14 13 18 25 20 15 11
console.log('min key:'+bst.minValue());//min key:3
console.log('max key:'+bst.maxValue());//max key:25
console.log(bst.search(1));//false
console.log(bst.search(18));//true
console.log(bst.remove(1));//false
console.log(bst.remove(15));//undefined因为删除成功并没有让返回什么东西,所以是undefined,也可以在删除方法中加入删除成功返回true
//使用中序遍历,查看bst中的节点
res='';
bst.midOrder(function(key){
res+=key+' ';
})
console.log(res);//3 5 7 8 9 10 11 12 13 14 18 20 25
//此时bst中已无15,说明删除成功
结果: