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.删除树中的节点

  1. 先找到要删除的节点,没找到就不删了;
  2. 找到了要删除的节点:
          ①要删除的节点是叶子结点或者是没有左右子树的根节点
          要删除的节点是叶子结点或者是没有左右子树的根节点,是叶子节点的情           况:找到要删除节点的父节点。判断要删除的节点是父节点的左孩子还是右        孩子,对应的指向为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&&current.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&&current.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,说明删除成功

结果:
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
二叉树JavaScript 实现可以使用对象来表示每个节点,每个节点包含一个值和两个指向左右子节点的指针。以下是一个简单的实现: ``` class Node { constructor(value) { this.value = value; this.left = null; this.right = null; } } class BinaryTree { constructor() { this.root = null; } insert(value) { const node = new Node(value); if (!this.root) { this.root = node; return; } let current = this.root; while (true) { if (value < current.value) { if (!current.left) { current.left = node; break; } current = current.left; } else { if (!current.right) { current.right = node; break; } current = current.right; } } } search(value) { let current = this.root; while (current) { if (value === current.value) { return current; } else if (value < current.value) { current = current.left; } else { current = current.right; } } return null; } } ``` 你可以使用 `insert` 方法来插入一个值,使用 `search` 方法来查找一个值。例如: ``` const tree = new BinaryTree(); tree.insert(5); tree.insert(3); tree.insert(7); tree.insert(1); tree.insert(4); tree.insert(6); tree.insert(8); console.log(tree.search(4)); // Node { value: 4, left: Node { value: 1, left: null, right: null }, right: Node { value: 5, left: Node { value: 3, left: null, right: [Node] }, right: Node { value: 7, left: Node { value: 6, left: null, right: null }, right: Node { value: 8, left: null, right: null } } } } ``` 这将输出包含值为 4 的节点及其子节点的对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值