一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 8 天,点击查看活动详情。
介绍
树是一种分层数据的抽象模型。现实生活中最常见的树的例子是家谱,或是公司的组织架构。
树的相关术语
一个树结构包含一系列存在父子关系的节点。每个节点都有一个父节点(除了顶部的第一个节点)以及零个或多个子节点。
根节点
位于树顶部的节点叫做根节点,它没有父节点。树种的每个元素都叫做节点,节点分为内部节点和外部节点。至少有一个子节点的节点叫做内部节点,没有子元素的节点叫做外部节点。
叶子结点
没有一个子元素的叫做叶子结点(3、6、8、10、12、14、18、25)。
子树
子树有节点和他的后代组成。上图当中(12、14、13)构成一个子树。
深度
节点的深度取决于它的祖先节点的数量。例如,节点 3 有 3 个祖先节点,那么它的深度就是 3。
二叉搜索树
二叉树
二叉树中的节点最多只能有两个节点:一个是左侧子节点,另外一个是右侧子节点。 二叉搜索树是二叉树的一种,但是只允许你在左侧节点添加比父节点小的值,而在右侧节点添加比父节点大的值。上面的图中展现了一颗二叉搜索树。
二叉搜索树实现
//实现二叉搜索树
class TreeNode<T> {
//二叉树节点的数据域
public key: T;
//指向左子树
public left: TreeNode<T>;
//指向右子树
public right: TreeNode<T>;
constructor(key: T) {
//初始化数据域
this.key = key;
this.left = null;
this.right = null;
}
}
interface IBinarySearchTree<T> {
// 插入节点
insert(key: T): boolean;
// 搜索树当中是否存在某个节点
search(key: T): boolean;
// 中序遍历
inOrderTraverse();
// 先序遍历
preOrderTraverse();
// 后序遍历
postOrderTraverse();
// 返回树当中的最小值
min(): T;
// 返回树当中的最大值
max(): T;
// 删除树当中的指定节点
remove(key: T): boolean;
// 删除指定节点树的节点
removeNode(node: TreeNode<T>, key: T): TreeNode<T>;
// 将值插入都某个节点当中
insertNode(node: TreeNode<T>, key: T): boolean;
// 搜索树当中是否存在某个节点值
searchNode(node: TreeNode<T>, key: T): boolean;
// 中序遍历某个节点树
inOrderTraverseNode(node: TreeNode<T>);
// 先序遍历某个节点树
preOrderTraverseNode(node: TreeNode<T>);
// 后序遍历某个节点
postOrderTraverseNode(node: TreeNode<T>);
// 查找树的最小值
minNode(node: TreeNode<T>): T;
// 查找树的最大值
maxNode(node: TreeNode<T>): T;
}
/**
*
* @param nodeKey 比较的节点值
* @param key 比较的插入值
* @returns 返回比较结果 -1节点值大于插入值 1插入值大于节点值
*/
function compare<T>(nodeKey: T, insertKey: T) {
if (nodeKey > insertKey) {
return -1;
} else if (nodeKey < insertKey) {
return 1;
} else {
return 0;
}
}
class BinarySearchTree<T> implements IBinarySearchTree<T> {
private root: TreeNode<T>;
private compare: (nodeKey: T, insertKey: T) => number;
constructor(compare: (nodeKey: T, insertKey: T) => number) {
//创建树的根节点
this.root = null;
//加入自定义比较函数
this.compare = compare;
}
/**
*
* @param key 搜索值
* @returns 返回是否搜索到
*/
searchNode(node: TreeNode<T>, key: T): boolean {
if (this.compare(node.key, key) === -1) {
//比当前节点值小
if (node.left === null) {
//判断当前节点的左子节点是否含有值,如果没有则说明没有找到
return false;
} else {
//有值的话就继续找
this.searchNode(node.left, key);
}
} else if (this.compare(node.key, key) === -1) {
//比当前节点值大
if (node.right === null) {
//判断当前节点的右子节点是否含有值,如果没有则说明没有找到
return false;
} else {
//有值的话就继续找
this.searchNode(node.right, key);
}
} else {
//等于当前节点值
return true;
}
}
/**
*
* @param node 需要插入值的节点
* @param key 插入的值
* @returns 返回是否插入成功
*/
insertNode(node: TreeNode<T>, key: T): boolean {
//先判断插入值比跟根节点大还是小,小的在左边,大的在右边
if (this.compare(node.key, key) === -1) {
//插入值比节点值小,放树的左边
if (node.left === null) {
//如果左子树为空,则直接创建节点
node.left = new TreeNode<T>(key);
} else {
//左子树含有值,那么就和左子树的值进行比较
this.insertNode(node.left, key);
}
} else {
// 插入值比节点值大,放树的右边
if (node.right === null) {
//当右子树为空,则直接创建节点
node.right = new TreeNode<T>(key);
} else {
//左子树含有值,我们需要继续和左子树的值进行比较
this.insertNode(node.right, key);
}
}
return false;
}
/**
*
* @param key 插入节点的值
* @returns 返回是否插入成功
*/
insert(key: T): boolean {
//先判断当前插入的是否为根节点
if (this.root === null) {
this.root = new TreeNode<T>(key);
return true;
} else {
//当前插入不是根节点,需要进行判断
return this.insertNode(this.root, key);
}
}
/**
*
* @param key 需要搜索的值
* @returns 返回是否搜索到对应的键值
*/
search(key: T): boolean {
//先判断当前搜索值比节点大还是小,如果大就在右子树,小就在左子树,相等就找到值
if (this.root === null) {
return false;
} else {
return this.searchNode(this.root, key);
}
}
/**
* @returns 返回节点当中值的中序排列
*/
inOrderTraverse() {
//进行先序遍历
this.inOrderTraverseNode(this.root);
}
/**
*中序遍历某个节点
* @param node 传入需要遍历的节点
*/
inOrderTraverseNode(node: TreeNode<T>) {
// 如果根节点不是为null,那么就执行递归
if (node !== null) {
this.inOrderTraverseNode(node.left);
//打印
console.log(node.key);
this.inOrderTraverseNode(node.right);
}
}
/**
*
* @returns 返回节点当中值的先序排列
*/
preOrderTraverse() {
this.preOrderTraverseNode(this.root);
}
/**
*
* @param node 传入需要遍历的节点
*/
preOrderTraverseNode(node: TreeNode<T>) {
if (node !== null) {
console.log(node.key);
this.preOrderTraverseNode(node.left);
this.preOrderTraverseNode(node.right);
}
}
/**
* @returns 返回节点当中值的后序排列
*/
postOrderTraverse() {
this.postOrderTraverseNode(this.root);
}
/**
*
* @param node 传入需要遍历的节点
*/
postOrderTraverseNode(node: TreeNode<T>) {
if (node !== null) {
this.postOrderTraverseNode(node.left);
this.postOrderTraverseNode(node.right);
console.log(node.key);
}
}
/**
* @returns 返回二叉树当中的最小节点值
*/
min(): T {
return this.minNode(this.root);
}
/**
*
* @param node 需要查找最小值的树
* @returns 返回找到的最小值
*/
minNode(node: TreeNode<T>): T {
//判断当前节点是否为null和左子节点是否为空
if (node != null && node.left !== null) {
return this.minNode(node.left);
}
return node.key;
//如果当前节点不为空,但是左子节点为空,那么这个就是我们要找的最小节点
}
/**
* 返回二叉树当中最大节点值
*/
max(): T {
return this.maxNode(this.root);
}
/**
*
* @param node 需要查找最大值的树
* @returns 返回找到的最大值
*/
maxNode(node: TreeNode<T>): T {
if (node !== null && node.right !== null) {
return this.maxNode(node.right);
}
return node.key;
}
/**
*
* @param key 删除某个节点
* @returns 是否删除成功
*/
remove(key: T): boolean {
this.root = this.removeNode(this.root, key);
return true;
}
/**
*
* @param node 需要查找删除节点的节点树
* @param key 需要删除的键
* @returns 返回更改后的节点
*/
removeNode(node: TreeNode<T>, key: T): TreeNode<T> {
if (node === null) {
return null;
}
//判断需要删除值的位置
if (this.compare(node.key, key) === -1) {
//如果需要删除的值比节点值小,那么在节点树的左边
node.left = this.removeNode(node.left, key);
//返回更新后的节点
return node;
} else if (this.compare(node.key, key) === 1) {
//如果删除的值比节点值小
node.right = this.removeNode(node.right, key);
//返回更新后的节点值
return node;
} else {
//删除的值等于节点值,这时候代表我们已经找到了节点值,我们需要判断以下三种情况
//第一种情况:当节点的左子节点和右子节点全部为null时
if (node.left === null && node.right === null) {
//将当前节点值清空
node = null;
//清空完当前节点,我们知道,父节点还对这个节点包含引用,还是可以找到这个节点,所以我们需要将父节点的引用删除
return node;
}
//第二种情况:当前节点的左子节点或右子节点有一个为null
if (node.left === null) {
//将当前节点的值清除
node.key = null;
//返回当前节点的右子节点引用
return node.right;
} else if (node.right === null) {
//将当前节点的值清除
node.key = null;
//返回当前节点的左子节点引用
return node.left;
}
// 第三种情况:当节点的两个子节点都有值时
//aux是当前节点右子树最小的节点
let aux = this.minNode(node);
//使用右子树的最小节点替换当前节点的值
node.key = aux;
//删除那个右子树最小节点
node = this.removeNode(node, aux);
//但会更新后的节点
return node;
}
}
}
const binarySearchTree = new BinarySearchTree<number>(compare);