From 60e08d3ac54c99df6ab12b682566a75bb1c72dc9 Mon Sep 17 00:00:00 2001 From: Revone Date: Tue, 22 Aug 2023 22:50:16 +0800 Subject: [PATCH] Successfully implemented recursive type inference for the BinaryTreeNode type by passing the node constructor through the constructor, effectively addressing the type inconsistency caused by invoking parent class methods after inheritance. --- .gitignore | 1 + .idea/data-structure-typed.iml | 1 + README.md | 259 ++++++++++---- package.json | 12 +- src/data-structures/binary-tree/avl-tree.ts | 44 +-- .../binary-tree/binary-tree.ts | 336 +++++++++--------- src/data-structures/binary-tree/bst.ts | 89 ++--- src/data-structures/binary-tree/rb-tree.ts | 28 +- .../binary-tree/tree-multiset.ts | 19 +- src/data-structures/graph/abstract-graph.ts | 183 +++++----- src/data-structures/graph/directed-graph.ts | 180 +++++----- src/data-structures/graph/undirected-graph.ts | 119 ++++--- .../interfaces/abstract-graph.ts | 37 +- src/data-structures/interfaces/binary-tree.ts | 56 ++- .../interfaces/directed-graph.ts | 13 +- src/data-structures/types/avl-tree.ts | 10 +- src/data-structures/types/binary-tree.ts | 7 +- src/data-structures/types/bst.ts | 3 +- src/data-structures/types/directed-graph.ts | 2 +- src/data-structures/types/tree-multiset.ts | 4 +- .../binary-tree/avl-tree.test.ts | 122 +++++++ .../data-structures/binary-tree/bst.test.ts | 1 - .../graph/directed-graph.test.ts | 37 +- 23 files changed, 940 insertions(+), 623 deletions(-) create mode 100644 tests/unit/data-structures/binary-tree/avl-tree.test.ts diff --git a/.gitignore b/.gitignore index 055da2e..2b7beb6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ __tests__/ notes/ docs/ +backup/ \ No newline at end of file diff --git a/.idea/data-structure-typed.iml b/.idea/data-structure-typed.iml index f8844dd..874fbb2 100644 --- a/.idea/data-structure-typed.iml +++ b/.idea/data-structure-typed.iml @@ -8,6 +8,7 @@ + diff --git a/README.md b/README.md index 5387d48..fb26862 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,200 @@ wide range of data structures ## Data Structures -Binary Tree, Binary Search Tree (BST), AVL Tree, Tree Multiset, Segment Tree, Binary Indexed Tree, Graph, Directed -Graph, Undirected Graph, Linked List, Singly Linked List, Doubly Linked List, Queue, Object Deque, Array Deque, Stack, -Hash, Coordinate Set, Coordinate Map, Heap, Priority Queue, Max Priority Queue, Min Priority Queue, Trie + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[//]: # () + +[//]: # () + +[//]: # () + +[//]: # () + +[//]: # () + +[//]: # () + +[//]: # () + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Data StructureUnit TestPerformance TestAPI DocumentationImplemented
Binary Tree + +Binary Tree
Binary Search Tree (BST)BST
AVL TreeAVLTree
Tree MultisetTreeMultiSet
Segment TreeSegmentTree
Binary Indexed TreeBinaryIndexedTree
GraphAbstractGraph
Directed GraphDirectedGraph
Undirected GraphUndirectedGraph
Linked ListSinglyLinkedList
Singly Linked ListSinglyLinkedList
Doubly Linked ListDoublyLinkedList
QueueQueue
Object DequeObjectDeque
Array DequeArrayDeque
StackStack
HashHashTable
Coordinate SetCoordinateSet
Coordinate MapCoordinateMap
HeapHeap
Priority QueuePriorityQueue
Max Priority QueueMaxPriorityQueue
Min Priority QueueMinPriorityQueue
TrieTrie
+ +## Algorithms list only a few out, you can discover more in API docs + +DFS, DFSIterative, BFS, morris, Bellman-Ford Algorithm, Dijkstra's Algorithm, Floyd-Warshall Algorithm, Tarjan's Algorithm # How @@ -409,69 +599,6 @@ describe('DirectedGraph Test3', () => { ## API docs -[//]: # ([api docs](https://data-structure-typed-docs.vercel.app/)) - - [//]: # ([Examples Repository](https://github.com/zrwusa/data-structure-typed-examples)) diff --git a/package.json b/package.json index 954dfb4..642e724 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "data-structure-typed", - "version": "1.18.0", + "version": "1.18.5", "description": "Explore our comprehensive Javascript Data Structure / TypeScript Data Structure Library, meticulously crafted to empower developers with a versatile set of essential data structures. Our library includes a wide range of data structures, such as Binary Tree, AVL Tree, Binary Search Tree (BST), Tree Multiset, Segment Tree, Binary Indexed Tree, Graph, Directed Graph, Undirected Graph, Singly Linked List, Hash, CoordinateSet, CoordinateMap, Heap, Doubly Linked List, Priority Queue, Max Priority Queue, Min Priority Queue, Queue, ObjectDeque, ArrayDeque, Stack, and Trie. Each data structure is thoughtfully designed and implemented using TypeScript to provide efficient, reliable, and easy-to-use solutions for your programming needs. Whether you're optimizing algorithms, managing data, or enhancing performance, our TypeScript Data Structure Library is your go-to resource. Elevate your coding experience with these fundamental building blocks for software development.", "main": "dist/index.js", "scripts": { @@ -38,7 +38,15 @@ "Priority Queue", "Max Priority Queue", "Min Priority Queue", - "Trie" + "Trie", + "DFS", + "DFSIterative", + "BFS", + "morris", + "Bellman-Ford ", + "Dijkstra's Algorithm", + "Floyd-Warshall Algorithm", + "Tarjan's Algorithm" ], "author": "Tyler Zeng zrwusa@gmail.com", "license": "MIT", diff --git a/src/data-structures/binary-tree/avl-tree.ts b/src/data-structures/binary-tree/avl-tree.ts index d095a27..7c5953d 100644 --- a/src/data-structures/binary-tree/avl-tree.ts +++ b/src/data-structures/binary-tree/avl-tree.ts @@ -6,19 +6,19 @@ * @license MIT License */ import {BST, BSTNode} from './bst'; -import type {AVLTreeDeleted, BinaryTreeNodeId} from '../types'; +import type {AVLTreeDeleted, BinaryTreeNodeId, RecursiveAVLTreeNode} from '../types'; import {IBinaryTreeNode} from '../interfaces'; -export class AVLTreeNode extends BSTNode implements IBinaryTreeNode { - override _createNode(id: BinaryTreeNodeId, val: T | null, count?: number): AVLTreeNode | null { - return val !== null ? new AVLTreeNode(id, val, count) : null; - } + +export class AVLTreeNode = RecursiveAVLTreeNode> extends BSTNode implements IBinaryTreeNode { + } -export class AVLTree extends BST { +export class AVLTree = AVLTreeNode> extends BST { - override _createNode(id: BinaryTreeNodeId, val: T, count?: number): AVLTreeNode { - return new AVLTreeNode(id, val, count); + override _createNode(id: BinaryTreeNodeId, val: N['val'], count?: number): N { + const node = new AVLTreeNode(id, val, count); + return node as N; } /** @@ -26,14 +26,14 @@ export class AVLTree extends BST { * balances the tree. * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier of the binary tree node that we want to add or * update in the AVL tree. - * @param {T | null} val - The `val` parameter represents the value that you want to assign to the node with the given - * `id`. It can be of type `T` (the generic type) or `null`. + * @param {N | null} val - The `val` parameter represents the value that you want to assign to the node with the given + * `id`. It can be of type `N` (the generic type) or `null`. * @param {number} [count] - The `count` parameter is an optional parameter of type `number`. It represents the number * of times the value `val` should be inserted into the AVL tree. If the `count` parameter is not provided, it defaults * to `1`, indicating that the value should be inserted once. - * @returns The method is returning either an AVLTreeNode object or null. + * @returns The method is returning either an N object or null. */ - override add(id: BinaryTreeNodeId, val: T | null, count?: number): AVLTreeNode | null { + override add(id: BinaryTreeNodeId, val: N['val'] | null, count?: number): N | null { const inserted = super.add(id, val, count); if (inserted) this.balancePath(inserted); return inserted; @@ -47,9 +47,9 @@ export class AVLTree extends BST { * @param {boolean} [isUpdateAllLeftSum] - The `isUpdateAllLeftSum` parameter is an optional boolean parameter that * determines whether the left sum of all nodes in the AVL tree should be updated after removing a node. If * `isUpdateAllLeftSum` is set to `true`, the left sum of all nodes will be recalculated. - * @returns The method is returning an array of `AVLTreeDeleted` objects. + * @returns The method is returning an array of `AVLTreeDeleted` objects. */ - override remove(id: BinaryTreeNodeId, isUpdateAllLeftSum?: boolean): AVLTreeDeleted[] { + override remove(id: BinaryTreeNodeId, isUpdateAllLeftSum?: boolean): AVLTreeDeleted[] { const deletedResults = super.remove(id, isUpdateAllLeftSum); for (const {needBalanced} of deletedResults) { if (needBalanced) { @@ -62,10 +62,10 @@ export class AVLTree extends BST { /** * The balance factor of a given AVL tree node is calculated by subtracting the height of its left subtree from the * height of its right subtree. - * @param node - The parameter "node" is of type AVLTreeNode, which represents a node in an AVL tree. + * @param node - The parameter "node" is of type N, which represents a node in an AVL tree. * @returns The balance factor of the given AVL tree node. */ - balanceFactor(node: AVLTreeNode): number { + balanceFactor(node: N): number { if (!node.right) // node has no right subtree return -node.height; else if (!node.left) // node has no left subtree @@ -78,7 +78,7 @@ export class AVLTree extends BST { * The function updates the height of a node in an AVL tree based on the heights of its left and right subtrees. * @param node - The parameter `node` is an AVLTreeNode object, which represents a node in an AVL tree. */ - updateHeight(node: AVLTreeNode): void { + updateHeight(node: N): void { if (!node.left && !node.right) // node is a leaf node.height = 0; else if (!node.left) { @@ -96,7 +96,7 @@ export class AVLTree extends BST { * each node in the path from the given node to the root. * @param node - The `node` parameter is an AVLTreeNode object, which represents a node in an AVL tree. */ - balancePath(node: AVLTreeNode): void { + balancePath(node: N): void { const path = this.getPathToRoot(node); for (let i = path.length - 1; i >= 0; i--) { const A = path[i]; @@ -127,7 +127,7 @@ export class AVLTree extends BST { * The `balanceLL` function performs a left-left rotation on an AVL tree to balance it. * @param A - The parameter A is an AVLTreeNode object. */ - balanceLL(A: AVLTreeNode): void { + balanceLL(A: N): void { const parentOfA = A.parent; const B = A.left; // A is left-heavy and B is left-heavy A.parent = B; @@ -157,7 +157,7 @@ export class AVLTree extends BST { * The `balanceLR` function performs a left-right rotation to balance an AVL tree. * @param A - A is an AVLTreeNode object. */ - balanceLR(A: AVLTreeNode): void { + balanceLR(A: N): void { const parentOfA = A.parent; const B = A.left; // A is left-heavy let C = null; @@ -205,7 +205,7 @@ export class AVLTree extends BST { * The `balanceRR` function performs a right-right rotation on an AVL tree to balance it. * @param A - The parameter A is an AVLTreeNode object. */ - balanceRR(A: AVLTreeNode): void { + balanceRR(A: N): void { const parentOfA = A.parent; const B = A.right; // A is right-heavy and B is right-heavy A.parent = B; @@ -240,7 +240,7 @@ export class AVLTree extends BST { * The `balanceRL` function performs a right-left rotation to balance an AVL tree. * @param A - A is an AVLTreeNode object. */ - balanceRL(A: AVLTreeNode): void { + balanceRL(A: N): void { const parentOfA = A.parent; const B = A.right; // A is right-heavy let C = null; diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index fb2faae..606a3c9 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -13,6 +13,7 @@ import type { BinaryTreeNodePropertyName, DFSOrderPattern, NodeOrPropertyName, + RecursiveBinaryTreeNode, ResultByProperty, ResultsByProperty } from '../types'; @@ -29,7 +30,7 @@ export enum FamilyPosition {root, left, right} */ export enum LoopType { iterative = 1, recursive = 2} -export class BinaryTreeNode implements IBinaryTreeNode { +export class BinaryTreeNode = RecursiveBinaryTreeNode> implements IBinaryTreeNode { constructor(id: BinaryTreeNodeId, val: T, count?: number) { this._id = id; @@ -57,41 +58,41 @@ export class BinaryTreeNode implements IBinaryTreeNode { this._val = v; } - private _left?: BinaryTreeNode | null; + private _left?: FAMILY | null; - get left(): BinaryTreeNode | null | undefined { + get left(): FAMILY | null | undefined { return this._left; } - set left(v: BinaryTreeNode | null | undefined) { + set left(v: FAMILY | null | undefined) { if (v) { - v.parent = this; + v.parent = this as unknown as FAMILY; v.familyPosition = FamilyPosition.left; } this._left = v; } - private _right?: BinaryTreeNode | null; + private _right?: FAMILY | null; - get right(): BinaryTreeNode | null | undefined { + get right(): FAMILY | null | undefined { return this._right; } - set right(v: BinaryTreeNode | null | undefined) { + set right(v: FAMILY | null | undefined) { if (v) { - v.parent = this; + v.parent = this as unknown as FAMILY; v.familyPosition = FamilyPosition.right; } this._right = v; } - private _parent: BinaryTreeNode | null | undefined; + private _parent: FAMILY | null | undefined; - get parent(): BinaryTreeNode | null | undefined { + get parent(): FAMILY | null | undefined { return this._parent; } - set parent(v: BinaryTreeNode | null | undefined) { + set parent(v: FAMILY | null | undefined) { this._parent = v; } @@ -125,11 +126,11 @@ export class BinaryTreeNode implements IBinaryTreeNode { this._height = v; } - _createNode(id: BinaryTreeNodeId, val: T | null, count?: number): BinaryTreeNode | null { - return val !== null ? new BinaryTreeNode(id, val, count) : null; + _createNode(id: BinaryTreeNodeId, val: T | null, count?: number): FAMILY | null { + return val !== null ? new BinaryTreeNode(id, val, count) as FAMILY : null; } - swapLocation(swapNode: BinaryTreeNode): BinaryTreeNode { + swapLocation(swapNode: FAMILY): FAMILY { const {val, count, height} = swapNode; const tempNode = this._createNode(swapNode.id, val); if (tempNode instanceof BinaryTreeNode) { @@ -150,12 +151,12 @@ export class BinaryTreeNode implements IBinaryTreeNode { return swapNode; } - clone(): BinaryTreeNode | null { + clone(): FAMILY | null { return this._createNode(this.id, this.val, this.count); } } -export class BinaryTree implements IBinaryTree { +export class BinaryTree = BinaryTreeNode> implements IBinaryTree { /** * The constructor function accepts an optional options object and sets the values of loopType, autoIncrementId, and @@ -190,15 +191,15 @@ export class BinaryTree implements IBinaryTree { return this._visitedId; } - private _visitedVal: Array = []; + private _visitedVal: Array = []; - get visitedVal(): Array { + get visitedVal(): Array { return this._visitedVal; } - private _visitedNode: BinaryTreeNode[] = []; + private _visitedNode: N[] = []; - get visitedNode(): BinaryTreeNode[] { + get visitedNode(): N[] { return this._visitedNode; } @@ -232,9 +233,9 @@ export class BinaryTree implements IBinaryTree { return this._isDuplicatedVal; } - private _root: BinaryTreeNode | null = null; + private _root: N | null = null; - get root(): BinaryTreeNode | null { + get root(): N | null { return this._root; } @@ -255,14 +256,15 @@ export class BinaryTree implements IBinaryTree { * it returns null. * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier for the binary tree node. It is of type * `BinaryTreeNodeId`. - * @param {T | null} val - The `val` parameter represents the value of the node. It can be of type `T` (generic type) + * @param {N | null} val - The `val` parameter represents the value of the node. It can be of type `N` (generic type) * or `null`. * @param {number} [count] - The `count` parameter is an optional parameter of type `number`. It represents the number * of occurrences of the value in the binary tree node. If not provided, the default value is `undefined`. * @returns a BinaryTreeNode object if the value is not null, otherwise it returns null. */ - _createNode(id: BinaryTreeNodeId, val: T | null, count?: number): BinaryTreeNode | null { - return val !== null ? new BinaryTreeNode(id, val, count) : null; + _createNode(id: BinaryTreeNodeId, val: N['val'] | null, count?: number): N | null { + const node = new BinaryTreeNode(id, val, count); + return node as N | null; } /** @@ -288,17 +290,17 @@ export class BinaryTree implements IBinaryTree { * already exists. * @param {BinaryTreeNodeId} id - The id parameter is the identifier of the binary tree node. It is used to uniquely * identify each node in the binary tree. - * @param {T} val - The value to be inserted into the binary tree. + * @param {N} val - The value to be inserted into the binary tree. * @param {number} [count] - The `count` parameter is an optional parameter that specifies the number of times the * value should be inserted into the binary tree. If not provided, it defaults to 1. - * @returns The function `add` returns a `BinaryTreeNode` object if a new node is inserted, or `null` if no new node + * @returns The function `add` returns a `N` object if a new node is inserted, or `null` if no new node * is inserted, or `undefined` if the insertion fails. */ - add(id: BinaryTreeNodeId, val: T, count?: number): BinaryTreeNode | null | undefined { + add(id: BinaryTreeNodeId, val: N['val'], count?: number): N | null | undefined { count = count ?? 1; - const _bfs = (root: BinaryTreeNode, newNode: BinaryTreeNode | null): BinaryTreeNode | undefined | null => { - const queue: Array | null> = [root]; + const _bfs = (root: N, newNode: N | null): N | undefined | null => { + const queue: Array = [root]; while (queue.length > 0) { const cur = queue.shift(); if (cur) { @@ -311,7 +313,7 @@ export class BinaryTree implements IBinaryTree { return; }; - let inserted: BinaryTreeNode | null | undefined; + let inserted: N | null | undefined; const needInsert = val !== null ? this._createNode(id, val, count) : null; const existNode = val !== null ? this.get(id, 'id') : null; if (this.root) { @@ -338,13 +340,13 @@ export class BinaryTree implements IBinaryTree { /** * The function inserts a new node into a binary tree as the left or right child of a given parent node. - * @param {BinaryTreeNode | null} newNode - The `newNode` parameter is an instance of the `BinaryTreeNode` class or + * @param {N | null} newNode - The `newNode` parameter is an instance of the `BinaryTreeNode` class or * `null`. It represents the node that needs to be inserted into the binary tree. * @param parent - The `parent` parameter is a BinaryTreeNode object representing the parent node to which the new node * will be inserted as a child. * @returns The method returns the newly inserted node, either as the left child or the right child of the parent node. */ - addTo(newNode: BinaryTreeNode | null, parent: BinaryTreeNode) { + addTo(newNode: N | null, parent: N) { if (parent) { if (parent.left === undefined) { if (newNode) { @@ -354,7 +356,7 @@ export class BinaryTree implements IBinaryTree { parent.left = newNode; if (newNode !== null) { this._setSize(this.size + 1); - this._setCount(this.count + newNode?.count ?? 0) + this._setCount(this.count + newNode.count ?? 0) } return parent.left; @@ -366,7 +368,7 @@ export class BinaryTree implements IBinaryTree { parent.right = newNode; if (newNode !== null) { this._setSize(this.size + 1); - this._setCount(this.count + newNode?.count ?? 0); + this._setCount(this.count + newNode.count ?? 0); } return parent.right; } else { @@ -380,13 +382,13 @@ export class BinaryTree implements IBinaryTree { /** * The `addMany` function inserts multiple items into a binary tree and returns an array of the inserted nodes or * null/undefined values. - * @param {T[] | BinaryTreeNode[]} data - The `data` parameter can be either an array of elements of type `T` or an - * array of `BinaryTreeNode` objects. - * @returns The function `addMany` returns an array of `BinaryTreeNode`, `null`, or `undefined` values. + * @param {N[] | N[]} data - The `data` parameter can be either an array of elements of type `N` or an + * array of `N` objects. + * @returns The function `addMany` returns an array of `N`, `null`, or `undefined` values. */ - addMany(data: T[] | BinaryTreeNode[]): (BinaryTreeNode | null | undefined)[] { - const inserted: (BinaryTreeNode | null | undefined)[] = []; - const map: Map, number> = new Map(); + addMany(data: N[] | Array): (N | null | undefined)[] { + const inserted: (N | null | undefined)[] = []; + const map: Map = new Map(); if (!this._isDuplicatedVal) { for (const i of data) map.set(i, (map.get(i) ?? 0) + 1); @@ -427,11 +429,11 @@ export class BinaryTree implements IBinaryTree { /** * The `fill` function clears the current data and inserts new data, returning a boolean indicating if the insertion * was successful. - * @param {T[] | BinaryTreeNode[]} data - The `data` parameter can be either an array of elements of type `T` or an - * array of `BinaryTreeNode` objects. + * @param {N[] | N[]} data - The `data` parameter can be either an array of elements of type `N` or an + * array of `N` objects. * @returns The method is returning a boolean value. */ - fill(data: T[] | BinaryTreeNode[]): boolean { + fill(data: N[] | Array): boolean { this.clear(); return data.length === this.addMany(data).length; } @@ -447,9 +449,9 @@ export class BinaryTree implements IBinaryTree { * "needBalanced". The "deleted" property contains the deleted node or undefined if no node was deleted. The * "needBalanced" property is always null. */ - remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeleted[] { + remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeleted[] { const nodes = this.getNodes(id, 'id', true); - let node: BinaryTreeNode | null | undefined = nodes[0]; + let node: N | null | undefined = nodes[0]; if (!node) node = undefined; else if (node.count > 1 && !ignoreCount) { @@ -485,11 +487,11 @@ export class BinaryTree implements IBinaryTree { /** * The function calculates the depth of a binary tree node by traversing its parent nodes. - * @param node - BinaryTreeNode - This is the node for which we want to calculate the depth. It is a generic type, + * @param node - N - This is the node for which we want to calculate the depth. It is a generic type, * meaning it can represent any type of data that we want to store in the node. * @returns The depth of the given binary tree node. */ - getDepth(node: BinaryTreeNode): number { + getDepth(node: N): number { let depth = 0; while (node.parent) { depth++; @@ -501,17 +503,17 @@ export class BinaryTree implements IBinaryTree { /** * The `getHeight` function calculates the maximum height of a binary tree using either a recursive or iterative * approach. - * @param {BinaryTreeNode | null} [beginRoot] - The `beginRoot` parameter is an optional parameter of type - * `BinaryTreeNode | null`. It represents the starting node from which to calculate the height of the binary tree. + * @param {N | null} [beginRoot] - The `beginRoot` parameter is an optional parameter of type + * `N | null`. It represents the starting node from which to calculate the height of the binary tree. * If no value is provided for `beginRoot`, the function will use the `root` property of the class instance as * @returns the height of the binary tree. */ - getHeight(beginRoot?: BinaryTreeNode | null): number { + getHeight(beginRoot?: N | null): number { beginRoot = beginRoot ?? this.root; if (!beginRoot) return -1; if (this._loopType === LoopType.recursive) { - const _getMaxHeight = (cur: BinaryTreeNode | null | undefined): number => { + const _getMaxHeight = (cur: N | null | undefined): number => { if (!cur) return -1; const leftHeight = _getMaxHeight(cur.left); const rightHeight = _getMaxHeight(cur.right); @@ -520,9 +522,9 @@ export class BinaryTree implements IBinaryTree { return _getMaxHeight(beginRoot); } else { - const stack: BinaryTreeNode[] = []; - let node: BinaryTreeNode | null | undefined = beginRoot, last: BinaryTreeNode | null = null; - const depths: Map, number> = new Map(); + const stack: N[] = []; + let node: N | null | undefined = beginRoot, last: N | null = null; + const depths: Map = new Map(); while (stack.length > 0 || node) { if (node) { @@ -550,17 +552,17 @@ export class BinaryTree implements IBinaryTree { /** * The `getMinHeight` function calculates the minimum height of a binary tree using either a recursive or iterative * approach. - * @param {BinaryTreeNode | null} [beginRoot] - The `beginRoot` parameter is an optional parameter of type - * `BinaryTreeNode | null`. It represents the starting node from which to calculate the minimum height of the binary + * @param {N | null} [beginRoot] - The `beginRoot` parameter is an optional parameter of type + * `N | null`. It represents the starting node from which to calculate the minimum height of the binary * tree. If no value is provided for `beginRoot`, the function will use the root node of the binary tree. * @returns The function `getMinHeight` returns the minimum height of the binary tree. */ - getMinHeight(beginRoot?: BinaryTreeNode | null): number { + getMinHeight(beginRoot?: N | null): number { beginRoot = beginRoot || this.root; if (!beginRoot) return -1; if (this._loopType === LoopType.recursive) { - const _getMinHeight = (cur: BinaryTreeNode | null | undefined): number => { + const _getMinHeight = (cur: N | null | undefined): number => { if (!cur) return 0; if (!cur.left && !cur.right) return 0; const leftMinHeight = _getMinHeight(cur.left); @@ -570,9 +572,9 @@ export class BinaryTree implements IBinaryTree { return _getMinHeight(beginRoot); } else { - const stack: BinaryTreeNode[] = []; - let node: BinaryTreeNode | null | undefined = beginRoot, last: BinaryTreeNode | null = null; - const depths: Map, number> = new Map(); + const stack: N[] = []; + let node: N | null | undefined = beginRoot, last: N | null = null; + const depths: Map = new Map(); while (stack.length > 0 || node) { if (node) { @@ -599,34 +601,34 @@ export class BinaryTree implements IBinaryTree { /** * The function checks if a binary tree is balanced by comparing the minimum height and the maximum height of the tree. - * @param {BinaryTreeNode | null} [beginRoot] - The `beginRoot` parameter is the root node of a binary tree. It is - * of type `BinaryTreeNode | null`, which means it can either be a `BinaryTreeNode` object or `null`. + * @param {N | null} [beginRoot] - The `beginRoot` parameter is the root node of a binary tree. It is + * of type `N | null`, which means it can either be a `BinaryTreeNode` object or `null`. * @returns The method is returning a boolean value. */ - isBalanced(beginRoot?: BinaryTreeNode | null): boolean { + isBalanced(beginRoot?: N | null): boolean { return (this.getMinHeight(beginRoot) + 1 >= this.getHeight(beginRoot)); } /** * The function `getNodes` returns an array of binary tree nodes that match a given property value, with options for * searching recursively or iteratively. - * @param {BinaryTreeNodeId | T} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a - * generic type `T`. It represents the property of the binary tree node that you want to search for. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a + * generic type `N`. It represents the property of the binary tree node that you want to search for. * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that * specifies the property name to use when searching for nodes. If not provided, it defaults to 'id'. * @param {boolean} [onlyOne] - The `onlyOne` parameter is an optional boolean parameter that determines whether to * return only one node that matches the `nodeProperty` or `propertyName` criteria. If `onlyOne` is set to `true`, the * function will stop traversing the tree and return the first matching node. If ` - * @returns The function `getNodes` returns an array of `BinaryTreeNode | null | undefined` objects. + * @returns The function `getNodes` returns an array of `N | null | undefined` objects. */ - getNodes(nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean) { + getNodes(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean) { if (!this.root) return [] as null[]; propertyName = propertyName ?? 'id'; - const result: (BinaryTreeNode | null | undefined)[] = []; + const result: (N | null | undefined)[] = []; if (this._loopType === LoopType.recursive) { - const _traverse = (cur: BinaryTreeNode) => { + const _traverse = (cur: N) => { if (this._pushByPropertyNameStopOrNot(cur, result, nodeProperty, propertyName, onlyOne)) return; if (!cur.left && !cur.right) return; cur.left && _traverse(cur.left); @@ -635,7 +637,7 @@ export class BinaryTree implements IBinaryTree { _traverse(this.root); } else { - const queue: BinaryTreeNode[] = [this.root]; + const queue: N[] = [this.root]; while (queue.length > 0) { const cur = queue.shift(); if (cur) { @@ -652,26 +654,26 @@ export class BinaryTree implements IBinaryTree { /** * The function checks if a binary tree node has a specific property or if any node in the tree has a specific * property. - * @param {BinaryTreeNodeId | T} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a - * generic type `T`. It represents the property of a binary tree node that you want to check. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a + * generic type `N`. It represents the property of a binary tree node that you want to check. * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that * specifies the name of the property to check for in the nodes. * @returns a boolean value. */ - has(nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName): boolean { + has(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): boolean { return this.getNodes(nodeProperty, propertyName).length > 0; } /** * The function returns the first binary tree node that matches the given property name and value, or null if no match * is found. - * @param {BinaryTreeNodeId | T} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a - * generic type `T`. It represents the property of the binary tree node that you want to search for. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a + * generic type `N`. It represents the property of the binary tree node that you want to search for. * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that * specifies the property of the binary tree node to search for. If not provided, it defaults to `'id'`. * @returns a BinaryTreeNode object or null. */ - get(nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName): BinaryTreeNode | null { + get(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): N | null { propertyName = propertyName ?? 'id'; return this.getNodes(nodeProperty, propertyName, true)[0] ?? null; } @@ -680,11 +682,11 @@ export class BinaryTree implements IBinaryTree { * The function getPathToRoot returns an array of BinaryTreeNode objects representing the path from a given node to the * root of a binary tree. * @param node - The `node` parameter is a BinaryTreeNode object. - * @returns The function `getPathToRoot` returns an array of `BinaryTreeNode` objects, representing the path from + * @returns The function `getPathToRoot` returns an array of `N` objects, representing the path from * the given `node` to the root of the binary tree. */ - getPathToRoot(node: BinaryTreeNode): BinaryTreeNode[] { - const result: BinaryTreeNode[] = []; + getPathToRoot(node: N): N[] { + const result: N[] = []; while (node.parent) { result.unshift(node); node = node.parent; @@ -693,25 +695,25 @@ export class BinaryTree implements IBinaryTree { return result; } - getLeftMost(): BinaryTreeNode | null; + getLeftMost(): N | null; - getLeftMost(node: BinaryTreeNode): BinaryTreeNode; + getLeftMost(node: N): N; /** * The `getLeftMost` function returns the leftmost node in a binary tree, either recursively or iteratively using tail * recursion optimization. - * @param {BinaryTreeNode | null} [node] - The `node` parameter is an optional parameter of type `BinaryTreeNode + * @param {N | null} [node] - The `node` parameter is an optional parameter of type `N * | null`. It represents the starting node from which to find the leftmost node in a binary tree. If no node is * provided, the function will use the root node of the binary tree. * @returns The `getLeftMost` function returns the leftmost node in a binary tree. */ - getLeftMost(node?: BinaryTreeNode | null): BinaryTreeNode | null { + getLeftMost(node?: N | null): N | null { node = node ?? this.root; if (!node) return node; if (this._loopType === LoopType.recursive) { - const _traverse = (cur: BinaryTreeNode): BinaryTreeNode => { + const _traverse = (cur: N): N => { if (!cur.left) return cur; return _traverse(cur.left); } @@ -719,7 +721,7 @@ export class BinaryTree implements IBinaryTree { return _traverse(node); } else { // Indirect implementation of iteration using tail recursion optimization - const _traverse = trampoline((cur: BinaryTreeNode) => { + const _traverse = trampoline((cur: N) => { if (!cur.left) return cur; return _traverse.cont(cur.left); }); @@ -728,24 +730,24 @@ export class BinaryTree implements IBinaryTree { } } - getRightMost(): BinaryTreeNode | null; + getRightMost(): N | null; - getRightMost(node: BinaryTreeNode): BinaryTreeNode; + getRightMost(node: N): N; /** * The `getRightMost` function returns the rightmost node in a binary tree, either recursively or iteratively using * tail recursion optimization. - * @param {BinaryTreeNode | null} [node] - The `node` parameter is an optional parameter of type `BinaryTreeNode + * @param {N | null} [node] - The `node` parameter is an optional parameter of type `N * | null`. It represents the starting node from which to find the rightmost node in a binary tree. If no node is * provided, the function will use the root node of the binary tree. * @returns The `getRightMost` function returns the rightmost node in a binary tree. */ - getRightMost(node?: BinaryTreeNode | null): BinaryTreeNode | null { + getRightMost(node?: N | null): N | null { node = node ?? this.root; if (!node) return node; if (this._loopType === LoopType.recursive) { - const _traverse = (cur: BinaryTreeNode): BinaryTreeNode => { + const _traverse = (cur: N): N => { if (!cur.right) return cur; return _traverse(cur.right); } @@ -753,7 +755,7 @@ export class BinaryTree implements IBinaryTree { return _traverse(node); } else { // Indirect implementation of iteration using tail recursion optimization - const _traverse = trampoline((cur: BinaryTreeNode) => { + const _traverse = trampoline((cur: N) => { if (!cur.right) return cur; return _traverse.cont(cur.right); }); @@ -764,18 +766,18 @@ export class BinaryTree implements IBinaryTree { /** * The `isBST` function checks if a binary tree is a binary search tree. - * @param {BinaryTreeNode | null} [node] - The `node` parameter is an optional parameter of type `BinaryTreeNode + * @param {N | null} [node] - The `node` parameter is an optional parameter of type `N * | null`. It represents the root node of the binary search tree (BST) that we want to check for validity. If no node * is provided, the function will default to using the root node of the BST instance that * @returns The `isBST` function returns a boolean value. It returns `true` if the binary tree is a valid binary search * tree, and `false` otherwise. */ - isBST(node?: BinaryTreeNode | null): boolean { + isBST(node?: N | null): boolean { node = node ?? this.root; if (!node) return true; if (this._loopType === LoopType.recursive) { - const dfs = (cur: BinaryTreeNode | null | undefined, min: BinaryTreeNodeId, max: BinaryTreeNodeId): boolean => { + const dfs = (cur: N | null | undefined, min: BinaryTreeNodeId, max: BinaryTreeNodeId): boolean => { if (!cur) return true; if (cur.id <= min || cur.id >= max) return false; return dfs(cur.left, min, cur.id) && dfs(cur.right, cur.id, max); @@ -784,7 +786,7 @@ export class BinaryTree implements IBinaryTree { return dfs(node, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); } else { const stack = []; - let prev = Number.MIN_SAFE_INTEGER, curr: BinaryTreeNode | null | undefined = node; + let prev = Number.MIN_SAFE_INTEGER, curr: N | null | undefined = node; while (curr || stack.length > 0) { while (curr) { stack.push(curr); @@ -802,17 +804,17 @@ export class BinaryTree implements IBinaryTree { /** * The function calculates the size and count of a subtree in a binary tree using either recursive or iterative * traversal. - * @param {BinaryTreeNode | null | undefined} subTreeRoot - The `subTreeRoot` parameter is the root node of a binary + * @param {N | null | undefined} subTreeRoot - The `subTreeRoot` parameter is the root node of a binary * tree. * @returns The function `getSubTreeSizeAndCount` returns an array `[number, number]`. The first element of the array * represents the size of the subtree, and the second element represents the count of the nodes in the subtree. */ - getSubTreeSizeAndCount(subTreeRoot: BinaryTreeNode | null | undefined) { + getSubTreeSizeAndCount(subTreeRoot: N | null | undefined) { const res: [number, number] = [0, 0]; if (!subTreeRoot) return res; if (this._loopType === LoopType.recursive) { - const _traverse = (cur: BinaryTreeNode) => { + const _traverse = (cur: N) => { res[0]++; res[1] += cur.count; cur.left && _traverse(cur.left); @@ -822,7 +824,7 @@ export class BinaryTree implements IBinaryTree { _traverse(subTreeRoot); return res; } else { - const stack: BinaryTreeNode[] = [subTreeRoot]; + const stack: N[] = [subTreeRoot]; while (stack.length > 0) { const cur = stack.pop()!; @@ -848,13 +850,13 @@ export class BinaryTree implements IBinaryTree { * provided, it defaults to `'val'`. * @returns a number, which is the sum of the values of the nodes in the subtree rooted at `subTreeRoot`. */ - subTreeSum(subTreeRoot: BinaryTreeNode, propertyName ?: BinaryTreeNodePropertyName): number { + subTreeSum(subTreeRoot: N, propertyName ?: BinaryTreeNodePropertyName): number { propertyName = propertyName ?? 'val'; if (!subTreeRoot) return 0; let sum = 0; - const _sumByProperty = (cur: BinaryTreeNode) => { + const _sumByProperty = (cur: N) => { let needSum: number; switch (propertyName) { case 'id': @@ -874,7 +876,7 @@ export class BinaryTree implements IBinaryTree { } if (this._loopType === LoopType.recursive) { - const _traverse = (cur: BinaryTreeNode): void => { + const _traverse = (cur: N): void => { sum += _sumByProperty(cur); cur.left && _traverse(cur.left); cur.right && _traverse(cur.right); @@ -882,7 +884,7 @@ export class BinaryTree implements IBinaryTree { _traverse(subTreeRoot); } else { - const stack: BinaryTreeNode[] = [subTreeRoot]; + const stack: N[] = [subTreeRoot]; while (stack.length > 0) { const cur = stack.pop()!; @@ -904,11 +906,11 @@ export class BinaryTree implements IBinaryTree { * specifies the property of the `BinaryTreeNode` that should be modified. It defaults to `'id'` if not provided. * @returns a boolean value, which is `true`. */ - subTreeAdd(subTreeRoot: BinaryTreeNode, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean { + subTreeAdd(subTreeRoot: N, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean { propertyName = propertyName ?? 'id'; if (!subTreeRoot) return false; - const _addByProperty = (cur: BinaryTreeNode) => { + const _addByProperty = (cur: N) => { switch (propertyName) { case 'id': cur.id += delta; @@ -924,7 +926,7 @@ export class BinaryTree implements IBinaryTree { } if (this._loopType === LoopType.recursive) { - const _traverse = (cur: BinaryTreeNode) => { + const _traverse = (cur: N) => { _addByProperty(cur); cur.left && _traverse(cur.left); cur.right && _traverse(cur.right); @@ -932,7 +934,7 @@ export class BinaryTree implements IBinaryTree { _traverse(subTreeRoot); } else { - const stack: BinaryTreeNode[] = [subTreeRoot]; + const stack: N[] = [subTreeRoot]; while (stack.length > 0) { const cur = stack.pop()!; @@ -949,9 +951,9 @@ export class BinaryTree implements IBinaryTree { BFS(nodeOrPropertyName: 'id'): BinaryTreeNodeId[]; - BFS(nodeOrPropertyName: 'val'): T[]; + BFS(nodeOrPropertyName: 'val'): N['val'][]; - BFS(nodeOrPropertyName: 'node'): BinaryTreeNode[]; + BFS(nodeOrPropertyName: 'node'): N[]; BFS(nodeOrPropertyName: 'count'): number[]; @@ -962,12 +964,12 @@ export class BinaryTree implements IBinaryTree { * represents either a node or a property name. If a node is provided, the breadth-first search algorithm will be * performed starting from that node. If a property name is provided, the breadth-first search algorithm will be * performed starting from the root node - * @returns an object of type `ResultsByProperty`. + * @returns an object of type `ResultsByProperty`. */ - BFS(nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { + BFS(nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { nodeOrPropertyName = nodeOrPropertyName ?? 'id'; this._resetResults(); - const queue: Array | null | undefined> = [this.root]; + const queue: Array = [this.root]; while (queue.length !== 0) { const cur = queue.shift(); @@ -985,9 +987,9 @@ export class BinaryTree implements IBinaryTree { DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; - DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): T[]; + DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; - DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): BinaryTreeNode[]; + DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; @@ -1001,13 +1003,13 @@ export class BinaryTree implements IBinaryTree { * either the name of a property in the `BinaryTreeNode` object or the value of the `id` property in the * `BinaryTreeNode` object. This parameter is used to accumulate the results based on the specified property name. If * no value - * @returns an object of type `ResultsByProperty`. + * @returns an object of type `ResultsByProperty`. */ - DFS(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { + DFS(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { pattern = pattern ?? 'in'; nodeOrPropertyName = nodeOrPropertyName ?? 'id'; this._resetResults(); - const _traverse = (node: BinaryTreeNode) => { + const _traverse = (node: N) => { switch (pattern) { case 'in': if (node.left) _traverse(node.left); @@ -1035,9 +1037,9 @@ export class BinaryTree implements IBinaryTree { DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; - DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): T[]; + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; - DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): BinaryTreeNode[]; + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; @@ -1048,13 +1050,13 @@ export class BinaryTree implements IBinaryTree { * @param nodeOrPropertyName * @constructor */ - DFSIterative(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { + DFSIterative(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { pattern = pattern || 'in'; nodeOrPropertyName = nodeOrPropertyName || 'id'; this._resetResults(); if (!this.root) return this._getResultByPropertyName(nodeOrPropertyName); // 0: visit, 1: print - const stack: { opt: 0 | 1, node: BinaryTreeNode | null | undefined }[] = [{opt: 0, node: this.root}]; + const stack: { opt: 0 | 1, node: N | null | undefined }[] = [{opt: 0, node: this.root}]; while (stack.length > 0) { const cur = stack.pop(); @@ -1090,35 +1092,35 @@ export class BinaryTree implements IBinaryTree { return this._getResultByPropertyName(nodeOrPropertyName); } - levelIterative(node: BinaryTreeNode | null): BinaryTreeNodeId[]; + levelIterative(node: N | null): BinaryTreeNodeId[]; - levelIterative(node: BinaryTreeNode | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + levelIterative(node: N | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; - levelIterative(node: BinaryTreeNode | null, nodeOrPropertyName?: 'val'): T[]; + levelIterative(node: N | null, nodeOrPropertyName?: 'val'): N['val'][]; - levelIterative(node: BinaryTreeNode | null, nodeOrPropertyName?: 'node'): BinaryTreeNode[]; + levelIterative(node: N | null, nodeOrPropertyName?: 'node'): N[]; - levelIterative(node: BinaryTreeNode | null, nodeOrPropertyName?: 'count'): number[]; + levelIterative(node: N | null, nodeOrPropertyName?: 'count'): number[]; /** * The `levelIterative` function performs a level-order traversal on a binary tree and returns the values of the nodes * in an array, based on a specified property name. - * @param {BinaryTreeNode | null} node - The `node` parameter is a BinaryTreeNode object representing the starting + * @param {N | null} node - The `node` parameter is a BinaryTreeNode object representing the starting * node for the level order traversal. It can be null if no specific node is provided, in which case the root node of * the tree is used as the starting node. * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is an optional parameter that * can be either a `BinaryTreeNode` property name or the string `'id'`. If a property name is provided, the function * will accumulate results based on that property. If no property name is provided, the function will default to * accumulating results - * @returns The function `levelIterative` returns an object of type `ResultsByProperty`. + * @returns The function `levelIterative` returns an object of type `ResultsByProperty`. */ - levelIterative(node: BinaryTreeNode | null, nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { + levelIterative(node: N | null, nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { nodeOrPropertyName = nodeOrPropertyName || 'id'; node = node || this.root; if (!node) return []; this._resetResults(); - const queue: BinaryTreeNode[] = [node]; + const queue: N[] = [node]; while (queue.length > 0) { const cur = queue.shift(); @@ -1136,33 +1138,33 @@ export class BinaryTree implements IBinaryTree { return this._getResultByPropertyName(nodeOrPropertyName); } - listLevels(node: BinaryTreeNode | null): BinaryTreeNodeId[][]; + listLevels(node: N | null): BinaryTreeNodeId[][]; - listLevels(node: BinaryTreeNode | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[][]; + listLevels(node: N | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[][]; - listLevels(node: BinaryTreeNode | null, nodeOrPropertyName?: 'val'): T[][]; + listLevels(node: N | null, nodeOrPropertyName?: 'val'): N['val'][][]; - listLevels(node: BinaryTreeNode | null, nodeOrPropertyName?: 'node'): BinaryTreeNode[][]; + listLevels(node: N | null, nodeOrPropertyName?: 'node'): N[][]; - listLevels(node: BinaryTreeNode | null, nodeOrPropertyName?: 'count'): number[][]; + listLevels(node: N | null, nodeOrPropertyName?: 'count'): number[][]; /** * The `listLevels` function collects nodes from a binary tree by a specified property and organizes them into levels. - * @param {BinaryTreeNode | null} node - The `node` parameter is a BinaryTreeNode object or null. It represents the + * @param {N | null} node - The `node` parameter is a BinaryTreeNode object or null. It represents the * root node of a binary tree. If it is null, the function will use the root node of the current binary tree instance. * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is an optional parameter that * specifies the property of the `BinaryTreeNode` object to collect at each level. It can be one of the following * values: - * @returns The function `listLevels` returns a 2D array of `ResultByProperty` objects. + * @returns The function `listLevels` returns a 2D array of `ResultByProperty` objects. */ - listLevels(node: BinaryTreeNode | null, nodeOrPropertyName?: NodeOrPropertyName): ResultByProperty[][] { + listLevels(node: N | null, nodeOrPropertyName?: NodeOrPropertyName): ResultByProperty[][] { nodeOrPropertyName = nodeOrPropertyName || 'id'; node = node || this.root; if (!node) return []; - const levelsNodes: ResultByProperty[][] = []; + const levelsNodes: ResultByProperty[][] = []; - const collectByProperty = (node: BinaryTreeNode, level: number) => { + const collectByProperty = (node: N, level: number) => { switch (nodeOrPropertyName) { case 'id': levelsNodes[level].push(node.id); @@ -1183,7 +1185,7 @@ export class BinaryTree implements IBinaryTree { } if (this._loopType === LoopType.recursive) { - const _recursive = (node: BinaryTreeNode, level: number) => { + const _recursive = (node: N, level: number) => { if (!levelsNodes[level]) levelsNodes[level] = []; collectByProperty(node, level); if (node.left) _recursive(node.left, level + 1); @@ -1192,7 +1194,7 @@ export class BinaryTree implements IBinaryTree { _recursive(node, 0); } else { - const stack: [BinaryTreeNode, number][] = [[node, 0]]; + const stack: [N, number][] = [[node, 0]]; while (stack.length > 0) { const head = stack.pop()!; @@ -1213,9 +1215,9 @@ export class BinaryTree implements IBinaryTree { * @param node - The parameter `node` is a BinaryTreeNode object, representing a node in a binary tree. * @returns the predecessor of the given node in a binary tree. */ - getPredecessor(node: BinaryTreeNode): BinaryTreeNode { + getPredecessor(node: N): N { if (node.left) { - let predecessor: BinaryTreeNode | null | undefined = node.left; + let predecessor: N | null | undefined = node.left; while (!(predecessor) || predecessor.right && predecessor.right !== node) { if (predecessor) { predecessor = predecessor.right; @@ -1231,9 +1233,9 @@ export class BinaryTree implements IBinaryTree { morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; - morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): T[]; + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; - morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): BinaryTreeNode[]; + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; @@ -1247,9 +1249,9 @@ export class BinaryTree implements IBinaryTree { * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is used to specify the * property of the nodes that you want to retrieve in the results. It can be either the node itself or the name of the * property. If not provided, it defaults to `'id'`. - * @returns The function `morris` returns an object of type `ResultsByProperty`. + * @returns The function `morris` returns an object of type `ResultsByProperty`. */ - morris(pattern?: 'in' | 'pre' | 'post', nodeOrPropertyName?: NodeOrPropertyName): ResultsByProperty { + morris(pattern?: 'in' | 'pre' | 'post', nodeOrPropertyName?: NodeOrPropertyName): ResultsByProperty { if (this.root === null) return []; pattern = pattern || 'in'; @@ -1257,10 +1259,10 @@ export class BinaryTree implements IBinaryTree { this._resetResults(); - let cur: BinaryTreeNode | null | undefined = this.root; - const _reverseEdge = (node: BinaryTreeNode | null | undefined) => { - let pre: BinaryTreeNode | null | undefined = null; - let next: BinaryTreeNode | null | undefined = null; + let cur: N | null | undefined = this.root; + const _reverseEdge = (node: N | null | undefined) => { + let pre: N | null | undefined = null; + let next: N | null | undefined = null; while (node) { next = node.right; node.right = pre; @@ -1269,9 +1271,9 @@ export class BinaryTree implements IBinaryTree { } return pre; }; - const _printEdge = (node: BinaryTreeNode | null) => { - const tail: BinaryTreeNode | null | undefined = _reverseEdge(node); - let cur: BinaryTreeNode | null | undefined = tail; + const _printEdge = (node: N | null) => { + const tail: N | null | undefined = _reverseEdge(node); + let cur: N | null | undefined = tail; while (cur) { this._accumulatedByPropertyName(cur, nodeOrPropertyName); cur = cur.right; @@ -1343,11 +1345,11 @@ export class BinaryTree implements IBinaryTree { this._visitedId = value; } - protected _setVisitedVal(value: Array) { + protected _setVisitedVal(value: Array) { this._visitedVal = value; } - protected _setVisitedNode(value: BinaryTreeNode[]) { + protected _setVisitedNode(value: N[]) { this._visitedNode = value; } @@ -1371,7 +1373,7 @@ export class BinaryTree implements IBinaryTree { this._isDuplicatedVal = value; } - protected _setRoot(v: BinaryTreeNode | null) { + protected _setRoot(v: N | null) { if (v) { v.parent = null; v.familyPosition = FamilyPosition.root; @@ -1402,9 +1404,9 @@ export class BinaryTree implements IBinaryTree { * The function checks if a given property of a binary tree node matches a specified value, and if so, adds the node to * a result array. * @param cur - The current binary tree node that is being checked. - * @param {(BinaryTreeNode | null | undefined)[]} result - An array that stores the matching nodes found during the + * @param {(N | null | undefined)[]} result - An array that stores the matching nodes found during the * traversal. - * @param {BinaryTreeNodeId | T} nodeProperty - The `nodeProperty` parameter is the value that we are searching for in + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter is the value that we are searching for in * the binary tree nodes. It can be either the `id`, `count`, or `val` property of the node. * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that * specifies the property of the `BinaryTreeNode` object that you want to compare with the `nodeProperty` value. It can @@ -1414,7 +1416,7 @@ export class BinaryTree implements IBinaryTree { * `true`, the function will stop after finding the first matching node and return `true`. If `onlyOne * @returns a boolean value indicating whether or not a node was pushed into the result array. */ - protected _pushByPropertyNameStopOrNot(cur: BinaryTreeNode, result: (BinaryTreeNode | null | undefined)[], nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean) { + protected _pushByPropertyNameStopOrNot(cur: N, result: (N | null | undefined)[], nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean) { switch (propertyName) { case 'id': if (cur.id === nodeProperty) { @@ -1446,12 +1448,12 @@ export class BinaryTree implements IBinaryTree { /** * The function `_accumulatedByPropertyName` pushes a property value of a binary tree node into an array based on the * provided property name or a default property name. - * @param node - The `node` parameter is of type `BinaryTreeNode`, which represents a node in a binary tree. + * @param node - The `node` parameter is of type `N`, which represents a node in a binary tree. * @param {NodeOrPropertyName} [nodeOrPropertyName] - The parameter `nodeOrPropertyName` is an optional parameter that * can be either a string representing a property name or a reference to a node object. If it is a string, it specifies * the property name of the node that should be accumulated. If it is a node object, it specifies the node itself */ - protected _accumulatedByPropertyName(node: BinaryTreeNode, nodeOrPropertyName ?: NodeOrPropertyName) { + protected _accumulatedByPropertyName(node: N, nodeOrPropertyName ?: NodeOrPropertyName) { nodeOrPropertyName = nodeOrPropertyName ?? 'id'; switch (nodeOrPropertyName) { @@ -1480,7 +1482,7 @@ export class BinaryTree implements IBinaryTree { * can accept a value of type `NodeOrPropertyName`. * @returns The method returns an object of type `ResultsByProperty`. */ - protected _getResultByPropertyName(nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { + protected _getResultByPropertyName(nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { nodeOrPropertyName = nodeOrPropertyName ?? 'id'; switch (nodeOrPropertyName) { diff --git a/src/data-structures/binary-tree/bst.ts b/src/data-structures/binary-tree/bst.ts index e06c078..be1b7b9 100644 --- a/src/data-structures/binary-tree/bst.ts +++ b/src/data-structures/binary-tree/bst.ts @@ -5,19 +5,23 @@ * @copyright Copyright (c) 2022 Tyler Zeng * @license MIT License */ -import type {BinaryTreeNodeId, BinaryTreeNodePropertyName, BSTComparator, BSTDeletedResult} from '../types'; +import type { + BinaryTreeNodeId, + BinaryTreeNodePropertyName, + BSTComparator, + BSTDeletedResult, + RecursiveBSTNode +} from '../types'; import {BinaryTree, BinaryTreeNode, FamilyPosition, LoopType,} from './binary-tree'; import {IBinaryTree, IBinaryTreeNode} from '../interfaces'; export enum CP {lt = -1, eq = 0, gt = 1} -export class BSTNode extends BinaryTreeNode implements IBinaryTreeNode { - override _createNode(id: BinaryTreeNodeId, val: T | null, count?: number): BSTNode | null { - return val !== null ? new BSTNode(id, val, count) : null; - } +export class BSTNode = RecursiveBSTNode> extends BinaryTreeNode implements IBinaryTreeNode { + } -export class BST extends BinaryTree implements IBinaryTree { +export class BST = BSTNode> extends BinaryTree implements IBinaryTree { /** * The constructor function accepts an optional options object and sets the comparator property if provided. * @param [options] - An optional object that can contain the following properties: @@ -35,8 +39,9 @@ export class BST extends BinaryTree implements IBinaryTree { } } - override _createNode(id: BinaryTreeNodeId, val: T | null, count?: number): BSTNode | null { - return val !== null ? new BSTNode(id, val, count) : null; + override _createNode(id: BinaryTreeNodeId, val: N['val'] | null, count?: number): N | null { + const node = val !== null ? new BSTNode(id, val, count) : null; + return node as N; } /** @@ -44,15 +49,15 @@ export class BST extends BinaryTree implements IBinaryTree { * the ID matches, and returns the inserted node. * @param {BinaryTreeNodeId} id - The `id` parameter represents the identifier of the binary tree node. It is used to * determine the position of the node in the binary search tree. - * @param {T | null} val - The `val` parameter represents the value to be stored in the binary search tree node. It can - * be of type `T` (the generic type) or `null`. + * @param {N | null} val - The `val` parameter represents the value to be stored in the binary search tree node. It can + * be of type `N` (the generic type) or `null`. * @param {number} [count=1] - The `count` parameter represents the number of times the value should be inserted into * the binary search tree. By default, it is set to 1, meaning that if no count is specified, the value will be * inserted once. - * @returns The method `add` returns a `BSTNode` object or `null`. + * @returns The method `add` returns a `N` object or `null`. */ - override add(id: BinaryTreeNodeId, val: T | null, count: number = 1): BSTNode | null { - let inserted: BSTNode | null = null; + override add(id: BinaryTreeNodeId, val: N['val'] | null, count: number = 1): N | null { + let inserted: N | null = null; const newNode = this._createNode(id, val, count); if (this.root === null) { this._setRoot(newNode); @@ -118,14 +123,14 @@ export class BST extends BinaryTree implements IBinaryTree { /** * The `get` function returns the first node in a binary search tree that matches the given property value or name. - * @param {BinaryTreeNodeId | T} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a - * generic type `T`. It represents the value of the property that you want to search for in the binary search tree. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a + * generic type `N`. It represents the value of the property that you want to search for in the binary search tree. * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that * specifies the property name to use for searching the binary search tree nodes. If not provided, it defaults to * `'id'`. - * @returns The method is returning a BSTNode object or null. + * @returns The method is returning a N object or null. */ - override get(nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName): BSTNode | null { + override get(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): N | null { propertyName = propertyName ?? 'id'; return this.getNodes(nodeProperty, propertyName, true)[0] ?? null; } @@ -152,17 +157,17 @@ export class BST extends BinaryTree implements IBinaryTree { * @param {boolean} [ignoreCount] - A boolean flag indicating whether to ignore the count of the node being removed. If * set to true, the count of the node will not be considered and the node will be removed regardless of its count. If * set to false or not provided, the count of the node will be taken into account and the - * @returns an array of `BSTDeletedResult` objects. + * @returns an array of `BSTDeletedResult` objects. */ - override remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BSTDeletedResult[] { - const bstDeletedResult: BSTDeletedResult[] = []; + override remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BSTDeletedResult[] { + const bstDeletedResult: BSTDeletedResult[] = []; if (!this.root) return bstDeletedResult; - const curr: BSTNode | null = this.get(id); + const curr: N | null = this.get(id); if (!curr) return bstDeletedResult; - const parent: BSTNode | null = curr?.parent ? curr.parent : null; - let needBalanced: BSTNode | null = null, orgCurrent = curr; + const parent: N | null = curr?.parent ? curr.parent : null; + let needBalanced: N | null = null, orgCurrent = curr; if (curr.count > 1 && !ignoreCount) { curr.count--; @@ -205,23 +210,23 @@ export class BST extends BinaryTree implements IBinaryTree { /** * The function `getNodes` returns an array of binary search tree nodes that match a given property value, with the * option to specify the property name and whether to return only one node. - * @param {BinaryTreeNodeId | T} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a - * generic type `T`. It represents the property value that you want to search for in the binary search tree. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a + * generic type `N`. It represents the property value that you want to search for in the binary search tree. * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that * specifies the property of the nodes to compare with the `nodeProperty` parameter. If not provided, it defaults to * `'id'`. * @param {boolean} [onlyOne] - A boolean value indicating whether to return only one node that matches the given * nodeProperty. If set to true, the function will stop traversing the tree and return the first matching node. If set * to false or not provided, the function will return all nodes that match the given nodeProperty. - * @returns an array of BSTNode objects. + * @returns an array of N objects. */ - override getNodes(nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean): BSTNode[] { + override getNodes(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean): N[] { propertyName = propertyName ?? 'id'; if (!this.root) return []; - const result: BSTNode[] = []; + const result: N[] = []; if (this.loopType === LoopType.recursive) { - const _traverse = (cur: BSTNode) => { + const _traverse = (cur: N) => { if (this._pushByPropertyNameStopOrNot(cur, result, nodeProperty, propertyName, onlyOne)) return; if (!cur.left && !cur.right) return; @@ -236,7 +241,7 @@ export class BST extends BinaryTree implements IBinaryTree { _traverse(this.root); } else { - const queue: BSTNode[] = [this.root]; + const queue: N[] = [this.root]; while (queue.length > 0) { const cur = queue.shift(); if (cur) { @@ -270,7 +275,7 @@ export class BST extends BinaryTree implements IBinaryTree { propertyName = propertyName ?? 'id'; if (!this.root) return 0; - const getSumByPropertyName = (cur: BSTNode) => { + const getSumByPropertyName = (cur: N) => { let needSum: number; switch (propertyName) { case 'id': @@ -289,7 +294,7 @@ export class BST extends BinaryTree implements IBinaryTree { let sum = 0; if (this.loopType === LoopType.recursive) { - const _traverse = (cur: BSTNode): void => { + const _traverse = (cur: N): void => { const compared = this._compare(cur.id, id); if (compared === CP.eq) { if (cur.right) sum += this.subTreeSum(cur.right, propertyName); @@ -307,7 +312,7 @@ export class BST extends BinaryTree implements IBinaryTree { _traverse(this.root); } else { - const queue: BSTNode[] = [this.root]; + const queue: N[] = [this.root]; while (queue.length > 0) { const cur = queue.shift(); if (cur) { @@ -334,7 +339,7 @@ export class BST extends BinaryTree implements IBinaryTree { /** * The function `allGreaterNodesAdd` updates the value of a specified property for all nodes in a binary search tree * that have a greater value than a given node. - * @param node - The `node` parameter is of type `BSTNode`, which represents a node in a binary search tree. It + * @param node - The `node` parameter is of type `N`, which represents a node in a binary search tree. It * contains properties such as `id` and `count`. * @param {number} delta - The `delta` parameter is a number that represents the amount by which the property value of * each node should be increased. @@ -343,11 +348,11 @@ export class BST extends BinaryTree implements IBinaryTree { * defaults to 'id'. * @returns a boolean value. */ - allGreaterNodesAdd(node: BSTNode, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean { + allGreaterNodesAdd(node: N, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean { propertyName = propertyName ?? 'id'; if (!this.root) return false; - const _sumByPropertyName = (cur: BSTNode) => { + const _sumByPropertyName = (cur: N) => { switch (propertyName) { case 'id': cur.id += delta; @@ -362,7 +367,7 @@ export class BST extends BinaryTree implements IBinaryTree { } if (this.loopType === LoopType.recursive) { - const _traverse = (cur: BSTNode) => { + const _traverse = (cur: N) => { const compared = this._compare(cur.id, node.id); _sumByPropertyName(cur); @@ -374,7 +379,7 @@ export class BST extends BinaryTree implements IBinaryTree { _traverse(this.root); return true; } else { - const queue: BSTNode[] = [this.root]; + const queue: N[] = [this.root]; while (queue.length > 0) { const cur = queue.shift(); if (cur) { @@ -441,7 +446,7 @@ export class BST extends BinaryTree implements IBinaryTree { let balanced = true; if (this.loopType === LoopType.recursive) { - const _height = (cur: BSTNode | null | undefined): number => { + const _height = (cur: N | null | undefined): number => { if (!cur) return 0; const leftHeight = _height(cur.left), rightHeight = _height(cur.right); if (Math.abs(leftHeight - rightHeight) > 1) balanced = false; @@ -449,9 +454,9 @@ export class BST extends BinaryTree implements IBinaryTree { }; _height(this.root); } else { - const stack: BSTNode[] = []; - let node: BSTNode | null | undefined = this.root, last: BSTNode | null = null; - const depths: Map, number> = new Map(); + const stack: N[] = []; + let node: N | null | undefined = this.root, last: N | null = null; + const depths: Map = new Map(); while (stack.length > 0 || node) { if (node) { diff --git a/src/data-structures/binary-tree/rb-tree.ts b/src/data-structures/binary-tree/rb-tree.ts index f9e0fe7..dedf8e6 100644 --- a/src/data-structures/binary-tree/rb-tree.ts +++ b/src/data-structures/binary-tree/rb-tree.ts @@ -3,7 +3,7 @@ import {IBinaryTree, IBinaryTreeNode} from '../interfaces'; enum RBColor { Red, Black } -class RBNode extends BinaryTreeNode implements IBinaryTreeNode { +class RBNode> extends BinaryTreeNode implements IBinaryTreeNode { // override createNode(id: BinaryTreeNodeId, val: T | null, count?: number): RBNode | null { // return val !== null ? new RBNode(id, val, count) : null; // } @@ -58,7 +58,7 @@ class RBNode extends BinaryTreeNode implements IBinaryTreeNode { // } } -class RBTree extends BinaryTree implements IBinaryTree { +class RBTree> extends BinaryTree implements IBinaryTree { constructor(options?: { loopType?: LoopType, autoIncrementId?: boolean, @@ -67,43 +67,43 @@ class RBTree extends BinaryTree implements IBinaryTree { super(options); } - // override _createNode(id: BinaryTreeNodeId, val: T | null, count?: number): RBNode | null { - // return val !== null ? new RBNode(id, val, count) : null; + // override _createNode(id: BinaryTreeNodeId, val: N | null, count?: number): RBNode | null { + // return val !== null ? new RBNode(id, val, count) : null; // } - // private override _root: BinaryTreeNode | null = null; + // private override _root: BinaryTreeNode | null = null; // - // override get root(): BinaryTreeNode | null { + // override get root(): BinaryTreeNode | null { // return this._root; // } - insert(id: number, val: T | null) { + insert(id: number, val: N | null) { } - private leftRotate(node: RBNode) { + private leftRotate(node: N) { } - private rightRotate(node: RBNode) { + private rightRotate(node: N) { } - private insertFixup(node: RBNode) { + private insertFixup(node: N) { } - private deleteFixup(node: RBNode) { + private deleteFixup(node: N) { } - private transplant(u: RBNode, v: RBNode) { + private transplant(u: N, v: N) { } - // override remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeleted[] { + // override remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeleted[] { // - // return [{deleted: new RBNode(0, 0), needBalanced: null}]; + // return [{deleted: new N(0, 0), needBalanced: null}]; // } diff --git a/src/data-structures/binary-tree/tree-multiset.ts b/src/data-structures/binary-tree/tree-multiset.ts index 46d9405..011386d 100644 --- a/src/data-structures/binary-tree/tree-multiset.ts +++ b/src/data-structures/binary-tree/tree-multiset.ts @@ -9,30 +9,31 @@ import {BST, BSTNode} from './bst'; import type {BinaryTreeNodeId, TreeMultiSetDeletedResult} from '../types'; import {IBinaryTree} from '../interfaces'; -export class TreeMultiSet extends BST implements IBinaryTree { +export class TreeMultiSet = BSTNode> extends BST implements IBinaryTree { /** * The function creates a new BSTNode with the given id, value, and count. * @param {BinaryTreeNodeId} id - The id parameter is the unique identifier for the binary tree node. It is used to * distinguish one node from another in the tree. - * @param {T} val - The `val` parameter represents the value that will be stored in the binary search tree node. + * @param {N} val - The `val` parameter represents the value that will be stored in the binary search tree node. * @param {number} [count] - The "count" parameter is an optional parameter of type number. It represents the number of * occurrences of the value in the binary search tree node. If not provided, the count will default to 1. * @returns A new instance of the BSTNode class with the specified id, value, and count (if provided). */ - override _createNode(id: BinaryTreeNodeId, val: T, count?: number): BSTNode { - return new BSTNode(id, val, count); + override _createNode(id: BinaryTreeNodeId, val: N['val'], count?: number): N { + const node = new BSTNode(id, val, count); + return node as N; } /** * The function overrides the add method of the BinarySearchTree class in TypeScript. * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier of the binary tree node that you want to add. - * @param {T | null} val - The `val` parameter represents the value that you want to add to the binary search tree. It - * can be of type `T` (the generic type) or `null`. + * @param {N | null} val - The `val` parameter represents the value that you want to add to the binary search tree. It + * can be of type `N` (the generic type) or `null`. * @param {number} [count] - The `count` parameter is an optional parameter of type `number`. It represents the number * of times the value should be added to the binary search tree. If not provided, the default value is `undefined`. - * @returns The `add` method is returning a `BSTNode` object or `null`. + * @returns The `add` method is returning a `BSTNode` object or `null`. */ - override add(id: BinaryTreeNodeId, val: T | null, count?: number): BSTNode | null { + override add(id: BinaryTreeNodeId, val: N | null, count?: number): N | null { return super.add(id, val, count); } @@ -46,7 +47,7 @@ export class TreeMultiSet extends BST implements IBinaryTree { * set to `true`, the left sum of all nodes will be recalculated. If it * @returns The method is returning an array of TreeMultiSetDeletedResult objects. */ - override remove(id: BinaryTreeNodeId, isUpdateAllLeftSum?: boolean): TreeMultiSetDeletedResult[] { + override remove(id: BinaryTreeNodeId, isUpdateAllLeftSum?: boolean): TreeMultiSetDeletedResult[] { return super.remove(id, isUpdateAllLeftSum); } } diff --git a/src/data-structures/graph/abstract-graph.ts b/src/data-structures/graph/abstract-graph.ts index 3ecd76b..c202a1a 100644 --- a/src/data-structures/graph/abstract-graph.ts +++ b/src/data-structures/graph/abstract-graph.ts @@ -10,9 +10,9 @@ import {PriorityQueue} from '../priority-queue'; import type {DijkstraResult, VertexId} from '../types'; import {IGraph} from '../interfaces'; -export abstract class AbstractVertex { +export abstract class AbstractVertex { - protected constructor(id: VertexId, val?: V) { + protected constructor(id: VertexId, val?: T) { this._id = id; this._val = val; } @@ -27,13 +27,13 @@ export abstract class AbstractVertex { this._id = v; } - private _val: V | undefined; + private _val: T | undefined; - get val(): V | undefined { + get val(): T | undefined { return this._val; } - set val(value: V | undefined) { + set val(value: T | undefined) { this._val = value; } @@ -43,24 +43,24 @@ export abstract class AbstractVertex { // * @param id // * @param val // */ - // abstract _createVertex(id: VertexId, val?: V): AbstractVertex; + // abstract _createVertex(id: VertexId, val?: T): AbstractVertex; } -export abstract class AbstractEdge { +export abstract class AbstractEdge { - protected constructor(weight?: number, val?: E) { + protected constructor(weight?: number, val?: T) { this._weight = weight !== undefined ? weight : 1; this._val = val; this._hashCode = uuidV4(); } - private _val: E | undefined; + private _val: T | undefined; - get val(): E | undefined { + get val(): T | undefined { return this._val; } - set val(value: E | undefined) { + set val(value: T | undefined) { this._val = value; } @@ -88,7 +88,7 @@ export abstract class AbstractEdge { // * @param weight // * @param val // */ - // abstract _createEdge(srcOrV1: VertexId | string, destOrV2: VertexId | string, weight?: number, val?: E): AbstractEdge; + // abstract _createEdge(srcOrV1: VertexId | string, destOrV2: VertexId | string, weight?: number, val?: E): E; protected _setHashCode(v: string) { this._hashCode = v; @@ -96,10 +96,10 @@ export abstract class AbstractEdge { } // Connected Component === Largest Connected Sub-Graph -export abstract class AbstractGraph implements IGraph { - private _vertices: Map> = new Map>(); +export abstract class AbstractGraph, E extends AbstractEdge> implements IGraph { + private _vertices: Map = new Map(); - get vertices(): Map> { + get vertices(): Map { return this._vertices; } @@ -109,7 +109,7 @@ export abstract class AbstractGraph implements IGraph; + abstract _createVertex(id: VertexId, val?: V): V; /** * In TypeScript, a subclass inherits the interface implementation of its parent class, without needing to implement the same interface again in the subclass. This behavior differs from Java's approach. In Java, if a parent class implements an interface, the subclass needs to explicitly implement the same interface, even if the parent class has already implemented it. @@ -119,22 +119,22 @@ export abstract class AbstractGraph implements IGraph; + abstract _createEdge(srcOrV1: VertexId | string, destOrV2: VertexId | string, weight?: number, val?: E): E; - abstract removeEdgeBetween(srcOrId: AbstractVertex | VertexId, destOrId: AbstractVertex | VertexId): AbstractEdge | null; + abstract removeEdgeBetween(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; - abstract removeEdge(edge: AbstractEdge): AbstractEdge | null; + abstract removeEdge(edge: E): E | null; - _getVertex(vertexOrId: VertexId | AbstractVertex): AbstractVertex | null { + _getVertex(vertexOrId: VertexId | V): V | null { const vertexId = this._getVertexId(vertexOrId); return this._vertices.get(vertexId) || null; } - getVertex(vertexId: VertexId): AbstractVertex | null { + getVertex(vertexId: VertexId): V | null { return this._vertices.get(vertexId) || null; } - _getVertexId(vertexOrId: AbstractVertex | VertexId): VertexId { + _getVertexId(vertexOrId: V | VertexId): VertexId { return vertexOrId instanceof AbstractVertex ? vertexOrId.id : vertexOrId; } @@ -144,20 +144,21 @@ export abstract class AbstractGraph implements IGraph | VertexId): boolean { + hasVertex(vertexOrId: V | VertexId): boolean { return this._vertices.has(this._getVertexId(vertexOrId)); } - abstract getEdge(srcOrId: AbstractVertex | VertexId, destOrId: AbstractVertex | VertexId): AbstractEdge | null; + abstract getEdge(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; - createAddVertex(id: VertexId, val?: V): boolean { + createAddVertex(id: VertexId, val?: V['val']): boolean { const newVertex = this._createVertex(id, val); return this.addVertex(newVertex); } - addVertex(newVertex: AbstractVertex): boolean { + addVertex(newVertex: V): boolean { if (this.hasVertex(newVertex)) { - throw (new Error('Duplicated vertex id is not allowed')); + return false; + // throw (new Error('Duplicated vertex id is not allowed')); } this._vertices.set(newVertex.id, newVertex); return true; @@ -169,7 +170,7 @@ export abstract class AbstractGraph implements IGraph | VertexId): boolean { + removeVertex(vertexOrId: V | VertexId): boolean { const vertexId = this._getVertexId(vertexOrId); return this._vertices.delete(vertexId); } @@ -181,7 +182,7 @@ export abstract class AbstractGraph implements IGraph[] | VertexId[]): boolean { + removeAllVertices(vertices: V[] | VertexId[]): boolean { const removed: boolean[] = []; for (const v of vertices) { removed.push(this.removeVertex(v)); @@ -189,11 +190,11 @@ export abstract class AbstractGraph implements IGraph 0; } - abstract degreeOf(vertexOrId: AbstractVertex | VertexId): number; + abstract degreeOf(vertexOrId: V | VertexId): number; - abstract edgeSet(): AbstractEdge[]; + abstract edgeSet(): E[]; - abstract edgesOf(vertexOrId: AbstractVertex | VertexId): AbstractEdge[]; + abstract edgesOf(vertexOrId: V | VertexId): E[]; /** * The function checks if there is an edge between two vertices in a graph. @@ -204,19 +205,19 @@ export abstract class AbstractGraph implements IGraph, v2: VertexId | AbstractVertex): boolean { + hasEdge(v1: VertexId | V, v2: VertexId | V): boolean { const edge = this.getEdge(v1, v2); return !!edge; } - createAddEdge(src: AbstractVertex | VertexId, dest: AbstractVertex | VertexId, weight: number, val: E): boolean { + createAddEdge(src: V | VertexId, dest: V | VertexId, weight: number, val: E['val']): boolean { if (src instanceof AbstractVertex) src = src.id; if (dest instanceof AbstractVertex) dest = dest.id; const newEdge = this._createEdge(src, dest, weight, val); return this.addEdge(newEdge); } - abstract addEdge(edge: AbstractEdge): boolean; + abstract addEdge(edge: E): boolean; /** * The function sets the weight of an edge between two vertices in a graph. @@ -229,7 +230,7 @@ export abstract class AbstractGraph implements IGraph, destOrId: VertexId | AbstractVertex, weight: number): boolean { + setEdgeWeight(srcOrId: VertexId | V, destOrId: VertexId | V, weight: number): boolean { const edge = this.getEdge(srcOrId, destOrId); if (edge) { edge.weight = weight; @@ -239,7 +240,7 @@ export abstract class AbstractGraph implements IGraph | VertexId): AbstractVertex[]; + abstract getNeighbors(vertexOrId: V | VertexId): V[]; /** * The function `getAllPathsBetween` finds all paths between two vertices in a graph using depth-first search. @@ -250,15 +251,15 @@ export abstract class AbstractGraph implements IGraph | VertexId, v2: AbstractVertex | VertexId): AbstractVertex[][] { - const paths: AbstractVertex[][] = []; + getAllPathsBetween(v1: V | VertexId, v2: V | VertexId): V[][] { + const paths: V[][] = []; const vertex1 = this._getVertex(v1); const vertex2 = this._getVertex(v2); if (!(vertex1 && vertex2)) { return []; } - const dfs = (cur: AbstractVertex, dest: AbstractVertex, visiting: Map, boolean>, path: AbstractVertex[]) => { + const dfs = (cur: V, dest: V, visiting: Map, path: V[]) => { visiting.set(cur, true); if (cur === dest) { @@ -270,14 +271,14 @@ export abstract class AbstractGraph implements IGraph) => vertex === neighbor); + arrayRemove(path, (vertex: V) => vertex === neighbor); } } visiting.set(cur, false); }; - dfs(vertex1, vertex2, new Map, boolean>(), []); + dfs(vertex1, vertex2, new Map(), []); return paths; } @@ -286,7 +287,7 @@ export abstract class AbstractGraph implements IGraph[]): number { + getPathSumWeight(path: V[]): number { let sum = 0; for (let i = 0; i < path.length; i++) { sum += this.getEdge(path[i], path[i + 1])?.weight || 0; @@ -308,7 +309,7 @@ export abstract class AbstractGraph implements IGraph | VertexId, v2: AbstractVertex | VertexId, isWeight?: boolean): number | null { + getMinCostBetween(v1: V | VertexId, v2: V | VertexId, isWeight?: boolean): number | null { if (isWeight === undefined) isWeight = false; if (isWeight) { @@ -326,8 +327,8 @@ export abstract class AbstractGraph implements IGraph, boolean> = new Map(); - const queue: AbstractVertex[] = [vertex1]; + const visited: Map = new Map(); + const queue: V[] = [vertex1]; visited.set(vertex1, true); let cost = 0; while (queue.length > 0) { @@ -365,7 +366,7 @@ export abstract class AbstractGraph implements IGraph | VertexId, v2: AbstractVertex | VertexId, isWeight?: boolean): AbstractVertex[] | null { + getMinPathBetween(v1: V | VertexId, v2: V | VertexId, isWeight?: boolean): V[] | null { if (isWeight === undefined) isWeight = false; if (isWeight) { @@ -384,14 +385,14 @@ export abstract class AbstractGraph implements IGraph[] = []; + let minPath: V[] = []; const vertex1 = this._getVertex(v1); const vertex2 = this._getVertex(v2); if (!(vertex1 && vertex2)) { return []; } - const dfs = (cur: AbstractVertex, dest: AbstractVertex, visiting: Map, boolean>, path: AbstractVertex[]) => { + const dfs = (cur: V, dest: V, visiting: Map, path: V[]) => { visiting.set(cur, true); if (cur === dest) { @@ -404,14 +405,14 @@ export abstract class AbstractGraph implements IGraph) => vertex === neighbor); + arrayRemove(path, (vertex: V) => vertex === neighbor); } } visiting.set(cur, false); }; - dfs(vertex1, vertex2, new Map, boolean>(), []); + dfs(vertex1, vertex2, new Map(), []); return minPath; } } @@ -433,20 +434,20 @@ export abstract class AbstractGraph implements IGraph`. */ - dijkstraWithoutHeap(src: AbstractVertex | VertexId, dest?: AbstractVertex | VertexId | null, getMinDist?: boolean, genPaths?: boolean): DijkstraResult> { + dijkstraWithoutHeap(src: V | VertexId, dest?: V | VertexId | null, getMinDist?: boolean, genPaths?: boolean): DijkstraResult { if (getMinDist === undefined) getMinDist = false; if (genPaths === undefined) genPaths = false; if (dest === undefined) dest = null; let minDist = Infinity; - let minDest: AbstractVertex | null = null; - let minPath: AbstractVertex[] = []; - const paths: AbstractVertex[][] = []; + let minDest: V | null = null; + let minPath: V[] = []; + const paths: V[][] = []; const vertices = this._vertices; - const distMap: Map, number> = new Map(); - const seen: Set> = new Set(); - const preMap: Map, AbstractVertex | null> = new Map(); // predecessor + const distMap: Map = new Map(); + const seen: Set = new Set(); + const preMap: Map = new Map(); // predecessor const srcVertex = this._getVertex(src); const destVertex = dest ? this._getVertex(dest) : null; @@ -464,7 +465,7 @@ export abstract class AbstractGraph implements IGraph { let min = Infinity; - let minV: AbstractVertex | null = null; + let minV: V | null = null; for (const [key, val] of distMap) { if (!seen.has(key)) { if (val < min) { @@ -476,12 +477,12 @@ export abstract class AbstractGraph implements IGraph | null) => { + const getPaths = (minV: V | null) => { for (const vertex of vertices) { const vertexOrId = vertex[1]; if (vertexOrId instanceof AbstractVertex) { - const path: AbstractVertex[] = [vertexOrId]; + const path: V[] = [vertexOrId]; let parent = preMap.get(vertexOrId); while (parent) { path.push(parent); @@ -560,19 +561,19 @@ export abstract class AbstractGraph implements IGraph`. */ - dijkstra(src: AbstractVertex | VertexId, dest?: AbstractVertex | VertexId | null, getMinDist?: boolean, genPaths?: boolean): DijkstraResult> { + dijkstra(src: V | VertexId, dest?: V | VertexId | null, getMinDist?: boolean, genPaths?: boolean): DijkstraResult { if (getMinDist === undefined) getMinDist = false; if (genPaths === undefined) genPaths = false; if (dest === undefined) dest = null; let minDist = Infinity; - let minDest: AbstractVertex | null = null; - let minPath: AbstractVertex[] = []; - const paths: AbstractVertex[][] = []; + let minDest: V | null = null; + let minPath: V[] = []; + const paths: V[][] = []; const vertices = this._vertices; - const distMap: Map, number> = new Map(); - const seen: Set> = new Set(); - const preMap: Map, AbstractVertex | null> = new Map(); // predecessor + const distMap: Map = new Map(); + const seen: Set = new Set(); + const preMap: Map = new Map(); // predecessor const srcVertex = this._getVertex(src); const destVertex = dest ? this._getVertex(dest) : null; @@ -586,17 +587,17 @@ export abstract class AbstractGraph implements IGraph }>({comparator: (a, b) => a.id - b.id}); + const heap = new PriorityQueue<{ id: number, val: V }>({comparator: (a, b) => a.id - b.id}); heap.add({id: 0, val: srcVertex}); distMap.set(srcVertex, 0); preMap.set(srcVertex, null); - const getPaths = (minV: AbstractVertex | null) => { + const getPaths = (minV: V | null) => { for (const vertex of vertices) { const vertexOrId = vertex[1]; if (vertexOrId instanceof AbstractVertex) { - const path: AbstractVertex[] = [vertexOrId]; + const path: V[] = [vertexOrId]; let parent = preMap.get(vertexOrId); while (parent) { path.push(parent); @@ -678,7 +679,7 @@ export abstract class AbstractGraph implements IGraph): [AbstractVertex, AbstractVertex] | null; + abstract getEndsOfEdge(edge: E): [V, V] | null; /** * BellmanFord time:O(VE) space:O(V) @@ -696,16 +697,16 @@ export abstract class AbstractGraph implements IGraph | VertexId, scanNegativeCycle?: boolean, getMin?: boolean, genPath?: boolean) { + bellmanFord(src: V | VertexId, scanNegativeCycle?: boolean, getMin?: boolean, genPath?: boolean) { if (getMin === undefined) getMin = false; if (genPath === undefined) genPath = false; const srcVertex = this._getVertex(src); - const paths: AbstractVertex[][] = []; - const distMap: Map, number> = new Map(); - const preMap: Map, AbstractVertex> = new Map(); // predecessor + const paths: V[][] = []; + const distMap: Map = new Map(); + const preMap: Map = new Map(); // predecessor let min = Infinity; - let minPath: AbstractVertex[] = []; + let minPath: V[] = []; // TODO let hasNegativeCycle: boolean | undefined; if (scanNegativeCycle) hasNegativeCycle = false; @@ -740,7 +741,7 @@ export abstract class AbstractGraph implements IGraph | null = null; + let minDest: V | null = null; if (getMin) { distMap.forEach((d, v) => { if (v !== srcVertex) { @@ -756,7 +757,7 @@ export abstract class AbstractGraph implements IGraph[] = [vertexOrId]; + const path: V[] = [vertexOrId]; let parent = preMap.get(vertexOrId); while (parent !== undefined) { path.push(parent); @@ -802,12 +803,12 @@ export abstract class AbstractGraph implements IGraph | null)[][] } { + floyd(): { costs: number[][], predecessor: (V | null)[][] } { const idAndVertices = [...this._vertices]; const n = idAndVertices.length; const costs: number[][] = []; - const predecessor: (AbstractVertex | null)[][] = []; + const predecessor: (V | null)[][] = []; // successors for (let i = 0; i < n; i++) { @@ -876,8 +877,8 @@ export abstract class AbstractGraph implements IGraph, number> = new Map(); - const lowMap: Map, number> = new Map(); + const dfnMap: Map = new Map(); + const lowMap: Map = new Map(); const vertices = this._vertices; vertices.forEach(v => { dfnMap.set(v, -1); @@ -886,10 +887,10 @@ export abstract class AbstractGraph implements IGraph[] = []; - const bridges: AbstractEdge[] = []; + const articulationPoints: V[] = []; + const bridges: E[] = []; let dfn = 0; - const dfs = (cur: AbstractVertex, parent: AbstractVertex | null) => { + const dfs = (cur: V, parent: V | null) => { dfn++; dfnMap.set(cur, dfn); lowMap.set(cur, dfn); @@ -933,10 +934,10 @@ export abstract class AbstractGraph implements IGraph[]> = new Map(); + let SCCs: Map = new Map(); const getSCCs = () => { - const SCCs: Map[]> = new Map(); + const SCCs: Map = new Map(); lowMap.forEach((low, vertex) => { if (!SCCs.has(low)) { SCCs.set(low, [vertex]); @@ -951,9 +952,9 @@ export abstract class AbstractGraph implements IGraph[]> = new Map(); + const cycles: Map = new Map(); if (needCycles) { - let SCCs: Map[]> = new Map(); + let SCCs: Map = new Map(); if (SCCs.size < 1) { SCCs = getSCCs(); } @@ -971,7 +972,7 @@ export abstract class AbstractGraph implements IGraph>) { + protected _setVertices(value: Map) { this._vertices = value; } diff --git a/src/data-structures/graph/directed-graph.ts b/src/data-structures/graph/directed-graph.ts index 29e651a..6379e4c 100644 --- a/src/data-structures/graph/directed-graph.ts +++ b/src/data-structures/graph/directed-graph.ts @@ -10,24 +10,24 @@ import {AbstractEdge, AbstractGraph, AbstractVertex} from './abstract-graph'; import type {TopologicalStatus, VertexId} from '../types'; import {IDirectedGraph} from '../interfaces'; -export class DirectedVertex extends AbstractVertex { +export class DirectedVertex extends AbstractVertex { /** * The constructor function initializes a vertex with an optional value. * @param {VertexId} id - The `id` parameter is the identifier for the vertex. It is of type `VertexId`, which is * typically a unique identifier for each vertex in a graph. - * @param {V} [val] - The "val" parameter is an optional parameter of type V. It is used to specify the value + * @param {T} [val] - The "val" parameter is an optional parameter of type T. It is used to specify the value * associated with the vertex. */ - constructor(id: VertexId, val?: V) { + constructor(id: VertexId, val?: T) { super(id, val); } - // _createVertex(id: VertexId, val?: V): DirectedVertex { - // return new DirectedVertex(id, val); + // _createVertex(id: VertexId, val?: T): DirectedVertex { + // return new DirectedVertex(id, val); // } } -export class DirectedEdge extends AbstractEdge { +export class DirectedEdge extends AbstractEdge { /** * The constructor function initializes the source and destination vertices of an edge, along with an optional weight @@ -38,10 +38,10 @@ export class DirectedEdge extends AbstractEdge { * @param {number} [weight] - The `weight` parameter is an optional number that represents the weight of the edge. It * is used to assign a numerical value to the edge, which can be used in algorithms such as shortest path algorithms. * If the weight is not provided, it will default to `undefined`. - * @param {E} [val] - The "val" parameter is an optional parameter of type E. It represents the value associated with + * @param {T} [val] - The "val" parameter is an optional parameter of type T. It represents the value associated with * the edge. */ - constructor(src: VertexId, dest: VertexId, weight?: number, val?: E) { + constructor(src: VertexId, dest: VertexId, weight?: number, val?: T) { super(weight, val); this._src = src; this._dest = dest; @@ -67,28 +67,32 @@ export class DirectedEdge extends AbstractEdge { this._dest = v; } - // _createEdge(src: VertexId, dest: VertexId, weight?: number, val?: E): DirectedEdge { + // _createEdge(src: VertexId, dest: VertexId, weight?: number, val?: T): DirectedEdge { // if (weight === undefined || weight === null) weight = 1; // return new DirectedEdge(src, dest, weight, val); // } } // Strongly connected, One direction connected, Weakly connected -export class DirectedGraph extends AbstractGraph implements IDirectedGraph { +export class DirectedGraph, E extends DirectedEdge> extends AbstractGraph implements IDirectedGraph { + private readonly _vertexConstructor: new (id: VertexId, val?: V['val']) => V; + private readonly _edgeConstructor: new (src: VertexId, dest: VertexId, weight?: number, val?: E['val']) => E; - constructor() { + constructor(vertexConstructor: new (id: VertexId, val?: V['val']) => V, edgeConstructor: new (src: VertexId, dest: VertexId, weight?: number, val?: E['val']) => E) { super(); + this._vertexConstructor = vertexConstructor; + this._edgeConstructor = edgeConstructor; } - private _outEdgeMap: Map, DirectedEdge[]> = new Map, DirectedEdge[]>(); + private _outEdgeMap: Map = new Map(); - get outEdgeMap(): Map, DirectedEdge[]> { + get outEdgeMap(): Map { return this._outEdgeMap; } - private _inEdgeMap: Map, DirectedEdge[]> = new Map, DirectedEdge[]>(); + private _inEdgeMap: Map = new Map(); - get inEdgeMap(): Map, DirectedEdge[]> { + get inEdgeMap(): Map { return this._inEdgeMap; } @@ -98,8 +102,8 @@ export class DirectedGraph extends AbstractGraph i * @param id * @param val */ - _createVertex(id: VertexId, val?: V): DirectedVertex { - return new DirectedVertex(id, val); + _createVertex(id: VertexId, val?: V['val']): V { + return new this._vertexConstructor(id, val); } /** @@ -110,25 +114,25 @@ export class DirectedGraph extends AbstractGraph i * @param weight * @param val */ - _createEdge(src: VertexId, dest: VertexId, weight?: number, val?: E): DirectedEdge { + _createEdge(src: VertexId, dest: VertexId, weight?: number, val?: E['val']): E { if (weight === undefined || weight === null) weight = 1; - return new DirectedEdge(src, dest, weight, val); + return new this._edgeConstructor(src, dest, weight, val); } /** * The function `getEdge` returns the directed edge between two vertices, given their source and destination. - * @param {DirectedVertex | null | VertexId} srcOrId - The source vertex or its ID. It can be either a + * @param {V | null | VertexId} srcOrId - The source vertex or its ID. It can be either a * DirectedVertex object or a VertexId. - * @param {DirectedVertex | null | VertexId} destOrId - The `destOrId` parameter is the destination vertex or its + * @param {V | null | VertexId} destOrId - The `destOrId` parameter is the destination vertex or its * ID. It can be either a `DirectedVertex` object or a `VertexId` value. - * @returns a DirectedEdge object or null. + * @returns a E object or null. */ - getEdge(srcOrId: DirectedVertex | null | VertexId, destOrId: DirectedVertex | null | VertexId): DirectedEdge | null { - let edges: DirectedEdge[] = []; + getEdge(srcOrId: V | null | VertexId, destOrId: V | null | VertexId): E | null { + let edges: E[] = []; if (srcOrId !== null && destOrId !== null) { - const src: DirectedVertex | null = this._getVertex(srcOrId); - const dest: DirectedVertex | null = this._getVertex(destOrId); + const src: V | null = this._getVertex(srcOrId); + const dest: V | null = this._getVertex(destOrId); if (src && dest) { const srcOutEdges = this._outEdgeMap.get(src); @@ -143,12 +147,12 @@ export class DirectedGraph extends AbstractGraph i /** * The `addEdge` function adds a directed edge to a graph if the source and destination vertices exist. - * @param edge - The parameter `edge` is of type `DirectedEdge`, which represents a directed edge in a graph. It + * @param edge - The parameter `edge` is of type `E`, which represents a directed edge in a graph. It * contains two properties: * @returns The method `addEdge` returns a boolean value. It returns `true` if the edge was successfully added to the * graph, and `false` if either the source or destination vertex of the edge is not present in the graph. */ - addEdge(edge: DirectedEdge): boolean { + addEdge(edge: E): boolean { if (!(this.hasVertex(edge.src) && this.hasVertex(edge.dest))) { return false; } @@ -180,18 +184,18 @@ export class DirectedGraph extends AbstractGraph i /** * The `removeEdgeBetween` function removes an edge between two vertices in a directed graph and returns the removed * edge, or null if the edge was not found. - * @param {DirectedVertex | VertexId} srcOrId - The `srcOrId` parameter represents either a `DirectedVertex` + * @param {V | VertexId} srcOrId - The `srcOrId` parameter represents either a `V` * object or a `VertexId` value. It is used to specify the source vertex of the edge that you want to remove. - * @param {DirectedVertex | VertexId} destOrId - The `destOrId` parameter represents the destination vertex of the - * edge that you want to remove. It can be either a `DirectedVertex` object or a `VertexId` value. - * @returns The function `removeEdgeBetween` returns the removed edge (`DirectedEdge`) if it exists, or `null` if + * @param {V | VertexId} destOrId - The `destOrId` parameter represents the destination vertex of the + * edge that you want to remove. It can be either a `V` object or a `VertexId` value. + * @returns The function `removeEdgeBetween` returns the removed edge (`E`) if it exists, or `null` if * the edge does not exist. */ - removeEdgeBetween(srcOrId: DirectedVertex | VertexId, destOrId: DirectedVertex | VertexId): DirectedEdge | null { + removeEdgeBetween(srcOrId: V | VertexId, destOrId: V | VertexId): E | null { - const src: DirectedVertex | null = this._getVertex(srcOrId); - const dest: DirectedVertex | null = this._getVertex(destOrId); - let removed: DirectedEdge | null = null; + const src: V | null = this._getVertex(srcOrId); + const dest: V | null = this._getVertex(destOrId); + let removed: E | null = null; if (!src || !dest) { return null; } @@ -201,17 +205,17 @@ export class DirectedGraph extends AbstractGraph i /** * The removeEdge function removes an edge from a graph and returns the removed edge, or null if the edge was not * found. - * @param {DirectedEdge} edge - The `edge` parameter represents the edge that you want to remove from the graph. It should be an + * @param {E} edge - The `edge` parameter represents the edge that you want to remove from the graph. It should be an * object that has `src` and `dest` properties, which represent the source and destination vertices of the edge, * respectively. - * @returns The method `removeEdge` returns the removed edge (`DirectedEdge`) if it exists, or `null` if the edge does not exist. + * @returns The method `removeEdge` returns the removed edge (`E`) if it exists, or `null` if the edge does not exist. */ - arrayRemove>(srcOutEdges, (edge: DirectedEdge) => edge.dest === dest.id); + arrayRemove(srcOutEdges, (edge: E) => edge.dest === dest.id); } const destInEdges = this._inEdgeMap.get(dest); if (destInEdges) { - removed = arrayRemove>(destInEdges, (edge: DirectedEdge) => edge.src === src.id)[0] || null; + removed = arrayRemove(destInEdges, (edge: E) => edge.src === src.id)[0] || null; } return removed; } @@ -219,24 +223,24 @@ export class DirectedGraph extends AbstractGraph i /** * The `removeEdge` function removes a directed edge from a graph and returns the removed edge, or null if the edge was * not found. - * @param edge - The `edge` parameter is an object of type `DirectedEdge`, which represents a directed edge in a + * @param edge - The `edge` parameter is an object of type `E`, which represents a directed edge in a * graph. It has two properties: - * @returns The function `removeEdge` returns a `DirectedEdge` object if an edge is successfully removed, or `null` + * @returns The function `removeEdge` returns a `E` object if an edge is successfully removed, or `null` * if no edge is removed. */ - removeEdge(edge: DirectedEdge): DirectedEdge | null { - let removed: DirectedEdge | null = null; + removeEdge(edge: E): E | null { + let removed: E | null = null; const src = this._getVertex(edge.src); const dest = this._getVertex(edge.dest); if (src && dest) { const srcOutEdges = this._outEdgeMap.get(src); if (srcOutEdges && srcOutEdges.length > 0) { - arrayRemove(srcOutEdges, (edge: DirectedEdge) => edge.src === src.id); + arrayRemove(srcOutEdges, (edge: E) => edge.src === src.id); } const destInEdges = this._inEdgeMap.get(dest); if (destInEdges && destInEdges.length > 0) { - removed = arrayRemove(destInEdges, (edge: DirectedEdge) => edge.dest === dest.id)[0]; + removed = arrayRemove(destInEdges, (edge: E) => edge.dest === dest.id)[0]; } } @@ -246,22 +250,22 @@ export class DirectedGraph extends AbstractGraph i /** * The function removeAllEdges removes all edges between two vertices. - * @param {VertexId | DirectedVertex} src - The `src` parameter can be either a `VertexId` or a `DirectedVertex`. - * @param {VertexId | DirectedVertex} dest - The `dest` parameter represents the destination vertex of an edge. It - * can be either a `VertexId` or a `DirectedVertex`. + * @param {VertexId | V} src - The `src` parameter can be either a `VertexId` or a `V`. + * @param {VertexId | V} dest - The `dest` parameter represents the destination vertex of an edge. It + * can be either a `VertexId` or a `V`. * @returns An empty array of DirectedEdge objects is being returned. */ - removeAllEdges(src: VertexId | DirectedVertex, dest: VertexId | DirectedVertex): DirectedEdge[] { + removeAllEdges(src: VertexId | V, dest: VertexId | V): E[] { return []; } /** * The function returns an array of incoming edges of a given vertex or vertex ID. - * @param {DirectedVertex | VertexId} vertexOrId - The parameter `vertexOrId` can be either a `DirectedVertex` + * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a `V` * object or a `VertexId`. - * @returns The method `incomingEdgesOf` returns an array of `DirectedEdge` objects. + * @returns The method `incomingEdgesOf` returns an array of `E` objects. */ - incomingEdgesOf(vertexOrId: DirectedVertex | VertexId): DirectedEdge[] { + incomingEdgesOf(vertexOrId: V | VertexId): E[] { const target = this._getVertex(vertexOrId); if (target) { return this.inEdgeMap.get(target) || [] @@ -271,11 +275,11 @@ export class DirectedGraph extends AbstractGraph i /** * The function `outgoingEdgesOf` returns an array of outgoing directed edges from a given vertex or vertex ID. - * @param {DirectedVertex | VertexId} vertexOrId - The parameter `vertexOrId` can be either a `DirectedVertex` + * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a `V` * object or a `VertexId`. - * @returns The method `outgoingEdgesOf` returns an array of `DirectedEdge` objects. + * @returns The method `outgoingEdgesOf` returns an array of `E` objects. */ - outgoingEdgesOf(vertexOrId: DirectedVertex | VertexId): DirectedEdge[] { + outgoingEdgesOf(vertexOrId: V | VertexId): E[] { const target = this._getVertex(vertexOrId); if (target) { return this._outEdgeMap.get(target) || []; @@ -286,41 +290,41 @@ export class DirectedGraph extends AbstractGraph i /** * The function "degreeOf" returns the total degree of a vertex in a directed graph, which is the sum of its out-degree * and in-degree. - * @param {VertexId | DirectedVertex} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a - * `DirectedVertex`. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a + * `V`. * @returns The sum of the out-degree and in-degree of the given vertex or vertex ID. */ - degreeOf(vertexOrId: VertexId | DirectedVertex): number { + degreeOf(vertexOrId: VertexId | V): number { return this.outDegreeOf(vertexOrId) + this.inDegreeOf(vertexOrId); } /** * The function "inDegreeOf" returns the number of incoming edges for a given vertex or vertex ID in a directed graph. - * @param {VertexId | DirectedVertex} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a - * `DirectedVertex`. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a + * `V`. * @returns The number of incoming edges of the specified vertex or vertex ID. */ - inDegreeOf(vertexOrId: VertexId | DirectedVertex): number { + inDegreeOf(vertexOrId: VertexId | V): number { return this.incomingEdgesOf(vertexOrId).length; } /** * The function "outDegreeOf" returns the number of outgoing edges from a given vertex. - * @param {VertexId | DirectedVertex} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a - * `DirectedVertex`. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a + * `V`. * @returns The number of outgoing edges from the specified vertex or vertex ID. */ - outDegreeOf(vertexOrId: VertexId | DirectedVertex): number { + outDegreeOf(vertexOrId: VertexId | V): number { return this.outgoingEdgesOf(vertexOrId).length; } /** * The function "edgesOf" returns an array of both outgoing and incoming directed edges of a given vertex or vertex ID. - * @param {VertexId | DirectedVertex} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a - * `DirectedVertex`. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a + * `V`. * @returns an array of directed edges. */ - edgesOf(vertexOrId: VertexId | DirectedVertex): DirectedEdge[] { + edgesOf(vertexOrId: VertexId | V): E[] { return [...this.outgoingEdgesOf(vertexOrId), ...this.incomingEdgesOf(vertexOrId)]; } @@ -329,31 +333,31 @@ export class DirectedGraph extends AbstractGraph i * @param e - A directed edge object of type E. * @returns either a DirectedVertex object or null. */ - getEdgeSrc(e: DirectedEdge): DirectedVertex | null { + getEdgeSrc(e: E): V | null { return this._getVertex(e.src); } /** * The function "getEdgeDest" returns the destination vertex of a directed edge. - * @param e - DirectedEdge - This is an object representing a directed edge in a graph. It contains information + * @param e - E - This is an object representing a directed edge in a graph. It contains information * about the source vertex, destination vertex, and any associated data. * @returns either a DirectedVertex object or null. */ - getEdgeDest(e: DirectedEdge): DirectedVertex | null { + getEdgeDest(e: E): V | null { return this._getVertex(e.dest); } /** * The function `getDestinations` returns an array of directed vertices that are the destinations of outgoing edges * from a given vertex. - * @param {DirectedVertex | VertexId | null} vertex - The `vertex` parameter can be one of the following: + * @param {V | VertexId | null} vertex - The `vertex` parameter can be one of the following: * @returns an array of DirectedVertex objects. */ - getDestinations(vertex: DirectedVertex | VertexId | null): DirectedVertex[] { + getDestinations(vertex: V | VertexId | null): V[] { if (vertex === null) { return []; } - const destinations: DirectedVertex[] = []; + const destinations: V[] = []; const outgoingEdges = this.outgoingEdgesOf(vertex); for (const outEdge of outgoingEdges) { const child = this.getEdgeDest(outEdge); @@ -367,20 +371,20 @@ export class DirectedGraph extends AbstractGraph i /** * The `topologicalSort` function performs a topological sort on a directed graph and returns the sorted vertices in * reverse order, or null if the graph contains a cycle. - * @returns The function `topologicalSort()` returns an array of `DirectedVertex` or `VertexId` objects in + * @returns The function `topologicalSort()` returns an array of `V` or `VertexId` objects in * topological order, or `null` if there is a cycle in the graph. */ - topologicalSort(): Array | VertexId> | null { + topologicalSort(): Array | null { // When judging whether there is a cycle in the undirected graph, all nodes with degree of **<= 1** are enqueued // When judging whether there is a cycle in the directed graph, all nodes with **in degree = 0** are enqueued - const statusMap: Map | VertexId, TopologicalStatus> = new Map | VertexId, TopologicalStatus>(); + const statusMap: Map = new Map(); for (const entry of this.vertices) { statusMap.set(entry[1], 0); } - const sorted: (DirectedVertex | VertexId)[] = []; + const sorted: (V | VertexId)[] = []; let hasCycle = false; - const dfs = (cur: DirectedVertex | VertexId) => { + const dfs = (cur: V | VertexId) => { statusMap.set(cur, 1); const children = this.getDestinations(cur); for (const child of children) { @@ -408,10 +412,10 @@ export class DirectedGraph extends AbstractGraph i /** * The `edgeSet` function returns an array of all directed edges in the graph. - * @returns The `edgeSet()` method returns an array of `DirectedEdge` objects. + * @returns The `edgeSet()` method returns an array of `E` objects. */ - edgeSet(): DirectedEdge[] { - let edges: DirectedEdge[] = []; + edgeSet(): E[] { + let edges: E[] = []; this._outEdgeMap.forEach(outEdges => { edges = [...edges, ...outEdges]; }); @@ -422,12 +426,12 @@ export class DirectedGraph extends AbstractGraph i /** * The function `getNeighbors` returns an array of neighboring vertices of a given vertex in a directed graph. - * @param {DirectedVertex | VertexId} vertexOrId - The parameter `vertexOrId` can be either a `DirectedVertex` + * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a `V` * object or a `VertexId`. * @returns an array of DirectedVertex objects. */ - getNeighbors(vertexOrId: DirectedVertex | VertexId): DirectedVertex[] { - const neighbors: DirectedVertex[] = []; + getNeighbors(vertexOrId: V | VertexId): V[] { + const neighbors: V[] = []; const vertex = this._getVertex(vertexOrId); if (vertex) { const outEdges = this.outgoingEdgesOf(vertex); @@ -451,7 +455,7 @@ export class DirectedGraph extends AbstractGraph i * @returns an array containing the starting and ending vertices of the given directed edge, or null if the edge does * not exist in the graph. */ - getEndsOfEdge(edge: DirectedEdge): [DirectedVertex, DirectedVertex] | null { + getEndsOfEdge(edge: E): [V, V] | null { if (!this.hasEdge(edge.src, edge.dest)) { return null; } @@ -464,11 +468,11 @@ export class DirectedGraph extends AbstractGraph i } } - protected _setOutEdgeMap(value: Map, DirectedEdge[]>) { + protected _setOutEdgeMap(value: Map) { this._outEdgeMap = value; } - protected _setInEdgeMap(value: Map, DirectedEdge[]>) { + protected _setInEdgeMap(value: Map) { this._inEdgeMap = value; } } diff --git a/src/data-structures/graph/undirected-graph.ts b/src/data-structures/graph/undirected-graph.ts index f02e151..2d448da 100644 --- a/src/data-structures/graph/undirected-graph.ts +++ b/src/data-structures/graph/undirected-graph.ts @@ -9,24 +9,24 @@ import {arrayRemove} from '../../utils'; import {AbstractEdge, AbstractGraph, AbstractVertex} from './abstract-graph'; import type {VertexId} from '../types'; -export class UndirectedVertex extends AbstractVertex { +export class UndirectedVertex extends AbstractVertex { /** * The constructor function initializes a vertex with an optional value. * @param {VertexId} id - The `id` parameter is the identifier for the vertex. It is of type `VertexId`, which is * typically a unique identifier for each vertex in a graph. - * @param {V} [val] - The "val" parameter is an optional parameter of type V. It is used to initialize the value of the + * @param {T} [val] - The "val" parameter is an optional parameter of type T. It is used to initialize the value of the * vertex. If no value is provided, the vertex will be initialized with a default value. */ - constructor(id: VertexId, val?: V) { + constructor(id: VertexId, val?: T) { super(id, val); } - // _createVertex(id: VertexId, val?: V): UndirectedVertex { - // return new UndirectedVertex(id, val); + // _createVertex(id: VertexId, val?: T): T { + // return new T(id, val); // } } -export class UndirectedEdge extends AbstractEdge { +export class UndirectedEdge extends AbstractEdge { /** * The constructor function initializes an instance of a class with two vertex IDs, an optional weight, and an optional * value. @@ -34,10 +34,10 @@ export class UndirectedEdge extends AbstractEdge { * @param {VertexId} v2 - The parameter `v2` is a `VertexId`, which represents the identifier of the second vertex in a * graph edge. * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge. - * @param {E} [val] - The "val" parameter is an optional parameter of type E. It represents the value associated with + * @param {T} [val] - The "val" parameter is an optional parameter of type T. It represents the value associated with * the edge. */ - constructor(v1: VertexId, v2: VertexId, weight?: number, val?: E) { + constructor(v1: VertexId, v2: VertexId, weight?: number, val?: T) { super(weight, val); this._vertices = [v1, v2]; } @@ -52,24 +52,27 @@ export class UndirectedEdge extends AbstractEdge { this._vertices = v; } - // _createEdge(src: VertexId, dest: VertexId, weight?: number, val?: E): UndirectedEdge { + // _createEdge(src: VertexId, dest: VertexId, weight?: number, val?: T): T { // if (weight === undefined || weight === null) weight = 1; // return new UndirectedEdge(src, dest, weight, val); // } } -export class UndirectedGraph extends AbstractGraph { - /** - * The constructor initializes a new instance of the class with an empty map of edges. - */ - constructor() { +export class UndirectedGraph, E extends UndirectedEdge> extends AbstractGraph { + + private readonly _vertexConstructor: new (id: VertexId, val?: V['val']) => V; + private readonly _edgeConstructor: new (src: VertexId, dest: VertexId, weight?: number, val?: E['val']) => E; + + constructor(vertexConstructor: new (id: VertexId, val?: V['val']) => V, edgeConstructor: new (src: VertexId, dest: VertexId, weight?: number, val?: E['val']) => E) { super(); - this._edges = new Map, UndirectedEdge[]>(); + this._vertexConstructor = vertexConstructor; + this._edgeConstructor = edgeConstructor; + this._edges = new Map(); } - protected _edges: Map, UndirectedEdge[]>; + protected _edges: Map; - get edges(): Map, UndirectedEdge[]> { + get edges(): Map { return this._edges; } @@ -79,8 +82,8 @@ export class UndirectedGraph { - return new UndirectedVertex(id, val); + _createVertex(id: VertexId, val?: V['val']): V { + return new this._vertexConstructor(id, val); } /** @@ -91,26 +94,26 @@ export class UndirectedGraph { + _createEdge(src: VertexId, dest: VertexId, weight?: number, val?: E['val']): E { if (weight === undefined || weight === null) weight = 1; - return new UndirectedEdge(src, dest, weight, val); + return new this._edgeConstructor(src, dest, weight, val); } /** * The function `getEdge` returns the first undirected edge that connects two given vertices, or null if no such edge * exists. - * @param {UndirectedVertex | null | VertexId} v1 - The parameter `v1` represents either an `UndirectedVertex` + * @param {V | null | VertexId} v1 - The parameter `v1` represents either an `V` * object, `null`, or a `VertexId`. It is used to specify one of the vertices of the edge. - * @param {UndirectedVertex | null | VertexId} v2 - The parameter `v2` represents either an `UndirectedVertex` + * @param {V | null | VertexId} v2 - The parameter `v2` represents either an `UndirectedVertex` * object or a `VertexId` (identifier) of an undirected vertex. - * @returns an instance of `UndirectedEdge` or `null`. + * @returns an instance of `E` or `null`. */ - getEdge(v1: UndirectedVertex | null | VertexId, v2: UndirectedVertex | null | VertexId): UndirectedEdge | null { - let edges: UndirectedEdge[] | undefined = []; + getEdge(v1: V | null | VertexId, v2: V | null | VertexId): E | null { + let edges: E[] | undefined = []; if (v1 !== null && v2 !== null) { - const vertex1: UndirectedVertex | null = this._getVertex(v1); - const vertex2: UndirectedVertex | null = this._getVertex(v2); + const vertex1: V | null = this._getVertex(v1); + const vertex2: V | null = this._getVertex(v2); if (vertex1 && vertex2) { edges = this._edges.get(vertex1)?.filter(e => e.vertices.includes(vertex2.id)); @@ -126,7 +129,7 @@ export class UndirectedGraph): boolean { + addEdge(edge: E): boolean { for (const end of edge.vertices) { const endVertex = this._getVertex(end); if (endVertex === null) return false; @@ -144,32 +147,32 @@ export class UndirectedGraph | VertexId} v1 - The parameter `v1` represents either an `UndirectedVertex` object or + * @param {V | VertexId} v1 - The parameter `v1` represents either an `V` object or * a `VertexId`. It is used to specify one of the vertices of the edge that needs to be removed. - * @param {UndirectedVertex | VertexId} v2 - The parameter `v2` represents either an instance of the + * @param {V | VertexId} v2 - The parameter `v2` represents either an instance of the * `UndirectedVertex` class or a `VertexId`. It is used to identify the second vertex of the edge that needs to be * removed. - * @returns The function `removeEdgeBetween` returns an `UndirectedEdge` object if an edge is successfully removed + * @returns The function `removeEdgeBetween` returns an `E` object if an edge is successfully removed * between the two vertices `v1` and `v2`. If either `v1` or `v2` is not found in the graph, or if there is no edge * between them, the function returns `null`. */ - removeEdgeBetween(v1: UndirectedVertex | VertexId, v2: UndirectedVertex | VertexId): UndirectedEdge | null { + removeEdgeBetween(v1: V | VertexId, v2: V | VertexId): E | null { - const vertex1: UndirectedVertex | null = this._getVertex(v1); - const vertex2: UndirectedVertex | null = this._getVertex(v2); + const vertex1: V | null = this._getVertex(v1); + const vertex2: V | null = this._getVertex(v2); if (!vertex1 || !vertex2) { return null; } const v1Edges = this._edges.get(vertex1); - let removed: UndirectedEdge | null = null; + let removed: E | null = null; if (v1Edges) { - removed = arrayRemove>(v1Edges, (e: UndirectedEdge) => e.vertices.includes(vertex2.id))[0] || null; + removed = arrayRemove(v1Edges, (e: E) => e.vertices.includes(vertex2.id))[0] || null; } const v2Edges = this._edges.get(vertex2); if (v2Edges) { - arrayRemove>(v2Edges, (e: UndirectedEdge) => e.vertices.includes(vertex1.id)); + arrayRemove(v2Edges, (e: E) => e.vertices.includes(vertex1.id)); } return removed; } @@ -180,17 +183,17 @@ export class UndirectedGraph): UndirectedEdge | null { + removeEdge(edge: E): E | null { return this.removeEdgeBetween(edge.vertices[0], edge.vertices[1]); } /** * The function "degreeOf" returns the degree of a given vertex in an undirected graph. - * @param {VertexId | UndirectedVertex} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or an - * `UndirectedVertex`. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or an + * `V`. * @returns the degree of the vertex. */ - degreeOf(vertexOrId: VertexId | UndirectedVertex): number { + degreeOf(vertexOrId: VertexId | V): number { const vertex = this._getVertex(vertexOrId); if (vertex) { return this._edges.get(vertex)?.length || 0; @@ -201,11 +204,11 @@ export class UndirectedGraph} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or an - * `UndirectedVertex`. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or an + * `V`. * @returns an array of UndirectedEdge objects. */ - edgesOf(vertexOrId: VertexId | UndirectedVertex): UndirectedEdge[] { + edgesOf(vertexOrId: VertexId | V): E[] { const vertex = this._getVertex(vertexOrId); if (vertex) { return this._edges.get(vertex) || []; @@ -216,10 +219,10 @@ export class UndirectedGraph` objects. + * @returns The method `edgeSet()` returns an array of `E` objects. */ - edgeSet(): UndirectedEdge[] { - const edgeSet: Set> = new Set(); + edgeSet(): E[] { + const edgeSet: Set = new Set(); this._edges.forEach(edges => { edges.forEach(edge => { edgeSet.add(edge); @@ -230,11 +233,11 @@ export class UndirectedGraph | VertexId} vertexOrId - The parameter `vertexOrId` can be either an - * `UndirectedVertex` object or a `VertexId`. - * @returns The function `getEdgesOf` returns an array of `UndirectedEdge` objects. + * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either an + * `V` object or a `VertexId`. + * @returns The function `getEdgesOf` returns an array of `E` objects. */ - getEdgesOf(vertexOrId: UndirectedVertex | VertexId): UndirectedEdge[] { + getEdgesOf(vertexOrId: V | VertexId): E[] { const vertex = this._getVertex(vertexOrId); if (!vertex) { return []; @@ -244,12 +247,12 @@ export class UndirectedGraph | VertexId} vertexOrId - The `vertexOrId` parameter can be either an - * `UndirectedVertex` object or a `VertexId`. It represents the vertex for which we want to find the neighbors. + * @param {V | VertexId} vertexOrId - The `vertexOrId` parameter can be either an + * `V` object or a `VertexId`. It represents the vertex for which we want to find the neighbors. * @returns an array of UndirectedVertex objects. */ - getNeighbors(vertexOrId: UndirectedVertex | VertexId): UndirectedVertex[] { - const neighbors: UndirectedVertex[] = []; + getNeighbors(vertexOrId: V | VertexId): V[] { + const neighbors: V[] = []; const vertex = this._getVertex(vertexOrId); if (vertex) { const neighborEdges = this.getEdgesOf(vertex); @@ -271,7 +274,7 @@ export class UndirectedGraph): [UndirectedVertex, UndirectedVertex] | null { + getEndsOfEdge(edge: E): [V, V] | null { if (!this.hasEdge(edge.vertices[0], edge.vertices[1])) { return null; } @@ -284,7 +287,7 @@ export class UndirectedGraph, UndirectedEdge[]>) { + protected _setEdges(v: Map) { this._edges = v; } } diff --git a/src/data-structures/interfaces/abstract-graph.ts b/src/data-structures/interfaces/abstract-graph.ts index 029b8b9..644a840 100644 --- a/src/data-structures/interfaces/abstract-graph.ts +++ b/src/data-structures/interfaces/abstract-graph.ts @@ -1,43 +1,42 @@ import {VertexId} from '../types'; -import {AbstractEdge, AbstractVertex} from '../graph'; export interface IGraph { - hasVertex(vertexOrId: AbstractVertex | VertexId): boolean; + hasVertex(vertexOrId: V | VertexId): boolean; - _getVertex(vertexOrId: VertexId | AbstractVertex): AbstractVertex | null; + _getVertex(vertexOrId: VertexId | V): V | null; - _getVertexId(vertexOrId: AbstractVertex | VertexId): VertexId; + _getVertexId(vertexOrId: V | VertexId): VertexId; createAddVertex(id: VertexId, val?: V): boolean; - addVertex(newVertex: AbstractVertex): boolean; + addVertex(newVertex: V): boolean; - removeVertex(vertexOrId: AbstractVertex | VertexId): boolean; + removeVertex(vertexOrId: V | VertexId): boolean; - removeAllVertices(vertices: AbstractVertex[] | VertexId[]): boolean; + removeAllVertices(vertices: V[] | VertexId[]): boolean; - degreeOf(vertexOrId: AbstractVertex | VertexId): number; + degreeOf(vertexOrId: V | VertexId): number; - edgesOf(vertexOrId: AbstractVertex | VertexId): AbstractEdge[]; + edgesOf(vertexOrId: V | VertexId): E[]; - hasEdge(src: AbstractVertex | VertexId, dest: AbstractVertex | VertexId): boolean; + hasEdge(src: V | VertexId, dest: V | VertexId): boolean; - getEdge(srcOrId: AbstractVertex | VertexId, destOrId: AbstractVertex | VertexId): AbstractEdge | null; + getEdge(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; - edgeSet(): AbstractEdge[]; + edgeSet(): E[]; - createAddEdge(src: AbstractVertex | VertexId, dest: AbstractVertex | VertexId, weight: number, val: E): boolean; + createAddEdge(src: V | VertexId, dest: V | VertexId, weight: number, val: E): boolean; - addEdge(edge: AbstractEdge): boolean; + addEdge(edge: E): boolean; - removeEdgeBetween(src: AbstractVertex | VertexId, dest: AbstractVertex | VertexId): AbstractEdge | null; + removeEdgeBetween(src: V | VertexId, dest: V | VertexId): E | null; - removeEdge(edge: AbstractEdge): AbstractEdge | null; + removeEdge(edge: E): E | null; - setEdgeWeight(srcOrId: AbstractVertex | VertexId, destOrId: AbstractVertex | VertexId, weight: number): boolean; + setEdgeWeight(srcOrId: V | VertexId, destOrId: V | VertexId, weight: number): boolean; - getMinPathBetween(v1: AbstractVertex | VertexId, v2: AbstractVertex | VertexId, isWeight?: boolean): AbstractVertex[] | null; + getMinPathBetween(v1: V | VertexId, v2: V | VertexId, isWeight?: boolean): V[] | null; - getNeighbors(vertexOrId: AbstractVertex | VertexId): AbstractVertex[]; + getNeighbors(vertexOrId: V | VertexId): V[]; } \ No newline at end of file diff --git a/src/data-structures/interfaces/binary-tree.ts b/src/data-structures/interfaces/binary-tree.ts index 2471ea0..cb464c1 100644 --- a/src/data-structures/interfaces/binary-tree.ts +++ b/src/data-structures/interfaces/binary-tree.ts @@ -1,10 +1,56 @@ import {BinaryTreeNodeId} from '../types'; -import {BinaryTreeNode} from '../binary-tree'; +import {FamilyPosition} from '../binary-tree'; -export interface IBinaryTreeNode { - _createNode(id: BinaryTreeNodeId, val: T | null, count?: number): BinaryTreeNode | null +export interface IBinaryTreeNode> { + _createNode(id: BinaryTreeNodeId, val: T | null, count?: number): FAMILY | null; + + + get id(): BinaryTreeNodeId + + set id(v: BinaryTreeNodeId) + + + get val(): T + + set val(v: T) + + + get left(): FAMILY | null | undefined + + set left(v: FAMILY | null | undefined) + + + get right(): FAMILY | null | undefined + + set right(v: FAMILY | null | undefined) + + + get parent(): FAMILY | null | undefined + + set parent(v: FAMILY | null | undefined) + + + get familyPosition(): FamilyPosition + + set familyPosition(v: FamilyPosition) + + + get count(): number + + set count(v: number) + + + get height(): number + + set height(v: number) + + _createNode(id: BinaryTreeNodeId, val: T | null, count?: number): FAMILY | null + + swapLocation(swapNode: FAMILY): FAMILY + + clone(): FAMILY | null; } -export interface IBinaryTree { - _createNode(id: BinaryTreeNodeId, val: T | null, count?: number): BinaryTreeNode | null +export interface IBinaryTree> { + _createNode(id: BinaryTreeNodeId, val: N['val'] | null, count?: number): N | null } \ No newline at end of file diff --git a/src/data-structures/interfaces/directed-graph.ts b/src/data-structures/interfaces/directed-graph.ts index 3142afd..87399c6 100644 --- a/src/data-structures/interfaces/directed-graph.ts +++ b/src/data-structures/interfaces/directed-graph.ts @@ -1,16 +1,15 @@ import {VertexId} from '../types'; -import {DirectedEdge, DirectedVertex} from '../graph'; export interface IDirectedGraph { - incomingEdgesOf(vertex: DirectedVertex): DirectedEdge[]; + incomingEdgesOf(vertex: V): E[]; - outgoingEdgesOf(vertex: DirectedVertex): DirectedEdge[]; + outgoingEdgesOf(vertex: V): E[]; - inDegreeOf(vertexOrId: DirectedVertex | VertexId): number; + inDegreeOf(vertexOrId: V | VertexId): number; - outDegreeOf(vertexOrId: DirectedVertex | VertexId): number; + outDegreeOf(vertexOrId: V | VertexId): number; - getEdgeSrc(e: DirectedEdge): DirectedVertex | null; + getEdgeSrc(e: E): V | null; - getEdgeDest(e: DirectedEdge): DirectedVertex | null; + getEdgeDest(e: E): V | null; } \ No newline at end of file diff --git a/src/data-structures/types/avl-tree.ts b/src/data-structures/types/avl-tree.ts index c3511d5..9c69860 100644 --- a/src/data-structures/types/avl-tree.ts +++ b/src/data-structures/types/avl-tree.ts @@ -1,6 +1,8 @@ import {AVLTreeNode} from '../binary-tree'; -export type AVLTreeDeleted = { - deleted: AVLTreeNode | null; - needBalanced: AVLTreeNode | null; -} \ No newline at end of file +export type AVLTreeDeleted = { + deleted: N | null; + needBalanced: N | null; +} + +export type RecursiveAVLTreeNode = AVLTreeNode>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> diff --git a/src/data-structures/types/binary-tree.ts b/src/data-structures/types/binary-tree.ts index df467e4..562c5f3 100644 --- a/src/data-structures/types/binary-tree.ts +++ b/src/data-structures/types/binary-tree.ts @@ -4,6 +4,7 @@ export type BinaryTreeNodePropertyName = 'id' | 'val' | 'count'; export type NodeOrPropertyName = 'node' | BinaryTreeNodePropertyName; export type DFSOrderPattern = 'in' | 'pre' | 'post'; export type BinaryTreeNodeId = number; -export type BinaryTreeDeleted = { deleted: BinaryTreeNode | null | undefined, needBalanced: BinaryTreeNode | null }; -export type ResultByProperty = T | BinaryTreeNode | number | BinaryTreeNodeId; -export type ResultsByProperty = ResultByProperty[]; \ No newline at end of file +export type BinaryTreeDeleted = { deleted: N | null | undefined, needBalanced: N | null }; +export type ResultByProperty> = N['val'] | N | number | BinaryTreeNodeId; +export type ResultsByProperty> = ResultByProperty[]; +export type RecursiveBinaryTreeNode = BinaryTreeNode>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> diff --git a/src/data-structures/types/bst.ts b/src/data-structures/types/bst.ts index dd18b61..ac03bf8 100644 --- a/src/data-structures/types/bst.ts +++ b/src/data-structures/types/bst.ts @@ -2,4 +2,5 @@ import {BSTNode} from '../binary-tree'; import type {BinaryTreeNodeId} from './binary-tree'; export type BSTComparator = (a: BinaryTreeNodeId, b: BinaryTreeNodeId) => number; -export type BSTDeletedResult = { deleted: BSTNode | null, needBalanced: BSTNode | null }; +export type BSTDeletedResult> = { deleted: N | null, needBalanced: N | null }; +export type RecursiveBSTNode = BSTNode>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> diff --git a/src/data-structures/types/directed-graph.ts b/src/data-structures/types/directed-graph.ts index 971b88d..4f7c4b3 100644 --- a/src/data-structures/types/directed-graph.ts +++ b/src/data-structures/types/directed-graph.ts @@ -5,4 +5,4 @@ export enum TopologicalProperty { VAL = 'VAL', NODE = 'NODE', ID = 'ID', -} \ No newline at end of file +} diff --git a/src/data-structures/types/tree-multiset.ts b/src/data-structures/types/tree-multiset.ts index 74ae62a..7b78278 100644 --- a/src/data-structures/types/tree-multiset.ts +++ b/src/data-structures/types/tree-multiset.ts @@ -1,3 +1 @@ -import {BSTNode} from '../binary-tree'; - -export type TreeMultiSetDeletedResult = { deleted: BSTNode | null, needBalanced: BSTNode | null }; +export type TreeMultiSetDeletedResult = { deleted: N | null, needBalanced: N | null }; diff --git a/tests/unit/data-structures/binary-tree/avl-tree.test.ts b/tests/unit/data-structures/binary-tree/avl-tree.test.ts new file mode 100644 index 0000000..7d5aea9 --- /dev/null +++ b/tests/unit/data-structures/binary-tree/avl-tree.test.ts @@ -0,0 +1,122 @@ +import {AVLTree} from '../../../../src'; + +describe('AVL Tree Test', () => { + it('should perform various operations on a AVL Tree', () => { + + const arr = [11, 3, 15, 1, 8, 13, 16, 2, 6, 9, 12, 14, 4, 7, 10, 5]; + const tree = new AVLTree(); + + for (const i of arr) tree.add(i, i); + + const node6 = tree.get(6); + + expect(node6 && tree.getHeight(node6)).toBe(3); + expect(node6 && tree.getDepth(node6)).toBe(1); + + const getNodeById = tree.get(10, 'id'); + expect(getNodeById?.id).toBe(10); + + const getNodesByCount = tree.getNodes(1, 'count'); + expect(getNodesByCount.length).toBe(16); + + const getMinNodeByRoot = tree.getLeftMost(); + expect(getMinNodeByRoot?.id).toBe(1); + + const node15 = tree.get(15); + const getMinNodeBySpecificNode = node15 && tree.getLeftMost(node15); + expect(getMinNodeBySpecificNode?.id).toBe(12); + + const subTreeSum = node15 && tree.subTreeSum(node15); + expect(subTreeSum).toBe(70); + + const lesserSum = tree.lesserSum(10); + expect(lesserSum).toBe(45); + + if (node15) { + const subTreeAdd = tree.subTreeAdd(node15, 1, 'count'); + expect(subTreeAdd).toBe(true); + } + // node15 has type problem. After the uniform design, the generics of containers (DirectedGraph, BST) are based on the type of value. However, this design has a drawback: when I attempt to inherit from the Vertex or BSTNode classes, the types of the results obtained by all methods are those of the parent class. + expect(node15?.val).toBe(15); + + const node11 = tree.get(11); + if (node11) { + const allGreaterNodesAdd = tree.allGreaterNodesAdd(node11, 2, 'count'); + expect(allGreaterNodesAdd).toBe(true); + } + + const dfs = tree.DFS('in', 'node'); + expect(dfs[0].id).toBe(1); + expect(dfs[dfs.length - 1].id).toBe(16); + + tree.balance(); + const bfs = tree.BFS('node'); + expect(tree.isBalanced()).toBe(true); + expect(bfs[0].id).toBe(8); + expect(bfs[bfs.length - 1].id).toBe(16); + + expect(tree.remove(11, true)[0].deleted?.id).toBe(11); + expect(tree.isAVLBalanced()).toBe(true); + expect(node15 && tree.getHeight(node15)).toBe(2); + + expect(tree.remove(1, true)[0].deleted?.id).toBe(1); + expect(tree.isAVLBalanced()).toBe(true); + expect(tree.getHeight()).toBe(4); + + expect(tree.remove(4, true)[0].deleted?.id).toBe(4); + expect(tree.isAVLBalanced()).toBe(true); + expect(tree.getHeight()).toBe(4); + + expect(tree.remove(10, true)[0].deleted?.id).toBe(10); + expect(tree.isAVLBalanced()).toBe(true); + expect(tree.getHeight()).toBe(3); + + expect(tree.remove(15, true)[0].deleted?.id).toBe(15); + expect(tree.isAVLBalanced()).toBe(true); + + expect(tree.getHeight()).toBe(3); + + expect(tree.remove(5, true)[0].deleted?.id).toBe(5); + expect(tree.isAVLBalanced()).toBe(true); + expect(tree.getHeight()).toBe(3); + + expect(tree.remove(13, true)[0].deleted?.id).toBe(13); + expect(tree.isAVLBalanced()).toBe(true); + expect(tree.getHeight()).toBe(3); + + expect(tree.remove(3, true)[0].deleted?.id).toBe(3); + expect(tree.isAVLBalanced()).toBe(true); + expect(tree.getHeight()).toBe(3); + + expect(tree.remove(8, true)[0].deleted?.id).toBe(8); + expect(tree.isAVLBalanced()).toBe(true); + expect(tree.getHeight()).toBe(3); + + expect(tree.remove(6, true)[0].deleted?.id).toBe(6); + expect(tree.remove(6, true).length).toBe(0); + expect(tree.isAVLBalanced()).toBe(true); + expect(tree.getHeight()).toBe(2); + + expect(tree.remove(7, true)[0].deleted?.id).toBe(7); + expect(tree.isAVLBalanced()).toBe(true); + expect(tree.getHeight()).toBe(2); + + expect(tree.remove(9, true)[0].deleted?.id).toBe(9); + expect(tree.isAVLBalanced()).toBe(true); + expect(tree.getHeight()).toBe(2); + expect(tree.remove(14, true)[0].deleted?.id).toBe(14); + expect(tree.isAVLBalanced()).toBe(true); + expect(tree.getHeight()).toBe(1); + + expect(tree.isAVLBalanced()).toBe(true); + const lastBFSIds = tree.BFS(); + expect(lastBFSIds[0]).toBe(12); + expect(lastBFSIds[1]).toBe(2); + expect(lastBFSIds[2]).toBe(16); + + const lastBFSNodes = tree.BFS('node'); + expect(lastBFSNodes[0].id).toBe(12); + expect(lastBFSNodes[1].id).toBe(2); + expect(lastBFSNodes[2].id).toBe(16); + }); +}); diff --git a/tests/unit/data-structures/binary-tree/bst.test.ts b/tests/unit/data-structures/binary-tree/bst.test.ts index ef3a751..9cebc53 100644 --- a/tests/unit/data-structures/binary-tree/bst.test.ts +++ b/tests/unit/data-structures/binary-tree/bst.test.ts @@ -2,7 +2,6 @@ import {BST, BSTNode} from '../../../../src'; describe('BST Case6', () => { it('should perform various operations on a Binary Search Tree', () => { - const tree = new BST(); expect(tree).toBeInstanceOf(BST); diff --git a/tests/unit/data-structures/graph/directed-graph.test.ts b/tests/unit/data-structures/graph/directed-graph.test.ts index f000252..29f7e5c 100644 --- a/tests/unit/data-structures/graph/directed-graph.test.ts +++ b/tests/unit/data-structures/graph/directed-graph.test.ts @@ -1,10 +1,10 @@ import {DirectedEdge, DirectedGraph, DirectedVertex, VertexId} from '../../../../src'; describe('DirectedGraph Operation Test', () => { - let graph: DirectedGraph; + let graph: DirectedGraph; beforeEach(() => { - graph = new DirectedGraph(); + graph = new DirectedGraph(DirectedVertex, DirectedEdge); }); @@ -59,13 +59,12 @@ describe('DirectedGraph Operation Test', () => { graph.addEdge(edgeBC); const topologicalOrder = graph.topologicalSort(); - if (topologicalOrder) expect(topologicalOrder.map(v => v instanceof DirectedVertex ? v.id: '')).toEqual(['A', 'B', 'C']); + if (topologicalOrder) expect(topologicalOrder.map(v => v instanceof DirectedVertex ? v.id : '')).toEqual(['A', 'B', 'C']); }); }); class MyVertex extends DirectedVertex { - constructor(id: VertexId, val?: V) { super(id, val); this._data = val; @@ -84,7 +83,7 @@ class MyVertex extends DirectedVertex { class MyEdge extends DirectedEdge { - constructor(v1: VertexId, v2: VertexId, weight: number, val?: E) { + constructor(v1: VertexId, v2: VertexId, weight?: number, val?: E) { super(v1, v2, weight, val); this._data = val; } @@ -100,21 +99,14 @@ class MyEdge extends DirectedEdge { } } -class MyDirectedGraph extends DirectedGraph { - _createVertex(id: VertexId, val?: V): MyVertex { - return new MyVertex(id, val); - } +class MyDirectedGraph, E extends MyEdge> extends DirectedGraph { - _createEdge(src: VertexId, dest: VertexId, weight?: number, val?: E): MyEdge { - if (weight === undefined || weight === null) weight = 1; - return new MyEdge(src, dest, weight, val); - } } describe('Inherit from DirectedGraph and perform operations', () => { - let myGraph = new MyDirectedGraph(); + let myGraph = new MyDirectedGraph, MyEdge>(MyVertex, MyEdge); beforeEach(() => { - myGraph = new MyDirectedGraph(); + myGraph = new MyDirectedGraph(MyVertex, MyEdge); }); it('Add vertices', () => { @@ -150,11 +142,16 @@ describe('Inherit from DirectedGraph and perform operations', () => { const edge2 = myGraph.getEdge(myGraph.getVertex(1), myGraph.getVertex(2)); const edge3 = myGraph.getEdge(1, '100'); // edge1.data has type problem. After the uniform design, the generics of containers (DirectedGraph, BST) are based on the type of value. However, this design has a drawback: when I attempt to inherit from the Vertex or BSTNode classes, the types of the results obtained by all methods are those of the parent class. - expect(edge1?.val).toBe('val1'); expect(edge1).toBeInstanceOf(MyEdge); - edge1 && expect(edge1.src).toBe(1); - expect(edge1).toEqual(edge2); - expect(edge3).toBeNull(); + if (edge1) { + expect(edge1.data).toBe('val1'); + expect(edge1?.val).toBe('val1'); + expect(edge1).toBeInstanceOf(MyEdge); + expect(edge1.src).toBe(1); + expect(edge1).toEqual(edge2); + expect(edge3).toBeNull(); + } + }); it('Edge set and vertex set', () => { @@ -214,7 +211,7 @@ describe('Inherit from DirectedGraph and perform operations', () => { }); describe('Inherit from DirectedGraph and perform operations test2.', () => { - const myGraph = new MyDirectedGraph(); + const myGraph = new MyDirectedGraph, MyEdge>(MyVertex, MyEdge); it('should test graph operations', () => { const vertex1 = new MyVertex(1, 'data1');