diff --git a/codes/typescript/chapter_tree/binary_search_tree.ts b/codes/typescript/chapter_tree/binary_search_tree.ts new file mode 100644 index 000000000..6dad83421 --- /dev/null +++ b/codes/typescript/chapter_tree/binary_search_tree.ts @@ -0,0 +1,172 @@ +/** + * File: binary_search_tree.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../module/TreeNode'; +import { printTree } from '../module/PrintUtil'; + +/* 二叉搜索树 */ +let root: TreeNode | null; + +function BinarySearchTree(nums: number[]): void { + nums.sort((a, b) => a - b); // 排序数组 + root = buildTree(nums, 0, nums.length - 1); // 构建二叉搜索树 +} + +/* 获取二叉树根结点 */ +function getRoot(): TreeNode | null { + return root; +} + +/* 构建二叉搜索树 */ +function buildTree(nums: number[], i: number, j: number): TreeNode | null { + if (i > j) { + return null; + } + // 将数组中间结点作为根结点 + let mid = Math.floor((i + j) / 2); + let root = new TreeNode(nums[mid]); + // 递归建立左子树和右子树 + root.left = buildTree(nums, i, mid - 1); + root.right = buildTree(nums, mid + 1, j); + return root; +} + +/* 查找结点 */ +function search(num: number): TreeNode | null { + let cur = root; + // 循环查找,越过叶结点后跳出 + while (cur !== null) { + if (cur.val < num) { + cur = cur.right; // 目标结点在 root 的右子树中 + } else if (cur.val > num) { + cur = cur.left; // 目标结点在 root 的左子树中 + } else { + break; // 找到目标结点,跳出循环 + } + } + // 返回目标结点 + return cur; +} + +/* 插入结点 */ +function insert(num: number): TreeNode | null { + // 若树为空,直接提前返回 + if (root === null) { + return null; + } + let cur = root, + pre: TreeNode | null = null; + // 循环查找,越过叶结点后跳出 + while (cur !== null) { + if (cur.val === num) { + return null; // 找到重复结点,直接返回 + } + pre = cur; + if (cur.val < num) { + cur = cur.right as TreeNode; // 插入位置在 root 的右子树中 + } else { + cur = cur.left as TreeNode; // 插入位置在 root 的左子树中 + } + } + // 插入结点 val + let node = new TreeNode(num); + if (pre!.val < num) { + pre!.right = node; + } else { + pre!.left = node; + } + return node; +} + +/* 删除结点 */ +function remove(num: number): TreeNode | null { + // 若树为空,直接提前返回 + if (root === null) { + return null; + } + let cur = root, + pre: TreeNode | null = null; + // 循环查找,越过叶结点后跳出 + while (cur !== null) { + // 找到待删除结点,跳出循环 + if (cur.val === num) { + break; + } + pre = cur; + if (cur.val < num) { + cur = cur.right as TreeNode; // 待删除结点在 root 的右子树中 + } else { + cur = cur.left as TreeNode; // 待删除结点在 root 的左子树中 + } + } + // 若无待删除结点,则直接返回 + if (cur === null) { + return null; + } + // 子结点数量 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 当子结点数量 = 0 / 1 时, child = null / 该子结点 + let child = cur.left !== null ? cur.left : cur.right; + // 删除结点 cur + if (pre!.left === cur) { + pre!.left = child; + } else { + pre!.right = child; + } + } + // 子结点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个结点 + let next = min(cur.right); + let tmp = next!.val; + // 递归删除结点 nex + remove(next!.val); + // 将 nex 的值复制给 cur + cur.val = tmp; + } + return cur; +} + +/* 获取最小结点 */ +function min(root: TreeNode | null): TreeNode | null { + if (root === null) { + return null; + } + // 循环访问左子结点,直到叶结点时为最小结点,跳出 + while (root.left !== null) { + root = root.left; + } + return root; +} + +/* Driver Code */ +/* 初始化二叉搜索树 */ +const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; +BinarySearchTree(nums); +console.log('\n初始化的二叉树为\n'); +printTree(getRoot()); + +/* 查找结点 */ +let node = search(5); +console.log('\n查找到的结点对象为 ' + node + ',结点值 = ' + node!.val); + +/* 插入结点 */ +node = insert(16); +console.log('\n插入结点 16 后,二叉树为\n'); +printTree(getRoot()); + +/* 删除结点 */ +remove(1); +console.log('\n删除结点 1 后,二叉树为\n'); +printTree(getRoot()); +remove(2); +console.log('\n删除结点 2 后,二叉树为\n'); +printTree(getRoot()); +remove(4); +console.log('\n删除结点 4 后,二叉树为\n'); +printTree(getRoot()); + +export {}; diff --git a/codes/typescript/chapter_tree/binary_tree.ts b/codes/typescript/chapter_tree/binary_tree.ts new file mode 100644 index 000000000..c7e16bd76 --- /dev/null +++ b/codes/typescript/chapter_tree/binary_tree.ts @@ -0,0 +1,35 @@ +/** + * File: binary_tree.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +import { TreeNode } from '../module/TreeNode'; +import { printTree } from '../module/PrintUtil'; + +/* 初始化二叉树 */ +// 初始化结点 +let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); +// 构建引用指向(即指针) +n1.left = n2; +n1.right = n3; +n2.left = n4; +n2.right = n5; +console.log('\n初始化二叉树\n'); +printTree(n1); + +/* 插入与删除结点 */ +const P = new TreeNode(0); +// 在 n1 -> n2 中间插入结点 P +n1.left = P; +P.left = n2; +console.log('\n插入结点 P 后\n'); +printTree(n1); +// 删除结点 P +n1.left = n2; +console.log('\n删除结点 P 后\n'); +printTree(n1); diff --git a/codes/typescript/chapter_tree/binary_tree_bfs.ts b/codes/typescript/chapter_tree/binary_tree_bfs.ts new file mode 100644 index 000000000..1e57ef62d --- /dev/null +++ b/codes/typescript/chapter_tree/binary_tree_bfs.ts @@ -0,0 +1,39 @@ +/** + * File: binary_tree_bfs.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../module/TreeNode'; +import { arrToTree } from '../module/TreeNode'; +import { printTree } from '../module/PrintUtil'; + +/* 层序遍历 */ +function hierOrder(root: TreeNode | null): number[] { + // 初始化队列,加入根结点 + const queue = [root]; + // 初始化一个列表,用于保存遍历序列 + const list: number[] = []; + while (queue.length) { + let node = queue.shift() as TreeNode; // 队列出队 + list.push(node.val); // 保存结点 + if (node.left) { + queue.push(node.left); // 左子结点入队 + } + if (node.right) { + queue.push(node.right); // 右子结点入队 + } + } + return list; +} + +/* Driver Code */ +/* 初始化二叉树 */ +// 这里借助了一个从数组直接生成二叉树的函数 +var root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]); +console.log('\n初始化二叉树\n'); +printTree(root); + +/* 层序遍历 */ +const list = hierOrder(root); +console.log('\n层序遍历的结点打印序列 = ' + list); diff --git a/codes/typescript/chapter_tree/binary_tree_dfs.ts b/codes/typescript/chapter_tree/binary_tree_dfs.ts new file mode 100644 index 000000000..d9e982df1 --- /dev/null +++ b/codes/typescript/chapter_tree/binary_tree_dfs.ts @@ -0,0 +1,67 @@ +/** + * File: binary_tree_dfs.ts + * Created Time: 2022-12-14 + * Author: Justin (xiefahit@gmail.com) + */ + +import { type TreeNode } from '../module/TreeNode'; +import { arrToTree } from '../module/TreeNode'; +import { printTree } from '../module/PrintUtil'; + +// 初始化列表,用于存储遍历序列 +const list: number[] = []; + +/* 前序遍历 */ +function preOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 访问优先级:根结点 -> 左子树 -> 右子树 + list.push(root.val); + preOrder(root.left); + preOrder(root.right); +} + +/* 中序遍历 */ +function inOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 访问优先级:左子树 -> 根结点 -> 右子树 + inOrder(root.left); + list.push(root.val); + inOrder(root.right); +} + +/* 后序遍历 */ +function postOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 访问优先级:左子树 -> 右子树 -> 根结点 + postOrder(root.left); + postOrder(root.right); + list.push(root.val); +} + +/* Driver Code */ +/* 初始化二叉树 */ +// 这里借助了一个从数组直接生成二叉树的函数 +const root = arrToTree([1, 2, 3, 4, 5, 6, 7, null, null, null, null, null, null, null, null]); +console.log('\n初始化二叉树\n'); +printTree(root); + +/* 前序遍历 */ +list.length = 0; +preOrder(root); +console.log('\n前序遍历的结点打印序列 = ' + list); + +/* 中序遍历 */ +list.length = 0; +inOrder(root); +console.log('\n中序遍历的结点打印序列 = ' + list); + +/* 后序遍历 */ +list.length = 0; +postOrder(root); +console.log('\n后序遍历的结点打印序列 = ' + list); diff --git a/codes/typescript/module/PrintUtil.ts b/codes/typescript/module/PrintUtil.ts index c2e3f3c75..9174be159 100644 --- a/codes/typescript/module/PrintUtil.ts +++ b/codes/typescript/module/PrintUtil.ts @@ -1,11 +1,26 @@ /* * File: PrintUtil.ts - * Created Time: 2022-12-10 + * Created Time: 2022-12-13 * Author: Justin (xiefahit@gmail.com) */ import ListNode from './ListNode'; +import { TreeNode } from './TreeNode'; +class Trunk { + prev: Trunk | null; + str: string; + + constructor(prev: Trunk | null, str: string) { + this.prev = prev; + this.str = str; + } +} + +/** + * Print a linked list + * @param head + */ function printLinkedList(head: ListNode | null): void { const list: string[] = []; while (head !== null) { @@ -15,4 +30,67 @@ function printLinkedList(head: ListNode | null): void { console.log(list.join(' -> ')); } -export { printLinkedList }; +/** + * The interface of the tree printer + * This tree printer is borrowed from TECHIE DELIGHT + * https://www.techiedelight.com/c-program-print-binary-tree/ + * @param root + */ +function printTree(root: TreeNode | null) { + printTreeHelper(root, null, false); +} + +/** + * Print a binary tree + * @param root + * @param prev + * @param isLeft + */ +function printTreeHelper(root: TreeNode | null, prev: Trunk | null, isLeft: boolean) { + if (root === null) { + return; + } + + let prev_str = ' '; + const trunk = new Trunk(prev, prev_str); + + printTreeHelper(root.right, trunk, true); + + if (prev === null) { + trunk.str = '———'; + } else if (isLeft) { + trunk.str = '/———'; + prev_str = ' |'; + } else { + trunk.str = '\\———'; + prev.str = prev_str; + } + + showTrunks(trunk); + console.log(' ' + root.val); + + if (prev) { + prev.str = prev_str; + } + trunk.str = ' |'; + + printTreeHelper(root.left, trunk, false); +} + +/** + * Helper function to print branches of the binary tree + * @param p + */ +function showTrunks(p: Trunk | null) { + if (p === null) { + return; + } + + showTrunks(p.prev); + process.stdout.write(p.str); + // ts-node to execute, we need to install type definitions for node + // solve: npm i --save-dev @types/node + // restart the vscode +} + +export { printLinkedList, printTree }; diff --git a/codes/typescript/module/TreeNode.ts b/codes/typescript/module/TreeNode.ts new file mode 100644 index 000000000..0bc783aa9 --- /dev/null +++ b/codes/typescript/module/TreeNode.ts @@ -0,0 +1,51 @@ +/* + * File: TreeNode.ts + * Created Time: 2022-12-13 + * Author: Justin (xiefahit@gmail.com) + */ + +/** + * Definition for a binary tree node. + */ +class TreeNode { + val: number; + left: TreeNode | null; + right: TreeNode | null; + + constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; // 结点值 + this.left = left === undefined ? null : left; // 左子结点指针 + this.right = right === undefined ? null : right; // 右子结点指针 + } +} + +/** + * Generate a binary tree with an array + * @param arr + * @return + */ +function arrToTree(arr: (number | null)[]): TreeNode | null { + if (arr.length === 0) { + return null; + } + + const root = new TreeNode(arr[0] as number); + const queue = [root]; + let i = 1; + while (queue.length) { + let node = queue.shift() as TreeNode; + if (arr[i] !== null) { + node.left = new TreeNode(arr[i] as number); + queue.push(node.left); + } + i++; + if (arr[i] !== null) { + node.right = new TreeNode(arr[i] as number); + queue.push(node.right); + } + i++; + } + return root; +} + +export { TreeNode, arrToTree }; diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index 6dac4f7d7..9ff364329 100644 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -132,7 +132,22 @@ comments: true === "TypeScript" ```typescript title="binary_search_tree.ts" - + /* 查找结点 */ + function search(num: number): TreeNode | null { + let cur = root; + // 循环查找,越过叶结点后跳出 + while (cur !== null) { + if (cur.val < num) { + cur = cur.right; // 目标结点在 root 的右子树中 + } else if (cur.val > num) { + cur = cur.left; // 目标结点在 root 的左子树中 + } else { + break; // 找到目标结点,跳出循环 + } + } + // 返回目标结点 + return cur; + } ``` === "C" @@ -280,7 +295,35 @@ comments: true === "TypeScript" ```typescript title="binary_search_tree.ts" - + /* 插入结点 */ + function insert(num: number): TreeNode | null { + // 若树为空,直接提前返回 + if (root === null) { + return null; + } + let cur = root, + pre: TreeNode | null = null; + // 循环查找,越过叶结点后跳出 + while (cur !== null) { + if (cur.val === num) { + return null; // 找到重复结点,直接返回 + } + pre = cur; + if (cur.val < num) { + cur = cur.right as TreeNode; // 插入位置在 root 的右子树中 + } else { + cur = cur.left as TreeNode; // 插入位置在 root 的左子树中 + } + } + // 插入结点 val + let node = new TreeNode(num); + if (pre!.val < num) { + pre!.right = node; + } else { + pre!.left = node; + } + return node; + } ``` === "C" @@ -547,7 +590,54 @@ comments: true === "TypeScript" ```typescript title="binary_search_tree.ts" - + /* 删除结点 */ + function remove(num: number): TreeNode | null { + // 若树为空,直接提前返回 + if (root === null) { + return null; + } + let cur = root, + pre: TreeNode | null = null; + // 循环查找,越过叶结点后跳出 + while (cur !== null) { + // 找到待删除结点,跳出循环 + if (cur.val === num) { + break; + } + pre = cur; + if (cur.val < num) { + cur = cur.right as TreeNode; // 待删除结点在 root 的右子树中 + } else { + cur = cur.left as TreeNode; // 待删除结点在 root 的左子树中 + } + } + // 若无待删除结点,则直接返回 + if (cur === null) { + return null; + } + // 子结点数量 = 0 or 1 + if (cur.left === null || cur.right === null) { + // 当子结点数量 = 0 / 1 时, child = null / 该子结点 + let child = cur.left !== null ? cur.left : cur.right; + // 删除结点 cur + if (pre!.left === cur) { + pre!.left = child; + } else { + pre!.right = child; + } + } + // 子结点数量 = 2 + else { + // 获取中序遍历中 cur 的下一个结点 + let next = min(cur.right); + let tmp = next!.val; + // 递归删除结点 nex + remove(next!.val); + // 将 nex 的值复制给 cur + cur.val = tmp; + } + return cur; + } ``` === "C" diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index 84270be01..81d6e17aa 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -74,7 +74,18 @@ comments: true === "TypeScript" ```typescript title="" - + /* 链表结点类 */ + class TreeNode { + val: number; + left: TreeNode | null; + right: TreeNode | null; + + constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) { + this.val = val === undefined ? 0 : val; // 结点值 + this.left = left === undefined ? null : left; // 左子结点指针 + this.right = right === undefined ? null : right; // 右子结点指针 + } + } ``` === "C" @@ -215,7 +226,18 @@ comments: true === "TypeScript" ```typescript title="binary_tree.ts" - + /* 初始化二叉树 */ + // 初始化结点 + let n1 = new TreeNode(1), + n2 = new TreeNode(2), + n3 = new TreeNode(3), + n4 = new TreeNode(4), + n5 = new TreeNode(5); + // 构建引用指向(即指针) + n1.left = n2; + n1.right = n3; + n2.left = n4; + n2.right = n5; ``` === "C" @@ -273,7 +295,6 @@ comments: true p := NewTreeNode(0) n1.Left = p p.Left = n2 - // 删除结点 P n1.Left = n2 ``` @@ -286,7 +307,6 @@ comments: true // 在 n1 -> n2 中间插入结点 P n1.left = P; P.left = n2; - // 删除结点 P n1.left = n2; ``` @@ -294,7 +314,13 @@ comments: true === "TypeScript" ```typescript title="binary_tree.ts" - + /* 插入与删除结点 */ + const P = new TreeNode(0); + // 在 n1 -> n2 中间插入结点 P + n1.left = P; + P.left = n2; + // 删除结点 P + n1.left = n2; ``` === "C" @@ -423,7 +449,6 @@ comments: true queue.push(node.left); // 左子结点入队 if (node.right) queue.push(node.right); // 右子结点入队 - } return list; } @@ -432,7 +457,24 @@ comments: true === "TypeScript" ```typescript title="binary_tree_bfs.ts" - + /* 层序遍历 */ + function hierOrder(root: TreeNode | null): number[] { + // 初始化队列,加入根结点 + const queue = [root]; + // 初始化一个列表,用于保存遍历序列 + const list: number[] = []; + while (queue.length) { + let node = queue.shift() as TreeNode; // 队列出队 + list.push(node.val); // 保存结点 + if (node.left) { + queue.push(node.left); // 左子结点入队 + } + if (node.right) { + queue.push(node.right); // 右子结点入队 + } + } + return list; + } ``` === "C" @@ -606,7 +648,38 @@ comments: true === "TypeScript" ```typescript title="binary_tree_dfs.ts" + /* 前序遍历 */ + function preOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 访问优先级:根结点 -> 左子树 -> 右子树 + list.push(root.val); + preOrder(root.left); + preOrder(root.right); + } + /* 中序遍历 */ + function inOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 访问优先级:左子树 -> 根结点 -> 右子树 + inOrder(root.left); + list.push(root.val); + inOrder(root.right); + } + + /* 后序遍历 */ + function postOrder(root: TreeNode | null): void { + if (root === null) { + return; + } + // 访问优先级:左子树 -> 右子树 -> 根结点 + postOrder(root.left); + postOrder(root.right); + list.push(root.val); + } ``` === "C"