数据结构学习笔记-二叉查找树(BinarySearchTree)

297 阅读4分钟

二叉搜索树

二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉排序树。二叉搜索树作为一种经典的数据结构,它既有链表的快速插入与删除操作的特点,又有数组快速查找的优势;所以应用十分广泛,例如在文件系统和数据库系统一般会采用这种数据结构进行高效率的排序与检索操作。

性质

1、若左子树不为空,则左子树上左右节点的值都小于根节点的值

2、若它的右子树不为空,则它的右子树上所有的节点的值都大于根节点的值

3、它的左右子树也要分别是二叉搜索树

二叉查找数的操作

 function BinarySearchTree() {
    // 属性
    this.root = null; 
}
 function node(key) {
  this.key = key;
  this.left = null;
  this.right = null;
}

insert方法

 BinarySearchTree.prototype.insert = function (key) {
  // 1 创建新节点
  let newNode = new node(key);
  // 2 判断是否有根节点
  if (this.root == null) {
    this.root = newNode;
  } else {
    this.insertNode(this.root, newNode);
  }
};

BinarySearchTree.prototype.insertNode = function (node, newNode) {
  // 向左查找,即新节点小于根节点
  if (newNode.key < node.key) {
    // 1判断有没有左节点
    if (node.left == null) {
      node.left = newNode;
    } else {
      // 2 有左节点  用左节点和newNode比较
      this.insertNode(node.left, newNode);
    }
  } else {
    // 向右查找 即新节点大于根节点
    if (node.right == null) {
      node.right = newNode;
    } else {
      // 2 有右节点  用右节点和newNode比较
      this.insertNode(node.right, newNode);
    }
  }
};

先序遍历 根-左-右

 BinarySearchTree.prototype.preOrderTranversal = function (callback) {
  this.hanlderNode_1(this.root, callback);
 };
BinarySearchTree.prototype.hanlderNode_1 = function (node, callback) {
  if (node != null) {
    // 1 处理经过的节点
    callback(node.key);
    // 2 处理经过节点的左子节点
    this.hanlderNode_1(node.left, callback);
    // 3 处理经过节点的右子节点
    this.hanlderNode_1(node.right, callback);
  }
};

中序遍历 左- 根-右

BinarySearchTree.prototype.midOrderTranversal = function (callback) {
  this.hanlderNode_2(this.root, callback);
};
BinarySearchTree.prototype.hanlderNode_2 = function (node, callback) {
  if (node != null) {
    // 1 处理左子节点
    this.hanlderNode_2(node.left, callback);
    // 2 处理经过的节点
    callback(node.key);
    // 3 处理右子节点
    this.hanlderNode_2(node.right, callback);
  }
};

后序遍历 左-右-根

BinarySearchTree.prototype.postOrderTranversal = function (callback) {
  this.hanlderNode_3(this.root, callback);
};
BinarySearchTree.prototype.hanlderNode_3 = function (node, callback) {
  if (node != null) {
    // 1 处理左子节点
    this.hanlderNode_3(node.left, callback);
    // 2 处理右子节点
    this.hanlderNode_3(node.right, callback);
    // 3 处理经过的节点
    callback(node.key);
  }
};

获取最大值

BinarySearchTree.prototype.max = function () {
  let node = this.root;
  let key = null;
  // 依次右节点查询
  while (node != null) {
    key = node.key;
    node = node.right;
  }
  return key;
};

获取最小值

BinarySearchTree.prototype.min = function () {
  let node = this.root;
  // 当node为null时,node.key会报错
  // while (node != null) {
  //   node = node.left;
  // }
  // return node.key; // 当node为null时 不走循坏 会报错

  let key = null;
  // 依次向左节点查询
  while (node != null) {
    key = node.key; // 先赋值
    node = node.left;
  }
  return key;
};

搜索特定值

BinarySearchTree.prototype.search = function (key) {
  return this.searchNode_1(this.root, key);
};
BinarySearchTree.prototype.searchNode = function (node, key) {
  if (node == null) return false;
  if (node.key > key) {
    // 说明在左边
    return this.searchNode(node.left, key);
  } else if (node.key < key) {
    // 说明在右边
    return this.searchNode(node.right, key);
  } else {
    // 找到了
    return true;
  }
};
BinarySearchTree.prototype.search_1 = function (key) {
  let node = this.root;
  while (node != null) {
    if (node.key > key) {
      node = node.left;
    } else if (node.key < key) {
      node = node.right;
    } else {
      return true;
    }
  }
  return false;
};

删除节点

删除叶子节点.png 删除节点只有一个子节点.png 删除的节点是根节点有一个子节点.png

删除有两个子节点的节点1.png

删除有两个子节点的节点2.png

后继节点是否直接是delNode的right节点.png

/*
  1找到节点,如果没找到不需要删除
  2 找到删除的节点
  2.1删除的是叶子节点
  2.2 删除的只有一个子节点的节点
  2.3 删除有两个子节点的节点

*/
BinarySearchTree.prototype.remove = function (key) {
  // 1 找到要删除的节点
  let current = this.root;
  let parent = null; // parent为删除的节点的父节点
  let isLeftChildren = true;

  while (current.key != key) {
    parent = current;
    if (current.key > key) {
      isLeftChildren = true;
      current = current.left;
    } else {
      isLeftChildren = false;
      current = current.right;
    }
    //存在某种情况  找到最后的节点依然没有找到
    if (current == null) return false;
  }
  // 结束循坏  说明找到了  current.key == key
  // 2.1 删除的是叶子节点  图实例
  if (current.left == null && current.right == null) {
    // 如果是根节点
    if (current == this.root) {
      this.root = null;
    } else {
      if (isLeftChildren) {
        parent.left = null;
      } else {
        parent.right = null;
      }
    }
  }
  // 2.2 删除的只有一个子节点的节点  2.2 图实例
  // current.right == null 只有左节点    2.2 图实例  第13两种情况 没有right节点
  else if (current.right == null) {
    if (current == this.root) {
      this.root = current.left; // 2.3图实例只有根节点和左节点
    } else if (isLeftChildren) {
      parent.left = current.left;
    } else {
      parent.right = current.left; //只有左节点  指向左节点
    }
  }
  // current.left == null 只有右节点    2.2 图实例  第24两种情况 没有left节点
  else if (current.left == null) {
    if (current == this.root) {
      this.root = current.right; // 2.3图实例只有根节点和右节点节点
    } else if (isLeftChildren) {
      parent.left = current.right; //只有右节点  指向右节点
    } else {
      parent.right = current.right;
    }
  }
  // 2.3 删除有两个子节点的节点
  else {
    // 1 获取后继节点
    let successor = this.getSuccessor(current);
    // 2 判断是不是根节点
    if (this.root == current) {
      this.root = successor;
    } else if (isLeftChildren) {
      parent.left = successor;
    } else {
      parent.right = successor;
    }
    successor.left = current.left; // 连接之前的左节点
  }
};
/*
1 如果我们要删除的节点有两个子节点, 甚至子节点还有子节点,这种情況下我们需要从下面的子节点中找到一个节点,
来替换当前的节点.
2 但是找到的这个节点有什么特征呢?应该是current节点下面所有节点中最接近current节点的.
  2.1要么比current节点小一点点, 要么比current节点大一点点.
  2.2总之你最接近current,你就可以用来替换current的位置,
3 这个节点怎么找呢?   --重点
比current小一点点的节点 一定是current左子树的最大值,
比current大一点点的节点,一定是current右子树的最小值.
4 前驱&后继
4.1 比current小一点点的节点,称为current节点的前驱。
4.2 比current大一点点的节点 称为current节点的后继。
也就是为了能够州除有两个子节点的current, 要么找到它的前驱,要么找到它的后继,
以找后继为例        
*/
BinarySearchTree.prototype.getSuccessor = function (delNode) {
  //  1 保存找到的后继
  let successor = delNode; // 删除的节点
  let current = delNode.right; // 后继
  let successorParent = delNode; // 后继的父节点

  while (current != null) {
    successorParent = successor;
    successor = current;
    current = current.left; // current右子树的最小值.
  }

  // 判断寻找的后继节点是否直接是delNode的right节点
  if (successor != delNode.right) {
    successorParent.left = successor.right;
    successor.right = delNode.right;
  }
  return successor;
};

1.png

let 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);
  bst.insert(6);
  console.dir(bst, {
    depth: 20,
  }); // 如图所示

  // ======================================================

  let res1 = "";
  bst.preOrderTranversal(function (key) {
    res1 += key + " ";
  });
  console.log("先序遍历", res1); //  11 7 5 3 6 9 8  10 15  13 12 14 20 18 25

  // =======================================================

  let res2 = "";
  bst.midOrderTranversal(function (key) {
    res2 += key + " ";
  });
  console.log("中序遍历", res2); //  3 5 6 7 8 9 10 11 12 13 14 15 18 20 25  从小到大排列

  //=========================================================

  let res3 = "";
  bst.postOrderTranversal(function (key) {
    res3 += key + " ";
  });
  console.log("后序遍历", res3); // 3 6 5 8 10 9 7 12 14 13 18 25 20 15 11

  // =========================================================

  let min = bst.min();
  console.log("最小值", min);

  let max = bst.max();
  console.log("最大值", max);

  // ==========================================================

  let flag1 = bst.search(18);
  console.log("特定值", flag1); // true

  let flag2 = bst.search_1(181);
  console.log("特定值", flag2); // false
// `删除操作`
 let bst_1 = new BinarySearchTree();
  bst_1.insert(11);
  bst_1.insert(7);
  bst_1.insert(15);
  bst_1.insert(5);
  bst_1.insert(3);
  bst_1.insert(9);
  bst_1.insert(8);
  bst_1.insert(10);
  bst_1.insert(13);
  bst_1.insert(12);
  bst_1.insert(14);
  bst_1.insert(20);
  bst_1.insert(18);
  bst_1.insert(25);
  bst_1.insert(6);

  bst_1.remove(9);
  console.dir(bst_1, {
    depth: 20,
  }