什么是树
树(Tree): 它是由 n(n>=1)个有限结点组成一个具有层次关系的集合。
真实的树:
树的特点
- 树一般都有一个根,连接着根的是树干;
- 树干会发生分叉,形成许多树枝,树枝会继续分化成更小的树枝;
- 树枝的最后是叶子;
树结构对比于数组/链表/哈希表有哪些优势呢?
数组:
- 优点:可以通过下标值访问,效率高;
- 缺点:查找数据时需要先对数据进行排序,生成有序数组,才能提高查找效率;并且在插入和删除元素时,需要大量的位移操作;
链表
- 优点:数据的插入和删除操作效率都很高;
- 缺点:查找效率低,需要从头开始依次查找,直到找到目标数据为止;当需要在链表中间位置插入或删除数据时,插入或删除的效率都不高。
哈希表
- 优点:哈希表的插入/查询/删除效率都非常高;
- 缺点:空间利用率不高,底层使用的数组中很多单元没有被利用;并且哈希表中的元素是无序的,不能按照固定顺序遍历哈希表中的元素;而且不能快速找出哈希表中最大值或最小值这些特殊值。
树结构
- 优点:树结构综合了上述三种结构的优点,同时也弥补了它们存在的缺点(虽然效率不一定都比它们高),比如树结构中数据都是有序的,查找效率高;空间利用率高;并且可以快速获取最大值和最小值等。
什么是二叉树
二叉树:如果树中的每一个节点最多只能由两个子节点,这样的树就称为二叉树
二叉搜索树也属于二叉树,也可以为空。
如果不为空必须满足以下三个条件:
- 条件 1:非空左子树的所有键值小于其根节点的键值。
- 条件 2:非空右子树的所有键值大于其根节点的键值。
- 条件 3:左、右子树本身也都是二叉搜索树。
如下图:
二叉搜索树的常见操作
- insert(key):向二叉搜索树中插入一个新的键;
- search(key):在二叉搜索树中查找一个键,如果节点存在,则返回 true;如果不存在,则返回 false;
- inorderTraversal:通过中序遍历方式遍历二叉搜索所有节点;
- preorderTraversal:通过先序遍历方式遍历二叉搜索所有节点;
- postorderTraversal:通过后序遍历方式遍历二叉搜索所有节点;
- min:返回二叉搜索树中最小的值/键;
- max:返回二叉搜索树中最大的值/键;
- remove(key):从二叉搜索树中移除某个键;
二叉搜索树的封装
二叉搜索树有四个最基本的属性:指向节点的根(root),节点中的键(key)、左指针(right)、右指针(right)。
- 节点类的封装
class TreeNode {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
- 二叉树的封装
class BST {
Root = null;
}
- insert(key):方法封装
// 1. insert(key):向二叉搜索树中插入一个新的键;
insert(key) {
// 1. 创建树节点
const Node = new TreeNode(key);
// 2. 判断是否存在根节点
if (this.Root === null) {
this.Root = Node;
} else {
this.insertNode(this.Root, Node);
}
}
insertNode(oldNode, newNode) {
// 先查找左边
if (oldNode.value > newNode.value) {
// 1. 判断子节点是否存在左节点
if (oldNode.left === null) {
oldNode.left = newNode;
} else {
this.insertNode(oldNode.left, newNode);
}
} else {
// 查找右边
// 1. 判断子节点是否存在右节点
if (oldNode.right === null) {
oldNode.right = newNode;
} else {
this.insertNode(oldNode.right, newNode);
}
}
}
- search(key):方法封装
// 2 search(key):在二叉搜索树中查找一个键,如果节点存在,则返回true;如果不存在,则返回false;
searchKey(key) {
let node = this.Root;
while (node !== null) {
if (key < node.key) {
node = node.left;
} else if (key > node.key) {
node = node.right;
} else {
return node;
}
}
return false;
}
- preorderTraversal:方法封装
// 3. preorderTraversal:通过先序遍历方式遍历所有节点;
preorderTraversal() {
// 先序遍历(根左右 DLR)
const dataArr = [];
this.preorderTraversalNode(this.Root, dataArr);
return dataArr;
}
preorderTraversalNode(oldNode, arr) {
if (oldNode === null) return;
const { key, value } = oldNode;
arr.push({ key, value });
this.preorderTraversalNode(oldNode.left, arr);
this.preorderTraversalNode(oldNode.right, arr);
}
- inorderTraversal:方法封装
// 3. 中序遍历
// 中序遍历(左根右 LDR)
inorderTraversal() {
const dataArr = [];
this.inorderTraversalNode(this.Root, dataArr);
return dataArr;
}
inorderTraversalNode(oldNode, arr) {
if (oldNode === null) return;
this.preorderTraversalNode(oldNode.left, arr);
const { key, value } = oldNode;
arr.push({ key, value });
this.preorderTraversalNode(oldNode.right, arr);
}
- postorderTraversal:方法封装
// 4. 后序遍历(左右根 LRD)
postorderTraversal() {
const dataArr = [];
this.postorderTraversalNode(this.Root, dataArr);
return dataArr;
}
postorderTraversalNode(oldNode, arr) {
if (oldNode === null) return;
this.postorderTraversalNode(oldNode.left, arr);
this.postorderTraversalNode(oldNode.right, arr);
const { key, value } = oldNode;
arr.push({ key, value });
}
- min:方法封装
//6. 寻找最小值
min() {
if (!this.Root) return false;
let node = this.Root;
while (node.left !== null) {
node = node.left;
}
return node;
}
- max: 方法封装
//5. 寻找最大值
max() {
if (!this.Root) return false;
let node = this.Root;
while (node.right !== null) {
node = node.right;
}
return node;
}
- remove(key):方法封装
// 删除节点
remove(key) {
let currentNode = this.Root;
let parentNode = null;
// 默认这个key是左节点
let isLeftChild = true;
// 查找节点
while (currentNode.key !== key) {
parentNode = currentNode;
// 小于往左查找
if (key < currentNode.key) {
isLeftChild = true;
currentNode = currentNode.left;
} else {
isLeftChild = false;
currentNode = currentNode.right;
}
// 如果到最后都没找到key,返回false
if (currentNode === null) {
return false;
}
}
// 删除叶子节点
if (currentNode.left === null && currentNode.right === null) {
if (currentNode === this.Root) {
this.Root = null;
} else if (isLeftChild) {
parentNode.left = null;
} else {
parentNode.right = null;
}
}
// 删除只有一个子节点的节点
else if (currentNode.right === null) {
if (currentNode === this.Root) {
this.Root = currentNode.left;
} else if (isLeftChild) {
parentNode.left = currentNode.left;
} else {
parentNode.right = currentNode.left;
}
} else if (currentNode.left === null) {
if (currentNode === this.Root) {
this.Root = currentNode.right;
} else if (isLeftChild) {
parentNode.left = currentNode.right;
} else {
parentNode.right = currentNode.left;
}
}
// 删除有两个子节点的节点
else {
// 找到后续节点
let successor = this.getSuccessor(currentNode);
// p、判断是否为根节点
if (currentNode === this.Root) {
this.Root = successor;
} else if (isLeftChild) {
parentNode.left = successor;
} else {
parentNode.right = successor;
}
// 将后续节点改为被删除的左节点
successor.left = currentNode.left;
}
}
// 获取后续节点,即从要删除的节点的右边开始查找最小的值
getSuccessor(delNode) {
// 定义变量,保存要找到的后续
let successor = delNode;
let current = delNode.right;
let successorParent = delNode;
// 循环查找 current 的右子树节点
while (current !== null) {
successorParent = successor;
successor = current;
current = current.left;
}
// 判断寻找到的后续节点是否直接就是要删除节点的 right
if (successor !== delNode.right) {
successorParent.left = successor.right;
successor.right = delNode.right;
}
return successor;
}
二叉搜索树的完整代码
class TreeNode {
constructor(key) {
this.key = key;
this.left = null;
this.right = null;
}
}
class BST {
Root = null;
insert(key) {
// 1. 创建树节点
const Node = new TreeNode(key);
// 2. 判断是否存在根节点
if (this.Root === null) {
this.Root = Node;
} else {
this.insertNode(this.Root, Node);
}
}
insertNode(oldNode, newNode) {
// 先查找左边
if (oldNode.value > newNode.value) {
// 1. 判断子节点是否存在左节点
if (oldNode.left === null) {
oldNode.left = newNode;
} else {
this.insertNode(oldNode.left, newNode);
}
} else {
// 查找右边
// 1. 判断子节点是否存在右节点
if (oldNode.right === null) {
oldNode.right = newNode;
} else {
this.insertNode(oldNode.right, newNode);
}
}
}
searchKey(key) {
let node = this.Root;
while (node !== null) {
if (key < node.key) {
node = node.left;
} else if (key > node.key) {
node = node.right;
} else {
return node;
}
}
return false;
}
preorderTraversal() {
// 先序遍历(根左右 DLR)
const dataArr = [];
this.preorderTraversalNode(this.Root, dataArr);
return dataArr;
}
preorderTraversalNode(oldNode, arr) {
if (oldNode === null) return;
const { key, value } = oldNode;
arr.push({ key, value });
this.preorderTraversalNode(oldNode.left, arr);
this.preorderTraversalNode(oldNode.right, arr);
}
inorderTraversal() {
const dataArr = [];
this.inorderTraversalNode(this.Root, dataArr);
return dataArr;
}
inorderTraversalNode(oldNode, arr) {
if (oldNode === null) return;
this.preorderTraversalNode(oldNode.left, arr);
const { key, value } = oldNode;
arr.push({ key, value });
this.preorderTraversalNode(oldNode.right, arr);
}
postorderTraversal() {
const dataArr = [];
this.postorderTraversalNode(this.Root, dataArr);
return dataArr;
}
postorderTraversalNode(oldNode, arr) {
if (oldNode === null) return;
this.postorderTraversalNode(oldNode.left, arr);
this.postorderTraversalNode(oldNode.right, arr);
const { key, value } = oldNode;
arr.push({ key, value });
}
min() {
if (!this.Root) return false;
let node = this.Root;
while (node.left !== null) {
node = node.left;
}
return node;
}
max() {
if (!this.Root) return false;
let node = this.Root;
while (node.right !== null) {
node = node.right;
}
return node;
}
remove(key) {
let currentNode = this.Root;
let parentNode = null;
// 默认这个key是左节点
let isLeftChild = true;
// 查找节点
while (currentNode.key !== key) {
parentNode = currentNode;
// 小于往左查找
if (key < currentNode.key) {
isLeftChild = true;
currentNode = currentNode.left;
} else {
isLeftChild = false;
currentNode = currentNode.right;
}
// 如果到最后都没找到key,返回false
if (currentNode === null) {
return false;
}
}
// 删除叶子节点
if (currentNode.left === null && currentNode.right === null) {
if (currentNode === this.Root) {
this.Root = null;
} else if (isLeftChild) {
parentNode.left = null;
} else {
parentNode.right = null;
}
}
// 删除只有一个子节点的节点
else if (currentNode.right === null) {
if (currentNode === this.Root) {
this.Root = currentNode.left;
} else if (isLeftChild) {
parentNode.left = currentNode.left;
} else {
parentNode.right = currentNode.left;
}
} else if (currentNode.left === null) {
if (currentNode === this.Root) {
this.Root = currentNode.right;
} else if (isLeftChild) {
parentNode.left = currentNode.right;
} else {
parentNode.right = currentNode.left;
}
}
// 删除有两个子节点的节点
else {
// 找到后续节点
let successor = this.getSuccessor(currentNode);
// p、判断是否为根节点
if (currentNode === this.Root) {
this.Root = successor;
} else if (isLeftChild) {
parentNode.left = successor;
} else {
parentNode.right = successor;
}
// 将后续节点改为被删除的左节点
successor.left = currentNode.left;
}
}
// 获取后续节点,即从要删除的节点的右边开始查找最小的值
getSuccessor(delNode) {
// 定义变量,保存要找到的后续
let successor = delNode;
let current = delNode.right;
let successorParent = delNode;
// 循环查找 current 的右子树节点
while (current !== null) {
successorParent = successor;
successor = current;
current = current.left;
}
// 判断寻找到的后续节点是否直接就是要删除节点的 right
if (successor !== delNode.right) {
successorParent.left = successor.right;
successor.right = delNode.right;
}
return successor;
}
}