树的特点
将里面的数据移除,仅仅抽象出来结构,就是树结构。
完美二叉(满二叉树):在二叉树中,除了最下一层的叶节点外,每层节点都有2个子节点,就构成了满二叉树。
完全二叉树 按从上至下、从左到右顺序存储。
<script>
// 封装二叉搜索树
function BinarySearchTree() {
//节点类
function Node(key) {
this.key = key;
this.left = null;
this.right = null;
}
// 属性
this.root = null
// 方法
}
// insert 插入方法
BinarySearchTree.prototype.insert = function (key) {
// 1. 根据key创建节点
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) { //向左边查找
if (node.left == null) {
node.left = newNode;
} else {
this.insertNode(node.left, newNode)
}
} else {
if (node.right == null) {
node.right = newNode;
} else {
this.insertNode(node.right, newNode)
}
}
}
//测试数据
let bts = 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);
//结果在图。
先序遍历会先访问节点本身,然后再访问它的左侧子节点,最后是右侧子节点,
遍历过程
应用场景: 先序遍历的一种应用是打印一个结构化的文档。
//原理 递归调用函数 处理节点 先访问根节点 然后 递归去访问左节点
BinarySearchTree.prototype.preOrderTraversal = function () {
let arr = [];
this.preOrderTraversalNode(this.root, arr);
return arr;
}
// 第一次 node -> 根节点 11
// 第二次: node -> 根的左节点 7
// 第三次: node -> 7的左节点 5
// 第四次: node -> 5的左节点 null
//
BinarySearchTree.prototype.preOrderTraversalNode = function (node, arr) {
// 比如 根节点 为11 上图 11 然后 开始执行, node不为空 然后去 递归调用把 第二行代码把左子节点 传入进去 等左子节点的 node ==null
// 开始返回上一层 然后 执行滴 三行代码 this.preOrderTraversalNode(node.right, arr) 继续向下遍历 每次把 node.key传入 arr中。
if (node != null) {
// 1. 处理经过的节点
// handler(node.key)
arr.push(node.key)
// 2. 处理经过的左子节点
this.preOrderTraversalNode(node.left, arr)
// 3. 处理经过节点的右子节点
this.preOrderTraversalNode(node.right, arr)
}
}
// 测试代码。
console.log(bst.preOrderTraversal());
中序遍历是一种以上行顺序访问BST所有节点的遍历方式,也就是以从最小到最大的顺序访问所有节点。
应用场景: 中序遍历的一种应用就是对树进行排序操作。
原理图
BinarySearchTree.prototype.midOrderTraversal = function () {
let arr = [];
this.midOrderTraversalNode(this.root, arr)
return arr;
}
BinarySearchTree.prototype.midOrderTraversalNode = function (node, arr) {
if (node != null) {
// 1. 处理左子树的 节点
this.midOrderTraversalNode(node.left, arr)
//2. 处理节点
arr.push(node.key);
// 3.处理右子树的节点
this.midOrderTraversalNode(node.right, arr)
}
}
// 测试代码: // console.log(bst.midOrderTraversal());
后序遍历则是先访问节点的后代节点,再访问节点本身。
应用场景:后序遍历的一种应用是计算一个目录和它的子目录中所有文件所占空间的大小。
//基本原理跟先序遍历一样
BinarySearchTree.prototype.postOrderTraversal = function () {
let arr = [];
this.postOrderTraversalNode(this.root, arr);
return arr;
}
BinarySearchTree.prototype.postOrderTraversalNode = function (node, arr) {
if (node != null) {
// 1.查找左子树中的节点
this.postOrderTraversalNode(node.left, arr);
//2. 查找右子树的节点
this.postOrderTraversalNode(node.right, arr);
// 3.访问根节点
arr.push(node.key);
}
}
//测试代码 : // console.log(bst.postOrderTraversal());
// 4. 寻找最值
// 4.1 最大值
BinarySearchTree.prototype.max = function () {
// 1.获取根节点
let node = this.root;
let key = null
//2. 依次向右不断地查找,直到节点 为 null
while (node != null) {
key = node.key;
node = node.right;
}
return key;
}
// 4.2 最小值
BinarySearchTree.prototype.min = function () {
// 1.获取根节点
let node = this.root;
let key = null;
//2. 依次向右不断地查找,直到节点 为 null
while (node != null) {
key = node.key;
node = node.left;
}
return key;
}
// 测试代码。
console.log(bst.max());
console.log(bst.min());
// 5.搜索某一个key
BinarySearchTree.prototype.search = function (key) {
// 1.获取节点
let node = this.root;
// 2. 循环搜索key
while (node != null) {
if (key < node.key) {
node = node.left
} else if (key > node.key) {
node = node.right
} else {
return true;
}
}
return false
}
删除节点要从查找要删的节点开始,找到节点后,需要考虑的情况
// 删除节点
BinarySerachTree.prototype.remove = function(key){
//1.1 寻找要删除的节点
let current = this.root;
let parent = null;
let isLeftChild = true;
//1.2. 开始寻找删除节点。
while (current.key != key){
parent = current
if(key < current.key){
isLeftChild = true;
current = current.left;
}else {
isLeftChild = false;
current = current.right
}
// 某种情况: 已经找到最后的节点,依然没有找到相等的key
if(current == null) return false;
}
//2. 根据对应的情况删除节点
//2.1. 删除的节点是叶子节点(没有子节点)
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. 删除的节点有一个子节点
else if (current.right = null){
if(isLeftChild){
parent.left = current.left
}else if{
parent.right = current.left
}
}else if (current.left == null){
if (current == this.root ){
this.root = current.right;
} else if (current == this.root){
this.root = current.left;
}
if(isLeftChild){
parent.left = current.right
} else {
parent.right = current.right;
}
}
//2.3 删除的节点有两个子节点。
// 找到了current.key == key 的情况。
else {
// 1. 获取后继节点
let successor = this.getSuccssor(current);
// 2.判断是否为根节点
if(current == this.root){
this.root = successor
} else if (isLeftChild) {
parent.left = successor
}else {
parent.right = successor
}
// 3. 将删除节点的左子树 = current.left
successor.left = current.left;
}
// 找后继的方法
BinarySerachTree.prototype.getSuccssor = function (del ){
// 1. 定义变量,保存找到的后继
let successor = delNode;
let current = delNode.right;
let successorParent = delNode;
//2. 循环查找
while(current != null){
successorParent = successor;
successor = current
current = current.left;
}
//3. 判断寻找的后继节点是否就是delNode的right节点
if(successor != delNode.right){
successorParent.left = successor.right;
successor.right = delNode.right;
}
return successor
}
}