树
树的定义
有根节点、子节点、叶子节点构成。
树有高度(距离叶子节点的最远距离)、深度(距离根节点的距离)、层次。

二叉树
最多有 2 个子节点。
满二叉树:叶子节点全在最底层,除叶子节点外,每个节点下面都有左右 2 个子节点。上图就是一个满二叉树。
完全二叉树:叶子节点都在最地下两层,最后一层的叶子节点都靠左排列,且除了最后一层外,其他层的节点个数都是 2 个。
完全二叉树可以用数组存储。见下图。

二叉树的遍历
前序遍历:自己 → 左节点 → 右节点 的顺序打印出所有节点
中序遍历:左节点 → 自己 → 右节点 的顺序打印出所有节点
后续遍历:左节点 → 右节点 → 自己 的顺序打印出所有节点
- 递归的方式
先构建一个二叉树:
class TreeNode {
data;
leftChild;
rightChild;
constructor(data) {
this.data = data;
}
}
function createBinaryTree(list) {
let node = null;
if (list === null || !(list && list.length)) {
return null;
}
data = list.shift();
if (data !== null) {
node = new TreeNode(data);
node.leftChild = createBinaryTree(list);
node.rightChild = createBinaryTree(list);
}
return node;
}
const treeNode = createBinaryTree([3, 2, 9, 1, null, null, 5, null, null, 10, null, null, 8, null, 4]);
前序遍历
function preOrder(node) {
if (node == null) return;
console.log(node.data);
preOrder(node.leftChild);
preOrder(node.rightChild);
}
preOrder(treeNode);
中序遍历
function midOrder(node) {
if (node == null) return;
midOrder(node.leftChild);
console.log(node.data);
midOrder(node.rightChild);
}
midOrder(treeNode);
后序遍历
function postOrder(node) {
if (node == null) return;
postOrder(node.leftChild);
postOrder(node.rightChild);
console.log(node.data);
}
postOrder(treeNode);
- 循环的方式
前序遍历
// 前序遍历 循环方法
function preOrderWithStack(node) {
const arr = new Array();
let treeNode = node;
while (treeNode !== null || arr.length) {
while (treeNode !== null) {
console.log(treeNode.data); // 打印出自己
arr.push(treeNode); // 将节点塞入栈中
treeNode = treeNode.leftChild; // 拿到左节点,用于打印和 push 到栈中
}
// 第二个 while 循环结束后,左节点都访问结束了,所以接下去要访问右节点了
if (arr.length) {
treeNode = arr.pop(); // 从栈中取出节点
treeNode = treeNode.rightChild; // 拿到右节点,用于打印 和 push 到栈中
}
}
}
中序遍历
// 中序遍历 循环方法
function midOrderWithStack(node) {
const arr = new Array();
let treeNode = node;
while (treeNode !== null || arr.length) {
while (treeNode !== null) {
arr.push(treeNode); // 将节点塞入栈中
treeNode = treeNode.leftChild;
}
if (arr.length) {
treeNode = arr.pop(); // 从栈中取出节点
console.log(treeNode.data);
treeNode = treeNode.rightChild;
}
}
}
后序遍历
// 后序遍历 循环方法
function postOrderWithStack(node) {
const arr1 = new Array();
const arr2 = new Array();
let treeNode = node;
while (treeNode !== null || arr1.length) {
while (treeNode !== null) {
arr1.push(treeNode);
arr2.push(treeNode);
treeNode = treeNode.rightChild; // 拿到右节点,以此塞到 arr1 和 arr2 中
}
if (arr1.length) {
treeNode = arr1.pop(); // 依次从 arr1 中取出节点,将每个节点的左节点取出,塞到 arr2 中。
treeNode = treeNode.leftChild;
}
}
while (arr2.length) {
treeNode = arr2.pop(); // arr2 中的顺序就是后序遍历的相反顺序,用 pop 从栈中依次取出即可
console.log(treeNode.data);
}
}
二叉查找树
节点的左节点比它小,右节点比它大。见下图。

二叉查找树的查找、插入、删除
class TreeNode {
data;
leftChild = null;
rightChild = null;
constructor(data) {
this.data = data;
}
}
class BinarySearchTree {
tree;
constructor() {}
// 插入
insert(data) {
if (!this.tree) {
this.tree = new TreeNode(data);
return;
}
let node = this.tree;
while (node) {
if (data > node.data) {
if (!node.rightChild) {
node.rightChild = new TreeNode(data);
return;
}
node = node.rightChild;
} else {
if (!node.leftChild) {
node.leftChild = new TreeNode(data);
return;
}
node = node.leftChild;
}
}
}
// 查找
find(data) {
if (!this.tree) return null;
let node = this.tree;
while (node) {
if (data === node.data) return node;
else if (data > node.data) {
node = node.rightChild;
} else {
node = node.leftChild;
}
}
}
// 删除
delete(data) {
if (!this.tree) return null;
let node = this.tree; // 要删除的节点
let nodeParent; // node 的父节点
while (node && node.data !== data) {
nodeParent = node;
if (data > node.data) {
node = node.rightChild;
} else {
node = node.leftChild;
}
}
// 没找到要删除的节点 node,返回 null
if (!node) return null;
// 找到要删除的节点 node
// 左右子节点都存在,寻找右子节点中最小的节点
if (node.leftChild && node.rightChild) {
let minNode = node.rightChild;
let minNodeParent = node;
while (minNode.leftChild) {
minNodeParent = minNode;
minNode = minNode.leftChild;
}
// 把右子节点中最小的节点的值替换到要删除的节点中
node.data = minNode.data;
// 下面要运用下面的规则删除这个最小的节点
// node 就是当前右子节点中最小的节点,nodeParent 就是当前右子节点中最小节点的父节点
node = minNode;
nodeParent = minNodeParent;
}
// 下面是删除节点的规则
// 不存在子节点
let child = null;
// 只存在左子节点
if (node.leftChild) {
child = node.leftChild;
}
// 只存在右子节点
if (node.rightChild) {
child = node.rightChild;
}
// 如果删除的是根节点,根节点是没有父节点的
if (!nodeParent) {
this.tree = child; // 将 tree 直接指向 要删除节点的 child,即删除了节点
}
// 删除的节点是它的父节点的左子节点,左子节点指向该删除节点的 child
if (nodeParent.leftChild === node) {
nodeParent.leftChild = child;
}
// 删除的节点是它的父节点的右子节点,右子节点指向该删除节点的 child
if (nodeParent.rightChild === node) {
nodeParent.rightChild = child;
}
}
}
const binarySearchTree = new BinarySearchTree();
const list = [33, 16, 13, 15, 18, 17, 25, 27, 19, 50, 34, 58, 51, 55, 66];
for (let item of list) {
binarySearchTree.insert(item);
}
console.log(binarySearchTree);
const res = binarySearchTree.find(55);
console.log(res);
const res2 = binarySearchTree.delete(18);
console.log(binarySearchTree);