JavaScript数据结构与算法5

107 阅读4分钟

参考

树(Tree)

  • 非线性结构
    • 查找、修改、删除效率较数组、链表快,慢于哈希表,但空间利用率高

二叉搜索树(Binary Search Tree)

  • 非空左子树的所有键值小于其根节点键值

  • 非空右子树的所有键值大于其根节点键值

  • 左、右子树本身也都是二叉搜索树

    • 类似有序数组的二分查找
  • BST的remove(key)

    • current:要删除的节点
    • parent:要删除的节点的父节点
    • isLeftChild:current是parent的左子树为true,右子树为false
    • 1、删除的节点无后继节点
      • current是根节点,直接删除根节点
        • 如:remove(53)
      • current是叶子节点,通过isLeftChild,将parent.left 或 parent.right置为null
        • 如:remove(23)
    • 2、删除的节点仅有一个后继节点
      • 后继节点是左子节点
        • current是根节点,将根指针指向左子节点
          • remove(53)
        • current是非根节点,通过isLeftChild,将parent.left 或 parent.right 指向 current.left
          • 如:remove(45)
      • 后继节点是右子节点
        • current是根节点,将根指针指向右子节点
          • 如:remove(53)
        • current是非根节点,用isLeftChild,将parent.left 或 parent.right 指向 current.right
          • 如:remove(3)
    • 3、删除的节点有两个后继节点
      • delNode:要删除的节点
      • successor:替换删除节点位置的节点(此处使用delNode右子树键值最小的节点)
      • successorParent:successor的父接电脑
      • 1、先找到要替换delNode的节点successor
        • 1-1、从delNode的右子树开始遍历它的左子树,找到最小的节点
        • 1-2、如果delNode的右子树最小节点是delNode.right
        • 1-2、不是,则需要将替换节点的右子树赋值给successorParent.left,并将delNode的右子树赋值给successor.right
        • 1-3、返回找到的最小节点
      • 2、current是根节点,将根指针指向successor
      • 2、current是非根节点,用isLeftChild,将parent.left 或 parent.right 指向 successor
      • 3、将要删除的节点的左子树赋值给successor.left
function BST() {
  // 根节点
  this.root = null;
  
  // Node(key):创建节点存储key
  function Node(key){
    this.key = key;
    this.left = null;
    this.right = null;
  }

  // insert(key):插入新的节点,3-1是非递归写法,3-2是递归写法
  BST.prototype.insert = function(key){
    // 1、创建新节点
    var newNode = new Node(key);
    // 2、根节点是否为null
    if(!this.root)
      this.root = newNode;

    // // 3-1、非递归写法:遍历判断节点大小
    // var currentNode = this.root;
    // while(currentNode){
    //   if(currentNode.key == newNode.key){
    //     return false;
    //   }
    //   else if(currentNode.key > newNode.key){
    //     // 左子树
    //     if(!currentNode.left)
    //       currentNode.left = newNode;
    //     currentNode = currentNode.left;
    //   }else{
    //     // 右子树
    //     if(!currentNode.right)
    //       currentNode.right = newNode;
    //     currentNode = currentNode.right;
    //   }
    // }
    
    // 3-2、递归写法
    else
      return this.insertNode(this.root,newNode);

    // return true;
  }

  // 3-2、递归写法:均可转成while循环的非递归完成
  BST.prototype.insertNode = function(Node,newNode){
    if(Node.key == newNode.key){
      return false;
    }
    else if(Node.key > newNode.key){
      // 左子树
      if(!Node.left){
        Node.left = newNode;
        return true;
      }
      return this.insertNode(Node.left,newNode);
    }else{
      // 右子树
      if(!Node.right){
        Node.right = newNode;
        return true;
      }
      return this.insertNode(Node.right,newNode);
    }
  }

  // search(key):查找键为key的节点,成功->currentNode,失败->false
  BST.prototype.search = function(key){
    // 从根节点开始比较key大小
    var currentNode = this.root;
    while(currentNode){
      if(currentNode.key == key){
        return currentNode;
      }else if(currentNode.key > key){
        // 左子树
        currentNode = currentNode.left;
      }else{
        // 右子树
        currentNode = currentNode.right;
      }
    }
    return false;
  }

  // inOrderTraverse():中序遍历,左根右
  BST.prototype.inOrderTraverse = function(){
    if(this.root){
      var OrderStr = [];
      this.inOrderTraverseNode(this.root,OrderStr);
      return OrderStr.join("-");
    }
    return false;  
  } 
  BST.prototype.inOrderTraverseNode = function(Node,OrderStr){
    if(Node){
      // 遍历左子树
      this.inOrderTraverseNode(Node.left,OrderStr);
      // 处理经过的节点
      OrderStr.push(Node.key);
      // 遍历右子树
      this.inOrderTraverseNode(Node.right,OrderStr);
    }
  }

  // preOrderTraverse():先序遍历,根左右
  BST.prototype.preOrderTraverse = function(){
    if(this.root){
      var OrderStr = [];
      this.preOrderTraverseNode(this.root,OrderStr);
      return OrderStr.join("-");
    }
    return false;  
  }
  BST.prototype.preOrderTraverseNode = function(Node,OrderStr){
    if(Node){
      // 处理经过的节点
      OrderStr.push(Node.key);
      // 遍历左子树
      this.preOrderTraverseNode(Node.left,OrderStr);
      // 遍历右子树
      this.preOrderTraverseNode(Node.right,OrderStr);
    }
  } 

  // postOrderTraverse():后序遍历,左右根
  BST.prototype.postOrderTraverse = function(){
    if(this.root){
      var OrderStr = [];
      this.postOrderTraverseNode(this.root,OrderStr);
      return OrderStr.join("-");
    }
    return false;
  } 
  BST.prototype.postOrderTraverseNode = function(Node,OrderStr){
    if(Node){
      // 遍历左子树
      this.postOrderTraverseNode(Node.left,OrderStr);
      // 遍历右子树
      this.postOrderTraverseNode(Node.right,OrderStr);
      // 处理经过的节点
      OrderStr.push(Node.key);
    }
  }

  // min:返回最小的键
  BST.prototype.min = function(){
    // 最左的节点
    var currentNode = this.root;
    while(currentNode.left){
      currentNode = currentNode.left;
    }
    return currentNode;
  }
  // max:返回最大的键
  BST.prototype.max = function(){
    // 最右的节点
    var currentNode = this.root;
    while(currentNode.right){
      currentNode = currentNode.right;
    }
    return currentNode;
  }

  // remove(key):从树中移除某个key
/*  
  无此节点:false
  有此节点:
    当前节点
    1、没有子节点(删除叶节点)
    2、只有左节点 | 只有右节点,连接上一层的节点
    3、两个子节点
*/
  BST.prototype.remove = function(key){
    // 1、寻找键为key的节点
    var current = this.root;
    var parent = null;
    var isLeftChild = null;// parent的左、右子树
    // 比较根节点,判断应该向左 or 右进行搜索
    while(current && current.key != key){
      parent = current;
      if(current.key > key){
        // 左子树
        current = current.left;
        isLeftChild = true;
      }else{
        // 右子树
        current = current.right;
        isLeftChild = false;
      }
    }
    // 空树,遍历到空节点还没找到key
    if(!current)
      return false;


    // 2、没有子节点(删除叶节点 或 删除根节点)
    if(!current.left && !current.right){
      if(current == this.root){
        // 删除根节点
        this.root = null;
      }else{
        // 删除叶子节点
        if(isLeftChild){
          parent.left = null;
        }else{
          parent.right = null;
        }
      }
    }

    // 3、一个子节点(左节点或右节点 -> 连接父节点)
    // 当前节点只有左节点
    else if(!current.right){
      if(current == this.root){
        this.root = current.left;
      }else if(isLeftChild){
        parent.left = current.left;
      }else{
        parent.right = current.left;
      }
    }
    // 当前节点只有右节点
    else if(!current.left){
      if(current == this.root){
        this.root = current.right;
      }else if(isLeftChild){
        parent.left = current.right;
      }else{
        parent.right = current.left;
      }
    }

    // 4、两个子节点
    // 将左子树最大的提上去 或 右子树最小来替换要删除的节点
    else {
        // 1.右子树中键值最小的节点
        var successor = this.getSuccessor(current);
        
        // 2.判断是否是根节点
        if(current == this.root){
          this.root = successor;
        }else if(isLeftChild){
          parent.left = successor;
        }else{
          parent.right = successor;
        }
        
        // 3.将删除节点的左子树赋值给successor
        successor.left = current.left;
    }
  }

  // 找要删除节点的右子树中最小的节点
  BST.prototype.getSuccessor = function (delNode) {
      // 1.使用变量保存临时的节点
      var successorParent = delNode;
      var successor = delNode;
      var current = delNode.right; // 要从右子树开始找

      // 2.寻找节点
      while(current != null){
        successorParent = successor;
        successor = current;
        current = current.left;
      }

      // 3.找到的节点是左子节点为null,右子节点可能为null 
      if(successor != delNode.right){
        // 将替换节点的右子树赋值给successorParent.left
        successorParent.left = successor.right;
        // 将删除节点的右子树赋值给successor.right
        successor.right = delNode.right;
      }      
      return successor;
  }
}

var bst = new BST();
bst.insert(53);
bst.insert(17);
bst.insert(78);
bst.insert(9);
bst.insert(3);
bst.insert(45);
bst.insert(70);
bst.insert(94);
bst.insert(23);
bst.insert(60);
bst.insert(75);
bst.insert(88);

console.log("inOrder:",bst.inOrderTraverse());
console.log("preOrder:",bst.preOrderTraverse());
console.log("postOrder:",bst.postOrderTraverse());

bst.remove(78);
console.log("----remove(78)----");

console.log("inOrder:",bst.inOrderTraverse());
console.log("preOrder:",bst.preOrderTraverse());
console.log("postOrder:",bst.postOrderTraverse());

console.log("min:",bst.min());
console.log("max:",bst.max());
console.log("search:");
console.log(bst.search(3),bst.search(53),bst.search(88));