├── .babelrc ├── .browserslistrc ├── .cz-config.js ├── .editorconfig ├── .env ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── config ├── config.js ├── webpack.config.base.js ├── webpack.config.dev.js └── webpack.config.prod.js ├── default.conf ├── docker-compose.yml ├── fileMock.js ├── jest.config.js ├── nginx.conf ├── package.json ├── postcss.config.js ├── src ├── core │ ├── algorithms │ │ ├── README.md │ │ ├── backtracking │ │ │ └── README.md │ │ ├── dynamic │ │ │ └── README.md │ │ ├── greedy │ │ │ └── README.md │ │ ├── recursion │ │ │ ├── 17.letter-combinations-of-a-phone-number.ts │ │ │ ├── 2.add-two-numbers.ts │ │ │ ├── 21.merge-two-sorted-lists.ts │ │ │ ├── 24.swap-nodes-in-pairs.ts │ │ │ ├── 98.validate-binary-search-tree.ts │ │ │ └── README.md │ │ ├── search │ │ │ ├── README.md │ │ │ ├── binarySearch.ts │ │ │ ├── blockSearch.ts │ │ │ ├── fibonacciSearch.ts │ │ │ ├── hashSearch.ts │ │ │ ├── interpolationSearch.ts │ │ │ ├── orderSearch.ts │ │ │ └── treeSearch.ts │ │ └── sorting │ │ │ ├── README.md │ │ │ ├── bubbleSort.ts │ │ │ ├── bucketSort.ts │ │ │ ├── cocktailSort.ts │ │ │ ├── countSort.ts │ │ │ ├── heapSort.ts │ │ │ ├── insertionSort.ts │ │ │ ├── mergeSort.ts │ │ │ ├── quickSort.ts │ │ │ ├── radixSort.ts │ │ │ ├── selectionSort.ts │ │ │ └── shellSort.ts │ ├── datastructures │ │ ├── README.md │ │ ├── dictionary │ │ │ ├── README.md │ │ │ └── dictionary.ts │ │ ├── graph │ │ │ ├── README.md │ │ │ └── graph.ts │ │ ├── hashTable │ │ │ ├── README.md │ │ │ └── hashTable.ts │ │ ├── heap │ │ │ ├── README.md │ │ │ └── heap.ts │ │ ├── linkedList │ │ │ ├── README.md │ │ │ ├── circularLinkedList.ts │ │ │ ├── doublyLinkedList.ts │ │ │ └── linkedList.ts │ │ ├── queue │ │ │ ├── README.md │ │ │ ├── circularQueue.ts │ │ │ ├── deque.ts │ │ │ ├── priorityQueueArray.ts │ │ │ └── queue.ts │ │ ├── stack │ │ │ ├── README.md │ │ │ └── stack.ts │ │ └── tree │ │ │ ├── AdelsonVelskiiLandiTree.ts │ │ │ ├── README.md │ │ │ ├── binarySearchTree.ts │ │ │ └── redBlackTree.ts │ ├── leetNode.ts │ ├── node.ts │ └── utils.ts ├── global.d.ts └── index.ts ├── static └── img │ └── qrcode-all1.png ├── tests ├── algorithms │ ├── recursion │ │ └── recursion.test.ts │ ├── search │ │ └── search.test.ts │ └── sorting │ │ └── sort.test.ts └── datastructures │ ├── dictionary │ └── dictionary.test.ts │ ├── graph │ └── graph.test.ts │ ├── hashTable │ └── hashTable.test.ts │ ├── heap │ ├── MaxHeap.test.ts │ ├── MinHeap.test.ts │ └── heap.test.ts │ ├── linkedList │ ├── circularLinkedList.test.ts │ ├── doublyLinkedList.test.ts │ └── linkedList.test.ts │ ├── queue │ ├── circularQueue.test.ts │ ├── deque.test.ts │ ├── priorityQueueArray.test.ts │ └── queue.test.ts │ ├── stack │ └── stack.test.ts │ └── tree │ ├── adelsonVelskiiLandiTree.test.ts │ ├── binarySearchTree.test.ts │ └── redBlackTree.test.ts ├── tsconfig.json ├── views └── index.html └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "useBuiltIns": "usage", 7 | "corejs": 3, 8 | "targets": { 9 | "esmodules": true, 10 | "chrome": "60", 11 | "ie": "10" 12 | } 13 | } 14 | ], 15 | "@babel/preset-typescript" 16 | ], 17 | "plugins": [ 18 | "@babel/plugin-transform-runtime", 19 | "@babel/plugin-transform-arrow-functions", 20 | "@babel/plugin-proposal-optional-chaining", 21 | [ 22 | "@babel/plugin-proposal-class-properties", 23 | { 24 | "loose": true 25 | } 26 | ], 27 | [ 28 | "@babel/plugin-proposal-decorators", 29 | { 30 | "legacy": true 31 | } 32 | ], 33 | [ 34 | "@babel/plugin-proposal-private-methods", 35 | { 36 | "loose": true 37 | } 38 | ] 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | last 2 version 2 | > 1% 3 | IE 11 -------------------------------------------------------------------------------- /.cz-config.js: -------------------------------------------------------------------------------- 1 | const czConfig = { 2 | types: [ 3 | { value: 'feat', name: '特性: 新增一个功能' }, 4 | { value: 'fix', name: '修复: 修复一个Bug' }, 5 | { value: 'docs', name: '文档: 文档变更' }, 6 | { value: 'style', name: '格式: 代码格式' }, 7 | { value: 'refactor', name: '重构: 代码重构' }, 8 | { value: 'perf', name: '性能: 改善性能' }, 9 | { value: 'test', name: '测试: 测试代码' }, 10 | { value: 'wip', name: '半成品: 开发中' }, 11 | { 12 | value: 'build', 13 | name: 14 | '工具: 变更项目构建或外部依赖(例如scopes: webpack、gulp、npm等)', 15 | }, 16 | { 17 | value: 'ci', 18 | name: 19 | '集成: 更改持续集成软件的配置文件和package中的scripts命令,例如scopes: Travis, Circle等', 20 | }, 21 | { 22 | value: 'style', 23 | name: '代码格式(不影响功能,例如空格、分号等格式修正)', 24 | }, 25 | { 26 | value: 'chore', 27 | name: '构建: 变更构建流程或辅助工具', 28 | }, 29 | { 30 | value: 'revert', 31 | name: '回退: 代码回退', 32 | }, 33 | ], 34 | scopes: [ 35 | { name: 'config' }, 36 | { name: 'src/core' }, 37 | { name: 'static' }, 38 | { name: 'tests' }, 39 | { name: 'views' }, 40 | { name: 'readme' }, 41 | { name: 'line' }, 42 | ], 43 | messages: { 44 | type: '选择一种你的提交类型:', 45 | scope: '选择一个scope (可选):', 46 | customScope: '选择更改范围:', 47 | subject: '短说明:\n', 48 | body: '长说明,使用"|"换行(可选):\n', 49 | breaking: '非兼容性说明 (可选):\n', 50 | footer: '关联关闭的issue,例如:#31, #34(可选):\n', 51 | confirmCommit: '确定提交说明?', 52 | }, 53 | allowCustomScopes: true, 54 | allowBreakingChanges: ['feat', 'fix', 'wip'], 55 | // 限制主题长度 56 | subjectLimit: 100, 57 | } 58 | 59 | module.exports = czConfig 60 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | SERVER_PORT=8033 2 | EXPOSE_PORT=80 3 | CONTAINER_NAME=ying 4 | IMAGE_NAME=template 5 | HOST=0.0.0.0 6 | PORT=8099 7 | VERSION=3.1.0 8 | IS_MOBILE=true 9 | AUTOPREFIXER_GRID=autoplace 10 | PUBLIC_PATH=/ 11 | WATCH_ANALYZER=false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /static/ 4 | /node_modules/ 5 | /dist/ 6 | /*.* 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:prettier/recommended" 8 | ], 9 | "plugins": ["@typescript-eslint/eslint-plugin", "prettier"], 10 | "parserOptions": { 11 | "ecmaVersion": 2020, 12 | "sourceType": "module", 13 | "ecmaFeatures": { 14 | "modules": true, 15 | "jsx": true 16 | }, 17 | "project": "./tsconfig.json" 18 | }, 19 | "env": { 20 | "browser": true, 21 | "node": true 22 | }, 23 | "rules": { 24 | "indent": [0, 4], 25 | "arrow-parens": 0, 26 | "generator-star-spacing": 0, 27 | "no-debugger": 0, 28 | "eol-last": 0, 29 | "eqeqeq": 2, 30 | "camelcase": 0, 31 | "space-before-function-paren": 0, 32 | "quotes": ["error", "single"], 33 | "@typescript-eslint/explicit-function-return-type": [ 34 | "off", 35 | { 36 | "allowExpressions": true, 37 | "allowTypedFunctionExpressions": true 38 | } 39 | ], 40 | "@typescript-eslint/no-explicit-any": 2, 41 | "prettier/prettier": "error", 42 | "no-var": "error", 43 | "@typescript-eslint/consistent-type-definitions": ["error", "interface"], 44 | "no-empty-function": ["error", { "allow": ["constructors"] }], 45 | "@typescript-eslint/no-empty-function": "off" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | coverage/ 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Editor directories and files 10 | .idea 11 | .vscode 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln 16 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /static/ 3 | /node_modules/ 4 | /dist/ 5 | /*.* 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 80, 4 | "semi": false, 5 | "singleQuote": true, 6 | "trailingComma": "all", 7 | "bracketSpacing": true, 8 | "arrowParens": "avoid", 9 | "requirePragma": false, 10 | "endOfLine": "auto" 11 | } 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:latest 2 | RUN nginx -v 3 | 4 | ENV SERVER_PORT=$SERVER_PORT 5 | 6 | ENV EXPOSE_PORT=$EXPOSE_PORT 7 | 8 | ENV CONTAINER_NAME=$CONTAINER_NAME 9 | 10 | ENV IMAGE_NAME=$IMAGE_NAME 11 | 12 | COPY nginx.conf /etc/nginx/nginx.conf 13 | COPY default.conf /etc/nginx/conf.d/default.conf 14 | COPY dist /usr/share/nginx/html 15 | 16 | CMD ["nginx", "-g", "daemon off;"] 17 | 18 | EXPOSE ${SERVER_PORT} 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 fish_head 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 算法与数据结构 - TS 描述 2 | 3 | ## 前言 4 | 5 | 本库是以 **`TypeScript`** 为描述语言的算法与数据结构入门仓库。 6 | 主要面向想要学习或者想要查漏补缺的前端开发者。 7 | 本库可以作为算法与数据结构的字典查询,也可以作为入门资料学习。 8 | 对此感兴趣的欢迎 **star** 9 | 10 | ## Todos 11 | 12 | ### 数据结构 13 | 14 | - [x] [栈(stack)](./src/core/datastructures/stack/README.md) 15 | - [x] [队列(queue)](./src/core/datastructures/queue/README.md) 16 | - [x] [链表(linkedList)](./src/core/datastructures/linkedList/README.md) 17 | - [x] [堆(heap)](./src/core/datastructures/heap/README.md) 18 | - [x] [树(tree)](./src/core/datastructures/tree/README.md) 19 | - [x] [字典(Dictionary)](./src/core/datastructures/dictionary/README.md) 20 | - [x] [散列表(hashTable)](./src/core/datastructures/hashTable/README.md) 21 | - [x] [图(graph)](./src/core/datastructures/graph/README.md) 22 | 23 | ### 算法 24 | 25 | - [x] [十大基础排序算法(sorting)](./src/core/algorithms/sorting/README.md) 26 | - [x] [七大基础查找算法(search)](./src/core/algorithms/search/README.md) 27 | - [x] [递归(recursion)](./src/core/algorithms/recursion/README.md) 28 | - [x] [动态规划(dynamic)](./src/core/algorithms/dynamic/README.md) 29 | - [x] [贪心算法(greedy)](./src/core/algorithms/greedy/README.md) 30 | - [x] [回溯(backtracking)](./src/core/algorithms/backtracking/README.md) 31 | 32 | ## 后记 33 | 34 | ### 参考资料 35 | 36 | 1. [学习JavaScript数据结构与算法](https://item.m.jd.com/product/12613100.html?wxa_abtest=o&ad_od=share&utm_source=androidapp&utm_medium=appshare&utm_campaign=t_335139774&utm_term=CopyURL) 37 | 2. [Learning-JavaScript-Data-Structures-and-Algorithms-Third-Edition](https://github.com/PacktPublishing/Learning-JavaScript-Data-Structures-and-Algorithms-Third-Edition) 38 | 39 | ### 个人说明 40 | 41 | 如果你喜欢探讨技术,或者对本仓库有任何的意见或建议,非常欢迎加鱼头微信好友一起探讨,当然,鱼头也非常希望能跟你一起聊生活,聊爱好,谈天说地。 42 | 鱼头的微信号是:krisChans95 43 | 也可以扫码关注公众号,订阅更多精彩内容。 44 | 45 | ![./static/img/qrcode-all1.png](./static/img/qrcode-all1.png) 46 | -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const dotenv = require('dotenv') 3 | const root = process.cwd() 4 | // 获取当前项目下的文件夹路径 5 | const resolve = dir => path.join(__dirname, '..', dir) 6 | // 环境变量初始化 7 | const initEnv = () => { 8 | dotenv.config({ 9 | path: resolve('.env'), 10 | }) 11 | } 12 | initEnv() 13 | // 核心的文件路径 14 | const corePath = { 15 | src: resolve('src'), 16 | core: resolve('src/core/'), 17 | views: resolve('views'), 18 | dist: resolve('dist'), 19 | } 20 | // 资源文件路径 21 | const assetsPath = { 22 | nodeModules: resolve('node_modules'), 23 | static: resolve('static'), 24 | tests: resolve('tests'), 25 | } 26 | // 输出的配置 27 | const config = { 28 | // 项目目录 29 | root, 30 | // 配置文件目录 31 | config: path.resolve(__dirname, '../'), 32 | // 开发环境配置 33 | dev: { 34 | // 路径重定向 35 | alias: { 36 | '@': corePath.src, 37 | src: corePath.src, 38 | core: corePath.core, 39 | tests: assetsPath.tests, 40 | static: assetsPath.static, 41 | }, 42 | // 处理的文件夹 43 | include: [corePath.src, assetsPath.tests, assetsPath.static], 44 | // 不处理的文件夹 45 | exclude: [assetsPath.nodeModules], 46 | }, 47 | // 生产环境配置 48 | prod: { 49 | // 不处理的文件夹 50 | exclude: [assetsPath.nodeModules, assetsPath.static], 51 | }, 52 | // 源文件目录 53 | src: corePath.src, 54 | // 打包目录 55 | dist: corePath.dist, 56 | // html文件目录 57 | views: corePath.views, 58 | // node_modules目录 59 | node_modules: corePath.nodeModules, 60 | // 静态资源文件夹 61 | static: corePath.static, 62 | } 63 | module.exports = config 64 | -------------------------------------------------------------------------------- /config/webpack.config.base.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | // https://www.npmjs.com/package/html-webpack-plugin 简化了 HTML 文件的创建 4 | const HTMLWebpackPlugin = require('html-webpack-plugin') 5 | // https://www.npmjs.com/package/copy-webpack-plugin 复制文件夹到构建目录 6 | const CopyWebpackPlugin = require('copy-webpack-plugin') 7 | const { 8 | views, 9 | src, 10 | root, 11 | dev: { alias, include, exclude }, 12 | dist, 13 | } = require('./config.js') 14 | // 生成 html 处理插件以及入口 15 | const genHTMLPluginsAndEntries = viewsPath => { 16 | // 获取html文件名,生成多页面入口 17 | const getViewEntries = viewsPath => { 18 | const viewsDir = fs.readdirSync(viewsPath) 19 | const genViewsNameWithoutSuffix = viewsDir 20 | .filter(e => e.indexOf('html') >= 0) 21 | .map(e => e.replace('.html', '')) 22 | return genViewsNameWithoutSuffix 23 | } 24 | const viewEntries = getViewEntries(viewsPath) 25 | // 保存HTMLWebpackPlugin实例 26 | const plugins = [] 27 | // 保存入口列表 28 | const entries = {} 29 | // 生成HTMLWebpackPlugin实例和入口列表 30 | viewEntries.forEach(view => { 31 | const htmlConfig = { 32 | filename: `${view}.html`, 33 | template: path.join(views, `./${view}.html`), // 模板文件 34 | } 35 | const entryFile = path.join(src, `./${view}.ts`) 36 | if (!fs.existsSync(entryFile)) { 37 | htmlConfig.chunks = [] 38 | } else { 39 | htmlConfig.chunks = [view, 'vendors'] 40 | entries[view] = `./src/${view}.ts` 41 | } 42 | const htmlPlugin = new HTMLWebpackPlugin(htmlConfig) 43 | plugins.push(htmlPlugin) 44 | }) 45 | return { 46 | entries, 47 | plugins, 48 | } 49 | } 50 | 51 | const { entries, plugins: htmlPlugins } = genHTMLPluginsAndEntries(views) 52 | 53 | const baseConfig = { 54 | // 入口路径 55 | context: root, 56 | entry: entries, 57 | output: { 58 | // 打包路径 59 | path: dist, 60 | }, 61 | resolve: { 62 | // 文件名简写 63 | alias, 64 | // 文件查询扩展 65 | extensions: ['.ts', '.js', '.tsx', '.jsx', '.json'], 66 | }, 67 | module: { 68 | rules: [ 69 | { 70 | test: /\.(woff|woff2|eot|ttf|otf)(\?.*)?$/, 71 | use: { 72 | loader: 'url-loader', 73 | options: { 74 | limit: 8192, 75 | name: 'font/[name]-[hash:8].[ext]', 76 | }, 77 | }, 78 | include, 79 | exclude, 80 | }, 81 | { 82 | test: /\.(png|svg|jpg|gif)(\?.*)?$/, 83 | use: { 84 | loader: 'url-loader', 85 | options: { 86 | limit: 8192, 87 | name: 'img/[name]-[hash:8].[ext]', 88 | }, 89 | }, 90 | include, 91 | exclude, 92 | }, 93 | { 94 | test: /\.(t|j)sx?$/, 95 | use: [ 96 | // https://www.npmjs.com/package/thread-loader 将下方的 loader 放入 worker 池里。每个 worker 都是一个单独的有 600ms 限制的 node.js 进程。同时跨进程的数据交换也会被限制。 97 | 'thread-loader', 98 | { 99 | loader: 'babel-loader', 100 | options: { 101 | cacheDirectory: true, 102 | cacheCompression: true, 103 | }, 104 | }, 105 | ], 106 | include, 107 | exclude, 108 | }, 109 | ], 110 | }, 111 | externals: {}, 112 | plugins: [ 113 | ...htmlPlugins, 114 | new CopyWebpackPlugin([ 115 | { 116 | from: 'static', 117 | to: 'static', 118 | }, 119 | ]), 120 | ], 121 | } 122 | module.exports = baseConfig 123 | -------------------------------------------------------------------------------- /config/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | // 开发环境配置 2 | const webpack = require('webpack') 3 | const webpackMerge = require('webpack-merge') 4 | const webpackBase = require('./webpack.config.base.js') 5 | const { 6 | dev: { include, exclude }, 7 | } = require('./config.js') 8 | 9 | const webpackDev = { 10 | mode: 'development', 11 | stats: { 12 | colors: true, 13 | }, 14 | devtool: 'eval-cheap-module-source-map', 15 | output: { 16 | filename: 'static/js/[name].[hash:8].bundle.js', 17 | }, 18 | devServer: { 19 | contentBase: './dist', 20 | historyApiFallback: true, 21 | overlay: true, 22 | inline: true, 23 | hot: true, 24 | host: process.env.HOST || '0.0.0.0', 25 | port: process.env.PORT || '8082', 26 | useLocalIp: true, 27 | proxy: {}, 28 | https: false, 29 | open: true, 30 | }, 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.css$/, 35 | include, 36 | exclude, 37 | use: ['style-loader', 'css-loader', 'postcss-loader'], 38 | }, 39 | ], 40 | }, 41 | plugins: [new webpack.HotModuleReplacementPlugin()], 42 | } 43 | module.exports = webpackMerge(webpackBase, webpackDev) 44 | -------------------------------------------------------------------------------- /config/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | // 生产环境配置 2 | const webpack = require('webpack') 3 | const webpackMerge = require('webpack-merge') 4 | // https://www.npmjs.com/package/clean-webpack-plugin 清除文件夹 5 | const cleanWebpackPlugin = require('clean-webpack-plugin') 6 | // https://www.npmjs.com/package/uglifyjs-webpack-plugin 压缩文件夹 7 | const uglifyJSPlugin = require('uglifyjs-webpack-plugin') 8 | // https://www.npmjs.com/package/mini-css-extract-plugin 将 CSS 提取到单独的文件中 9 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 10 | // https://www.npmjs.com/package/optimize-css-assets-webpack-plugin CSS 优化 11 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') 12 | // https://www.npmjs.com/package/compression-webpack-plugin 提供带 Content-Encoding 编码的压缩版的资源 13 | const compressionPlugin = require('compression-webpack-plugin') 14 | // https://www.npmjs.com/package/webpack-bundle-analyzer 可视化的输出文件详情 15 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') 16 | const webpackBase = require('./webpack.config.base.js') 17 | const { root, prod } = require('./config.js') 18 | 19 | const plugins = [ 20 | new MiniCssExtractPlugin({ 21 | filename: 'css/[name].[chunkhash:8].css', 22 | chunkFilename: 'css/[id].[chunkhash:8].css', 23 | }), 24 | new cleanWebpackPlugin(['./dist/'], { 25 | root, 26 | }), 27 | new compressionPlugin({ 28 | filename: '[path].gz[query]', 29 | test: /(\.js|\.css|\.html|\.png|\.jpg|\.webp|\.svg)(\?.*)?$/, 30 | cache: true, 31 | algorithm: 'gzip', 32 | deleteOriginalAssets: false, 33 | minRatio: 0.8, 34 | }), 35 | new OptimizeCssAssetsPlugin({ 36 | assetNameRegExp: /\.css$/g, 37 | cssProcessorPluginOptions: { 38 | preset: [ 39 | 'default', 40 | { 41 | discardComments: { 42 | removeAll: true, 43 | }, 44 | }, 45 | ], 46 | }, 47 | canPrint: true, 48 | }), 49 | ] 50 | 51 | const WATCH_ANALYZER = process.env.WATCH_ANALYZER !== 'false' 52 | 53 | if (WATCH_ANALYZER) { 54 | plugins.push(new BundleAnalyzerPlugin()) 55 | } 56 | 57 | const webpackProd = { 58 | mode: 'production', 59 | stats: { 60 | colors: true, 61 | }, 62 | // 可与 hidden-source-map 之间二选一 63 | // 打开 https://developers.google.com/web/tools/chrome-devtools/javascript/source-maps 64 | // https://webpack.js.org/configuration/devtool/ 65 | devtool: 'nosources-source-map', 66 | output: { 67 | filename: 'js/[name].[chunkhash:8].bundle.js', 68 | publicPath: process.env.PUBLIC_PATH || '/', 69 | }, 70 | optimization: { 71 | moduleIds: 'deterministic', 72 | minimizer: [ 73 | new uglifyJSPlugin({ 74 | sourceMap: true, 75 | exclude: prod.exclude, 76 | uglifyOptions: { 77 | compress: { 78 | drop_console: true, 79 | drop_debugger: true, 80 | }, 81 | comments: false, 82 | }, 83 | }), 84 | ], 85 | usedExports: true, 86 | splitChunks: { 87 | chunks: 'all', 88 | cacheGroups: { 89 | defaultVendors: { 90 | test: /[\\/]node_modules[\\/]/, 91 | name: 'vendors', 92 | }, 93 | }, 94 | }, 95 | }, 96 | module: { 97 | rules: [ 98 | { 99 | test: /\.css$/, 100 | use: [ 101 | MiniCssExtractPlugin.loader, 102 | 'css-loader', 103 | 'postcss-loader', 104 | ], 105 | }, 106 | ], 107 | }, 108 | plugins, 109 | } 110 | module.exports = webpackMerge(webpackBase, webpackProd) 111 | -------------------------------------------------------------------------------- /default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | location / { 3 | root /usr/share/nginx/html; 4 | } 5 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | ying-front: 4 | env_file: 5 | - .env 6 | container_name: ${CONTAINER_NAME} 7 | image: ${IMAGE_NAME} 8 | build: 9 | context: . 10 | dockerfile: Dockerfile 11 | volumes: 12 | - ./dist:/usr/share/nginx/html:ro 13 | ports: 14 | - target: ${EXPOSE_PORT} 15 | published: ${SERVER_PORT} 16 | protocol: tcp 17 | mode: host 18 | -------------------------------------------------------------------------------- /fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {} 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const tsconfig = require('./tsconfig.json') 3 | const moduleNameMapper = require('tsconfig-paths-jest')(tsconfig) 4 | // Maybe you can check: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping 5 | const jestConfig = { 6 | rootDir: path.join(__dirname, ''), 7 | roots: ['/tests'], 8 | globals: {}, 9 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node'], 10 | moduleNameMapper: { 11 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 12 | '/fileMock.js', 13 | '\\.css$': 'identity-obj-proxy', 14 | ...moduleNameMapper, 15 | }, 16 | coverageDirectory: 'coverage', 17 | coverageReporters: ['lcov'], 18 | testPathIgnorePatterns: ['/node_modules/'], 19 | coveragePathIgnorePatterns: [ 20 | '/node_modules/', 21 | '/dist/', 22 | '/config/', 23 | '/view/', 24 | ], 25 | coverageReporters: [ 26 | 'json', 27 | 'lcov', 28 | 'text', 29 | 'clover', 30 | 'html', 31 | 'text-summary', 32 | ], 33 | transform: { 34 | '^.+\\.(ts|tsx)$': 'ts-jest', 35 | }, 36 | testMatch: [ 37 | '/tests/*.(ts|tsx|js|jsx)', 38 | '/tests/**/*.(ts|tsx|js|jsx)', 39 | ], 40 | collectCoverage: true, 41 | } 42 | module.exports = jestConfig 43 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | user root; 2 | worker_processes 1; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | 8 | events { 9 | worker_connections 1024; 10 | } 11 | 12 | 13 | http { 14 | include /etc/nginx/mime.types; 15 | default_type application/octet-stream; 16 | 17 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 18 | '$status $body_bytes_sent "$http_referer" ' 19 | '"$http_user_agent" "$http_x_forwarded_for"'; 20 | 21 | access_log /var/log/nginx/access.log main; 22 | 23 | sendfile off; 24 | #tcp_nopush on; 25 | 26 | keepalive_timeout 65; 27 | 28 | gzip on; 29 | 30 | include /etc/nginx/conf.d/*.conf; 31 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ying-datastructures-algorithms", 3 | "version": "2.0.0", 4 | "description": "a datastructures and algorithms lib with typescript", 5 | "main": "src/index.ts", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "@babel/core": "^7.2.2", 9 | "@babel/plugin-proposal-class-properties": "^7.5.5", 10 | "@babel/plugin-proposal-decorators": "^7.8.3", 11 | "@babel/plugin-proposal-object-rest-spread": "^7.6.2", 12 | "@babel/plugin-proposal-optional-chaining": "^7.6.0", 13 | "@babel/plugin-proposal-private-methods": "^7.8.3", 14 | "@babel/plugin-transform-arrow-functions": "^7.10.1", 15 | "@babel/plugin-transform-runtime": "^7.2.0", 16 | "@babel/preset-env": "^7.6.2", 17 | "@babel/preset-typescript": "^7.6.0", 18 | "@babel/runtime": "^7.9.2", 19 | "@babel/runtime-corejs2": "^7.9.2", 20 | "@types/eslint": "^7.2.1", 21 | "@types/jest": "^24.0.23", 22 | "@types/node": "^12.12.8", 23 | "@typescript-eslint/eslint-plugin": "^4.19.0", 24 | "@typescript-eslint/parser": "^4.19.0", 25 | "@typescript-eslint/typescript-estree": "^4.19.0", 26 | "acorn": "^7.1.0", 27 | "autoprefixer": "^10.2.5", 28 | "babel-jest": "^26.3.0", 29 | "babel-loader": "^8.0.5", 30 | "babel-polyfill": "^6.26.0", 31 | "chalk": "^2.4.2", 32 | "clean-webpack-plugin": "^1.0.0", 33 | "commitizen": "^4.1.5", 34 | "compression-webpack-plugin": "^2.0.0", 35 | "copy-webpack-plugin": "^4.6.0", 36 | "core-js": "^3.6.5", 37 | "cross-env": "^5.2.1", 38 | "css-loader": "^2.1.0", 39 | "cssnano": "^4.1.10", 40 | "cz-conventional-changelog": "^3.2.0", 41 | "cz-customizable": "^6.3.0", 42 | "dotenv": "^8.2.0", 43 | "es6-promise": "^4.2.5", 44 | "eslint": "^7.22.0", 45 | "eslint-config-prettier": "^6.11.0", 46 | "eslint-config-standard": "^14.1.1", 47 | "eslint-friendly-formatter": "^4.0.1", 48 | "eslint-loader": "^4.0.2", 49 | "eslint-plugin-import": "^2.22.0", 50 | "eslint-plugin-node": "^11.1.0", 51 | "eslint-plugin-prettier": "^3.1.4", 52 | "eslint-plugin-promise": "^4.2.1", 53 | "eslint-plugin-standard": "^4.0.1", 54 | "express": "^4.17.3", 55 | "file-loader": "^3.0.1", 56 | "html-webpack-plugin": "^5.3.1", 57 | "husky": "^4.2.5", 58 | "identity-obj-proxy": "^3.0.0", 59 | "jest": "^26.6.3", 60 | "lint-staged": "^10.2.11", 61 | "mini-css-extract-plugin": "^0.5.0", 62 | "npx": "^10.2.0", 63 | "optimize-css-assets-webpack-plugin": "^5.0.1", 64 | "postcss": "^8.2.13", 65 | "postcss-import": "^12.0.1", 66 | "postcss-load-config": "^2.0.0", 67 | "postcss-loader": "^5.2.0", 68 | "postcss-plugin": "^1.0.0", 69 | "postcss-px-to-viewport": "^1.1.1", 70 | "postcss-url": "^8.0.0", 71 | "pre-commit": "^1.2.2", 72 | "precss": "^4.0.0", 73 | "prettier": "^2.0.4", 74 | "prettierrc": "0.0.0-5", 75 | "pretty-quick": "^2.0.1", 76 | "proxy": "^0.2.4", 77 | "puppeteer": "^2.0.0", 78 | "style-loader": "^0.23.1", 79 | "thread-loader": "^2.1.3", 80 | "ts-jest": "^26.5.4", 81 | "ts-loader": "^6.2.0", 82 | "tsconfig-paths": "^3.9.0", 83 | "tsconfig-paths-jest": "0.0.1", 84 | "tslib": "^1.11.1", 85 | "typescript": "^4.2.3", 86 | "uglifyjs-webpack-plugin": "^2.1.1", 87 | "url-loader": "^1.1.2", 88 | "webpack": "^5.76.0", 89 | "webpack-bundle-analyzer": "^3.7.0", 90 | "webpack-cli": "^4.3.0", 91 | "webpack-dev-server": "^3.11.2", 92 | "webpack-merge": "^4.2.1" 93 | }, 94 | "scripts": { 95 | "build": "cross-env NODE_ENV=production webpack --progress --config ./config/webpack.config.prod.js", 96 | "dev": "cross-env NODE_ENV=development webpack serve --progress --config ./config/webpack.config.dev.js", 97 | "jest": "cross-env NODE_ENV=development jest --cache --colors --coverage tests", 98 | "lint": "cross-env NODE_ENV=development eslint --fix src", 99 | "prettier": "cross-env NODE_ENV=development prettier config src tests *.js .*.js --write", 100 | "cz": "yarn jest && yarn lint && yarn prettier && git add . && git cz && git push" 101 | }, 102 | "keywords": [ 103 | "javascript", 104 | "typescript", 105 | "webpack", 106 | "jest", 107 | "eslint", 108 | "prettier", 109 | "babel", 110 | "datastructures", 111 | "algorithms" 112 | ], 113 | "author": "chenjinwen77@foxmail.com", 114 | "license": "MIT", 115 | "config": { 116 | "commitizen": { 117 | "path": "node_modules/cz-customizable" 118 | }, 119 | "cz-customizable": { 120 | "config": ".cz-config.js" 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const precss = require('precss') 2 | const pxToViewport = require('postcss-px-to-viewport') 3 | 4 | const postcssConfig = { 5 | plugins: [ 6 | /** 7 | * @url https://cssdb.org/ 8 | */ 9 | precss({ 10 | stage: 3, 11 | features: { 12 | 'color-mod-function': { unresolved: 'warn' }, 13 | }, 14 | }), 15 | pxToViewport({ 16 | unitToConvert: 'px', 17 | viewportWidth: 750, 18 | viewportHeight: 1334, 19 | unitPrecision: 3, 20 | viewportUnit: 'vw', 21 | fontViewportUnit: 'vw', 22 | mediaQuery: false, 23 | }), 24 | ], 25 | } 26 | 27 | module.exports = postcssConfig 28 | -------------------------------------------------------------------------------- /src/core/algorithms/README.md: -------------------------------------------------------------------------------- 1 | # 算法 - TS 描述 2 | 3 | > - 作者:陈大鱼头 4 | > 5 | > - GitHub:https://github.com/KRISACHAN 6 | > 7 | > - 说明:本库是以 **`TypeScript`** 为描述语言的算法与数据结构入门仓库的算法部分 8 | 9 | ## 算法是什么? 10 | 11 | **算法(Algorithm)** 已经是一个老生常谈的概念了,最早来自于数学领域。 12 | 13 | **算法(Algorithm)** 代表着用系统的方法描述解决问题的策略机制,可以通过一定规范的 **输入**,在有限时间内获得所需要的 **输出**。 14 | 15 | **如下图示便是算法:** 16 | 17 | 18 | 19 | ### 算法的好坏 20 | 21 | 一个算法的好坏是通过 **时间复杂度** 与 **空间复杂度** 来衡量的。 22 | 23 | 简单来说,**时间复杂度** 就是执行算法的 **时间成本** ,**空间复杂度** 则是执行算法的 **空间成本** 。 24 | 25 | ### 复杂度 26 | 27 | **时间复杂度** 与 **空间复杂度** 都是用 **“大 O”** 来表示,写作 **O(\*)**。有一点值得注意的是,我们谈论复杂度,一般谈论的都是时间复杂度。 28 | 29 | 常见时间复杂度的 **“大 O 表示法”** 描述有以下几种: 30 | 31 | | 时间复杂度 | 非正式术语 | 32 | | :--------------- | :--------- | 33 | | O(1) | 常数阶 | 34 | | O(n) | 线性阶 | 35 | | O(n2) | 平方阶 | 36 | | O(log n) | 对数阶 | 37 | | O(n log n) | 线性对数阶 | 38 | | O(n3) | 立方阶 | 39 | | O(2n) | 指数阶 | 40 | 41 | 一个算法在 N 规模下所消耗的时间消耗从大到小如下: 42 | 43 | **O(1) < O(log n) < O(n) < O(n log n) < O(n2) < O(n3) < O(2n)** 44 | 45 | 上面括号的数据是啥意思?别问,问就让你回去看数学书。 46 | 47 | **以下便为不同时间复杂度的资源消耗增长图示:** 48 | 49 | 50 | 51 | **常见概念:** 52 | 53 | 1. **最好时间复杂度:** 在最理想情况下执行代码的时间复杂度,它花的时间最短; 54 | 2. **最坏时间复杂度:** 最糟糕情况下执行代码的时间复杂度,它花的时间最长; 55 | 3. **平均时间复杂度:** 执行代码时间的平均水平,这个值就是概率论中的加权平均值,也叫期望值。 56 | -------------------------------------------------------------------------------- /src/core/algorithms/backtracking/README.md: -------------------------------------------------------------------------------- 1 | # 回溯 2 | 3 | > [维基百科](https://zh.wikipedia.org/wiki/%E5%9B%9E%E6%BA%AF%E6%B3%95) 4 | > 5 | > 回溯法(英语:backtracking)是暴力搜索法中的一种。 6 | > 7 | > 对于某些计算问题而言,回溯法是一种可以找出所有(或一部分)解的一般性算法,尤其适用于约束补偿问题(在解决约束满足问题时,我们逐步构造更多的候选解,并且在确定某一部分候选解不可能补全成正确解之后放弃继续搜索这个部分候选解本身及其可以拓展出的子候选解,转而测试其他的部分候选解)。 8 | > 9 | > 在经典的教科书中,八皇后问题展示了回溯法的用例。(八皇后问题是在标准国际象棋棋盘中寻找八个皇后的所有分布,使得没有一个皇后能攻击到另外一个。) 10 | > 11 | > 回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现,现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况: 12 | > 13 | > 找到一个可能存在的正确的答案 14 | > 在尝试了所有可能的分步方法后宣告该问题没有答案 15 | > 在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。 16 | -------------------------------------------------------------------------------- /src/core/algorithms/dynamic/README.md: -------------------------------------------------------------------------------- 1 | # 动态规划 2 | 3 | > [维基百科](https://zh.wikipedia.org/wiki/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92): 4 | > 5 | > 动态规划(英语:Dynamic programming,简称 DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 6 | > 7 | > 动态规划常常适用于有重叠子问题[1]和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。 8 | > 9 | > 动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。 10 | > 11 | > 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量:一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。 12 | 13 | ## 概述 14 | 15 | 动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。 16 | 17 | 动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。 18 | 19 | ## 适用情况 20 | 21 | 1. 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。 22 | 2. 无后效性。即子问题的解一旦确定,就不再改变,不受在这之后、包含它的更大的问题的求解决策影响。 23 | 3. 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率,降低了时间复杂度。 24 | -------------------------------------------------------------------------------- /src/core/algorithms/greedy/README.md: -------------------------------------------------------------------------------- 1 | # 贪心算法 2 | 3 | > [维基百科](https://zh.wikipedia.org/wiki/%E8%B4%AA%E5%BF%83%E7%AE%97%E6%B3%95) 4 | > 5 | > 贪心算法(英语:greedy algorithm),又称贪婪算法,是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。[1]比如在旅行推销员问题中,如果旅行员每次都选择最近的城市,那这就是一种贪心算法。 6 | > 7 | > 贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是局部最优解能决定全局最优解。简单地说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。 8 | > 9 | > 贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。 10 | 11 | ## 细节 12 | 13 | 1. 创建数学模型来描述问题。 14 | 2. 把求解的问题分成若干个**子问题**。 15 | 3. 对每一子问题求解,得到子问题的局部最优解。 16 | 4. 把子问题的解局部最优解合成原来解问题的一个解。 17 | 18 | 实现该算法的过程: 19 | 从问题的某一初始解出发;while 能朝给定总目标前进一步 do,求出可行解的一个解元素; 20 | 最后,由所有解元素组合成问题的一个可行解。 21 | -------------------------------------------------------------------------------- /src/core/algorithms/recursion/17.letter-combinations-of-a-phone-number.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @url https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/ 3 | * @title 电话号码的字母组合 4 | * @desc 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 5 | * @example1 6 | * @input digits = "23" 7 | * @output ["ad","ae","af","bd","be","bf","cd","ce","cf"] 8 | * 9 | * @example2 10 | * @input digits = "" 11 | * @output [] 12 | * 13 | * @example3 14 | * @input digits = "2" 15 | * @output ["a","b","c"] 16 | */ 17 | import { eq, gte } from 'core/utils' 18 | interface DigitsMapType { 19 | [propName: string]: string[] 20 | } 21 | 22 | export const letterCombinations = (digits: string): string[] => { 23 | const res: string[] = [] 24 | 25 | // 边界处理 26 | if (eq(digits.length, 0)) { 27 | return res 28 | } 29 | 30 | const digitsMap: DigitsMapType = { 31 | 2: ['a', 'b', 'c'], 32 | 3: ['d', 'e', 'f'], 33 | 4: ['g', 'h', 'i'], 34 | 5: ['j', 'k', 'l'], 35 | 6: ['m', 'n', 'o'], 36 | 7: ['p', 'q', 'r', 's'], 37 | 8: ['t', 'u', 'v'], 38 | 9: ['w', 'x', 'y', 'z'], 39 | } 40 | 41 | // 深度遍历(回溯),各个组合都尝试一遍,把符合的结果保存起来,不符合的剪枝剪掉 42 | const coreRecursiver = (curLetter = '', curDigit = 0): void => { 43 | if (gte(curDigit, digits.length)) { 44 | res.push(curLetter) 45 | return 46 | } 47 | 48 | const letterGroup = digitsMap[digits[curDigit]] 49 | 50 | letterGroup.forEach(letter => { 51 | coreRecursiver(curLetter + letter, curDigit + 1) 52 | }) 53 | } 54 | 55 | coreRecursiver('', 0) 56 | 57 | return res 58 | } 59 | -------------------------------------------------------------------------------- /src/core/algorithms/recursion/2.add-two-numbers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @url https://leetcode-cn.com/problems/add-two-numbers/ 3 | * @title 两数相加 4 | * @desc 给你两个非空的链表,表示两个非负的整数。它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。 5 | * @example1 6 | * @input l1 = [2,4,3], l2 = [5,6,4] 7 | * @output [7,0,8] 8 | * @explanation 342 + 465 = 807. 9 | * 10 | * @example2 11 | * @input l1 = [0], l2 = [0] 12 | * @output [0] 13 | * 14 | * @example3 15 | * @input l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9] 16 | * @output [8,9,9,9,0,0,0,1] 17 | */ 18 | import { ListNode, ListNodeType } from 'core/leetNode' 19 | import { eq } from 'core/utils' 20 | 21 | const coreRecursiver = ( 22 | l1: ListNodeType, 23 | l2: ListNodeType, 24 | prevCount = 0, 25 | ): ListNodeType => { 26 | // 边界处理 27 | if (!l1 && !l2 && eq(prevCount, 0)) { 28 | return null 29 | } 30 | const l1Val: number = l1?.val || 0 31 | const l2Val: number = l2?.val || 0 32 | // 当前节点需要的值,要确保小于10 33 | const curCount: number = l1Val + l2Val + prevCount 34 | const curNode: ListNodeType = new ListNode(curCount % 10) 35 | const l1Next: ListNodeType = l1?.next || null 36 | const l2Next: ListNodeType = l2?.next || null 37 | // 当前子节点需要的值,往下要传递超过10的部分,不超过10则传递0 38 | curNode.next = coreRecursiver(l1Next, l2Next, Math.floor(curCount / 10)) 39 | return curNode 40 | } 41 | 42 | export const addTwoNumbers = ( 43 | l1: ListNodeType, 44 | l2: ListNodeType, 45 | ): ListNodeType => coreRecursiver(l1, l2, 0) 46 | -------------------------------------------------------------------------------- /src/core/algorithms/recursion/21.merge-two-sorted-lists.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @url https://leetcode-cn.com/problems/merge-two-sorted-lists/ 3 | * @title 合并两个有序链表 4 | * @desc 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 5 | * @example1 6 | * @input l1 = [1,2,4], l2 = [1,3,4] 7 | * @output [1,1,2,3,4,4] 8 | * 9 | * @example2 10 | * @input l1 = [], l2 = [] 11 | * @output [] 12 | * 13 | * @example3 14 | * @input l1 = [], l2 = [0] 15 | * @output [0] 16 | */ 17 | import { ListNodeType } from 'core/leetNode' 18 | import { lte } from 'core/utils' 19 | 20 | const coreRecursiver = (l1: ListNodeType, l2: ListNodeType): ListNodeType => { 21 | // 边界处理 22 | if (!l1 && !l2) { 23 | return null 24 | } 25 | 26 | if (!l1) { 27 | return l2 28 | } 29 | 30 | if (!l2) { 31 | return l1 32 | } 33 | 34 | const l1Val: number = l1.val 35 | const l2Val: number = l2.val 36 | 37 | // 确保每次最小的值都在前 38 | if (lte(l1Val, l2Val)) { 39 | l1.next = coreRecursiver(l1.next, l2) 40 | return l1 41 | } 42 | l2.next = coreRecursiver(l1, l2.next) 43 | return l2 44 | } 45 | 46 | export const mergeTwoLists = ( 47 | l1: ListNodeType, 48 | l2: ListNodeType, 49 | ): ListNodeType => coreRecursiver(l1, l2) 50 | -------------------------------------------------------------------------------- /src/core/algorithms/recursion/24.swap-nodes-in-pairs.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @url https://leetcode-cn.com/problems/swap-nodes-in-pairs/ 3 | * @title 两两交换链表中的节点 4 | * @desc 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 5 | * @example1 6 | * @input head = [1,2,3,4] 7 | * @output [2,1,4,3] 8 | * 9 | * @example2 10 | * @input head = [] 11 | * @output [] 12 | * 13 | * @example3 14 | * @input head = [1] 15 | * @output [1] 16 | */ 17 | import { ListNodeType } from 'core/leetNode' 18 | 19 | const coreRecursiver = (head: ListNodeType): ListNodeType => { 20 | // 边界处理 21 | if (!head || !head.next) { 22 | return head 23 | } 24 | const tmpNode: ListNodeType = head.next 25 | head.next = coreRecursiver(tmpNode.next) 26 | tmpNode.next = head 27 | return tmpNode 28 | } 29 | 30 | export const swapPairs = (head: ListNodeType): ListNodeType => 31 | coreRecursiver(head) 32 | -------------------------------------------------------------------------------- /src/core/algorithms/recursion/98.validate-binary-search-tree.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @url https://leetcode-cn.com/problems/validate-binary-search-tree/ 3 | * @title 验证二叉搜索树 4 | * @desc 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 5 | * 假设一个二叉搜索树具有如下特征: 6 | * 节点的左子树只包含小于当前节点的数。 7 | * 节点的右子树只包含大于当前节点的数。 8 | * 所有左子树和右子树自身必须也是二叉搜索树。 9 | * 10 | * @example1 11 | * @input 12 | * 2 13 | * / \ 14 | * 1 3 15 | * @output true 16 | * 17 | * @example2 18 | * @input 19 | * 5 20 | * / \ 21 | * 1 4 22 | * / \ 23 | * 3 6 24 | * @output false 25 | * @answer 输入为: [5,1,4,null,null,3,6]。根节点的值为 5 ,但是其右子节点值为 4 。 26 | */ 27 | import { TreeNodeType } from 'core/leetNode' 28 | import { lte, gte, isExist } from 'core/utils' 29 | 30 | // 使用中序遍历去递归判断当前二叉树元素的值是否匹配规则 31 | // 左子节点一定比根节点小,所以可以传递左子节点跟根节点去比较,一旦有一个左子节点比根节点大,则说明不是一个二叉搜索数 32 | // 右子节点的情况则相反 33 | const coreRecursiver = ( 34 | left: null | number, 35 | right: null | number, 36 | root: TreeNodeType, 37 | ): boolean => { 38 | if (!isExist(root)) { 39 | return true 40 | } 41 | if (isExist(left) && lte(root.val, left)) { 42 | return false 43 | } 44 | if (isExist(right) && gte(root.val, right)) { 45 | return false 46 | } 47 | return ( 48 | coreRecursiver(left, root.val, root.left) && 49 | coreRecursiver(root.val, right, root.right) 50 | ) 51 | } 52 | 53 | export const isValidBST = (root: TreeNodeType): boolean => 54 | coreRecursiver(null, null, root) 55 | -------------------------------------------------------------------------------- /src/core/algorithms/recursion/README.md: -------------------------------------------------------------------------------- 1 | # 递归 2 | 3 | > [维基百科](https://zh.wikipedia.org/wiki/%E9%80%92%E5%BD%92): 4 | > 5 | > 递归(英语:Recursion),又译为递回,在数学与计算机科学中,是指在函数的定义中使用函数自身的方法。递归一词还较常用于描述以自相似方法重复事物的过程。例如,当两面镜子相互之间近似平行时,镜中嵌套的图像是以无限递归的形式出现的。也可以理解为自我复制的过程。 6 | > 7 | > 🌰: 8 | > 9 | > 1. 一只狗来到厨房,偷走一小块面包。厨子举起杓子,把那只狗打死了。于是所有的狗都跑来了,给那只狗掘了一个坟墓,还在墓碑上刻了墓志铭,让未来的狗可以看到:“一只狗来到厨房,偷走一小块面包。厨子举起杓子,把那只狗打死了。于是所有的狗都跑来了,给那只狗掘了一个坟墓,还在墓碑上刻了墓志铭,让未来的狗可以看到:‘一只狗来到厨房,偷走一小块面包。厨子举起杓子,把那只狗打死了。于是所有的狗都跑来了,给那只狗掘了一个坟墓,还在墓碑上刻了墓志铭,让未来的狗可以看到……’” 10 | > 2. 大雄在房里,用时光电视看着从前的情况。电视画面中的那个时候,他正在房里,用时光电视,看着从前的情况。电视画面中的电视画面的那个时候,他正在房里,用时光电视,看着从前的情况…… 11 | -------------------------------------------------------------------------------- /src/core/algorithms/search/README.md: -------------------------------------------------------------------------------- 1 | # 七大基础查找算法(Search) 2 | 3 | > 查找是在大量的信息中寻找一个特定的信息元素,在计算机应用中,查找是常用的基本运算,例如编译程序中符号表的查找。 4 | 5 | ### 顺序查找(Order Search) 6 | 7 | 在计算机科学中,线性搜索或顺序搜索是一种寻找某一特定值的搜索算法,指按一定的顺序检查数组中每一个元素,直到找到所要寻找的特定值为止。是最简单的一种搜索算法。 8 | 9 | | 名称 | 最好时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 10 | | -------- | -------------- | -------------- | -------------- | 11 | | 顺序查找 | O(1) | O(n) | O(n) | 12 | 13 | ### 二分查找(Binary Search) 14 | 15 | 在计算机科学中,二分查找算法(英语:binary search algorithm),也称折半查找算法(英语:half-interval search algorithm)、对数查找算法(英语:logarithmic search algorithm),是一种在有序数组中查找某一特定元素的查找算法。 16 | 17 | | 名称 | 最好时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 空间复杂度 | 18 | | -------- | -------------- | -------------- | -------------- | ------------------------- | 19 | | 顺序查找 | O(log n) | O(log n) | O(log n) | 迭代:O(1) 递归:O(log n) | 20 | 21 | ### 插值查找(Interpolation search) 22 | 23 | 插值查找法(Interpolation search)是利用插值公式来计算猜测查找键值的位置。查找方式与二分查找相同。 24 | 25 | 插值 = `(设算数 -­ 最小数) / (最大数 -­ 最小数):` 26 | 27 | 搜索键值 = `left + parseInt( ( key - data[left] ) / ( data[right] - data[left] ) )*( right - left ) )` 28 | 29 | | 名称 | 最好时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 空间复杂度 | 30 | | -------- | -------------- | -------------- | -------------- | ---------- | 31 | | 插值查找 | O(1) | O(n) | O(log n) | O(1) | 32 | 33 | ### 斐波那契查找(Fibonacci search) 34 | 35 | 斐波那契查找(Fibonacci search) ,是区间中单峰函数的搜索技术。 36 | 37 | 斐波那契查找就是在二分查找的基础上根据斐波那契数列进行分割的。在斐波那契数列找一个等于略大于查找表中元素个数的数 **`F[n] `** ,将原查找表扩展为长度为 **`F[n](如果要补充元素,则补充重复最后一个元素,直到满足F[n]个元素)`** ,完成后进行斐波那契分割,即 **`F[n]`** 个元素分割为前半部分 **`F[n-1]`** 个元素,后半部分 **`F[n-2]`** 个元素,找出要查找的元素在那一部分并递归,直到找到。 38 | 39 | ### 树表查找(Tree search) 40 | 41 | 树表查找是对树型存储结构所做的查找。树型存储结构是一种多链表,该表中的每个结点包含有一个数据域和多个指针域,每个指针域指向一个后继结点。 42 | 43 | 树型存储结构和树型逻辑结构是完全对应的,都是表示一个树形图,只是用存储结构中的链指针代替逻辑结构中的抽象指针罢了,因此,往往把树型存储结构(即树表)和树型逻辑结构(树)统称为树结构或树。在本节中,将分别讨论在树表上进行查找和修改操作的方法。 44 | 45 | ### 分块查找(Block Search) 46 | 47 | 分块查找又称索引顺序查找,是二分查找和顺序查找的一种改进方法,分块查找由于只要求索引表是有序的,对块内节点没有排序要求,因此特别适合于节点动态变化的情况。 48 | 49 | ### 哈希查找(Hash Search) 50 | 51 | 哈希查找是通过计算数据元素的存储地址进行查找的一种方法。 52 | 53 | 哈希查找的操作步骤: 54 | 55 | 1. 用给定的哈希函数构造哈希表 56 | 2. 根据选择的冲突处理方法解决地址冲突 57 | 3. 在哈希表的基础上执行哈希查找 58 | 59 | | 名称 | 最好时间复杂度 | 最坏时间复杂度 | 平均时间复杂度 | 空间复杂度 | 60 | | -------- | -------------- | -------------- | -------------- | ---------- | 61 | | 哈希查找 | O(1) | O(n) | O(1) | O(n) | 62 | -------------------------------------------------------------------------------- /src/core/algorithms/search/binarySearch.ts: -------------------------------------------------------------------------------- 1 | import { gt, lt } from 'core/utils' 2 | // 递归实现 3 | export const recursionBinarySearch = (list: number[], data: number): number => { 4 | if (!list?.length) { 5 | return -1 6 | } 7 | 8 | const sortedList = list.sort((a, b) => a - b) 9 | 10 | const coreSearch = ( 11 | sortedList: number[], 12 | start: number, 13 | end: number, 14 | key: number, 15 | ): number => { 16 | if (gt(start, end)) { 17 | return -1 18 | } 19 | 20 | const mid: number = Math.round(start + (end - start) / 2) 21 | 22 | if (gt(list[mid], key)) { 23 | return coreSearch(sortedList, start, mid - 1, key) 24 | } else if (lt(list[mid], key)) { 25 | return coreSearch(sortedList, mid + 1, end, key) 26 | } else { 27 | return mid 28 | } 29 | } 30 | return coreSearch(sortedList, 0, list.length - 1, data) 31 | } 32 | // 迭代实现 33 | export const loopBinarySearch = (list: number[], data: number): number => { 34 | if (!list?.length) { 35 | return -1 36 | } 37 | 38 | const sortedList: number[] = list.sort((a, b) => a - b) 39 | let start = 0 40 | let end: number = sortedList.length - 1 41 | 42 | while (!gt(start, end)) { 43 | const mid: number = Math.round((start + end) / 2) 44 | if (gt(data, sortedList[mid])) { 45 | start = mid + 1 46 | } else if (lt(data, sortedList[mid])) { 47 | end = mid - 1 48 | } else { 49 | return mid 50 | } 51 | } 52 | 53 | return -1 54 | } 55 | -------------------------------------------------------------------------------- /src/core/algorithms/search/blockSearch.ts: -------------------------------------------------------------------------------- 1 | import { gt, gte, lt, lte, eq, neq, toString } from 'core/utils' 2 | 3 | interface BlockTypes extends Array { 4 | key?: number 5 | } 6 | 7 | export default class BlockSearch { 8 | private blocks: BlockTypes[] = [] 9 | constructor(list: number[] = [], depth = 3) { 10 | if (!list?.length) { 11 | return 12 | } 13 | 14 | list = list.sort((a, b) => a - b) 15 | 16 | if (lte(depth, 0)) { 17 | throw new Error('depth of block must be a positive integer !') 18 | } 19 | 20 | const newList: BlockTypes[] = [] 21 | let blockIndex = 0 22 | 23 | // 先按着间隙分好数列元素,然后每一块加上key,最后再进行排序 24 | for (let i = 0, len: number = list.length; i < len; ++i) { 25 | if (!newList[blockIndex]) { 26 | newList[blockIndex] = [] 27 | } 28 | 29 | if (eq(i % depth, 0)) { 30 | newList[blockIndex].key = list[i] 31 | } 32 | 33 | newList[blockIndex].push(list[i]) 34 | 35 | if (gte(newList[blockIndex].length, depth)) { 36 | blockIndex++ 37 | } 38 | } 39 | 40 | this.blocks = newList.sort((a, b) => a.key - b.key) 41 | } 42 | insert(value: number): BlockSearch { 43 | const targetIndex: number = this.blocks.findIndex(block => 44 | lt(value, block.key), 45 | ) 46 | 47 | this.blocks[targetIndex].push(value) 48 | this.blocks[targetIndex].key = value 49 | 50 | return this 51 | } 52 | remove(value: number): BlockSearch { 53 | const resPos: number[] = this.search(value) 54 | 55 | if (neq(resPos[0], -1)) { 56 | const key = this.blocks[resPos[0]].key 57 | 58 | this.blocks[resPos[0]] = this.blocks[ 59 | resPos[0] 60 | ].filter((data: number) => neq(data, value)) 61 | 62 | if (!this.blocks[resPos[0]].length) { 63 | this.blocks = this.blocks.filter(block => gt(block.length, 0)) 64 | } 65 | 66 | if (eq(value, key)) { 67 | this.blocks[resPos[0]].key = Math.min(...this.blocks[resPos[0]]) 68 | } 69 | } 70 | 71 | return this 72 | } 73 | search(value: number): number[] { 74 | const blockIndex: number = 75 | this.blocks.findIndex(block => gt(block.key, value)) - 1 76 | 77 | if (lt(blockIndex, 0)) { 78 | return [-1] 79 | } 80 | 81 | const resIndex: number = this.blocks[ 82 | blockIndex 83 | ].findIndex((block: number) => eq(block, value)) 84 | 85 | if (resIndex < 0) { 86 | return [-1] 87 | } 88 | 89 | return [blockIndex, resIndex] 90 | } 91 | size(): number { 92 | return this.blocks.flat(Infinity).length 93 | } 94 | blockSize(): number { 95 | return this.blocks.length 96 | } 97 | toString(): string { 98 | return toString(this.blocks) 99 | } 100 | print(): void { 101 | console.log(this.blocks) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/core/algorithms/search/fibonacciSearch.ts: -------------------------------------------------------------------------------- 1 | import { gt, lt, eq, neq } from 'core/utils' 2 | 3 | const fibonacci = (size: number): number[] => { 4 | const list = [1, 1] 5 | 6 | for (let i = 2; i < size; ++i) { 7 | list[i] = list[i - 2] + list[i - 1] 8 | } 9 | 10 | return list 11 | } 12 | 13 | export const loopFibonacciSearch = (list: number[], data: number): number => { 14 | if (!list?.length) { 15 | return -1 16 | } 17 | 18 | let start = 0 19 | let end: number = list.length - 1 20 | const n: number = list.length - 1 21 | let k = 0 22 | const F: number[] = fibonacci(end + 1) 23 | 24 | while (gt(end, F[k] - 1)) { 25 | // 寻找第k项 26 | k++ 27 | } 28 | for (let i = end; i < F[k] - 1; i++) { 29 | // 扩充数组长度至满足斐波那契数组 30 | list[i] = list[end] 31 | } 32 | while (!gt(start, end)) { 33 | const mid: number = start + F[k - 1] - 1 34 | 35 | if (lt(list[mid], data)) { 36 | start = mid + 1 37 | k = k - 2 // 缩减长度为 F[k-2] -1 38 | } else if (gt(list[mid], data)) { 39 | end = mid - 1 40 | k = k - 1 // 缩减长度为 F[k-1] -1 41 | } else { 42 | if (!gt(mid, n)) { 43 | // 找到位置 44 | return mid 45 | } else { 46 | // 大于原始长度,则说明等于数组最后一项 47 | return n 48 | } 49 | } 50 | } 51 | 52 | return -1 53 | } 54 | 55 | // 递归 56 | export const recursionFibonacciSearch = ( 57 | list: number[], 58 | data: number, 59 | ): number => { 60 | if (!list?.length) { 61 | return 62 | } 63 | 64 | const start = 0 65 | const end: number = list.length - 1 66 | const n: number = list.length - 1 67 | let k = 0 68 | const F: number[] = fibonacci(end + 1) 69 | 70 | while (gt(end, F[k] - 1)) { 71 | // 寻找第k项 72 | k++ 73 | } 74 | 75 | for (let i = end; i < F[k] - 1; i++) { 76 | // 扩充数组长度至满足斐波那契数组 77 | list[i] = list[end] 78 | } 79 | 80 | const coreSearch = (start: number, end: number, k: number): number => { 81 | let result: number 82 | let mid: number 83 | 84 | if (eq(start, end)) { 85 | //当开始和结束在同一位置,判断是否找到,未找到返回false,找到继续下面的判断 86 | if (neq(list[start], data)) { 87 | return -1 88 | } 89 | 90 | mid = start 91 | } else { 92 | mid = start + F[k - 1] - 1 93 | } 94 | if (lt(data, list[mid])) { 95 | result = coreSearch(start, mid, k - 1) 96 | } else if (gt(data, list[mid])) { 97 | result = coreSearch(mid + 1, end, k - 2) 98 | } else { 99 | if (lt(mid, n)) { 100 | // 判断找到的位置是否大于原数组长度 101 | return mid 102 | } else { 103 | return n - 1 104 | } 105 | } 106 | 107 | return result 108 | } 109 | 110 | return coreSearch(start, end, k) 111 | } 112 | -------------------------------------------------------------------------------- /src/core/algorithms/search/hashSearch.ts: -------------------------------------------------------------------------------- 1 | import HashTable from 'core/datastructures/hashTable/hashTable' 2 | 3 | export default class HashSearch extends HashTable { 4 | constructor() { 5 | super() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/core/algorithms/search/interpolationSearch.ts: -------------------------------------------------------------------------------- 1 | import { gt, lt } from 'core/utils' 2 | 3 | // 递归实现 4 | export const recursionInterpolationSearch = ( 5 | list: number[], 6 | data: number, 7 | ): number => { 8 | if (!list?.length) { 9 | return 10 | } 11 | 12 | const sortedList = list.sort((a, b) => a - b) 13 | 14 | const coreSearch = ( 15 | sortedList: number[], 16 | start: number, 17 | end: number, 18 | key: number, 19 | ): number => { 20 | if (gt(start, end)) { 21 | return -1 22 | } 23 | 24 | if (!gt(start, end)) { 25 | const mid: number = 26 | Math.round( 27 | ((end - start) * (data - list[start])) / 28 | (list[end] - list[start]), 29 | ) + start 30 | 31 | if (lt(key, sortedList[mid])) { 32 | return coreSearch(sortedList, start, mid - 1, key) 33 | } else if (gt(key, sortedList[mid])) { 34 | return coreSearch(sortedList, mid + 1, end, key) 35 | } else { 36 | return mid 37 | } 38 | } 39 | } 40 | 41 | return coreSearch(sortedList, 0, list.length - 1, data) 42 | } 43 | 44 | // 迭代实现 45 | export const loopInterpolationSearch = ( 46 | list: number[], 47 | data: number, 48 | ): number => { 49 | if (!list?.length) { 50 | return 51 | } 52 | 53 | const sortedList: number[] = list.sort((a, b) => a - b) 54 | let start = 0 55 | let end: number = sortedList.length - 1 56 | 57 | while (!gt(start, end)) { 58 | const mid: number = 59 | Math.round( 60 | ((end - start) * (data - list[start])) / 61 | (list[end] - list[start]), 62 | ) + start 63 | 64 | if (lt(mid, start) || gt(mid, end)) { 65 | break 66 | } 67 | 68 | if (lt(data, sortedList[mid])) { 69 | end = mid - 1 70 | } else if (gt(data, sortedList[mid])) { 71 | start = mid + 1 72 | } else { 73 | return mid 74 | } 75 | } 76 | 77 | return -1 78 | } 79 | -------------------------------------------------------------------------------- /src/core/algorithms/search/orderSearch.ts: -------------------------------------------------------------------------------- 1 | import { eq } from 'core/utils' 2 | 3 | // 常规法 4 | export const orderSearch = (list: number[], data: number): number => { 5 | if (!list?.length) { 6 | return 7 | } 8 | 9 | for (let i = 0, len = list.length; i < len; ++i) { 10 | if (eq(list[i], data)) { 11 | return i 12 | } 13 | } 14 | 15 | return -1 16 | } 17 | // 折半法 18 | export const halfOrderSearch = (list: number[], data: number): number => { 19 | if (!list?.length) { 20 | return 21 | } 22 | 23 | const size: number = Math.ceil(list.length / 2) 24 | let count = 0 25 | 26 | for (let i = 0; i < size; ++i) { 27 | if (eq(list[count], data)) { 28 | return count 29 | } 30 | count++ 31 | 32 | if (eq(list[count], data)) { 33 | return count 34 | } 35 | 36 | count++ 37 | } 38 | 39 | return -1 40 | } 41 | -------------------------------------------------------------------------------- /src/core/algorithms/search/treeSearch.ts: -------------------------------------------------------------------------------- 1 | import BinarySearchTree from 'core/datastructures/tree/binarySearchTree' 2 | 3 | export default class TreeSearch extends BinarySearchTree { 4 | constructor() { 5 | super() 6 | } 7 | // 广度优先遍历 8 | getBFS(): T[] { 9 | const list: T[] = [] 10 | this.breadthFirstSearch((data: T): void => { 11 | list.push(data) 12 | }) 13 | return list 14 | } 15 | // 深度优先遍历 16 | getDFS(): T[] { 17 | const list: T[] = [] 18 | this.depthFirstSearch((data: T): void => { 19 | list.push(data) 20 | }) 21 | return list 22 | } 23 | // 中序遍历 24 | getInOrder(): T[] { 25 | const list: T[] = [] 26 | this.inOrderTraverse((data: T): void => { 27 | list.push(data) 28 | }) 29 | return list 30 | } 31 | // 前序遍历 32 | getPreOrder(): T[] { 33 | const list: T[] = [] 34 | this.preOrderTraverse((data: T): void => { 35 | list.push(data) 36 | }) 37 | return list 38 | } 39 | // 后序遍历 40 | getPostOrder(): T[] { 41 | const list: T[] = [] 42 | this.postOrderTraverse((data: T): void => { 43 | list.push(data) 44 | }) 45 | return list 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/core/algorithms/sorting/bubbleSort.ts: -------------------------------------------------------------------------------- 1 | import { Swap, gt } from 'core/utils' 2 | 3 | const BubbleSort = (list: number[]): number[] => { 4 | const len: number = list.length - 1 5 | 6 | for (let i = 0; i < len; ++i) { 7 | /* 外循环为排序趟数,len个数进行len-1趟 */ 8 | for (let j = 0; j < len - i; ++j) { 9 | /* 内循环为每趟比较的次数,第i趟比较len-i次 */ 10 | if (gt(list[j], list[j + 1])) { 11 | /* 相邻元素比较,若逆序则交换(升序为左大于右,逆序反之) */ 12 | Swap(list, j, j + 1) 13 | } 14 | } 15 | } 16 | 17 | return list 18 | } 19 | export default BubbleSort 20 | -------------------------------------------------------------------------------- /src/core/algorithms/sorting/bucketSort.ts: -------------------------------------------------------------------------------- 1 | import { Swap, lt } from 'core/utils' 2 | 3 | const BucketSort = ( 4 | list: number[], 5 | bucketsCount = 10 /* 默认桶的数量 */, 6 | ): number[] => { 7 | const max: number = Math.max(...list) /* 序列最大数字 */ 8 | const min: number = Math.min(...list) /* 数列最小数字 */ 9 | const bucketsSize: number = 10 | Math.floor((max - min) / bucketsCount) + 1 /* 桶的深度 */ 11 | const __buckets: number[][] = [] /* 空桶 */ 12 | for (let i = 0, len: number = list.length; i < len; ++i) { 13 | const index: number = ~~( 14 | list[i] / bucketsSize 15 | ) /* 骚操作,取数列中最大或最小的序列 */ 16 | if (!__buckets[index]) { 17 | __buckets[index] = [] /* 创建子桶 */ 18 | } 19 | __buckets[index].push(list[i]) 20 | let bLen = __buckets[index].length 21 | while (bLen > 0) { 22 | /* 子桶排序 */ 23 | if (lt(__buckets[index][bLen], __buckets[index][bLen - 1])) { 24 | Swap(__buckets[index], bLen, bLen - 1) 25 | } 26 | bLen-- 27 | } 28 | } 29 | const buckets = [] /* 真实序列 */ 30 | for (let i = 0, len = __buckets.length; i < len; ++i) { 31 | if (__buckets[i]) { 32 | buckets.push(...__buckets[i]) 33 | } 34 | } 35 | return buckets 36 | } 37 | export default BucketSort 38 | -------------------------------------------------------------------------------- /src/core/algorithms/sorting/cocktailSort.ts: -------------------------------------------------------------------------------- 1 | import { Swap, lt, gt } from 'core/utils' 2 | 3 | const CocktailSort = (list: number[]): number[] => { 4 | let i: number, 5 | left = 0, 6 | right: number = list.length - 1 7 | 8 | while (lt(left, right)) { 9 | for (i = left; i < right; ++i) { 10 | if (gt(list[i], list[i + 1])) { 11 | Swap(list, i, i + 1) 12 | } 13 | } 14 | right-- 15 | 16 | for (i = right; i > left; --i) { 17 | if (lt(list[i], list[i - 1])) { 18 | Swap(list, i, i - 1) 19 | } 20 | } 21 | left++ 22 | } 23 | 24 | return list 25 | } 26 | export default CocktailSort 27 | -------------------------------------------------------------------------------- /src/core/algorithms/sorting/countSort.ts: -------------------------------------------------------------------------------- 1 | import { gte, gt } from 'core/utils' 2 | 3 | const CountSort = (list: number[]): number[] => { 4 | const C: number[] = [] 5 | for (let i = 0, iLen: number = list.length; i < iLen; ++i) { 6 | const j: number = list[i] 7 | 8 | if (gte(C[j], 1)) { 9 | C[j]++ 10 | } else { 11 | C[j] = 1 12 | } 13 | } 14 | 15 | const D: number[] = [] 16 | for (let j = 0, jLen: number = C.length; j < jLen; ++j) { 17 | if (C[j]) { 18 | while (gt(C[j], 0)) { 19 | D.push(j) 20 | C[j]-- 21 | } 22 | } 23 | } 24 | 25 | return D 26 | } 27 | export default CountSort 28 | -------------------------------------------------------------------------------- /src/core/algorithms/sorting/heapSort.ts: -------------------------------------------------------------------------------- 1 | import { Swap, gt, lt, eq } from 'core/utils' 2 | // 使数组变为堆 3 | const heapify = (list: number[], index: number, heapSize: number): void => { 4 | let largest = index 5 | const left = 2 * index + 1 6 | const right = 2 * index + 2 7 | 8 | if (lt(left, heapSize) && gt(list[left], list[index])) { 9 | largest = left 10 | } 11 | 12 | if (lt(right, heapSize) && gt(list[right], list[largest])) { 13 | largest = right 14 | } 15 | 16 | if (!eq(largest, index)) { 17 | Swap(list, index, largest) 18 | heapify(list, largest, heapSize) 19 | } 20 | } 21 | 22 | // 创建最大堆 23 | const buildMaxHeap = (list: number[]): number[] => { 24 | for (let i = Math.floor(list.length / 2); i >= 0; i -= 1) { 25 | heapify(list, i, list.length) 26 | } 27 | 28 | return list 29 | } 30 | 31 | // 堆排序也是一种很高效的算法,因其把数组当作二叉树来排序而得名。这个算法会根据以下信息,把数组当作二叉树来管理。 32 | // 1. 索引0是树的根节点; 33 | // 2. 除根节点外,任意节点N的父节点是N/2; 34 | // 3. 节点L的左子节点是2*L; 35 | // 4. 节点R的右子节点是2*R+1。 36 | 37 | const HeapSort = (list: number[]): number[] => { 38 | // 最大堆排序 39 | let heapSize = list.length 40 | 41 | buildMaxHeap(list) // 创建最大堆 42 | 43 | while (heapSize > 1) { 44 | Swap(list, 0, --heapSize) // 交换堆里第一个元素(数组中较大的值)和最后一个元素的位置。这样,最大的值就会出现在它已排序的位置。 45 | heapify(list, 0, heapSize) // 当堆属性失去时,重新将数组转换成堆 46 | } 47 | 48 | return list 49 | } 50 | 51 | export default HeapSort 52 | -------------------------------------------------------------------------------- /src/core/algorithms/sorting/insertionSort.ts: -------------------------------------------------------------------------------- 1 | import { gt, gte } from 'core/utils' 2 | 3 | const InsertionSort = (list: number[]): number[] => { 4 | const len: number = list.length 5 | let j: number, temp: number 6 | 7 | for (let i = 0; i < len; ++i) { 8 | j = i - 1 // 取出前一个位置 9 | temp = list[i] // 缓存当前位置数字 10 | 11 | while (gte(j, 0) && gt(list[j], temp)) { 12 | // 如果前一个位置的数组大于当前数字 13 | list[j + 1] = list[j] // 将该元素移到下一位置 14 | j-- // 保存当前 - 1 的 位置 15 | } 16 | list[j + 1] = temp // 将新元素插入到该位置 17 | } 18 | 19 | return list 20 | } 21 | export default InsertionSort 22 | -------------------------------------------------------------------------------- /src/core/algorithms/sorting/mergeSort.ts: -------------------------------------------------------------------------------- 1 | import { lt, lte } from 'core/utils' 2 | 3 | const Merge = (left: number[], right: number[]): number[] => { 4 | const resArr = [] 5 | 6 | while (left.length && right.length) { 7 | if (lt(left[0], right[0])) { 8 | resArr.push(left.shift()) 9 | } else { 10 | resArr.push(right.shift()) 11 | } 12 | } 13 | 14 | return resArr.concat(left, right) 15 | } 16 | 17 | const MergeSort = (list: number[]): number[] => { 18 | if (!list || lte(list.length, 1)) { 19 | return list 20 | } 21 | 22 | const middle: number = Math.floor(list.length / 2) 23 | const left: number[] = list.slice(0, middle) 24 | const right: number[] = list.slice(middle) 25 | 26 | return Merge(MergeSort(left), MergeSort(right)) 27 | } 28 | export default MergeSort 29 | -------------------------------------------------------------------------------- /src/core/algorithms/sorting/quickSort.ts: -------------------------------------------------------------------------------- 1 | import { lt, eq } from 'core/utils' 2 | 3 | export const QuickSort = (list: number[]): number[] => { 4 | const len: number = list.length 5 | 6 | if (lt(len, 2)) { 7 | return list 8 | } 9 | 10 | const pivot: number = list[0] 11 | const left: number[] = [] 12 | const right: number[] = [] 13 | 14 | for (let i = 1; i < len; ++i) { 15 | if (lt(list[i], pivot)) { 16 | left.push(list[i]) 17 | } else { 18 | right.push(list[i]) 19 | } 20 | } 21 | 22 | return [...QuickSort(left), pivot, ...QuickSort(right)] 23 | } 24 | export const QuickSort3 = (list: number[]): number[] => { 25 | const len: number = list.length 26 | 27 | if (lt(len, 2)) { 28 | return list 29 | } 30 | 31 | const left: number[] = [] 32 | const center: number[] = [] 33 | const right: number[] = [] 34 | const pivot: number = list[0] 35 | 36 | for (let i = 0; i < len; ++i) { 37 | if (lt(list[i], pivot)) { 38 | left.push(list[i]) 39 | } else if (eq(list[i], pivot)) { 40 | center.push(list[i]) 41 | } else { 42 | right.push(list[i]) 43 | } 44 | } 45 | 46 | return [...QuickSort3(left), ...center, ...QuickSort3(right)] 47 | } 48 | -------------------------------------------------------------------------------- /src/core/algorithms/sorting/radixSort.ts: -------------------------------------------------------------------------------- 1 | import { gt, toString } from 'core/utils' 2 | 3 | const LSDRadixSort = (list: number[]): number[] => { 4 | const max: number = Math.max(...list) /* 获取最大值 */ 5 | let digit: number = toString(max).length /* 获取最大值位数 */ 6 | let start = 1 /* 桶编号 */ 7 | let buckets: number[][] = [] /* 空桶 */ 8 | 9 | while (gt(digit, 0)) { 10 | start *= 10 11 | 12 | /* 入桶 */ 13 | for (let i = 0; i < list.length; i++) { 14 | const index = list[i] % start 15 | 16 | if (!buckets[index]) { 17 | buckets[index] = [] 18 | } 19 | 20 | buckets[index].push(list[i]) /* 往不同桶里添加数据 */ 21 | } 22 | 23 | list = [] 24 | 25 | /* 出桶 */ 26 | for (let i = 0; i < buckets.length; i++) { 27 | if (buckets[i]) { 28 | list = list.concat(buckets[i]) 29 | } 30 | } 31 | 32 | buckets = [] 33 | 34 | digit-- 35 | } 36 | 37 | return list 38 | } 39 | export default LSDRadixSort 40 | -------------------------------------------------------------------------------- /src/core/algorithms/sorting/selectionSort.ts: -------------------------------------------------------------------------------- 1 | import { Swap, lt } from 'core/utils' 2 | 3 | const SelectionSort = (list: number[]): number[] => { 4 | const len = list.length 5 | let min: number 6 | 7 | for (let i = 0; i < len - 1; ++i) { 8 | min = i /* 初始化未排序序列中最小数据数组下标 */ 9 | 10 | for (let j = i + 1; j < len; ++j) { 11 | /* 访问未排序的元素 */ 12 | if (lt(list[j], list[min])) { 13 | /* 找到目前最小值 */ 14 | min = j /* 记录最小值 */ 15 | } 16 | } 17 | 18 | Swap(list, i, min) /* 交换位置 */ 19 | } 20 | return list 21 | } 22 | export default SelectionSort 23 | -------------------------------------------------------------------------------- /src/core/algorithms/sorting/shellSort.ts: -------------------------------------------------------------------------------- 1 | import { lt, gt } from 'core/utils' 2 | 3 | const ShellSort = (list: number[]): number[] => { 4 | const gaps: number[] = [5, 3, 1] // 定义步长以及分割次数 5 | const len: number = list.length 6 | 7 | for (let g = 0, gLen: number = gaps.length; g < gLen; ++g) { 8 | // 按步长的长度K,对数组进行K趟排序 9 | for (let i = gaps[g]; i < len; ++i) { 10 | const temp = list[i] 11 | let j: number 12 | 13 | for ( 14 | j = i; 15 | !lt(j, gaps[g]) && gt(list[j - gaps[g]], list[i]); 16 | j -= gaps[g] 17 | ) { 18 | list[j] = list[j - gaps[g]] 19 | } 20 | 21 | list[j] = temp 22 | } 23 | } 24 | 25 | return list 26 | } 27 | export default ShellSort 28 | -------------------------------------------------------------------------------- /src/core/datastructures/dictionary/README.md: -------------------------------------------------------------------------------- 1 | # 字典(Dictionary) 2 | 3 | > 维基百科: 4 | > 5 | > 在计算机科学中,关联数组(英语:Associative Array),又称映射(Map)、字典(Dictionary)是一个抽象的数据结构,它包含着类似于(键,值)的有序对。 6 | > 7 | > 一个关联数组中的有序对可以重复(如 C++中的 multimap)也可以不重复(如 C++中的 map)。 8 | > 9 | > 这种数据结构包含以下几种常见的操作: 10 | > 11 | > - 向关联数组添加配对 12 | > - 从关联数组内删除配对 13 | > - 修改关联数组内的配对 14 | > - 根据已知的键寻找配对 15 | 16 | ### 核心方法 17 | 18 | | 操作 | 描述 | 19 | | :------------: | :---------------------------------------------------------: | 20 | | set(key,value) | 向字典中添加新元素 | 21 | | remove(key) | 通过使用键值来从字典中移除键值对应的数据值 | 22 | | has(key) | 如果某个键值存在于这个字典中,则返回 true,反之则返回 false | 23 | | get(key) | 通过键值查找特定的数值并返回 | 24 | | clear() | 将这个字典中的所有元素全部删除 | 25 | | size() | 返回字典所包含元素的数量。与数组的 length 属性类似 | 26 | | keys() | 将字典所包含的所有键名以数组形式返回 | 27 | | values() | 将字典所包含的所有数值以数组形式返回 | 28 | -------------------------------------------------------------------------------- /src/core/datastructures/dictionary/dictionary.ts: -------------------------------------------------------------------------------- 1 | import { toString, isExist, eq } from 'core/utils' 2 | import { ValuePair, tableType } from 'core/node' 3 | 4 | /** 5 | * 在计算机科学中,关联数组(英语:Associative Array),又称映射(Map)、字典(Dictionary)是一个抽象的数据结构,它包含着类似于(键,值)的有序对。 6 | * 一个关联数组中的有序对可以重复(如C++中的multimap)也可以不重复(如C++中的map)。 7 | */ 8 | export default class Dictionary { 9 | private table: tableType = {} // 数据源 10 | constructor() {} 11 | 12 | // 向字典中添加新元素 13 | set(key: K, value: V): Dictionary { 14 | if (key) { 15 | const tableKey = toString(key) 16 | this.table[tableKey] = new ValuePair(key, value) 17 | } 18 | 19 | return this 20 | } 21 | 22 | // 通过键值查找特定的数值并返回 23 | get(key: K): V { 24 | return this.table[toString(key)]?.value 25 | } 26 | 27 | // 如果某个键值存在于这个字典中,则返回true,反之则返回false 28 | hasKey(key: K): boolean { 29 | return isExist(this.table[toString(key)]) 30 | } 31 | 32 | // 通过使用键值来从字典中移除键值对应的数据值 33 | remove(key: K): Dictionary { 34 | if (this.hasKey(key)) { 35 | delete this.table[toString(key)] 36 | } 37 | 38 | return this 39 | } 40 | 41 | // 将字典所包含的所有数值以数组形式返回 42 | values(): V[] { 43 | return this.keyValues().map( 44 | (valuePair: ValuePair) => valuePair.value, 45 | ) 46 | } 47 | 48 | // 将字典所包含的所有键名以数组形式返回 49 | keys(): K[] { 50 | return this.keyValues().map( 51 | (valuePair: ValuePair) => valuePair.key, 52 | ) 53 | } 54 | 55 | // 将字典所包含的所有键与值以数组形式返回 56 | keyValues(): ValuePair[] { 57 | return Object.values(this.table) 58 | } 59 | 60 | // 字典循环forEach 61 | forEach(callbackFn: (key: K, value: V) => unknown): void { 62 | const valuePairs = this.keyValues() 63 | 64 | for (let i = 0, len = valuePairs.length; i < len; ++i) { 65 | callbackFn(valuePairs[i].key, valuePairs[i].value) 66 | } 67 | } 68 | 69 | // 字典循环map 70 | map(callbackFn: (key: K, value: V) => unknown): unknown[] { 71 | const valuePairs = this.keyValues() 72 | const resList: unknown[] = [] 73 | 74 | for (let i = 0, len = valuePairs.length; i < len; ++i) { 75 | const result = callbackFn(valuePairs[i].key, valuePairs[i].value) 76 | resList.push(result) 77 | } 78 | 79 | return resList 80 | } 81 | 82 | // 字典循环filter 83 | filter(callbackFn: (key: K, value: V) => unknown): unknown[] { 84 | const valuePairs = this.keyValues() 85 | const resList: unknown[] = [] 86 | 87 | for (let i = 0, len = valuePairs.length; i < len; ++i) { 88 | const result = callbackFn(valuePairs[i].key, valuePairs[i].value) 89 | 90 | if (!result) { 91 | continue 92 | } 93 | 94 | resList.push(result) 95 | } 96 | 97 | return resList 98 | } 99 | 100 | // 返回字典所包含元素的数量。与数组的length属性类似 101 | size(): number { 102 | return Object.keys(this.table).length 103 | } 104 | 105 | // 是否为空 106 | isEmpty(): boolean { 107 | return eq(this.size(), 0) 108 | } 109 | 110 | // 将这个字典中的所有元素全部删除 111 | clear(): void { 112 | this.table = {} 113 | } 114 | 115 | // 打印字典 116 | print(): void { 117 | console.log(this.table) 118 | } 119 | 120 | // 打印字典字符串 121 | toString(): string { 122 | if (this.isEmpty()) { 123 | return '' 124 | } 125 | 126 | const objString = this.map( 127 | (key, value) => `[${key}: ${value}]`, 128 | ).toString() 129 | 130 | return objString 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/core/datastructures/graph/README.md: -------------------------------------------------------------------------------- 1 | # 图(Graph) 2 | 3 | > 图是网络结构的抽象模型。图是一组由边连接的节点(或顶点)。学习图是重要的,因为任 何二元关系都可以用图来表示。 4 | 5 | ### 概念 6 | 7 | 一个图 G = (V, E)由以下元素组成 8 | 9 |  V:一组顶点 10 | 11 |  E:一组边,连接 V 中的顶点 12 | 13 | 相邻顶点:由一条边连接在一起的顶点 14 | 15 | 一个顶点的度:其相邻顶点的数量 16 | 17 | 路径:顶点 v1, v2,…,vk 的一个连续序列,其中 vi 和 vi+1 是相邻的 18 | 19 | 简单路径:求不包含重复的顶点 20 | 21 | 无环:不存在环 22 | 23 | 连 通:每两个顶点间都存在路径 24 | 25 | 强连通:每两个顶点间在双向上都存在路径 26 | 27 | 稀疏图:不是强连通的图 28 | 29 | 邻接表:由图中每个顶点的相邻顶 点列表所组成(存在好几种方式来表示这种数据结构) 30 | 31 | 加权:带有权重 32 | 33 | 有向图:边没有方向的图 34 | 35 | 无向图:边有方向的图 36 | 37 | ### 核心方法 38 | 39 | | 操作 | 描述 | 40 | | ------------- | ---------------------- | 41 | | aaddVertex(v) | 向图中添加一个新的顶点 | 42 | | addEdge(a, b) | 添加顶点之间的边 | 43 | -------------------------------------------------------------------------------- /src/core/datastructures/graph/graph.ts: -------------------------------------------------------------------------------- 1 | import Dictionary from 'core/datastructures/dictionary/dictionary' 2 | 3 | export default class Graph { 4 | private vertices: T[] = [] // 存储图中所有顶点的名字 5 | private adjList: Dictionary = new Dictionary() // 字典存储邻接表 6 | 7 | constructor(private isDirected = false) {} 8 | 9 | // 向图中添加一个新的顶点 10 | addVertex(v: T): Graph { 11 | if (!this.vertices.includes(v)) { 12 | this.vertices.push(v) // 将该顶点添加到顶点列表中 13 | this.adjList.set(v, []) // 设置顶点v作为键对应的字典值为一个空数组 14 | } 15 | 16 | return this 17 | } 18 | 19 | // 添加顶点之间的边 20 | addEdge(a: T, b: T): Graph { 21 | if (!this.adjList.get(a)) { 22 | this.addVertex(a) 23 | } 24 | 25 | if (!this.adjList.get(b)) { 26 | this.addVertex(b) 27 | } 28 | 29 | this.adjList.get(a).push(b) 30 | 31 | if (!this.isDirected) { 32 | // 当图为无向图时,再添加一条边 33 | this.adjList.get(b).push(a) 34 | } 35 | 36 | return this 37 | } 38 | 39 | // 获取顶点 40 | getVertices(): T[] { 41 | return this.vertices 42 | } 43 | 44 | // 邻接表 45 | getAdjList(): Dictionary { 46 | return this.adjList 47 | } 48 | 49 | toString(): string { 50 | let res = '' 51 | 52 | for (let i = 0, len: number = this.vertices.length; i < len; ++i) { 53 | res += this.vertices[i] + ' -> ' 54 | const neighbors = this.adjList.get(this.vertices[i]) 55 | for (let j = 0; j < neighbors.length; j++) { 56 | res += neighbors[j] + ' ' 57 | } 58 | res += '\n' 59 | } 60 | 61 | return res 62 | } 63 | 64 | print(): void { 65 | console.log({ 66 | vertices: this.getVertices(), 67 | adjList: this.getAdjList(), 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/core/datastructures/hashTable/README.md: -------------------------------------------------------------------------------- 1 | # 散列表(HashTable) 2 | 3 | > [维基百科](https://zh.wikipedia.org/wiki/哈希表) 4 | > 5 | > 散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存储存位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。 6 | > 7 | > 散列算法的作用是尽可能快地在数据结构中找到一个值。 8 | 9 | ### 基本概念 10 | 11 | - 若关键字为**k**,则其值存放在**f(k)**的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系**f**为散列函数,按这个思想建立的表为散列表。 12 | - 对不同的关键字可能得到同一散列地址,即**k1\*\***≠k2**,而**f(k1)=f(k2)**,这种现象称为冲突(英语:Collision)。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数**f(k)\*\*和处理冲突的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为散列表,这一映射过程称为散列造表或散列,所得的存储位置称散列地址。 13 | - 若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少冲突。 14 | 15 | ### 常用方法 16 | 17 | - 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即 H(key)=key 或 H(key) = a·key + b,其中 a 和 b 为常数(这种散列函数叫做自身函数)。若其中 H(key)中已经有值了,就往下一个找,直到 H(key)中没有值了,就放进去。 18 | - 数字分析法:分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。 19 | - 平方取中法:当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址。 20 | - 折叠法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。 21 | - 随机数法:选择一随机函数,取关键字的随机值作为散列地址,即 H(key)=random(key)其中 random 为随机函数,通常用于关键字长度不等的场合。 22 | - 除留余数法:取关键字被某个不大于散列表表长 m 的数 p 除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对 p 的选择很重要,一般取素数或 m,若 p 选的不好,容易产生同义词。+9 23 | 24 | ### 处理冲突 25 | 26 | - 开放寻址法:Hi=(H(key) + di) MOD m,i=1,2,…,k(k<=m-1),其中 H(key)为散列函数,m 为散列表长,di 为增量序列,可有下列三种取法: 27 | 1. di=1,2,3,…,m-1,称线性探测再散列; 28 | 2. di=1^2,-1^2,2^2,-2^2,⑶^2,…,±(k)^2,(k<=m/2)称二次探测再散列; 29 | 3. di=伪随机数序列,称伪随机探测再散列。 30 | - 再散列法:Hi=RHi(key),i=1,2,…,k RHi 均是不同的散列函数,即在同义词产生地址冲突时计算另一个散列函数地址,直到冲突不再发生,这种方法不易产生“聚集”,但增加了计算时间。 31 | - 链地址法(拉链法) 32 | - 建立一个公共溢出区 33 | 34 | ### 核心方法 35 | 36 | | 操作 | 描述 | 37 | | -------------- | ---------------------------------------- | 38 | | put(key,value) | 向散列表增加一个新的项(也能更新散列表) | 39 | | remove(key) | 根据键值从散列表中移除值 | 40 | | get(key) | 返回根据键值检索到的特定的值 | 41 | -------------------------------------------------------------------------------- /src/core/datastructures/hashTable/hashTable.ts: -------------------------------------------------------------------------------- 1 | import { toString, dataType, isExistAll, eq } from 'core/utils' 2 | import { ValuePair, tableType } from 'core/node' 3 | 4 | /** 5 | * 散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存储存位置的数据结构。 6 | * 也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。 7 | * 这个映射函数称做散列函数,存放记录的数组称做散列表。 8 | * 散列算法的作用是尽可能快地在数据结构中找到一个值。 9 | */ 10 | 11 | type KeyType = number | string 12 | 13 | export default class HashTable { 14 | private table: tableType = {} // 数据源 15 | constructor() {} 16 | // 散列函数 17 | // 给定一个key参数,我们就能根据组成key的每个字符的ASCII码值的和得到一个数字。 18 | private loseloseHashCode(key: KeyType, div = 37): KeyType { 19 | if (eq(dataType(key), 'number')) { 20 | return key 21 | } 22 | 23 | const tableKey: string = toString(key) 24 | 25 | let hash = 0 26 | for (let i = 0, len = tableKey.length; i < len; ++i) { 27 | hash += tableKey.charCodeAt(i) 28 | } 29 | 30 | return hash % div 31 | } 32 | // 创建hash 33 | hashCode(key: KeyType, div = 37): KeyType { 34 | return this.loseloseHashCode(key, div) 35 | } 36 | // 向散列表增加一个新的项 37 | put(key: KeyType, value: V): HashTable { 38 | if (isExistAll(key, value)) { 39 | const position: KeyType = this.hashCode(key) // 查看表中位置 40 | this.table[position] = new ValuePair(key, value) // 赋值 41 | } 42 | 43 | return this 44 | } 45 | // 返回根据键值检索到的特定的值。 46 | get(key: KeyType): V { 47 | return this.table[this.hashCode(key)]?.value 48 | } 49 | // 根据键值从散列表中移除值。 50 | remove(key: KeyType): HashTable { 51 | const hash: KeyType = this.hashCode(key) 52 | const valuePair = this.table[hash] 53 | if (valuePair) { 54 | delete this.table[hash] 55 | } 56 | return this 57 | } 58 | 59 | // 将表中所包含的所有数值以数组形式返回 60 | values(): V[] { 61 | return this.keyValues().map( 62 | (valuePair: ValuePair) => valuePair.value, 63 | ) 64 | } 65 | 66 | // 将表中所包含的所有键名以数组形式返回 67 | keys(): KeyType[] { 68 | return this.keyValues().map( 69 | (valuePair: ValuePair) => valuePair.key, 70 | ) 71 | } 72 | 73 | // 将表中所包含的所有键与值以数组形式返回 74 | keyValues(): ValuePair[] { 75 | return Object.values(this.table) 76 | } 77 | 78 | // 表中循环forEach 79 | forEach(callbackFn: (key: KeyType, value: V) => unknown): void { 80 | const valuePairs = this.keyValues() 81 | for (let i = 0, len = valuePairs.length; i < len; ++i) { 82 | callbackFn(valuePairs[i].key, valuePairs[i].value) 83 | } 84 | } 85 | 86 | // 表中循环map 87 | map(callbackFn: (key: KeyType, value: V) => unknown): unknown[] { 88 | const valuePairs = this.keyValues() 89 | const resList: unknown[] = [] 90 | for (let i = 0, len = valuePairs.length; i < len; ++i) { 91 | const result = callbackFn(valuePairs[i].key, valuePairs[i].value) 92 | resList.push(result) 93 | } 94 | return resList 95 | } 96 | 97 | // 表中循环filter 98 | filter(callbackFn: (key: KeyType, value: V) => unknown): unknown[] { 99 | const valuePairs = this.keyValues() 100 | const resList: unknown[] = [] 101 | for (let i = 0, len = valuePairs.length; i < len; ++i) { 102 | const result = callbackFn(valuePairs[i].key, valuePairs[i].value) 103 | if (!result) { 104 | continue 105 | } 106 | resList.push(result) 107 | } 108 | return resList 109 | } 110 | 111 | // 是否为空 112 | isEmpty(): boolean { 113 | return eq(this.size(), 0) 114 | } 115 | 116 | // 返回表所包含元素的数量。与数组的length属性类似 117 | size(): number { 118 | return Object.keys(this.table).length 119 | } 120 | 121 | // 删除表内元素 122 | clear(): void { 123 | this.table = {} 124 | } 125 | 126 | // 获取当前表 127 | getTable(): tableType { 128 | return this.table 129 | } 130 | 131 | // 打印当前表 132 | print(): void { 133 | console.log(this.table) 134 | } 135 | 136 | // 打印当前表字符串 137 | toString(): string { 138 | if (this.isEmpty()) { 139 | return '' 140 | } 141 | const objString = this.map( 142 | (key, value) => `[#${key}: ${value}]`, 143 | ).toString() 144 | return objString 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/core/datastructures/heap/README.md: -------------------------------------------------------------------------------- 1 | # 堆 2 | 3 | > 维基百科: 4 | > 5 | > 堆(英语:Heap):堆是树状结构,给定堆中任意节点 P 和 C,若 P 是 C 的母节点,那么 P 的值会小于等于(或大于等于)C 的值。 6 | > 7 | > 最小堆(min heap):若母节点的值恒小于等于子节点的值,此堆称为最小堆(min heap); 8 | > 9 | > 最大堆(max heap:反之,若母节点的值恒大于等于子节点的值,此堆称为最大堆(max heap)。 10 | > 11 | > 根节点(root node):在堆中最顶端的那一个节点,称作根节点(root node),根节点本身没有母节点(parent node)。 12 | 13 | ### 性质 14 | 15 | - 任意节点小于(或大于)它的所有后裔,最小元(或最大元)在堆的根上(**堆序性**)。 16 | - 堆总是一棵[完全树](https://zh.wikipedia.org/wiki/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)。即除了最底层,其他层的节点都被元素填满,且最底层尽可能地从左到右填入。 17 | - 将根节点最大的堆叫做**最大堆**或**大根堆**,根节点最小的堆叫做**最小堆**或**小根堆**。 18 | 19 | ### 核心方法 20 | 21 | | 操作 | 描述 | 22 | | :----------------: | :------------------: | 23 | | insert(element(s)) | 向堆中插入一个新元素 | 24 | | find(element(s)) | 在堆中寻找指定元素 | 25 | | remove(element(s)) | 在堆中删除指定元素 | 26 | | peek() | 查看堆顶 | 27 | | poll() | 将堆尾换到堆头 | 28 | -------------------------------------------------------------------------------- /src/core/datastructures/heap/heap.ts: -------------------------------------------------------------------------------- 1 | import { Swap, eq, lt, gt, toString } from 'core/utils' 2 | /** 3 | * 堆(英语:Heap):给定堆中任意节点P和C,若P是C的母节点,那么P的值会小于等于(或大于等于)C的值。 4 | * 最小堆(min heap):若母节点的值恒小于等于子节点的值,此堆称为最小堆(min heap); 5 | * 最大堆(max heap:反之,若母节点的值恒大于等于子节点的值,此堆称为最大堆(max heap)。 6 | * 根节点(root node):在堆中最顶端的那一个节点,称作根节点(root node),根节点本身没有母节点(parent node)。 7 | */ 8 | export class Heap { 9 | private items: T[] = [] // 保存堆结构的元素 10 | constructor() { 11 | if (eq(new.target, Heap)) { 12 | throw new TypeError('Cannot construct Heap instance directly') 13 | } 14 | } 15 | private getLeftChildIndex(parentIndex: number): number { 16 | // 获取左子节点下标 17 | return 2 * parentIndex + 1 18 | } 19 | 20 | private getRightChildIndex(parentIndex: number): number { 21 | // 获取右子节点下标 22 | return 2 * parentIndex + 2 23 | } 24 | 25 | private getParentIndex(childIndex: number): number { 26 | // 获取父节点下标 27 | if (eq(childIndex, 0)) { 28 | return undefined 29 | } 30 | return Math.floor((childIndex - 1) / 2) 31 | } 32 | 33 | private hasParent(childIndex: number): boolean { 34 | // 判断是否有父节点 35 | return this.getParentIndex(childIndex) >= 0 36 | } 37 | 38 | private hasLeftChild(parentIndex: number): boolean { 39 | // 判断是否有左子节点 40 | return this.getLeftChildIndex(parentIndex) < this.items.length 41 | } 42 | 43 | private hasRightChild(parentIndex: number): boolean { 44 | // 判断是否有右子节点 45 | return this.getRightChildIndex(parentIndex) < this.items.length 46 | } 47 | 48 | private getLeftChild(parentIndex: number): T { 49 | // 获取左子节点 50 | return this.items[this.getLeftChildIndex(parentIndex)] 51 | } 52 | 53 | private getRightChild(parentIndex: number): T { 54 | // 获取右子节点 55 | return this.items[this.getRightChildIndex(parentIndex)] 56 | } 57 | 58 | private getParent(childIndex: number): T { 59 | // 获取父节点 60 | return this.items[this.getParentIndex(childIndex)] 61 | } 62 | 63 | size(): number { 64 | return this.items.length 65 | } 66 | 67 | insert(element: T): Heap { 68 | // 添加元素 69 | this.items.push(element) 70 | this.heapifyUp() 71 | 72 | return this 73 | } 74 | 75 | find(element: T): number[] { 76 | // 寻找指定元素 77 | const foundList: number[] = [] 78 | 79 | for (let i = 0, len: number = this.size(); i < len; ++i) { 80 | if (eq(element, this.items[i])) { 81 | foundList.push(i) 82 | } 83 | } 84 | 85 | return foundList 86 | } 87 | 88 | remove(element: T): Heap { 89 | // 删除指定元素 90 | for (let i = 0, len: number = this.find(element).length; i < len; ++i) { 91 | const tail: number = this.find(element).pop() 92 | 93 | if (eq(tail, this.size() - 1)) { 94 | this.items.pop() 95 | } else { 96 | this.items[tail] = this.items.pop() 97 | const parentItem = this.getParent(tail) 98 | 99 | if ( 100 | this.hasLeftChild(tail) && 101 | (!parentItem || 102 | this.heapCompare(parentItem, this.items[tail])) 103 | ) { 104 | this.heapifyDown(tail) 105 | } else { 106 | this.heapifyUp(tail) 107 | } 108 | } 109 | } 110 | 111 | return this 112 | } 113 | 114 | peek(): T { 115 | // 查看堆顶 116 | if (this.isEmpty()) { 117 | return null 118 | } 119 | 120 | return this.items[0] 121 | } 122 | 123 | poll(): T { 124 | // 将堆尾换到堆头 125 | if (this.isEmpty()) { 126 | return null 127 | } 128 | 129 | if (eq(this.size(), 1)) { 130 | return this.items.pop() 131 | } 132 | 133 | const item: T = this.items[0] 134 | this.items[0] = this.items.pop() 135 | this.heapifyDown() 136 | 137 | return item 138 | } 139 | 140 | private heapifyUp(startIndex?: number): void { 141 | // 下标上浮 142 | let currentIndex: number = startIndex || this.size() - 1 143 | while ( 144 | this.hasParent(currentIndex) && 145 | !this.heapCompare( 146 | this.getParent(currentIndex), 147 | this.items[currentIndex], 148 | ) 149 | ) { 150 | Swap(this.items, currentIndex, this.getParentIndex(currentIndex)) 151 | currentIndex = this.getParentIndex(currentIndex) 152 | } 153 | } 154 | 155 | private heapifyDown(startIndex = 0): void { 156 | // 下标下沉 157 | let currentIndex: number = startIndex 158 | let nextIndex: number | null = null 159 | while (this.hasLeftChild(currentIndex)) { 160 | if ( 161 | this.hasRightChild(currentIndex) && 162 | this.heapCompare( 163 | this.getRightChild(currentIndex), 164 | this.getLeftChild(currentIndex), 165 | ) 166 | ) { 167 | nextIndex = this.getRightChildIndex(currentIndex) 168 | } else { 169 | nextIndex = this.getLeftChildIndex(currentIndex) 170 | } 171 | 172 | if ( 173 | this.heapCompare( 174 | this.items[currentIndex], 175 | this.items[nextIndex], 176 | ) 177 | ) { 178 | break 179 | } 180 | 181 | Swap(this.items, currentIndex, nextIndex) 182 | 183 | currentIndex = nextIndex 184 | } 185 | } 186 | 187 | heapCompare(data1: T, data2: T): boolean { 188 | console.log(data1) 189 | console.log(data2) 190 | // 设置堆类型的对比方法 191 | throw new Error('Need to rewrite !') 192 | } 193 | 194 | isEmpty(): boolean { 195 | return eq(this.size(), 0) 196 | } 197 | 198 | toString(): string { 199 | return toString(this.items) 200 | } 201 | 202 | print(): void { 203 | console.log(this.items) 204 | } 205 | } 206 | 207 | export class MinHeap extends Heap { 208 | // 最小堆 209 | constructor() { 210 | super() 211 | } 212 | heapCompare(data1: T, data2: T): boolean { 213 | return lt(data1, data2) 214 | } 215 | } 216 | 217 | export class MaxHeap extends Heap { 218 | // 最大堆 219 | constructor() { 220 | super() 221 | } 222 | heapCompare(data1: T, data2: T): boolean { 223 | return gt(data1, data2) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/core/datastructures/linkedList/README.md: -------------------------------------------------------------------------------- 1 | # 链表 2 | 3 | ## 链表(linked list) 4 | 5 | > 链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。 6 | > 7 | > 每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。 8 | 9 | ### 核心方法 10 | 11 | | 方法 | 描述 | 12 | | ---------------------------- | ------------------------------------------------------ | 13 | | append(element(s)) | 向链表尾部添加一个新的元素。 | 14 | | insert(position, element(s)) | 向链表的特定位置插入一个新的元素。 | 15 | | getAt(position) | 从链表的特定位置返回元素。 | 16 | | removeAt(position) | 从链表的特定位置移除元素。 | 17 | | indexOf(element(s)) | 返回元素在链表中的索引。如果列表中没有该元素则返回-1。 | 18 | | remove(element(s)) | 从列表中移除一个元素。 | 19 | 20 | ## 双向链表(DoublyLinkedList) 21 | 22 | > 双向链表和普通链表的区别在于,在链表中, 23 | > 24 | > 一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素, 25 | > 26 | > 另一个链向前一个元素, 27 | 28 | ### 核心方法 29 | 30 | | 方法 | 描述 | 31 | | ---------------------------- | ------------------------------------------------------ | 32 | | append(element(s)) | 向链表添加一个新的元素。 | 33 | | insert(position, element(s)) | 向链表的特定位置插入一个新的元素。 | 34 | | getTail() | 返回尾部信息。 | 35 | | removeAt(position) | 删除指定位置元素。 | 36 | | indexOf(element(s)) | 返回元素在链表中的索引。如果列表中没有该元素则返回-1。 | 37 | | remove(element(s)) | 从列表中移除一个元素。 | 38 | 39 | ## 循环链表(CircularLinkedList) 40 | 41 | > 循环链表(CircularLinkedList):循环链表跟普通链表的区别就是循环链表是头尾相连的 42 | 43 | ### 核心方法 44 | 45 | | 方法 | 描述 | 46 | | ---------------------------- | ---------------------------------- | 47 | | append(element(s)) | 向链表尾部添加一个新的元素。 | 48 | | insert(position, element(s)) | 向链表的特定位置插入一个新的元素。 | 49 | | removeAt(position) | 从链表的特定位置移除元素。 | 50 | -------------------------------------------------------------------------------- /src/core/datastructures/linkedList/circularLinkedList.ts: -------------------------------------------------------------------------------- 1 | import { LLNode } from 'core/node' 2 | import LinkedList from './linkedList' 3 | import { eq, isExist, gte, lte, lt } from 'core/utils' 4 | 5 | /** 6 | * 循环链表(CircularLinkedList):循环链表跟普通链表的区别就是循环链表是头尾相连的 7 | */ 8 | export default class CircularLinkedList extends LinkedList { 9 | constructor() { 10 | super() 11 | } 12 | 13 | append(element: T): CircularLinkedList { 14 | // 向链表尾部添加一个新的元素。 15 | const node: LLNode = new LLNode(element) 16 | let current: LLNode 17 | 18 | if (!isExist(this.head)) { 19 | // 链表中第一个节点 20 | this.head = node 21 | } else { 22 | current = this.getAt(this.size() - 1) 23 | current.next = node 24 | } 25 | 26 | node.next = this.head 27 | this.length++ 28 | 29 | return this 30 | } 31 | 32 | insert(position: number, element: T): CircularLinkedList { 33 | // 向链表的特定位置插入一个新的元素。 34 | if (gte(position, 0) && lte(position, this.length)) { 35 | const node: LLNode = new LLNode(element) 36 | let current: LLNode = this.head 37 | 38 | if (eq(position, 0)) { 39 | if (!isExist(this.head)) { 40 | this.head = node 41 | node.next = this.head 42 | } else { 43 | node.next = current 44 | current = this.getAt(this.size()) 45 | this.head = node 46 | current.next = this.head 47 | } 48 | } else { 49 | const previous: LLNode = this.getAt(position - 1) 50 | node.next = previous.next 51 | previous.next = node 52 | } 53 | 54 | this.length++ 55 | } 56 | return this 57 | } 58 | 59 | removeAt(position: number): T | null { 60 | // 向链表的特定位置插入一个元素。 61 | if (gte(position, 0) && lt(position, this.length)) { 62 | let current: LLNode = this.head 63 | 64 | if (eq(position, 0)) { 65 | if (eq(this.size(), 1)) { 66 | this.head = null 67 | } else { 68 | const removed = this.head 69 | current = this.getAt(this.size() - 1) 70 | this.head = this.head.next 71 | current.next = this.head 72 | current = removed 73 | } 74 | } else { 75 | const previous = this.getAt(position - 1) 76 | current = previous.next 77 | previous.next = current.next 78 | } 79 | 80 | this.length-- 81 | return current.element 82 | } 83 | 84 | return null 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/core/datastructures/linkedList/doublyLinkedList.ts: -------------------------------------------------------------------------------- 1 | import { DLLNode } from 'core/node' 2 | import LinkedList from './linkedList' 3 | import { eq, isExist, gte, lte, lt } from 'core/utils' 4 | 5 | /** 6 | * 双向链表(DoublyLinkedList):双向链表与普通链表的区别在于,双向链表是双向的....有点废话 7 | */ 8 | export default class DoublyLinkedList extends LinkedList { 9 | public head: DLLNode | undefined // 表头 10 | public tail: DLLNode | undefined // 表尾 11 | 12 | constructor() { 13 | super() 14 | } 15 | 16 | append(element: T): DoublyLinkedList { 17 | const node: DLLNode = new DLLNode(element) 18 | if (!isExist(this.head)) { 19 | // 链表中第一个节点 20 | this.head = node 21 | this.tail = node 22 | } else { 23 | this.tail.next = node 24 | node.prev = this.tail 25 | this.tail = node 26 | } 27 | 28 | this.length++ // 更新链表的长度 29 | 30 | return this 31 | } 32 | 33 | insert(position: number, element: T): DoublyLinkedList { 34 | // 向链表的特定位置插入一个新的元素。 35 | //检查越界值 36 | if (gte(position, 0) && lte(position, this.length)) { 37 | const node: DLLNode = new DLLNode(element) 38 | let current: DLLNode = this.head 39 | let previous: DLLNode 40 | let index = 0 41 | 42 | if (eq(position, 0)) { 43 | if (!this.head) { 44 | this.head = node 45 | this.tail = node 46 | } else { 47 | node.next = current 48 | current.prev = node 49 | this.head = node 50 | } 51 | } else if (eq(position, this.length)) { 52 | current = this.tail 53 | current.next = node 54 | node.prev = current 55 | this.tail = node 56 | } else { 57 | while (lt(index++, position)) { 58 | previous = current 59 | current = current.next 60 | } 61 | 62 | node.next = current 63 | previous.next = node 64 | 65 | current.prev = node 66 | node.prev = previous 67 | } 68 | this.length++ 69 | } 70 | return this 71 | } 72 | 73 | removeAt(position: number): T | null { 74 | // 删除指定位置元素 75 | if (gte(position, 0) && lt(position, this.length)) { 76 | let current = this.head 77 | 78 | if (eq(position, 0)) { 79 | this.head = this.head.next 80 | 81 | if (eq(this.length, 1)) { 82 | this.tail = null 83 | } else { 84 | this.head.prev = null 85 | } 86 | } else if (eq(position, this.length - 1)) { 87 | current = this.tail 88 | this.tail = current.prev 89 | this.tail.next = null 90 | } else { 91 | current = this.getAt(position) 92 | const previous = current.prev 93 | previous.next = current.next 94 | current.next.prev = previous 95 | } 96 | 97 | this.length-- 98 | 99 | return current.element 100 | } else { 101 | return null 102 | } 103 | } 104 | 105 | getTail(): DLLNode { 106 | // 返回尾部信息 107 | return this.tail || null 108 | } 109 | 110 | toString(): string { 111 | // 转换为字符串 112 | if (this.head === undefined) { 113 | return '' 114 | } 115 | let objString = `${this.head?.element || ''}` 116 | let current = this.head?.next 117 | while (current !== undefined) { 118 | objString = `${objString},${current.element}` 119 | current = current.next 120 | } 121 | return objString 122 | } 123 | 124 | print(): void { 125 | console.log(this.toString()) 126 | } 127 | 128 | clear(): void { 129 | super.clear() 130 | this.tail = null 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/core/datastructures/linkedList/linkedList.ts: -------------------------------------------------------------------------------- 1 | import { LLNode } from 'core/node' 2 | import { eq, isExist, gte, lte, lt, gt } from 'core/utils' 3 | 4 | /** 5 | * 链表(LinkedList):链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个 6 | 元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。 7 | */ 8 | export default class LinkedList { 9 | public head: null | LLNode = null 10 | public length = 0 11 | constructor() { 12 | this.head = null // 链表头部 13 | this.length = 0 // 链表长度 14 | } 15 | 16 | append(element: T): LinkedList { 17 | // 插入节点 18 | const node: LLNode = new LLNode(element) 19 | let current: null | LLNode 20 | 21 | if (!isExist(this.head)) { 22 | // 列表中第一个节点 23 | this.head = node 24 | } else { 25 | current = this.head 26 | // 循环列表,直到找到最后一项 27 | while (current.next) { 28 | current = current.next 29 | } 30 | // 找到最后一项,将其next赋为node,建立链接 31 | current.next = node 32 | } 33 | 34 | this.length++ // 更新列表的长度 35 | 36 | return this 37 | } 38 | 39 | insert(position = -1, element: T): LinkedList { 40 | // 向列表的特点位置插入一个新的元素 41 | //检查越界值 42 | if (lt(position, 0)) { 43 | throw new Error('position must equal or bigger than 0') 44 | } 45 | 46 | if (gt(position, this.length)) { 47 | let index: number = position - this.length 48 | 49 | while (gt(index--, 0)) { 50 | this.append(null) 51 | } 52 | 53 | this.append(element) 54 | } else if (lte(position, this.length)) { 55 | if (eq(this.length, 0)) { 56 | this.append(element) 57 | } else { 58 | const node: LLNode = new LLNode(element) 59 | let current: null | LLNode = this.head 60 | let previous: null | LLNode 61 | let index = 0 62 | 63 | while (lt(index++, position)) { 64 | previous = current 65 | current = current.next 66 | } 67 | 68 | node.next = current 69 | previous.next = node 70 | this.length++ 71 | } 72 | } 73 | 74 | return this 75 | } 76 | 77 | getAt(position: number): null | LLNode { 78 | // 获取指定位置的元素。 79 | if (gte(position, 0) && lte(position, this.length)) { 80 | let node: null | LLNode = this.head 81 | 82 | for (let i = 0; i < position && isExist(node); ++i) { 83 | node = node.next 84 | } 85 | 86 | return node 87 | } 88 | 89 | return null 90 | } 91 | 92 | removeAt(position: number): T | null { 93 | // 从列表的特定位置移除一项。 94 | //检查越界值 95 | if (gt(position, -1) && lt(position, this.length)) { 96 | let current: null | LLNode = this.head 97 | let previous: null | LLNode 98 | let index = 0 99 | 100 | //移除第一项 101 | if (eq(position, 0)) { 102 | this.head = current.next 103 | } else { 104 | while (lt(index++, position)) { 105 | previous = current 106 | current = current.next 107 | } 108 | 109 | //将previous与current的下一项链接起来:跳过current,从而移除它 110 | previous.next = current.next 111 | } 112 | 113 | this.length-- 114 | 115 | return current.element 116 | } 117 | return null 118 | } 119 | 120 | indexOf(element: T): number { 121 | // 返回元素在列表中的索引。如果列表中没有该元素则返回-1。 122 | let current: null | LLNode = this.head 123 | let index = 0 124 | 125 | while (current) { 126 | if (eq(element, current.element)) { 127 | return index 128 | } 129 | 130 | index++ 131 | current = current.next 132 | } 133 | 134 | return -1 135 | } 136 | 137 | remove(element: T): T | null { 138 | // 从列表中移除一项。 139 | const index: number = this.indexOf(element) 140 | return this.removeAt(index) 141 | } 142 | 143 | isEmpty(): boolean { 144 | // 如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。 145 | return eq(this.length, 0) 146 | } 147 | 148 | size(): number { 149 | // 返回链表包含的元素个数。与数组的length属性类似。 150 | return this.length 151 | } 152 | 153 | getHead(): LLNode { 154 | // 返回头部信息 155 | return this.head || null 156 | } 157 | 158 | clear(): void { 159 | this.head = null 160 | this.length = 0 161 | } 162 | 163 | toString(): string { 164 | if (!isExist(this.head)) { 165 | return '' 166 | } 167 | 168 | let objString = `${this.head.element}` 169 | let current: null | LLNode = this.head.next 170 | 171 | for (let i = 1; lt(i, this.size()) && isExist(current); i++) { 172 | objString = `${objString},${current.element}` 173 | current = current.next 174 | } 175 | 176 | return objString 177 | } 178 | 179 | print(): void { 180 | // 打印 181 | console.log(this.toString()) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/core/datastructures/queue/README.md: -------------------------------------------------------------------------------- 1 | # 队列 2 | 3 | ## 队列(Queue) 4 | 5 | > 队列是遵循 FIFO(First In First Out,先进先出,也称为先来先服务)原则的一组有序集合。 6 | > 7 | > 队列在尾部添加新元素,并从顶部移除元素。 8 | > 9 | > 最新添加的元素必须排在队列的末尾。 10 | 11 | ### 核心方法 12 | 13 | | 方法 | 描述 | 14 | | ------------------- | ------------------------------------------------ | 15 | | enqueue(element(s)) | 向队列尾部添加一个(或多个)新的元素。 | 16 | | dequeue() | 移除队列的第一(即排在队列最前面的)元素并返回。 | 17 | | peek() | 返回队列中第一个元素。 | 18 | 19 | ## 双端队列 20 | 21 | > 双端队列(deque,全名 double-ended queue)是一种具有队列和栈性质的抽象数据类型。 22 | > 23 | > 双端队列中的元素可以从两端弹出, 24 | > 25 | > 插入和删除操作限定在队列的两边进行。 26 | 27 | ### 核心方法 28 | 29 | | 方法 | 描述 | 30 | | -------------------- | ---------------------------------------------------- | 31 | | addFront(element(s)) | 向队列头部插入添加一个(或多个)新的元素。 | 32 | | addBack(element(s)) | 向队列尾部插入添加一个(或多个)新的元素。 | 33 | | peekFront() | 返回队列中第一个元素。 | 34 | | peekBack() | 返回队列中最后一个元素。 | 35 | | removeFront() | 移除队列的第一(即排在队列最前面的)元素并返回。 | 36 | | removeBack() | 移除队列的最后一个(即排在队列最前面的)元素并返回。 | 37 | 38 | ## 优先队列 39 | 40 | > 优先队列(Priority Queue):带有权重的队列 41 | > 42 | > 优先队列往往用堆来实现。(JS 里可以是数组) 43 | 44 | ### 核心方法 45 | 46 | | 方法 | 描述 | 47 | | ----------------------------- | -------------------------- | 48 | | enqueue(element(s), priority) | 向队尾添加一个元素以及权重 | 49 | | dequeue() | 删除队首的元素 | 50 | | front() | 读取队首 | 51 | | back() | 读取队尾 | 52 | 53 | ## 循环队列 54 | 55 | > 循环队列(Circular Queue):循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。 56 | 57 | ### 核心方法 58 | 59 | | 方法 | 描述 | 60 | | ------------------- | ------------------------------------------------ | 61 | | enqueue(element(s)) | 向队列尾部添加一个(或多个)新的元素。 | 62 | | dequeue() | 移除队列的第一(即排在队列最前面的)元素并返回。 | 63 | | peek() | 返回队列中第一个元素。 | 64 | -------------------------------------------------------------------------------- /src/core/datastructures/queue/circularQueue.ts: -------------------------------------------------------------------------------- 1 | import { eq } from 'core/utils' 2 | 3 | /** 4 | * 循环队列(Circular Queue):是一种线性数据结构,其操作表现基于 FIFO 原则 5 | * 并且队尾被连接在队首之后以形成一个循环 6 | */ 7 | export default class CircularQueue { 8 | private items: T[] // 保存队列的元素 9 | private capacity: number // 队列容量 10 | private front: number // 队首指针 11 | private rear: number // 队尾指针 12 | private currentLength: number // 当前队列长度 13 | 14 | constructor(capacity: number) { 15 | this.capacity = Math.max(Number(capacity), 0) || 5 // 默认容量为5 16 | this.items = new Array(this.capacity) 17 | this.front = 0 18 | this.rear = -1 19 | this.currentLength = 0 20 | } 21 | 22 | enqueue(element: T): boolean { 23 | // 队列已满则无法入队 24 | if (this.isFull()) { 25 | return false 26 | } 27 | // 移动rear指针并添加元素,为了防止越界,需要取模 28 | /** 29 | * 如果不用取模,就这么写: 30 | * this.rear++ 31 | * 当到达末尾时手动重置 32 | * if (this.rear === this.capacity) { 33 | * this.rear = 0 34 | * } 35 | * 这样代码就不够简洁了 36 | */ 37 | this.rear = (this.rear + 1) % this.capacity 38 | this.items[this.rear] = element 39 | this.currentLength++ 40 | return true 41 | } 42 | 43 | dequeue(): T | null { 44 | // 队列为空则返回null 45 | if (this.isEmpty()) { 46 | return null 47 | } 48 | const item = this.items[this.front] 49 | this.items[this.front] = null 50 | // 移动front指针,为了防止越界,需要取模 51 | /** 52 | * 如果不用取模,就这么写: 53 | * this.front++ 54 | * 当到达末尾时手动重置 55 | * if (this.front === this.capacity) { 56 | * this.front = 0 57 | * } 58 | * 这样代码就不够简洁了 59 | */ 60 | this.front = (this.front + 1) % this.capacity 61 | this.currentLength-- 62 | return item 63 | } 64 | 65 | peek(): T | null { 66 | // 返回队首元素 67 | if (this.isEmpty()) { 68 | return null 69 | } 70 | return this.items[this.front] 71 | } 72 | 73 | isEmpty(): boolean { 74 | return eq(this.currentLength, 0) 75 | } 76 | 77 | isFull(): boolean { 78 | return eq(this.currentLength, this.capacity) 79 | } 80 | 81 | size(): number { 82 | return this.currentLength 83 | } 84 | 85 | clear(): void { 86 | this.items = new Array(this.capacity) 87 | this.front = 0 88 | this.rear = -1 89 | this.currentLength = 0 90 | } 91 | 92 | toString(): string { 93 | if (this.isEmpty()) { 94 | return '' 95 | } 96 | 97 | let str = '' 98 | let i = this.front 99 | for (let count = 0; count < this.currentLength; count++) { 100 | str += `${this.items[i]}${ 101 | count < this.currentLength - 1 ? ',' : '' 102 | }` 103 | i = (i + 1) % this.capacity 104 | } 105 | return str 106 | } 107 | 108 | print(): void { 109 | console.log(this.toString()) 110 | } 111 | } 112 | 113 | // 判断是否使用取模的关键点: 114 | 115 | // 1. **是否需要循环?** 116 | // - 数据结构是否需要循环访问 117 | // - 是否需要在固定范围内循环 118 | 119 | // 2. **是否有固定范围?** 120 | // - 值是否需要限制在特定范围内 121 | // - 是否需要将大数映射到小范围 122 | 123 | // 3. **是否需要环形结构?** 124 | // - 是否需要首尾相连 125 | // - 是否需要循环利用空间 126 | 127 | // 4. **是否涉及周期性?** 128 | // - 是否有重复的周期 129 | // - 是否需要循环执行 130 | -------------------------------------------------------------------------------- /src/core/datastructures/queue/deque.ts: -------------------------------------------------------------------------------- 1 | import { eq, toString } from 'core/utils' 2 | /** 3 | * 双端队列(deque,全名double-ended queue):是一种具有队列和栈性质的抽象数据类型。双端队列中的元素可以从两端弹出,插入和删除操作限定在队列的两边进行。 4 | */ 5 | export default class Deque { 6 | private items: T[] = [] // 保存队列的元素 7 | constructor() { 8 | this.items = [] 9 | } 10 | addFront(element: T): void { 11 | // 从头部插入 12 | this.items.unshift(element) 13 | } 14 | addBack(element: T): void { 15 | // 从尾部插入 16 | this.items.push(element) 17 | } 18 | removeFront(): T { 19 | // 从头部清除 20 | const deletedHead = this.items.shift() 21 | return deletedHead 22 | } 23 | removeBack(): T { 24 | // 从尾部清除 25 | const deletedTail = this.items.pop() 26 | return deletedTail 27 | } 28 | peekFront(): T { 29 | // 选择头部 30 | return this.items[0] 31 | } 32 | peekBack(): T { 33 | // 选择尾部 34 | return this.items[this.size() - 1] 35 | } 36 | isEmpty(): boolean { 37 | // 能简单地判断队列的长度是否为0 38 | return eq(this.items.length, 0) 39 | } 40 | clear(): void { 41 | // 把队列中的元素全部移除 42 | this.items = [] 43 | this.items.length = 0 44 | } 45 | size(): number { 46 | // 数组长度 47 | return this.items.length 48 | } 49 | toString(): string { 50 | // 字符串化 51 | return toString(this.items) 52 | } 53 | print(): void { 54 | // 打印队列 55 | console.log(this.toString()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/core/datastructures/queue/priorityQueueArray.ts: -------------------------------------------------------------------------------- 1 | import { eq, lt } from 'core/utils' 2 | 3 | class QueueElement { 4 | // 带权重的队列元素 5 | public element: T 6 | public priority: number 7 | constructor(element: T, priority: number) { 8 | this.element = element 9 | this.priority = priority 10 | } 11 | } 12 | 13 | /** 14 | * 优先队列(Priority Queue):带有权重的队列 15 | * 16 | * 这里用js的数组来实现 17 | */ 18 | export default class PriorityQueue { 19 | private items: QueueElement[] // 保存队列的元素 20 | constructor() { 21 | this.items = [] 22 | } 23 | enqueue(element: T, priority: number): PriorityQueue { 24 | // 向队尾添加一个元素以及权重 25 | const queueElement: QueueElement = new QueueElement( 26 | element, 27 | priority, 28 | ) 29 | let added = false 30 | if (this.isEmpty()) { 31 | this.items.push(queueElement) 32 | } else { 33 | for (let i = 0; i < this.size(); ++i) { 34 | if (lt(queueElement.priority, this.items[i].priority)) { 35 | this.items.splice(i, 0, queueElement) 36 | added = true 37 | break 38 | } 39 | } 40 | if (!added) { 41 | this.items.push(queueElement) 42 | } 43 | } 44 | return this 45 | } 46 | dequeue(): QueueElement { 47 | // 删除队首的元素 48 | return this.items.shift() 49 | } 50 | front(): QueueElement { 51 | // 读取队首 52 | return this.items[0] 53 | } 54 | back(): QueueElement { 55 | // 读取队尾 56 | return this.items[this.size() - 1] 57 | } 58 | isEmpty(): boolean { 59 | // 能简单地判断内部队列的长度是否为0 60 | return eq(this.items.length, 0) 61 | } 62 | clear(): void { 63 | // 把队列中的元素全部移除 64 | this.items = [] 65 | this.items.length = 0 66 | } 67 | size(): number { 68 | // 队列长度 69 | return this.items.length 70 | } 71 | toString(): string { 72 | // 字符串化 73 | if (this.isEmpty()) { 74 | return '' 75 | } 76 | 77 | let objString = '' 78 | for (let i = 0, len = this.size(); i < len; ++i) { 79 | objString += `${this.items[i].element}${i < len - 1 ? ',' : ''}` 80 | } 81 | 82 | return objString 83 | } 84 | print(): void { 85 | // 打印队列 86 | console.log(this.toString()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/core/datastructures/queue/queue.ts: -------------------------------------------------------------------------------- 1 | import { eq, toString } from 'core/utils' 2 | /** 3 | * 队列(Queue):先进先出(FIFO, First In First Out)的数据结构 4 | */ 5 | export default class Queue { 6 | private items: T[] = [] // 保存队列的元素 7 | constructor() { 8 | this.items = [] 9 | } 10 | enqueue(element: T): Queue { 11 | // 向队列尾部添加一个新的元素。 12 | this.items.push(element) 13 | return this 14 | } 15 | dequeue(): T { 16 | // 可删除并返回队列的第一个元素。 17 | const deletedHead: T = this.items.shift() 18 | return deletedHead 19 | } 20 | peek(): T { 21 | // 返回队列中第一个元素 22 | return this.items[0] 23 | } 24 | isEmpty(): boolean { 25 | // 能简单地判断队列的长度是否为0 26 | return eq(this.items.length, 0) 27 | } 28 | clear(): void { 29 | // 把队列中的元素全部移除 30 | this.items = [] 31 | } 32 | size(): number { 33 | // 队列长度 34 | return this.items.length 35 | } 36 | toString(): string { 37 | // 字符串化 38 | return toString(this.items) 39 | } 40 | print(): void { 41 | // 打印队列 42 | console.log(this.toString()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/core/datastructures/stack/README.md: -------------------------------------------------------------------------------- 1 | # 栈(Stack) 2 | 3 | > 栈是一种遵从后进先出(LIFO, last-in-first-out)原则的有序集合。 4 | > 5 | > 新添加的或待删除的元素都保存在栈的 同一端,称作栈顶,另一端就叫栈底。 6 | > 7 | > 在栈里,新元素都靠近栈顶,旧元素都接近栈底。 8 | 9 | ### 核心方法 10 | 11 | | 方法 | 描述 | 12 | | ---------------- | ----------------------------------------------------------------------------- | 13 | | push(element(s)) | 添加一个(或几个)新元素到栈顶。 | 14 | | pop() | 移除栈顶的元素,同时返回被移除的元素。 | 15 | | peek() | 返回栈顶的元素,不对栈做任何修改(这个方法不会移除栈顶的元素,仅仅返 回它)。 | 16 | -------------------------------------------------------------------------------- /src/core/datastructures/stack/stack.ts: -------------------------------------------------------------------------------- 1 | import { eq, toString } from 'core/utils' 2 | /** 3 | * 栈(Stack):栈是一种遵从后进先出(LIFO, last-in-first-out)原则的有序集合。 4 | */ 5 | export default class Stack { 6 | private items: T[] = [] // 保存栈里的元素 7 | constructor() { 8 | this.items = [] 9 | } 10 | push(element: T): Stack { 11 | // 添加一个新元素到栈顶 12 | this.items.push(element) 13 | return this 14 | } 15 | pop(): T { 16 | // 出栈 17 | const deletedTail = this.items.pop() 18 | return deletedTail 19 | } 20 | peek(): T { 21 | // 将返回栈顶的元素 22 | return this.items[this.size() - 1] 23 | } 24 | isEmpty(): boolean { 25 | // 能简单地判断内部栈的长度是否为0 26 | return eq(this.items.length, 0) 27 | } 28 | clear(): void { 29 | // 把栈中的元素全部移除 30 | this.items = [] 31 | this.items.length = 0 32 | } 33 | size(): number { 34 | // 栈长度 35 | return this.items.length 36 | } 37 | toString(): string { 38 | // 字符串化 39 | return toString(this.items) 40 | } 41 | print(): void { 42 | // 打印栈 43 | console.log(this.toString()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/core/datastructures/tree/README.md: -------------------------------------------------------------------------------- 1 | # 树(Tree) 2 | 3 | ## 二叉查找树(Binary Search Tree) 4 | 5 | > 二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。 6 | > 7 | > 二叉搜索树(BST)是二叉树的一种,但是它只允许你在左侧节点存储(比父节点)小的值, 在右侧节点存储(比父节点)大(或者等于)的值。上一节的图中就展现了一棵二叉搜索树。 8 | 9 | ### 核心方法 10 | 11 | | 操作 | 描述 | 12 | | :---------------: | :---------------------------------------------------------------------: | 13 | | insert(key) | 向树中插入一个新的键。 | 14 | | search(key) | 在树中查找一个键,如果节点存在,则返回 true;如果不存在,则返回 false。 | 15 | | inOrderTraverse | 通过中序遍历方式遍历所有节点。 | 16 | | preOrderTraverse | 通过先序遍历方式遍历所有节点。 | 17 | | postOrderTraverse | 通过后序遍历方式遍历所有节点。 | 18 | | min | 返回树中最小的值/键。 | 19 | | max | 返回树中最大的值/键。 | 20 | | remove(key) | 从树中移除某个键。 | 21 | 22 | ### 其他 23 | 24 | - 满二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。 25 | - 完全二叉树:对于深度为 K 的,有 n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 至 n 的结点一一对应时称之为完全二叉树。 26 | 27 | ## AVL 树(Adelson VelskiiLandi Tree) 28 | 29 | > AVL 树是最早被发明的自平衡二叉查找树。 30 | > 31 | > 在 AVL 树中,任一节点对应的两棵子树的最大高度差为 1,因此它也被称为高度平衡树。 32 | > 33 | > 查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(log n)。 34 | > 35 | > 增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。 36 | > 节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。 37 | > 38 | > 带有平衡因子 1、0 或 -1 的节点被认为是平衡的。 39 | > 40 | > 带有平衡因子 -2 或 2 的节点被认为是不平衡的,并需要重新平衡这个树。 41 | > 42 | > 平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来。 43 | 44 | ### AVL 旋转方法 45 | 46 | | 操作 | 描述 | 47 | | ---------------------- | --------------------------- | 48 | | rotationLL(node) | 左 - 左(LL):向右的单旋转 | 49 | | rotationRR(node) | 右 - 右(RR):向左的单旋转 | 50 | | rotationLR(node) | 左 - 右(LR):向右的双旋转 | 51 | | rotationRL(node) | 右 - 左(RL):向左的双旋转 | 52 | | getBalanceFactor(node) | 获取平衡系数 | 53 | 54 | ## 红黑树(Red Black Tree) 55 | 56 | > 红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。 57 | > 58 | > 红黑树是在 1972 年由 Rudolf Bayer 发明的,当时被称为平衡二叉 B 树(symmetric binary B-trees)。后来,在 1978 年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。 59 | > 60 | > 红黑树是一种特化的 AVL 树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。 61 | 62 | ### 性质 63 | 64 | 1. 节点是红色或黑色。 65 | 2. 根是黑色。 66 | 3. 所有叶子都是黑色(叶子是 null 节点)。 67 | 4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。) 68 | 5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。 69 | 70 | 这些约束确保了红黑树的关键特性: **从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。** 71 | -------------------------------------------------------------------------------- /src/core/leetNode.ts: -------------------------------------------------------------------------------- 1 | // leetcode 用 2 | 3 | export type ListNodeType = ListNode | null 4 | export class ListNode { 5 | val: number 6 | next: ListNodeType 7 | constructor(val?: number, next?: ListNodeType) { 8 | this.val = val === undefined ? 0 : val 9 | this.next = next === undefined ? null : next 10 | } 11 | } 12 | 13 | export type TreeNodeType = TreeNode | null 14 | export class TreeNode { 15 | val: number 16 | left: TreeNodeType 17 | right: TreeNodeType 18 | constructor(val?: number, left?: TreeNodeType, right?: TreeNodeType) { 19 | this.val = val === undefined ? 0 : val 20 | this.left = left === undefined ? null : left 21 | this.right = right === undefined ? null : right 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/core/node.ts: -------------------------------------------------------------------------------- 1 | import { Colors, ColorTexts } from './utils' 2 | 3 | // 链表节点 4 | export class LLNode { 5 | constructor(public element: T, public next?: LLNode) { 6 | this.element = element 7 | this.next = next 8 | } 9 | } 10 | 11 | // 双向链表节点 12 | export class DLLNode extends LLNode { 13 | constructor( 14 | public element: T, 15 | public next?: DLLNode, 16 | public prev?: DLLNode, 17 | ) { 18 | super(element, next) 19 | this.element = element 20 | this.next = next 21 | this.prev = prev 22 | } 23 | } 24 | 25 | // 二叉树节点 26 | export class BSTNode { 27 | left: BSTNode 28 | right: BSTNode 29 | constructor(public key: T) { 30 | this.key = key 31 | } 32 | 33 | toString(): string { 34 | return `${this.key}` 35 | } 36 | } 37 | 38 | // AVL树节点 39 | export class AVLNode extends BSTNode { 40 | constructor(public key: T) { 41 | super(key) 42 | this.key = key 43 | } 44 | } 45 | 46 | // 红黑树节点 47 | export class RBNode extends BSTNode { 48 | left: RBNode 49 | right: RBNode 50 | parent: RBNode 51 | color: Colors 52 | colorText: ColorTexts 53 | constructor(public key: T) { 54 | super(key) 55 | this.key = key 56 | this.color = Colors.RED 57 | this.colorText = ColorTexts.RED 58 | } 59 | // 判断是否为红色节点 60 | isRed(): boolean { 61 | return this.color === Colors.RED 62 | } 63 | // 颜色翻转 64 | reverseColor(): void { 65 | this.color = this.color === Colors.RED ? Colors.BLACK : Colors.RED 66 | } 67 | } 68 | 69 | // 字符串数据节点 70 | export class MyObj { 71 | constructor(public el1: unknown, public el2: unknown) {} 72 | toString(): string { 73 | return `${this.el1.toString()}|${this.el2.toString()}` 74 | } 75 | } 76 | 77 | // 键值对节点 78 | export class ValuePair { 79 | constructor(public key: K, public value: V) {} 80 | 81 | toString(): string { 82 | return `[#${this.key}: ${this.value}]` 83 | } 84 | } 85 | 86 | // 表格 87 | export interface tableType { 88 | [key: string]: ValuePair 89 | } 90 | -------------------------------------------------------------------------------- /src/core/utils.ts: -------------------------------------------------------------------------------- 1 | // 获取数据类型 2 | export const dataType = (data: unknown): string => { 3 | const type: string = Object.prototype.toString.call(data) 4 | return type.replace(/^\[object\s(.+)\]$/, '$1').toLowerCase() 5 | } 6 | // 是否存在该数据 7 | export const isExist = (data: unknown): boolean => { 8 | return !['undefined', 'null'].includes(dataType(data)) 9 | } 10 | // 是否存在当前所有数据 11 | export const isExistAll = (...dataList: unknown[]): boolean => { 12 | return dataList.every(isExist) 13 | } 14 | // 数组元素交换 15 | export const Swap = (array: unknown[], a: number, b: number): void => { 16 | const tmpl: unknown = array[a] 17 | array[a] = array[b] 18 | array[b] = tmpl 19 | } 20 | // 数据转为字符串 21 | export const toString = (data: unknown): string => { 22 | if (dataType(data) === 'null') { 23 | return 'NULL' 24 | } 25 | if (dataType(data) === 'undefined') { 26 | return 'UNDEFINED' 27 | } 28 | if (dataType(data) === 'array') { 29 | return `${(data as unknown & string & []).map(item => 30 | !isExist(item) ? item : toString(item), 31 | )}` 32 | } 33 | return data.toString() 34 | } 35 | // 大于判断 36 | export const gt = (data1: unknown, data2: unknown): boolean => { 37 | return data1 > data2 38 | } 39 | // 大于等于判断 40 | export const gte = (data1: unknown, data2: unknown): boolean => { 41 | return data1 >= data2 42 | } 43 | // 小于判断 44 | export const lt = (data1: unknown, data2: unknown): boolean => { 45 | return data1 < data2 46 | } 47 | // 小于等于判断 48 | export const lte = (data1: unknown, data2: unknown): boolean => { 49 | return data1 <= data2 50 | } 51 | // 等于判断 52 | export const eq = (data1: unknown, data2: unknown): boolean => { 53 | return data1 === data2 || (data1 !== data1 && data2 !== data2) 54 | } 55 | // 不等于判断 56 | export const neq = (data1: unknown, data2: unknown): boolean => { 57 | return !eq(data1, data2) 58 | } 59 | 60 | export type DefalutListType = number[] 61 | export type ICompareFunction = (a: T, b: T) => number 62 | export type IEqualsFunction = (a: T, b: T) => boolean 63 | export type IDiffFunction = (a: T, b: T) => number 64 | // 红黑色色值枚举 65 | export enum Colors { 66 | RED = 0, 67 | BLACK = 1, 68 | } 69 | // 红黑树色值文本枚举 70 | export enum ColorTexts { 71 | RED = 'RED', 72 | BLACK = 'BLACK', 73 | } 74 | // 对比的枚举 75 | export enum Compare { 76 | LESS_THAN = -1, 77 | BIGGER_THAN = 1, 78 | EQUALS = 0, 79 | } 80 | 81 | // 是否存在当前数据 82 | export const DOES_NOT_EXIST = -1 83 | 84 | // a 小于 b 85 | export const lesserEquals = ( 86 | a: T, 87 | b: T, 88 | compareFn: ICompareFunction, 89 | ): boolean => { 90 | const comp = compareFn(a, b) 91 | return comp === Compare.LESS_THAN || comp === Compare.EQUALS 92 | } 93 | 94 | // a 大于 b 95 | export const biggerEquals = ( 96 | a: T, 97 | b: T, 98 | compareFn: ICompareFunction, 99 | ): boolean => { 100 | const comp = compareFn(a, b) 101 | return comp === Compare.BIGGER_THAN || comp === Compare.EQUALS 102 | } 103 | 104 | // a 等于 a 105 | export const defaultEquals = (a: T, b: T): boolean => a === b 106 | 107 | // a 与 b的大小判断 108 | export const defaultCompare = (a: T, b: T): number => { 109 | if (a === b) { 110 | return Compare.EQUALS 111 | } 112 | return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN 113 | } 114 | 115 | // 将数据转换成字符串 116 | export const defaultToString = (item: unknown): string => { 117 | if (item === null) { 118 | return 'NULL' 119 | } else if (item === undefined) { 120 | return 'UNDEFINED' 121 | } else if (typeof item === 'string') { 122 | return item 123 | } 124 | return item.toString() 125 | } 126 | 127 | // 反向对比 128 | export const reverseCompare = ( 129 | compareFn: ICompareFunction, 130 | ): ICompareFunction => (a, b) => compareFn(b, a) 131 | 132 | // 获取a 与 b的差值 133 | export const defaultDiff = (a: T, b: T): number => Number(a) - Number(b) 134 | 135 | // 随机数组 136 | export const RandomList: number[] = [1, 2, 3, 4, 7, 5] 137 | 138 | // 排列数组 139 | export const SortedList: number[] = [1, 2, 3, 4, 5, 7] 140 | 141 | // 随机数组集合 142 | export const RandomLists: number[][] = [ 143 | [1, 2, 3, 4, 7, 5], 144 | [3, 9, 8, 7, 5, 1, 2, 3], 145 | [30, 4, 8, 1, 9, 5, 10, 5, 9, 3, 7], 146 | [5, 8, 9, 7, 2, 3, 6, 4, 5, 1, 22, 88, 66, 102, 1024], 147 | [5, 9, 8, 7, 3, 2, 0, 1, 88, 5, 125, 98, 127, 888, 555], 148 | ] 149 | 150 | // 排列数组集合 151 | export const SortedLists: number[][] = [ 152 | [1, 2, 3, 4, 5, 7], 153 | [1, 2, 3, 3, 5, 7, 8, 9], 154 | [1, 3, 4, 5, 5, 7, 8, 9, 9, 10, 30], 155 | [1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 22, 66, 88, 102, 1024], 156 | [0, 1, 2, 3, 5, 5, 7, 8, 9, 88, 98, 125, 127, 555, 888], 157 | ] 158 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KRISACHAN/ying-datastructures-algorithms/154d500bd3030dacbc606ff3778fae07d2d5e706/src/global.d.ts -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { MaxHeap, MinHeap } from 'core/datastructures/heap/heap' 2 | 3 | const maxHeap: MinHeap = new MaxHeap() 4 | maxHeap.insert('a') 5 | maxHeap.insert('bb') 6 | maxHeap.insert('ccc') 7 | maxHeap.insert('dddd') 8 | 9 | console.log(maxHeap.toString()) 10 | 11 | const minHeap: MinHeap = new MinHeap() 12 | minHeap.insert('dddd') 13 | minHeap.insert('ccc') 14 | minHeap.insert('bb') 15 | minHeap.insert('a') 16 | 17 | console.log(minHeap.toString()) 18 | -------------------------------------------------------------------------------- /static/img/qrcode-all1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KRISACHAN/ying-datastructures-algorithms/154d500bd3030dacbc606ff3778fae07d2d5e706/static/img/qrcode-all1.png -------------------------------------------------------------------------------- /tests/algorithms/recursion/recursion.test.ts: -------------------------------------------------------------------------------- 1 | import { ListNode, ListNodeType } from 'core/leetNode' 2 | import { addTwoNumbers } from 'core/algorithms/recursion/2.add-two-numbers' 3 | import { letterCombinations } from 'core/algorithms/recursion/17.letter-combinations-of-a-phone-number' 4 | import { mergeTwoLists } from 'core/algorithms/recursion/21.merge-two-sorted-lists' 5 | import { swapPairs } from 'core/algorithms/recursion/24.swap-nodes-in-pairs' 6 | import { isValidBST } from 'core/algorithms/recursion/98.validate-binary-search-tree' 7 | import { TreeNodeType, TreeNode } from 'core/leetNode' 8 | 9 | describe('recursion', () => { 10 | const createNode = ( 11 | data: number | undefined | null | number[], 12 | ): ListNodeType => { 13 | let curNode: ListNodeType 14 | if (data === undefined || data === null) { 15 | curNode = new ListNode(null) 16 | } else if (Array.isArray(data)) { 17 | const [head, ...list] = data 18 | curNode = new ListNode(head) 19 | let nextNode = curNode 20 | list.forEach(item => { 21 | nextNode.next = new ListNode(item) 22 | nextNode = nextNode.next 23 | }) 24 | } else { 25 | curNode = new ListNode(data) 26 | } 27 | return curNode 28 | } 29 | 30 | const getRes = (node: ListNode): number[] => { 31 | const res = [] 32 | while (node && node.val !== null) { 33 | res.push(node.val) 34 | node = node.next 35 | } 36 | return res 37 | } 38 | 39 | test(` 40 | add-two-numbers 41 | l1 = [2,4,3] 42 | l2 = [5,6,4] 43 | `, () => { 44 | const l1 = createNode([2, 4, 3]) 45 | const l2 = createNode([5, 6, 4]) 46 | const resNode = addTwoNumbers(l1, l2) 47 | const res = getRes(resNode) 48 | expect(res).toEqual([7, 0, 8]) 49 | }) 50 | 51 | test(` 52 | add-two-numbers 53 | l1 = [0] 54 | l2 = [0] 55 | `, () => { 56 | const l1 = createNode(0) 57 | const l2 = createNode(0) 58 | const resNode = addTwoNumbers(l1, l2) 59 | const res = getRes(resNode) 60 | expect(res).toEqual([0]) 61 | }) 62 | 63 | test(` 64 | add-two-numbers 65 | l1 = [9,9,9,9,9,9,9] 66 | l2 = [9,9,9,9] 67 | `, () => { 68 | const l1 = createNode([9, 9, 9, 9, 9, 9, 9]) 69 | const l2 = createNode([9, 9, 9, 9]) 70 | const resNode = addTwoNumbers(l1, l2) 71 | const res = getRes(resNode) 72 | expect(res).toEqual([8, 9, 9, 9, 0, 0, 0, 1]) 73 | }) 74 | 75 | test(` 76 | letter-combinations-of-a-phone-number 77 | "23" 78 | `, () => { 79 | const res = letterCombinations('23') 80 | expect(res).toEqual([ 81 | 'ad', 82 | 'ae', 83 | 'af', 84 | 'bd', 85 | 'be', 86 | 'bf', 87 | 'cd', 88 | 'ce', 89 | 'cf', 90 | ]) 91 | }) 92 | 93 | test(` 94 | merge-two-sorted-lists 95 | l1 = [1,2,4] 96 | l2 = [1,3,4] 97 | `, () => { 98 | const l1 = createNode([1, 2, 4]) 99 | const l2 = createNode([1, 3, 4]) 100 | const resNode = mergeTwoLists(l1, l2) 101 | const res = getRes(resNode) 102 | expect(res).toEqual([1, 1, 2, 3, 4, 4]) 103 | }) 104 | 105 | test(` 106 | merge-two-sorted-lists 107 | l1 = [] 108 | l2 = [] 109 | `, () => { 110 | const l1 = createNode(null) 111 | const l2 = createNode(null) 112 | const resNode = mergeTwoLists(l1, l2) 113 | const res = getRes(resNode) 114 | expect(res).toEqual([]) 115 | }) 116 | 117 | test(` 118 | merge-two-sorted-lists 119 | l1 = [] 120 | l2 = [0] 121 | `, () => { 122 | const l1 = createNode(null) 123 | const l2 = createNode(0) 124 | const resNode = mergeTwoLists(l1, l2) 125 | const res = getRes(resNode.val ? resNode : resNode.next) 126 | expect(res).toEqual([0]) 127 | }) 128 | 129 | test(` 130 | swap-nodes-in-pairs 131 | head = [1,2,3,4] 132 | [2,1,4,3] 133 | `, () => { 134 | const head = createNode([1, 2, 3, 4]) 135 | const swapedHead = swapPairs(head) 136 | const res = createNode([2, 1, 4, 3]) 137 | expect(swapedHead).toEqual(res) 138 | }) 139 | 140 | test(` 141 | swap-nodes-in-pairs 142 | head = [] 143 | [] 144 | `, () => { 145 | const head = createNode([]) 146 | const swapedHead = swapPairs(head) 147 | const res = createNode([]) 148 | expect(swapedHead).toEqual(res) 149 | }) 150 | 151 | test(` 152 | swap-nodes-in-pairs 153 | head = [1] 154 | [1] 155 | `, () => { 156 | const head = createNode([1]) 157 | const swapedHead = swapPairs(head) 158 | const res = createNode([1]) 159 | expect(swapedHead).toEqual(res) 160 | }) 161 | 162 | test(` 163 | validate-binary-search-tree 164 | 输入: [2, 1, 3] 165 | 输出: true 166 | `, () => { 167 | const left: TreeNodeType = new TreeNode(1) 168 | const right: TreeNodeType = new TreeNode(3) 169 | const root: TreeNodeType = new TreeNode(2, left, right) 170 | expect(isValidBST(root)).toEqual(true) 171 | }) 172 | 173 | test(` 174 | validate-binary-search-tree 175 | 输入: [5,1,4,null,null,3,6] 176 | 输出: false 177 | `, () => { 178 | const left: TreeNodeType = new TreeNode(1) 179 | const rightLeft: TreeNodeType = new TreeNode(3) 180 | const rightRight: TreeNodeType = new TreeNode(3) 181 | const right: TreeNodeType = new TreeNode(4, rightLeft, rightRight) 182 | const root: TreeNodeType = new TreeNode(5, left, right) 183 | expect(isValidBST(root)).toEqual(false) 184 | }) 185 | }) 186 | -------------------------------------------------------------------------------- /tests/algorithms/search/search.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | recursionBinarySearch, 3 | loopBinarySearch, 4 | } from 'core/algorithms/search/binarySearch' 5 | import { 6 | orderSearch, 7 | halfOrderSearch, 8 | } from 'core/algorithms/search/orderSearch' 9 | import { 10 | loopInterpolationSearch, 11 | recursionInterpolationSearch, 12 | } from 'core/algorithms/search/interpolationSearch' 13 | import { 14 | recursionFibonacciSearch, 15 | loopFibonacciSearch, 16 | } from 'core/algorithms/search/fibonacciSearch' 17 | import TreeSearch from 'core/algorithms/search/treeSearch' 18 | import HashSearch from 'core/algorithms/search/hashSearch' 19 | import BlockSearch from 'core/algorithms/search/blockSearch' 20 | 21 | describe('Search', () => { 22 | const list: number[] = [50, 17, 72, 12, 13, 54, 76, 9, 14, 19, 67] 23 | const key1 = 17 24 | const key2 = 3 25 | test('BinarySearch', () => { 26 | expect(recursionBinarySearch(list, key1)).toEqual(4) 27 | expect(recursionBinarySearch(list, key2)).toEqual(-1) 28 | 29 | expect(loopBinarySearch(list, key1)).toEqual(4) 30 | expect(loopBinarySearch(list, key2)).toEqual(-1) 31 | }) 32 | 33 | test('OrderSearch', () => { 34 | expect(orderSearch(list, key1)).toEqual(4) 35 | expect(orderSearch(list, key2)).toEqual(-1) 36 | 37 | expect(halfOrderSearch(list, key1)).toEqual(4) 38 | expect(halfOrderSearch(list, key2)).toEqual(-1) 39 | }) 40 | 41 | test('InterpolationSearch', () => { 42 | expect(loopInterpolationSearch(list, key1)).toEqual(4) 43 | expect(loopInterpolationSearch(list, key2)).toEqual(-1) 44 | 45 | expect(recursionInterpolationSearch(list, key1)).toEqual(4) 46 | expect(recursionInterpolationSearch(list, key2)).toEqual(-1) 47 | }) 48 | 49 | test('FibonacciSearch', () => { 50 | expect(recursionFibonacciSearch(list, key1)).toEqual(4) 51 | expect(recursionFibonacciSearch(list, key2)).toEqual(-1) 52 | 53 | expect(loopFibonacciSearch(list, key1)).toEqual(4) 54 | expect(loopFibonacciSearch(list, key2)).toEqual(-1) 55 | }) 56 | 57 | test('TreeSearch', () => { 58 | const ts: TreeSearch = new TreeSearch() 59 | const list = [10, 3, 18, 2, 4, 13, 21, 9, 8, 9] 60 | list.forEach(item => { 61 | ts.insert(item) 62 | }) 63 | expect(ts.getInOrder().toString()).toStrictEqual( 64 | '2,3,4,8,9,9,10,13,18,21', 65 | ) 66 | expect(ts.getPreOrder().toString()).toStrictEqual( 67 | '10,3,2,4,9,8,9,18,13,21', 68 | ) 69 | expect(ts.getPostOrder().toString()).toStrictEqual( 70 | '2,8,9,9,4,3,13,21,18,10', 71 | ) 72 | expect(ts.getBFS().toString()).toStrictEqual('10,3,18,2,4,13,21,9,8,9') 73 | expect(ts.getDFS().toString()).toStrictEqual('10,3,2,4,9,8,9,18,13,21') 74 | }) 75 | 76 | test('HashTable', () => { 77 | const hs: HashSearch = new HashSearch< 78 | string | number 79 | >() 80 | const list = [50, 17, 72, 12, 13, 54, 76, 9, 14, 19, 67] 81 | list.forEach((item, idx) => { 82 | hs.put(`id-${idx}`, item) 83 | }) 84 | list.forEach((item, idx) => { 85 | expect(hs.get(`id-${idx}`)).toEqual(item) 86 | }) 87 | expect(hs.toString()).toStrictEqual( 88 | list.map((item, idx) => `[#id-${idx}: ${item}]`).join(','), 89 | ) 90 | expect(hs.remove(`id-${1}`).get(`id-${1}`)).toEqual(undefined) 91 | }) 92 | 93 | test('BlockSearch', () => { 94 | const LIST = [50, 17, 72, 12, 13, 54, 76, 9, 14, 19, 67, 50] 95 | const DEPTH = 3 96 | const bs: BlockSearch = new BlockSearch(LIST, DEPTH) 97 | const NOT_EXIST = [-1] 98 | expect(bs.search(3)).toEqual(NOT_EXIST) 99 | expect(bs.search(9)).toEqual([0, 0]) 100 | bs.insert(38) 101 | expect(bs.search(38)).toEqual([2, 3]) 102 | bs.remove(9) 103 | expect(bs.search(9)).toEqual(NOT_EXIST) 104 | bs.remove(50) 105 | expect(bs.size()).toEqual(10) 106 | expect(bs.blockSize()).toEqual(4) 107 | }) 108 | }) 109 | -------------------------------------------------------------------------------- /tests/algorithms/sorting/sort.test.ts: -------------------------------------------------------------------------------- 1 | import BubbleSort from 'core/algorithms/sorting/bubbleSort' 2 | import BucketSort from 'core/algorithms/sorting/bucketSort' 3 | import CocktailSort from 'core/algorithms/sorting/cocktailSort' 4 | import CountSort from 'core/algorithms/sorting/countSort' 5 | import HeapSort from 'core/algorithms/sorting/heapSort' 6 | import InsertionSort from 'core/algorithms/sorting/insertionSort' 7 | import MergeSort from 'core/algorithms/sorting/mergeSort' 8 | import { QuickSort, QuickSort3 } from 'core/algorithms/sorting/quickSort' 9 | import RadixSort from 'core/algorithms/sorting/radixSort' 10 | import SelectionSort from 'core/algorithms/sorting/selectionSort' 11 | import ShellSort from 'core/algorithms/sorting/shellSort' 12 | 13 | import { RandomLists, SortedLists } from 'core/utils' 14 | describe('Sorting', () => { 15 | test('BubbleSort', () => { 16 | const newSortLists = RandomLists.map(list => BubbleSort(list)) 17 | expect(newSortLists).toStrictEqual(SortedLists) 18 | }) 19 | test('BucketSort', () => { 20 | const newSortLists = RandomLists.map(list => BucketSort(list)) 21 | expect(newSortLists).toStrictEqual(SortedLists) 22 | }) 23 | test('CocktailSort', () => { 24 | const newSortLists = RandomLists.map(list => CocktailSort(list)) 25 | expect(newSortLists).toStrictEqual(SortedLists) 26 | }) 27 | test('CountSort', () => { 28 | const newSortLists = RandomLists.map(list => CountSort(list)) 29 | expect(newSortLists).toStrictEqual(SortedLists) 30 | }) 31 | test('HeapSort', () => { 32 | const newSortLists = RandomLists.map(list => HeapSort(list)) 33 | expect(newSortLists).toStrictEqual(SortedLists) 34 | }) 35 | test('InsertionSort', () => { 36 | const newSortLists = RandomLists.map(list => InsertionSort(list)) 37 | expect(newSortLists).toStrictEqual(SortedLists) 38 | }) 39 | test('MergeSort', () => { 40 | const newSortLists = RandomLists.map(list => MergeSort(list)) 41 | expect(newSortLists).toStrictEqual(SortedLists) 42 | }) 43 | test('QuickSort', () => { 44 | const newSortLists = RandomLists.map(list => QuickSort(list)) 45 | expect(newSortLists).toStrictEqual(SortedLists) 46 | }) 47 | test('QuickSort3', () => { 48 | const newSortLists = RandomLists.map(list => QuickSort3(list)) 49 | expect(newSortLists).toStrictEqual(SortedLists) 50 | }) 51 | test('RadixSort', () => { 52 | const newSortLists = RandomLists.map(list => RadixSort(list)) 53 | expect(newSortLists).toStrictEqual(SortedLists) 54 | }) 55 | test('SelectionSort', () => { 56 | const newSortLists = RandomLists.map(list => SelectionSort(list)) 57 | expect(newSortLists).toStrictEqual(SortedLists) 58 | }) 59 | test('ShellSort', () => { 60 | const newSortLists = RandomLists.map(list => ShellSort(list)) 61 | expect(newSortLists).toStrictEqual(SortedLists) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /tests/datastructures/graph/graph.test.ts: -------------------------------------------------------------------------------- 1 | import Graph from 'core/datastructures/graph/graph' 2 | 3 | describe('Graph', () => { 4 | it('starts empty', () => { 5 | const graph: Graph = new Graph() 6 | expect(graph.toString()).toEqual('') 7 | expect(graph.getAdjList().isEmpty()).toEqual(true) 8 | expect(graph.getVertices().length === 0).toEqual(true) 9 | }) 10 | 11 | it('add vertices', () => { 12 | const graph: Graph = new Graph() 13 | 14 | graph.addVertex('top') 15 | expect(graph.getVertices()).toEqual(['top']) 16 | expect(graph.getVertices().length === 1).toEqual(true) 17 | expect(graph.getAdjList().keys()).toEqual(['top']) 18 | expect(graph.getAdjList().size() === 1).toEqual(true) 19 | expect(graph.toString().trim()).toEqual('top ->') 20 | 21 | graph.addVertex('right') 22 | expect(graph.getVertices()).toEqual(['top', 'right']) 23 | expect(graph.getVertices().length === 2).toEqual(true) 24 | expect(graph.getAdjList().keys()).toEqual(['top', 'right']) 25 | expect(graph.getAdjList().size() === 2).toEqual(true) 26 | expect(graph.toString().trim()).toEqual('top -> \nright ->') 27 | 28 | graph.addVertex('bottom') 29 | expect(graph.getVertices()).toEqual(['top', 'right', 'bottom']) 30 | expect(graph.getVertices().length === 3).toEqual(true) 31 | expect(graph.getAdjList().keys()).toEqual(['top', 'right', 'bottom']) 32 | expect(graph.getAdjList().size() === 3).toEqual(true) 33 | expect(graph.toString().trim()).toEqual('top -> \nright -> \nbottom ->') 34 | 35 | graph.addVertex('left') 36 | expect(graph.getVertices()).toEqual(['top', 'right', 'bottom', 'left']) 37 | expect(graph.getVertices().length === 4).toEqual(true) 38 | expect(graph.getAdjList().keys()).toEqual([ 39 | 'top', 40 | 'right', 41 | 'bottom', 42 | 'left', 43 | ]) 44 | expect(graph.getAdjList().size() === 4).toEqual(true) 45 | expect(graph.toString().trim()).toEqual( 46 | 'top -> \nright -> \nbottom -> \nleft ->', 47 | ) 48 | 49 | expect( 50 | graph 51 | .getAdjList() 52 | .values() 53 | .every((v: number[] | string[]) => v.length === 0), 54 | ).toEqual(true) 55 | }) 56 | 57 | it('add adjList', () => { 58 | const graph: Graph = new Graph() 59 | graph 60 | .addVertex('top') 61 | .addVertex('right') 62 | .addVertex('bottom') 63 | .addVertex('left') 64 | 65 | graph.addEdge('top', 'right') 66 | expect(graph.getAdjList().values()[0]).toEqual(['right']) 67 | expect(graph.getAdjList().values()[1]).toEqual(['top']) 68 | expect(graph.getAdjList().values()[2]).toEqual([]) 69 | expect(graph.getAdjList().values()[3]).toEqual([]) 70 | expect(graph.toString().trim()).toEqual( 71 | 'top -> right \nright -> top \nbottom -> \nleft ->', 72 | ) 73 | 74 | graph.addEdge('right', 'bottom') 75 | expect(graph.getAdjList().values()[0]).toEqual(['right']) 76 | expect(graph.getAdjList().values()[1]).toEqual(['top', 'bottom']) 77 | expect(graph.getAdjList().values()[2]).toEqual(['right']) 78 | expect(graph.getAdjList().values()[3]).toEqual([]) 79 | expect(graph.toString().trim()).toEqual( 80 | 'top -> right \nright -> top bottom \nbottom -> right \nleft ->', 81 | ) 82 | 83 | graph.addEdge('bottom', 'left') 84 | expect(graph.getAdjList().values()[0]).toEqual(['right']) 85 | expect(graph.getAdjList().values()[1]).toEqual(['top', 'bottom']) 86 | expect(graph.getAdjList().values()[2]).toEqual(['right', 'left']) 87 | expect(graph.getAdjList().values()[3]).toEqual(['bottom']) 88 | expect(graph.toString().trim()).toEqual( 89 | 'top -> right \nright -> top bottom \nbottom -> right left \nleft -> bottom', 90 | ) 91 | 92 | graph.addEdge('left', 'top') 93 | expect(graph.getAdjList().values()[0]).toEqual(['right', 'left']) 94 | expect(graph.getAdjList().values()[1]).toEqual(['top', 'bottom']) 95 | expect(graph.getAdjList().values()[2]).toEqual(['right', 'left']) 96 | expect(graph.getAdjList().values()[3]).toEqual(['bottom', 'top']) 97 | expect(graph.toString().trim()).toEqual( 98 | 'top -> right left \nright -> top bottom \nbottom -> right left \nleft -> bottom top', 99 | ) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /tests/datastructures/hashTable/hashTable.test.ts: -------------------------------------------------------------------------------- 1 | import HashTable from 'core/datastructures/hashTable/hashTable' 2 | import { MyObj } from 'core/node' 3 | 4 | describe('HashTable', () => { 5 | it('starts empty', () => { 6 | const hashTable = new HashTable() 7 | expect(hashTable.size()).toEqual(0) 8 | expect(hashTable.isEmpty()).toEqual(true) 9 | }) 10 | 11 | it('generates hashcode', () => { 12 | // numbers 13 | let hashTable: HashTable< 14 | number | string | MyObj 15 | > = new HashTable() 16 | expect(hashTable.hashCode(1)).toEqual(1) 17 | expect(hashTable.hashCode(10)).toEqual(10) 18 | expect(hashTable.hashCode(100)).toEqual(100) 19 | expect(hashTable.hashCode(1000)).toEqual(1000) 20 | 21 | // strings 22 | hashTable = new HashTable() 23 | expect(hashTable.hashCode('1')).toEqual(12) 24 | expect(hashTable.hashCode('10')).toEqual(23) 25 | expect(hashTable.hashCode('100')).toEqual(34) 26 | expect(hashTable.hashCode('1000')).toEqual(8) 27 | expect(hashTable.hashCode('a')).toEqual(23) 28 | expect(hashTable.hashCode('A')).toEqual(28) 29 | expect(hashTable.hashCode('Aba')).toEqual(1) 30 | 31 | // objects 32 | hashTable = new HashTable() 33 | const myObjList = [] 34 | for (let i = 1; i <= 5; i++) { 35 | myObjList.push(new MyObj(i, i + 1).toString()) 36 | } 37 | expect(hashTable.hashCode(myObjList[0])).toEqual(1) 38 | expect(hashTable.hashCode(myObjList[1])).toEqual(3) 39 | expect(hashTable.hashCode(myObjList[2])).toEqual(5) 40 | expect(hashTable.hashCode(myObjList[3])).toEqual(7) 41 | expect(hashTable.hashCode(myObjList[4])).toEqual(9) 42 | }) 43 | 44 | it('puts undefined and null keys and values', () => { 45 | const hashTable = new HashTable() 46 | 47 | expect(hashTable.get('undefined')).toEqual(undefined) 48 | 49 | expect(hashTable.get('null')).toEqual(undefined) 50 | 51 | hashTable.clear() 52 | expect(hashTable.get(undefined)).toEqual(undefined) 53 | 54 | expect(hashTable.get(undefined)).toEqual(undefined) 55 | 56 | expect(hashTable.get(null)).toEqual(undefined) 57 | 58 | expect(hashTable.get(null)).toEqual(undefined) 59 | }) 60 | 61 | it('puts values with number key', () => { 62 | const min = 1 63 | const max = 5 64 | const size = max - min + 1 65 | const hashTable = new HashTable() 66 | 67 | for (let i = min; i <= max; i++) { 68 | hashTable.put(i, i) 69 | } 70 | expect(hashTable.size()).toEqual(size) 71 | 72 | const table = hashTable.getTable() 73 | for (let i = min; i <= max; i++) { 74 | expect(table[i].key).toEqual(i) 75 | expect(table[i].value).toEqual(i) 76 | } 77 | }) 78 | 79 | it('puts values with string key', () => { 80 | const hashTable = new HashTable() 81 | hashTable.put('1', 1) 82 | hashTable.put('10', 10) 83 | hashTable.put('100', 100) 84 | hashTable.put('1000', 1000) 85 | 86 | const table = hashTable.getTable() 87 | 88 | expect(table[12].key).toEqual('1') 89 | expect(table[12].value).toEqual(1) 90 | 91 | expect(table[23].key).toEqual('10') 92 | expect(table[23].value).toEqual(10) 93 | 94 | expect(table[34].key).toEqual('100') 95 | expect(table[34].value).toEqual(100) 96 | 97 | expect(table[8].key).toEqual('1000') 98 | expect(table[8].value).toEqual(1000) 99 | }) 100 | 101 | it('puts values with object key', () => { 102 | const hashTable = new HashTable() 103 | 104 | const myObjList = [] 105 | for (let i = 1; i <= 5; i++) { 106 | myObjList.push(new MyObj(i, i + 1).toString()) 107 | hashTable.put(myObjList[i - 1], myObjList[i - 1]) 108 | } 109 | 110 | const table = hashTable.getTable() 111 | 112 | expect(table[1].key).toEqual(myObjList[0]) 113 | expect(table[1].value).toEqual(myObjList[0]) 114 | 115 | expect(table[3].key).toEqual(myObjList[1]) 116 | expect(table[3].value).toEqual(myObjList[1]) 117 | 118 | expect(table[5].key).toEqual(myObjList[2]) 119 | expect(table[5].value).toEqual(myObjList[2]) 120 | 121 | expect(table[7].key).toEqual(myObjList[3]) 122 | expect(table[7].value).toEqual(myObjList[3]) 123 | 124 | expect(table[9].key).toEqual(myObjList[4]) 125 | expect(table[9].value).toEqual(myObjList[4]) 126 | }) 127 | 128 | it('does NOT handle collision, replaces values', () => { 129 | const hashTable = new HashTable() 130 | 131 | for (let i = 0; i < 5; i++) { 132 | hashTable.put(1, i) 133 | } 134 | expect(hashTable.size()).toEqual(1) 135 | }) 136 | 137 | it('removes elements', () => { 138 | const min = 1 139 | const max = 5 140 | const size = max - min + 1 141 | const hashTable = new HashTable() 142 | 143 | for (let i = min; i <= max; i++) { 144 | hashTable.put(i, i) 145 | } 146 | expect(hashTable.size()).toEqual(size) 147 | 148 | for (let i = min; i <= max; i++) { 149 | hashTable.remove(i) 150 | } 151 | 152 | // elements do not exist 153 | for (let i = min; i <= max; i++) { 154 | hashTable.remove(i) 155 | } 156 | 157 | expect(hashTable.isEmpty()).toEqual(true) 158 | }) 159 | 160 | it('returns toString primitive types', () => { 161 | const hashTable = new HashTable() 162 | 163 | expect(hashTable.toString()).toEqual('') 164 | 165 | hashTable.put(1, 1) 166 | expect(hashTable.toString()).toEqual('[#1: 1]') 167 | 168 | hashTable.put(2, 2) 169 | expect(hashTable.toString()).toEqual('[#1: 1],[#2: 2]') 170 | 171 | hashTable.clear() 172 | expect(hashTable.toString()).toEqual('') 173 | }) 174 | 175 | it('returns toString primitive types', () => { 176 | const hashTable = new HashTable() 177 | 178 | hashTable.put('el1', 1) 179 | expect(hashTable.toString()).toEqual('[#el1: 1]') 180 | 181 | hashTable.put('el2', 2) 182 | expect(hashTable.toString()).toEqual('[#el2: 2],[#el1: 1]') 183 | }) 184 | 185 | it('returns toString objects', () => { 186 | const hashTable = new HashTable() 187 | 188 | let myObj = new MyObj(1, 2).toString() 189 | hashTable.put(myObj, myObj) 190 | expect(hashTable.toString()).toEqual('[#1|2: 1|2]') 191 | 192 | myObj = new MyObj(3, 4).toString() 193 | hashTable.put(myObj, myObj) 194 | expect(hashTable.toString()).toEqual('[#1|2: 1|2],[#3|4: 3|4]') 195 | }) 196 | }) 197 | -------------------------------------------------------------------------------- /tests/datastructures/heap/MaxHeap.test.ts: -------------------------------------------------------------------------------- 1 | import { MaxHeap } from 'core/datastructures/heap/heap' 2 | 3 | describe('MaxHeap', () => { 4 | it('should create an empty max heap', () => { 5 | const maxHeap = new MaxHeap() 6 | 7 | expect(maxHeap).toBeDefined() 8 | expect(maxHeap.peek()).toBeNull() 9 | expect(maxHeap.isEmpty()).toBe(true) 10 | }) 11 | 12 | it('should add items to the heap and heapify it up', () => { 13 | const maxHeap = new MaxHeap() 14 | 15 | maxHeap.insert(5) 16 | expect(maxHeap.isEmpty()).toBe(false) 17 | expect(maxHeap.peek()).toBe(5) 18 | expect(maxHeap.toString()).toBe('5') 19 | 20 | maxHeap.insert(3) 21 | expect(maxHeap.peek()).toBe(5) 22 | expect(maxHeap.toString()).toBe('5,3') 23 | 24 | maxHeap.insert(10) 25 | expect(maxHeap.peek()).toBe(10) 26 | expect(maxHeap.toString()).toBe('10,3,5') 27 | 28 | maxHeap.insert(1) 29 | expect(maxHeap.peek()).toBe(10) 30 | expect(maxHeap.toString()).toBe('10,3,5,1') 31 | 32 | maxHeap.insert(1) 33 | expect(maxHeap.peek()).toBe(10) 34 | expect(maxHeap.toString()).toBe('10,3,5,1,1') 35 | 36 | expect(maxHeap.poll()).toBe(10) 37 | expect(maxHeap.toString()).toBe('5,3,1,1') 38 | 39 | expect(maxHeap.poll()).toBe(5) 40 | expect(maxHeap.toString()).toBe('3,1,1') 41 | 42 | expect(maxHeap.poll()).toBe(3) 43 | expect(maxHeap.toString()).toBe('1,1') 44 | }) 45 | 46 | it('should poll items from the heap and heapify it down', () => { 47 | const maxHeap = new MaxHeap() 48 | 49 | maxHeap.insert(5) 50 | maxHeap.insert(3) 51 | maxHeap.insert(10) 52 | maxHeap.insert(11) 53 | maxHeap.insert(1) 54 | 55 | expect(maxHeap.toString()).toBe('11,10,5,3,1') 56 | 57 | expect(maxHeap.poll()).toBe(11) 58 | expect(maxHeap.toString()).toBe('10,3,5,1') 59 | 60 | expect(maxHeap.poll()).toBe(10) 61 | expect(maxHeap.toString()).toBe('5,3,1') 62 | 63 | expect(maxHeap.poll()).toBe(5) 64 | expect(maxHeap.toString()).toBe('3,1') 65 | 66 | expect(maxHeap.poll()).toBe(3) 67 | expect(maxHeap.toString()).toBe('1') 68 | 69 | expect(maxHeap.poll()).toBe(1) 70 | expect(maxHeap.toString()).toBe('') 71 | 72 | expect(maxHeap.poll()).toBeNull() 73 | expect(maxHeap.toString()).toBe('') 74 | }) 75 | 76 | it('should heapify down through the right branch as well', () => { 77 | const maxHeap = new MaxHeap() 78 | 79 | maxHeap.insert(3) 80 | maxHeap.insert(12) 81 | maxHeap.insert(10) 82 | 83 | expect(maxHeap.toString()).toBe('12,3,10') 84 | 85 | maxHeap.insert(11) 86 | expect(maxHeap.toString()).toBe('12,11,10,3') 87 | 88 | expect(maxHeap.poll()).toBe(12) 89 | expect(maxHeap.toString()).toBe('11,3,10') 90 | }) 91 | 92 | it('should be possible to find item indices in heap', () => { 93 | const maxHeap = new MaxHeap() 94 | 95 | maxHeap.insert(3) 96 | maxHeap.insert(12) 97 | maxHeap.insert(10) 98 | maxHeap.insert(11) 99 | maxHeap.insert(11) 100 | 101 | expect(maxHeap.toString()).toBe('12,11,10,3,11') 102 | 103 | expect(maxHeap.find(5)).toEqual([]) 104 | expect(maxHeap.find(12)).toEqual([0]) 105 | expect(maxHeap.find(11)).toEqual([1, 4]) 106 | }) 107 | 108 | it('should be possible to remove items from heap with heapify down', () => { 109 | const maxHeap = new MaxHeap() 110 | 111 | maxHeap.insert(3) 112 | maxHeap.insert(12) 113 | maxHeap.insert(10) 114 | maxHeap.insert(11) 115 | maxHeap.insert(11) 116 | 117 | expect(maxHeap.toString()).toBe('12,11,10,3,11') 118 | 119 | expect(maxHeap.remove(12).toString()).toEqual('11,11,10,3') 120 | expect(maxHeap.remove(12).peek()).toEqual(11) 121 | expect(maxHeap.remove(11).toString()).toEqual('10,3') 122 | expect(maxHeap.remove(10).peek()).toEqual(3) 123 | }) 124 | 125 | it('should be possible to remove items from heap with heapify up', () => { 126 | const maxHeap = new MaxHeap() 127 | 128 | maxHeap.insert(3) 129 | maxHeap.insert(10) 130 | maxHeap.insert(5) 131 | maxHeap.insert(6) 132 | maxHeap.insert(7) 133 | maxHeap.insert(4) 134 | maxHeap.insert(6) 135 | maxHeap.insert(8) 136 | maxHeap.insert(2) 137 | maxHeap.insert(1) 138 | 139 | expect(maxHeap.toString()).toBe('10,8,6,7,6,4,5,3,2,1') 140 | expect(maxHeap.remove(4).toString()).toEqual('10,8,6,7,6,1,5,3,2') 141 | expect(maxHeap.remove(3).toString()).toEqual('10,8,6,7,6,1,5,2') 142 | expect(maxHeap.remove(5).toString()).toEqual('10,8,6,7,6,1,2') 143 | expect(maxHeap.remove(10).toString()).toEqual('8,7,6,2,6,1') 144 | expect(maxHeap.remove(6).toString()).toEqual('8,7,1,2') 145 | expect(maxHeap.remove(2).toString()).toEqual('8,7,1') 146 | expect(maxHeap.remove(1).toString()).toEqual('8,7') 147 | expect(maxHeap.remove(7).toString()).toEqual('8') 148 | expect(maxHeap.remove(8).toString()).toEqual('') 149 | }) 150 | }) 151 | -------------------------------------------------------------------------------- /tests/datastructures/heap/MinHeap.test.ts: -------------------------------------------------------------------------------- 1 | import { MinHeap } from 'core/datastructures/heap/heap' 2 | 3 | describe('MinHeap', () => { 4 | it('should create an empty min heap', () => { 5 | const minHeap = new MinHeap() 6 | 7 | expect(minHeap).toBeDefined() 8 | expect(minHeap.peek()).toBeNull() 9 | expect(minHeap.isEmpty()).toBe(true) 10 | }) 11 | 12 | it('should add items to the heap and heapify it up', () => { 13 | const minHeap = new MinHeap() 14 | 15 | minHeap.insert(5) 16 | expect(minHeap.isEmpty()).toBe(false) 17 | expect(minHeap.peek()).toBe(5) 18 | expect(minHeap.toString()).toBe('5') 19 | 20 | minHeap.insert(3) 21 | expect(minHeap.peek()).toBe(3) 22 | expect(minHeap.toString()).toBe('3,5') 23 | 24 | minHeap.insert(10) 25 | expect(minHeap.peek()).toBe(3) 26 | expect(minHeap.toString()).toBe('3,5,10') 27 | 28 | minHeap.insert(1) 29 | expect(minHeap.peek()).toBe(1) 30 | expect(minHeap.toString()).toBe('1,3,10,5') 31 | 32 | minHeap.insert(1) 33 | expect(minHeap.peek()).toBe(1) 34 | expect(minHeap.toString()).toBe('1,1,10,5,3') 35 | 36 | expect(minHeap.poll()).toBe(1) 37 | expect(minHeap.toString()).toBe('1,3,10,5') 38 | 39 | expect(minHeap.poll()).toBe(1) 40 | expect(minHeap.toString()).toBe('3,5,10') 41 | 42 | expect(minHeap.poll()).toBe(3) 43 | expect(minHeap.toString()).toBe('5,10') 44 | }) 45 | 46 | it('should poll items from the heap and heapify it down', () => { 47 | const minHeap = new MinHeap() 48 | 49 | minHeap.insert(5) 50 | minHeap.insert(3) 51 | minHeap.insert(10) 52 | minHeap.insert(11) 53 | minHeap.insert(1) 54 | 55 | expect(minHeap.toString()).toBe('1,3,10,11,5') 56 | 57 | expect(minHeap.poll()).toBe(1) 58 | expect(minHeap.toString()).toBe('3,5,10,11') 59 | 60 | expect(minHeap.poll()).toBe(3) 61 | expect(minHeap.toString()).toBe('5,11,10') 62 | 63 | expect(minHeap.poll()).toBe(5) 64 | expect(minHeap.toString()).toBe('10,11') 65 | 66 | expect(minHeap.poll()).toBe(10) 67 | expect(minHeap.toString()).toBe('11') 68 | 69 | expect(minHeap.poll()).toBe(11) 70 | expect(minHeap.toString()).toBe('') 71 | 72 | expect(minHeap.poll()).toBeNull() 73 | expect(minHeap.toString()).toBe('') 74 | }) 75 | 76 | it('should heapify down through the right branch as well', () => { 77 | const minHeap = new MinHeap() 78 | 79 | minHeap.insert(3) 80 | minHeap.insert(12) 81 | minHeap.insert(10) 82 | 83 | expect(minHeap.toString()).toBe('3,12,10') 84 | 85 | minHeap.insert(11) 86 | expect(minHeap.toString()).toBe('3,11,10,12') 87 | 88 | expect(minHeap.poll()).toBe(3) 89 | expect(minHeap.toString()).toBe('10,11,12') 90 | }) 91 | 92 | it('should be possible to find item indices in heap', () => { 93 | const minHeap = new MinHeap() 94 | 95 | minHeap.insert(3) 96 | minHeap.insert(12) 97 | minHeap.insert(10) 98 | minHeap.insert(11) 99 | minHeap.insert(11) 100 | 101 | expect(minHeap.toString()).toBe('3,11,10,12,11') 102 | 103 | expect(minHeap.find(5)).toEqual([]) 104 | expect(minHeap.find(3)).toEqual([0]) 105 | expect(minHeap.find(11)).toEqual([1, 4]) 106 | }) 107 | 108 | it('should be possible to remove items from heap with heapify down', () => { 109 | const minHeap = new MinHeap() 110 | 111 | minHeap.insert(3) 112 | minHeap.insert(12) 113 | minHeap.insert(10) 114 | minHeap.insert(11) 115 | minHeap.insert(11) 116 | 117 | expect(minHeap.toString()).toBe('3,11,10,12,11') 118 | 119 | expect(minHeap.remove(3).toString()).toEqual('10,11,11,12') 120 | expect(minHeap.remove(3).peek()).toEqual(10) 121 | expect(minHeap.remove(11).toString()).toEqual('10,12') 122 | expect(minHeap.remove(3).peek()).toEqual(10) 123 | }) 124 | 125 | it('should be possible to remove items from heap with heapify up', () => { 126 | const minHeap = new MinHeap() 127 | 128 | minHeap.insert(3) 129 | minHeap.insert(10) 130 | minHeap.insert(5) 131 | minHeap.insert(6) 132 | minHeap.insert(7) 133 | minHeap.insert(4) 134 | minHeap.insert(6) 135 | minHeap.insert(8) 136 | minHeap.insert(2) 137 | minHeap.insert(1) 138 | 139 | expect(minHeap.toString()).toBe('1,2,4,6,3,5,6,10,8,7') 140 | expect(minHeap.remove(8).toString()).toEqual('1,2,4,6,3,5,6,10,7') 141 | expect(minHeap.remove(7).toString()).toEqual('1,2,4,6,3,5,6,10') 142 | expect(minHeap.remove(1).toString()).toEqual('2,3,4,6,10,5,6') 143 | expect(minHeap.remove(2).toString()).toEqual('3,6,4,6,10,5') 144 | expect(minHeap.remove(6).toString()).toEqual('3,5,4,10') 145 | expect(minHeap.remove(10).toString()).toEqual('3,5,4') 146 | expect(minHeap.remove(5).toString()).toEqual('3,4') 147 | expect(minHeap.remove(3).toString()).toEqual('4') 148 | expect(minHeap.remove(4).toString()).toEqual('') 149 | }) 150 | 151 | it('should remove values from heap and correctly re-order the tree', () => { 152 | const minHeap = new MinHeap() 153 | 154 | minHeap.insert(1) 155 | minHeap.insert(2) 156 | minHeap.insert(3) 157 | minHeap.insert(4) 158 | minHeap.insert(5) 159 | minHeap.insert(6) 160 | minHeap.insert(7) 161 | minHeap.insert(8) 162 | minHeap.insert(9) 163 | 164 | expect(minHeap.toString()).toBe('1,2,3,4,5,6,7,8,9') 165 | 166 | minHeap.remove(2) 167 | expect(minHeap.toString()).toBe('1,4,3,8,5,6,7,9') 168 | 169 | minHeap.remove(4) 170 | expect(minHeap.toString()).toBe('1,5,3,8,9,6,7') 171 | }) 172 | }) 173 | -------------------------------------------------------------------------------- /tests/datastructures/heap/heap.test.ts: -------------------------------------------------------------------------------- 1 | import { Heap } from 'core/datastructures/heap/heap' 2 | 3 | describe('Heap', () => { 4 | it('should not allow to create instance of the Heap directly', () => { 5 | const instantiateHeap = () => { 6 | const heap = new Heap() 7 | heap.insert(5) 8 | } 9 | 10 | expect(instantiateHeap).toThrow() 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/datastructures/linkedList/circularLinkedList.test.ts: -------------------------------------------------------------------------------- 1 | import CircularLinkedList from 'core/datastructures/linkedList/circularLinkedList' 2 | import { MyObj } from 'core/node' 3 | 4 | describe('CircularLinkedList', () => { 5 | let list: CircularLinkedList 6 | let min: number 7 | let max: number 8 | 9 | beforeEach(() => { 10 | list = new CircularLinkedList() 11 | min = 1 12 | max = 3 13 | }) 14 | 15 | it('starts empty', () => { 16 | expect(list.size()).toEqual(0) 17 | expect(list.isEmpty()).toEqual(true) 18 | expect(list.getHead()).toBeNull() 19 | }) 20 | 21 | it('returns element at specific index: invalid position', () => { 22 | // list is empty 23 | expect(list.getAt(3)).toBeNull() 24 | }) 25 | 26 | it('inserts elements invalid position empty list', () => { 27 | expect(list.insert(1, 1)).toEqual({ head: null, length: 0 }) 28 | }) 29 | 30 | it('inserts elements invalid position not empty list', () => { 31 | const element = 1 32 | expect(list.insert(0, element)).not.toEqual({ head: null, length: 0 }) 33 | expect(list.insert(2, element)).not.toEqual({ head: null, length: 0 }) 34 | }) 35 | 36 | it('removes element invalid position empty list', () => { 37 | let element 38 | 39 | for (let i = min; i <= max; i++) { 40 | element = list.removeAt(i - 1) 41 | expect(element).toBeNull() 42 | } 43 | }) 44 | 45 | it('removes first element list single element', () => { 46 | const value = 1 47 | list.append(value) 48 | 49 | const element = list.removeAt(0) 50 | expect(element).not.toBeNull() 51 | expect(element).toEqual(value) 52 | 53 | expect(list.getHead()).toBeNull() 54 | expect(list.isEmpty()).toEqual(true) 55 | }) 56 | 57 | it('returns the head of the list', () => { 58 | expect(list.getHead()).toBeNull() 59 | 60 | list.append(1) 61 | expect(list.getHead()).not.toBeNull() 62 | }) 63 | 64 | it('returns the correct size', () => { 65 | expect(list.size()).toEqual(0) 66 | 67 | for (let i = min; i <= max; i++) { 68 | list.append(i) 69 | expect(list.size()).toEqual(i) 70 | } 71 | 72 | const size = max 73 | for (let i = min; i <= max; i++) { 74 | list.remove(i) 75 | expect(list.size()).toEqual(size - i) 76 | } 77 | 78 | expect(list.size()).toEqual(0) 79 | }) 80 | 81 | it('returns toString primitive types', () => { 82 | expect(list.toString()).toEqual('') 83 | 84 | list.append(1) 85 | expect(list.toString()).toEqual('1') 86 | 87 | list.append(2) 88 | expect(list.toString()).toEqual('1,2') 89 | 90 | list.clear() 91 | expect(list.toString()).toEqual('') 92 | }) 93 | 94 | it('returns toString primitive types: string', () => { 95 | const ds = new CircularLinkedList() 96 | ds.append('el1') 97 | expect(ds.toString()).toEqual('el1') 98 | 99 | ds.append('el2') 100 | expect(ds.toString()).toEqual('el1,el2') 101 | }) 102 | 103 | it('returns toString objects', () => { 104 | const ds = new CircularLinkedList() 105 | expect(ds.toString()).toEqual('') 106 | 107 | ds.append(new MyObj(1, 2)) 108 | expect(ds.toString()).toEqual('1|2') 109 | 110 | ds.append(new MyObj(3, 4)) 111 | expect(ds.toString()).toEqual('1|2,3|4') 112 | }) 113 | }) 114 | -------------------------------------------------------------------------------- /tests/datastructures/linkedList/doublyLinkedList.test.ts: -------------------------------------------------------------------------------- 1 | import { DLLNode } from 'core/node' 2 | import DoublyLinkedList from 'core/datastructures/linkedList/doublyLinkedList' 3 | describe('DoublyLinkedList', () => { 4 | let list: DoublyLinkedList 5 | let min: number 6 | let max: number 7 | 8 | beforeEach(() => { 9 | list = new DoublyLinkedList() 10 | min = 1 11 | max = 3 12 | }) 13 | 14 | function verifyNode(current: DLLNode, i: number) { 15 | expect(current.element).not.toBeUndefined() 16 | expect(current.element).toEqual(i) 17 | 18 | // verify next node 19 | if (i < max) { 20 | expect(current.next).not.toBeUndefined() 21 | // TS strictNullChecks 22 | if (current.next) { 23 | expect(current.next.element).toEqual(i + 1) 24 | } 25 | } else { 26 | expect(current.next).toBeUndefined() 27 | } 28 | 29 | // verify previous node 30 | if (i > min) { 31 | expect(current.prev).not.toBeUndefined() 32 | if (current.prev) { 33 | expect(current.prev.element).toEqual(i - 1) 34 | } 35 | } else { 36 | expect(current.prev).toBeUndefined() 37 | } 38 | } 39 | 40 | function verifyList() { 41 | let current = list.getHead() 42 | for (let i = min; i <= max; i++) { 43 | expect(current).not.toBeNull() 44 | if (current) { 45 | verifyNode(current, i) 46 | current = current.next 47 | } 48 | } 49 | verifyListFromTail() 50 | } 51 | 52 | function verifyListFromTail() { 53 | let current = list.getTail() 54 | for (let i = max; i >= min; i--) { 55 | expect(current).not.toBeNull() 56 | if (current) { 57 | verifyNode(current, i) 58 | current = current.prev 59 | } 60 | } 61 | } 62 | 63 | it('starts empty', () => { 64 | expect(list.size()).toEqual(0) 65 | expect(list.isEmpty()).toEqual(true) 66 | expect(list.getHead()).toBeNull() 67 | expect(list.getTail()).toBeNull() 68 | }) 69 | 70 | it('returns element at specific index: invalid position', () => { 71 | expect(list.getAt(3)).toBeNull() 72 | }) 73 | 74 | it('inserts elements first position empty list', () => { 75 | const element = 1 76 | max = element 77 | expect(list.insert(0, element)).not.toEqual({ head: null, length: 0 }) 78 | verifyList() 79 | }) 80 | 81 | it('inserts elements first position not empty list', () => { 82 | max = 2 83 | expect(list.insert(0, max)).not.toEqual({ head: null, length: 0 }) 84 | 85 | expect(list.insert(0, min)).not.toEqual({ head: null, length: 0 }) 86 | 87 | verifyList() 88 | }) 89 | 90 | it('inserts elements invalid position empty list', () => { 91 | expect(list.insert(1, 1)).toEqual({ head: null, length: 0 }) 92 | }) 93 | 94 | it('inserts elements invalid position not empty list', () => { 95 | const element = 1 96 | expect(list.insert(0, element)).not.toEqual({ head: null, length: 0 }) 97 | expect(list.insert(0, element)).not.toEqual({ head: null, length: 0 }) 98 | }) 99 | 100 | it('removes element invalid position empty list', () => { 101 | let element 102 | 103 | for (let i = min; i <= max; i++) { 104 | element = list.removeAt(i - 1) 105 | expect(element).toBeNull() 106 | } 107 | }) 108 | 109 | it('removes first element list single element', () => { 110 | const value = 1 111 | list.append(value) 112 | 113 | const element = list.removeAt(0) 114 | expect(element).not.toBeNull() 115 | expect(element).toEqual(value) 116 | 117 | expect(list.getHead()).toBeNull() 118 | expect(list.getTail()).toBeNull() 119 | expect(list.isEmpty()).toEqual(true) 120 | }) 121 | 122 | it('returns the head of the list', () => { 123 | expect(list.getHead()).toBeNull() 124 | 125 | list.append(1) 126 | expect(list.getHead()).not.toBeUndefined() 127 | }) 128 | 129 | it('returns the tail of the list', () => { 130 | expect(list.getTail()).toBeNull() 131 | 132 | list.append(1) 133 | expect(list.getTail()).not.toBeUndefined() 134 | }) 135 | 136 | it('returns the correct size', () => { 137 | expect(list.size()).toEqual(0) 138 | 139 | for (let i = min; i <= max; i++) { 140 | list.append(i) 141 | expect(list.size()).toEqual(i) 142 | } 143 | 144 | const size = max 145 | for (let i = min; i <= max; i++) { 146 | list.remove(i) 147 | expect(list.size()).toEqual(size - i) 148 | } 149 | 150 | expect(list.size()).toEqual(0) 151 | }) 152 | 153 | it('returns toString primitive types', () => { 154 | expect(list.toString()).toEqual('') 155 | 156 | list.append(1) 157 | expect(list.toString()).toEqual('1') 158 | 159 | list.append(2) 160 | expect(list.toString()).toEqual('1,2') 161 | 162 | list.clear() 163 | expect(list.toString()).toEqual('') 164 | }) 165 | 166 | it('returns toString primitive types: string', () => { 167 | const ds = new DoublyLinkedList() 168 | ds.append('el1') 169 | expect(ds.toString()).toEqual('el1') 170 | 171 | ds.append('el2') 172 | expect(ds.toString()).toEqual('el1,el2') 173 | }) 174 | 175 | it('returns toString primitive types', () => { 176 | expect(list.toString()).toEqual('') 177 | 178 | list.append(1) 179 | expect(list.toString()).toEqual('1') 180 | 181 | list.append(2) 182 | expect(list.toString()).toEqual('1,2') 183 | 184 | list.clear() 185 | expect(list.toString()).toEqual('') 186 | }) 187 | 188 | it('returns toString primitive types: string', () => { 189 | const ds = new DoublyLinkedList() 190 | ds.append('el1') 191 | expect(ds.toString()).toEqual('el1') 192 | 193 | ds.append('el2') 194 | expect(ds.toString()).toEqual('el1,el2') 195 | }) 196 | }) 197 | -------------------------------------------------------------------------------- /tests/datastructures/linkedList/linkedList.test.ts: -------------------------------------------------------------------------------- 1 | import LinkedList from 'core/datastructures/linkedList/linkedList' 2 | import { MyObj } from 'core/node' 3 | 4 | describe('LinkedList', () => { 5 | let list: LinkedList 6 | let min: number 7 | let max: number 8 | 9 | beforeEach(() => { 10 | list = new LinkedList() 11 | min = 1 12 | max = 3 13 | }) 14 | 15 | it('starts empty', () => { 16 | expect(list.size()).toEqual(0) 17 | expect(list.isEmpty()).toEqual(true) 18 | expect(list.getHead()).toBeNull() 19 | }) 20 | 21 | it('returns element at specific index: invalid position', () => { 22 | expect(list.getAt(3)).toBeNull() 23 | }) 24 | 25 | it('inserts elements at specific index', () => { 26 | list.insert(max, max) 27 | expect(list.length).toEqual(4) 28 | }) 29 | 30 | it('returns toString primitive types', () => { 31 | expect(list.toString()).toEqual('') 32 | 33 | list.append(1) 34 | expect(list.toString()).toEqual('1') 35 | 36 | list.append(2) 37 | expect(list.toString()).toEqual('1,2') 38 | 39 | list.clear() 40 | expect(list.toString()).toEqual('') 41 | }) 42 | 43 | it('clears the list', () => { 44 | expect(list.size()).toEqual(0) 45 | list.clear() 46 | expect(list.size()).toEqual(0) 47 | }) 48 | 49 | it('returns the head of the list', () => { 50 | expect(list.getHead()).toBeNull() 51 | 52 | list.append(1) 53 | expect(list.getHead()).not.toBeNull() 54 | }) 55 | 56 | it('removes first element list single element', () => { 57 | const value = 1 58 | list.append(value) 59 | 60 | const element = list.removeAt(0) 61 | expect(element).not.toBeNull() 62 | expect(element).toEqual(value) 63 | 64 | expect(list.getHead()).toBeNull() 65 | expect(list.isEmpty()).toEqual(true) 66 | }) 67 | 68 | it('removes element invalid position empty list', () => { 69 | let element: number 70 | 71 | for (let i: number = min; i <= max; i++) { 72 | element = list.removeAt(i - 1) 73 | expect(element).toBeNull() 74 | } 75 | }) 76 | it('returns toString objects', () => { 77 | const ds = new LinkedList() 78 | expect(ds.toString()).toEqual('') 79 | 80 | ds.append(new MyObj(1, 2)) 81 | expect(ds.toString()).toEqual('1|2') 82 | 83 | ds.append(new MyObj(3, 4)) 84 | expect(ds.toString()).toEqual('1|2,3|4') 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /tests/datastructures/queue/circularQueue.test.ts: -------------------------------------------------------------------------------- 1 | import CircularQueue from 'core/datastructures/queue/circularQueue' 2 | import { MyObj } from 'core/node' 3 | 4 | describe('CircularQueue', () => { 5 | let queue: CircularQueue 6 | 7 | beforeEach(() => { 8 | queue = new CircularQueue(5) // 使用默认容量5 9 | }) 10 | 11 | it('starts empty', () => { 12 | expect(queue.size()).toBe(0) 13 | expect(queue.isEmpty()).toBe(true) 14 | expect(queue.isFull()).toBe(false) 15 | }) 16 | 17 | it('enqueues elements', () => { 18 | expect(queue.enqueue(1)).toBe(true) 19 | expect(queue.size()).toBe(1) 20 | expect(queue.enqueue(2)).toBe(true) 21 | expect(queue.size()).toBe(2) 22 | expect(queue.enqueue(3)).toBe(true) 23 | expect(queue.size()).toBe(3) 24 | expect(queue.isEmpty()).toBe(false) 25 | }) 26 | 27 | it('respects capacity limit', () => { 28 | expect(queue.enqueue(1)).toBe(true) 29 | expect(queue.enqueue(2)).toBe(true) 30 | expect(queue.enqueue(3)).toBe(true) 31 | expect(queue.enqueue(4)).toBe(true) 32 | expect(queue.enqueue(5)).toBe(true) 33 | expect(queue.enqueue(6)).toBe(false) // 超出容量,入队失败 34 | expect(queue.size()).toBe(5) 35 | expect(queue.isFull()).toBe(true) 36 | }) 37 | 38 | it('dequeues elements', () => { 39 | queue.enqueue(1) 40 | queue.enqueue(2) 41 | queue.enqueue(3) 42 | 43 | expect(queue.dequeue()).toBe(1) 44 | expect(queue.dequeue()).toBe(2) 45 | expect(queue.dequeue()).toBe(3) 46 | expect(queue.dequeue()).toBe(null) 47 | }) 48 | 49 | it('implements FIFO logic', () => { 50 | queue.enqueue(1) 51 | expect(queue.peek()).toBe(1) 52 | queue.enqueue(2) 53 | expect(queue.peek()).toBe(1) 54 | queue.enqueue(3) 55 | expect(queue.peek()).toBe(1) 56 | 57 | expect(queue.dequeue()).toBe(1) 58 | expect(queue.dequeue()).toBe(2) 59 | expect(queue.dequeue()).toBe(3) 60 | expect(queue.dequeue()).toBe(null) 61 | }) 62 | 63 | it('allows circular operations', () => { 64 | // 填满队列 65 | queue.enqueue(1) 66 | queue.enqueue(2) 67 | queue.enqueue(3) 68 | queue.enqueue(4) 69 | queue.enqueue(5) 70 | 71 | // 移除两个元素 72 | expect(queue.dequeue()).toBe(1) 73 | expect(queue.dequeue()).toBe(2) 74 | 75 | // 在末尾添加新元素 76 | expect(queue.enqueue(6)).toBe(true) 77 | expect(queue.enqueue(7)).toBe(true) 78 | 79 | // 验证顺序正确 80 | expect(queue.dequeue()).toBe(3) 81 | expect(queue.dequeue()).toBe(4) 82 | expect(queue.dequeue()).toBe(5) 83 | expect(queue.dequeue()).toBe(6) 84 | expect(queue.dequeue()).toBe(7) 85 | }) 86 | 87 | it('allows to peek at the front element without removing it', () => { 88 | expect(queue.peek()).toBe(null) 89 | 90 | queue.enqueue(1) 91 | expect(queue.peek()).toBe(1) 92 | queue.enqueue(2) 93 | expect(queue.peek()).toBe(1) 94 | queue.dequeue() 95 | expect(queue.peek()).toBe(2) 96 | }) 97 | 98 | it('returns the correct size', () => { 99 | expect(queue.size()).toBe(0) 100 | queue.enqueue(1) 101 | expect(queue.size()).toBe(1) 102 | queue.enqueue(2) 103 | expect(queue.size()).toBe(2) 104 | queue.dequeue() 105 | expect(queue.size()).toBe(1) 106 | queue.dequeue() 107 | expect(queue.size()).toBe(0) 108 | }) 109 | 110 | it('returns if it is empty', () => { 111 | expect(queue.isEmpty()).toBe(true) 112 | queue.enqueue(1) 113 | expect(queue.isEmpty()).toBe(false) 114 | queue.dequeue() 115 | expect(queue.isEmpty()).toBe(true) 116 | }) 117 | 118 | it('returns if it is full', () => { 119 | expect(queue.isFull()).toBe(false) 120 | queue.enqueue(1) 121 | queue.enqueue(2) 122 | queue.enqueue(3) 123 | queue.enqueue(4) 124 | expect(queue.isFull()).toBe(false) 125 | queue.enqueue(5) 126 | expect(queue.isFull()).toBe(true) 127 | }) 128 | 129 | it('clears the queue', () => { 130 | queue.enqueue(1) 131 | queue.enqueue(2) 132 | queue.enqueue(3) 133 | expect(queue.isEmpty()).toBe(false) 134 | 135 | queue.clear() 136 | expect(queue.isEmpty()).toBe(true) 137 | expect(queue.size()).toBe(0) 138 | }) 139 | 140 | it('returns toString primitive types', () => { 141 | expect(queue.toString()).toBe('') 142 | 143 | queue.enqueue(1) 144 | expect(queue.toString()).toBe('1') 145 | 146 | queue.enqueue(2) 147 | expect(queue.toString()).toBe('1,2') 148 | 149 | queue.clear() 150 | expect(queue.toString()).toBe('') 151 | 152 | const queueString = new CircularQueue(3) 153 | queueString.enqueue('el1') 154 | expect(queueString.toString()).toBe('el1') 155 | 156 | queueString.enqueue('el2') 157 | expect(queueString.toString()).toBe('el1,el2') 158 | }) 159 | 160 | it('returns toString objects', () => { 161 | const queueMyObj = new CircularQueue(3) 162 | expect(queueMyObj.toString()).toBe('') 163 | 164 | queueMyObj.enqueue(new MyObj(1, 2)) 165 | expect(queueMyObj.toString()).toBe('1|2') 166 | 167 | queueMyObj.enqueue(new MyObj(3, 4)) 168 | expect(queueMyObj.toString()).toBe('1|2,3|4') 169 | }) 170 | }) 171 | -------------------------------------------------------------------------------- /tests/datastructures/queue/deque.test.ts: -------------------------------------------------------------------------------- 1 | import Deque from 'core/datastructures/queue/deque' 2 | import { MyObj } from 'core/node' 3 | 4 | describe('Deque', () => { 5 | let deque: Deque 6 | beforeEach(() => { 7 | deque = new Deque() 8 | }) 9 | it('starts empty', () => { 10 | expect(deque.size()).toBe(0) 11 | expect(deque.isEmpty()).toBe(true) 12 | }) 13 | 14 | it('add elements in the back', () => { 15 | deque.addBack(1) 16 | expect(deque.size()).toBe(1) 17 | 18 | deque.addBack(2) 19 | expect(deque.size()).toBe(2) 20 | 21 | deque.addBack(3) 22 | expect(deque.size()).toBe(3) 23 | }) 24 | 25 | it('add elements in the front', () => { 26 | deque.addFront(1) 27 | expect(deque.size()).toBe(1) 28 | 29 | deque.addFront(2) 30 | expect(deque.size()).toBe(2) 31 | 32 | deque.addFront(3) 33 | expect(deque.size()).toBe(3) 34 | 35 | deque.removeFront() 36 | deque.addFront(4) 37 | expect(deque.size()).toBe(3) 38 | }) 39 | 40 | it('remove elements from the back', () => { 41 | deque.addBack(1) 42 | deque.addBack(2) 43 | deque.addBack(3) 44 | deque.addFront(0) 45 | 46 | expect(deque.removeBack()).toBe(3) 47 | expect(deque.removeBack()).toBe(2) 48 | expect(deque.removeBack()).toBe(1) 49 | expect(deque.removeBack()).toBe(0) 50 | expect(deque.removeBack()).toBe(undefined) 51 | }) 52 | 53 | it('remove elements from the front', () => { 54 | deque.addFront(1) 55 | deque.addBack(2) 56 | deque.addBack(3) 57 | deque.addFront(0) 58 | deque.addFront(-1) 59 | deque.addFront(-2) 60 | 61 | expect(deque.removeFront()).toBe(-2) 62 | expect(deque.removeFront()).toBe(-1) 63 | expect(deque.removeFront()).toBe(0) 64 | expect(deque.removeFront()).toBe(1) 65 | expect(deque.removeFront()).toBe(2) 66 | expect(deque.removeFront()).toBe(3) 67 | expect(deque.removeFront()).toBe(undefined) 68 | }) 69 | 70 | it('allows to peek at the front element in the deque without removing it', () => { 71 | expect(deque.peekFront()).toBe(undefined) 72 | 73 | deque.addFront(1) 74 | expect(deque.peekFront()).toBe(1) 75 | deque.addBack(2) 76 | expect(deque.peekFront()).toBe(1) 77 | deque.addBack(3) 78 | expect(deque.peekFront()).toBe(1) 79 | deque.addFront(0) 80 | expect(deque.peekFront()).toBe(0) 81 | deque.addFront(-1) 82 | expect(deque.peekFront()).toBe(-1) 83 | deque.addFront(-2) 84 | expect(deque.peekFront()).toBe(-2) 85 | }) 86 | 87 | it('allows to peek at the last element in the deque without removing it', () => { 88 | expect(deque.peekBack()).toBe(undefined) 89 | 90 | deque.addFront(1) 91 | expect(deque.peekBack()).toBe(1) 92 | deque.addBack(2) 93 | expect(deque.peekBack()).toBe(2) 94 | deque.addBack(3) 95 | expect(deque.peekBack()).toBe(3) 96 | deque.addFront(0) 97 | expect(deque.peekBack()).toBe(3) 98 | deque.addFront(-1) 99 | expect(deque.peekBack()).toBe(3) 100 | deque.addFront(-2) 101 | expect(deque.peekBack()).toBe(3) 102 | }) 103 | 104 | it('returns the correct size', () => { 105 | expect(deque.size()).toBe(0) 106 | 107 | deque.addFront(1) 108 | expect(deque.size()).toBe(1) 109 | deque.addBack(2) 110 | expect(deque.size()).toBe(2) 111 | deque.addBack(3) 112 | expect(deque.size()).toBe(3) 113 | deque.addFront(0) 114 | expect(deque.size()).toBe(4) 115 | deque.addFront(-1) 116 | expect(deque.size()).toBe(5) 117 | deque.addFront(-2) 118 | expect(deque.size()).toBe(6) 119 | 120 | deque.clear() 121 | expect(deque.size()).toBe(0) 122 | 123 | deque.addFront(1) 124 | deque.addBack(2) 125 | expect(deque.size()).toBe(2) 126 | 127 | deque.removeFront() 128 | deque.removeBack() 129 | expect(deque.size()).toBe(0) 130 | }) 131 | 132 | it('returns if it is empty', () => { 133 | expect(deque.isEmpty()).toBe(true) 134 | 135 | deque.addFront(1) 136 | expect(deque.isEmpty()).toBe(false) 137 | deque.addBack(2) 138 | expect(deque.isEmpty()).toBe(false) 139 | 140 | deque.clear() 141 | expect(deque.isEmpty()).toBe(true) 142 | 143 | deque.addFront(1) 144 | deque.addBack(2) 145 | expect(deque.isEmpty()).toBe(false) 146 | 147 | deque.removeFront() 148 | expect(deque.isEmpty()).toBe(false) 149 | deque.removeBack() 150 | expect(deque.isEmpty()).toBe(true) 151 | }) 152 | 153 | it('clears the queue', () => { 154 | deque.clear() 155 | expect(deque.isEmpty()).toBe(true) 156 | 157 | deque.addFront(1) 158 | deque.addBack(2) 159 | expect(deque.isEmpty()).toBe(false) 160 | 161 | deque.clear() 162 | expect(deque.isEmpty()).toBe(true) 163 | }) 164 | it('returns toString primitive types', () => { 165 | expect(deque.toString()).toBe('') 166 | 167 | deque.addFront(1) 168 | expect(deque.toString()).toBe('1') 169 | 170 | deque.addBack(2) 171 | expect(deque.toString()).toBe('1,2') 172 | 173 | deque.clear() 174 | expect(deque.toString()).toBe('') 175 | 176 | const queueString = new Deque() 177 | queueString.addFront('el1') 178 | expect(queueString.toString()).toBe('el1') 179 | 180 | queueString.addBack('el2') 181 | expect(queueString.toString()).toBe('el1,el2') 182 | }) 183 | 184 | it('returns toString objects', () => { 185 | const dequeMyObj = new Deque() 186 | expect(dequeMyObj.toString()).toBe('') 187 | 188 | dequeMyObj.addFront(new MyObj(1, 2)) 189 | expect(dequeMyObj.toString()).toBe('1|2') 190 | 191 | dequeMyObj.addBack(new MyObj(3, 4)) 192 | expect(dequeMyObj.toString()).toBe('1|2,3|4') 193 | }) 194 | }) 195 | -------------------------------------------------------------------------------- /tests/datastructures/queue/priorityQueueArray.test.ts: -------------------------------------------------------------------------------- 1 | import PriorityQueue from 'core/datastructures/queue/priorityQueueArray' 2 | import { MyObj } from 'core/node' 3 | 4 | describe('priorityQueue', () => { 5 | let queue: PriorityQueue 6 | beforeEach(() => { 7 | queue = new PriorityQueue() 8 | }) 9 | it('starts empty', () => { 10 | expect(queue.size()).toBe(0) 11 | expect(queue.isEmpty()).toBe(true) 12 | }) 13 | it('enqueues elements', () => { 14 | queue.enqueue('a', 1) 15 | expect(queue.size()).toBe(1) 16 | queue.enqueue('b', 2) 17 | expect(queue.size()).toBe(2) 18 | queue.enqueue('c', 3) 19 | expect(queue.size()).toBe(3) 20 | expect(queue.isEmpty()).toBe(false) 21 | }) 22 | it('dequeue elements', () => { 23 | queue.enqueue(2, 1).enqueue(3, 2).enqueue(1, 0) 24 | 25 | expect(queue.dequeue()).toEqual({ element: 1, priority: 0 }) 26 | expect(queue.dequeue()).toEqual({ element: 2, priority: 1 }) 27 | expect(queue.dequeue()).toEqual({ element: 3, priority: 2 }) 28 | expect(queue.dequeue()).toBe(undefined) 29 | }) 30 | it('implements FIFO logic', () => { 31 | queue.enqueue(1, 0) 32 | expect(queue.front()).toEqual({ element: 1, priority: 0 }) 33 | queue.enqueue(2, 1) 34 | expect(queue.front()).toEqual({ element: 1, priority: 0 }) 35 | queue.enqueue(3, 2) 36 | expect(queue.front()).toEqual({ element: 1, priority: 0 }) 37 | expect(queue.dequeue()).toEqual({ element: 1, priority: 0 }) 38 | expect(queue.dequeue()).toEqual({ element: 2, priority: 1 }) 39 | expect(queue.dequeue()).toEqual({ element: 3, priority: 2 }) 40 | expect(queue.dequeue()).toBe(undefined) 41 | }) 42 | it('allows to peek at the front element in the queue without dequeuing it', () => { 43 | expect(queue.front()).toBe(undefined) 44 | queue.enqueue(1, 0) 45 | expect(queue.front()).toEqual({ element: 1, priority: 0 }) 46 | queue.enqueue(2, 1) 47 | expect(queue.front()).toEqual({ element: 1, priority: 0 }) 48 | queue.dequeue() 49 | expect(queue.front()).toEqual({ element: 2, priority: 1 }) 50 | }) 51 | it('returns the correct size', () => { 52 | expect(queue.size()).toBe(0) 53 | queue.enqueue(1, 0) 54 | expect(queue.size()).toBe(1) 55 | queue.enqueue(2, 1) 56 | expect(queue.size()).toBe(2) 57 | queue.enqueue(3, 2) 58 | expect(queue.size()).toBe(3) 59 | queue.clear() 60 | expect(queue.isEmpty()).toBe(true) 61 | queue.enqueue(2, 1).enqueue(3, 2).enqueue(1, 0) 62 | expect(queue.size()).toBe(3) 63 | queue.dequeue() 64 | expect(queue.size()).toBe(2) 65 | queue.dequeue() 66 | expect(queue.size()).toBe(1) 67 | queue.dequeue() 68 | expect(queue.size()).toBe(0) 69 | queue.dequeue() 70 | expect(queue.size()).toBe(0) 71 | }) 72 | it('returns if it is empty', () => { 73 | expect(queue.isEmpty()).toBe(true) 74 | queue.enqueue(1, 0) 75 | expect(queue.isEmpty()).toBe(false) 76 | queue.enqueue(2, 1) 77 | expect(queue.isEmpty()).toBe(false) 78 | queue.enqueue(3, 2) 79 | expect(queue.isEmpty()).toBe(false) 80 | queue.clear() 81 | expect(queue.isEmpty()).toBe(true) 82 | queue.enqueue(1, 0).enqueue(2, 1).enqueue(3, 2) 83 | expect(queue.isEmpty()).toBe(false) 84 | queue.dequeue() 85 | expect(queue.isEmpty()).toBe(false) 86 | queue.dequeue() 87 | expect(queue.isEmpty()).toBe(false) 88 | queue.dequeue() 89 | expect(queue.isEmpty()).toBe(true) 90 | queue.dequeue() 91 | expect(queue.isEmpty()).toBe(true) 92 | }) 93 | it('clears the queue', () => { 94 | queue.clear() 95 | expect(queue.isEmpty()).toBe(true) 96 | queue.enqueue(2, 1).enqueue(1, 0) 97 | expect(queue.isEmpty()).toBe(false) 98 | queue.clear() 99 | expect(queue.isEmpty()).toBe(true) 100 | }) 101 | it('returns toString primitive types', () => { 102 | expect(queue.toString()).toBe('') 103 | 104 | queue.enqueue(1, 0) 105 | expect(queue.toString()).toBe('1') 106 | 107 | queue.enqueue(2, 1) 108 | expect(queue.toString()).toBe('1,2') 109 | 110 | queue.clear() 111 | expect(queue.toString()).toBe('') 112 | 113 | const queueString = new PriorityQueue() 114 | queueString.enqueue('el1', 0) 115 | expect(queueString.toString()).toBe('el1') 116 | 117 | queueString.enqueue('el2', 1) 118 | expect(queueString.toString()).toBe('el1,el2') 119 | }) 120 | 121 | it('returns toString objects', () => { 122 | const queueMyObj = new PriorityQueue() 123 | expect(queueMyObj.toString()).toBe('') 124 | 125 | queueMyObj.enqueue(new MyObj(1, 2), 0) 126 | expect(queueMyObj.toString()).toBe('1|2') 127 | 128 | queueMyObj.enqueue(new MyObj(3, 4), 1) 129 | expect(queueMyObj.toString()).toBe('1|2,3|4') 130 | }) 131 | }) 132 | -------------------------------------------------------------------------------- /tests/datastructures/queue/queue.test.ts: -------------------------------------------------------------------------------- 1 | import Queue from 'core/datastructures/queue/queue' 2 | import { MyObj } from 'core/node' 3 | 4 | describe('Queue', () => { 5 | let queue: Queue 6 | beforeEach(() => { 7 | queue = new Queue() 8 | }) 9 | it('starts empty', () => { 10 | expect(queue.size()).toBe(0) 11 | expect(queue.isEmpty()).toBe(true) 12 | }) 13 | it('enqueues elements', () => { 14 | queue.enqueue(1) 15 | expect(queue.size()).toBe(1) 16 | queue.enqueue(2) 17 | expect(queue.size()).toBe(2) 18 | queue.enqueue(3) 19 | expect(queue.size()).toBe(3) 20 | expect(queue.isEmpty()).toBe(false) 21 | }) 22 | it('dequeue elements', () => { 23 | queue.enqueue(1) 24 | queue.enqueue(2) 25 | queue.enqueue(3) 26 | expect(queue.dequeue()).toBe(1) 27 | expect(queue.dequeue()).toBe(2) 28 | expect(queue.dequeue()).toBe(3) 29 | expect(queue.dequeue()).toBe(undefined) 30 | }) 31 | it('implements FIFO logic', () => { 32 | queue.enqueue(1) 33 | expect(queue.peek()).toBe(1) 34 | queue.enqueue(2) 35 | expect(queue.peek()).toBe(1) 36 | queue.enqueue(3) 37 | expect(queue.peek()).toBe(1) 38 | expect(queue.dequeue()).toBe(1) 39 | expect(queue.dequeue()).toBe(2) 40 | expect(queue.dequeue()).toBe(3) 41 | expect(queue.dequeue()).toBe(undefined) 42 | }) 43 | it('allows to peek at the front element in the queue without dequeuing it', () => { 44 | expect(queue.peek()).toBe(undefined) 45 | queue.enqueue(1) 46 | expect(queue.peek()).toBe(1) 47 | queue.enqueue(2) 48 | expect(queue.peek()).toBe(1) 49 | queue.dequeue() 50 | expect(queue.peek()).toBe(2) 51 | }) 52 | it('returns the correct size', () => { 53 | expect(queue.size()).toBe(0) 54 | queue.enqueue(1) 55 | expect(queue.size()).toBe(1) 56 | queue.enqueue(2) 57 | expect(queue.size()).toBe(2) 58 | queue.enqueue(3) 59 | expect(queue.size()).toBe(3) 60 | queue.clear() 61 | expect(queue.isEmpty()).toBe(true) 62 | queue.enqueue(1) 63 | queue.enqueue(2) 64 | queue.enqueue(3) 65 | expect(queue.size()).toBe(3) 66 | queue.dequeue() 67 | expect(queue.size()).toBe(2) 68 | queue.dequeue() 69 | expect(queue.size()).toBe(1) 70 | queue.dequeue() 71 | expect(queue.size()).toBe(0) 72 | queue.dequeue() 73 | expect(queue.size()).toBe(0) 74 | }) 75 | it('returns if it is empty', () => { 76 | expect(queue.isEmpty()).toBe(true) 77 | queue.enqueue(1) 78 | expect(queue.isEmpty()).toBe(false) 79 | queue.enqueue(2) 80 | expect(queue.isEmpty()).toBe(false) 81 | queue.enqueue(3) 82 | expect(queue.isEmpty()).toBe(false) 83 | queue.clear() 84 | expect(queue.isEmpty()).toBe(true) 85 | queue.enqueue(1) 86 | queue.enqueue(2) 87 | queue.enqueue(3) 88 | expect(queue.isEmpty()).toBe(false) 89 | queue.dequeue() 90 | expect(queue.isEmpty()).toBe(false) 91 | queue.dequeue() 92 | expect(queue.isEmpty()).toBe(false) 93 | queue.dequeue() 94 | expect(queue.isEmpty()).toBe(true) 95 | queue.dequeue() 96 | expect(queue.isEmpty()).toBe(true) 97 | }) 98 | it('clears the queue', () => { 99 | queue.clear() 100 | expect(queue.isEmpty()).toBe(true) 101 | queue.enqueue(1) 102 | queue.enqueue(2) 103 | expect(queue.isEmpty()).toBe(false) 104 | queue.clear() 105 | expect(queue.isEmpty()).toBe(true) 106 | }) 107 | it('returns toString primitive types', () => { 108 | expect(queue.toString()).toBe('') 109 | 110 | queue.enqueue(1) 111 | expect(queue.toString()).toBe('1') 112 | 113 | queue.enqueue(2) 114 | expect(queue.toString()).toBe('1,2') 115 | 116 | queue.clear() 117 | expect(queue.toString()).toBe('') 118 | 119 | const queueString = new Queue() 120 | queueString.enqueue('el1') 121 | expect(queueString.toString()).toBe('el1') 122 | 123 | queueString.enqueue('el2') 124 | expect(queueString.toString()).toBe('el1,el2') 125 | }) 126 | 127 | it('returns toString objects', () => { 128 | const queueMyObj = new Queue() 129 | expect(queueMyObj.toString()).toBe('') 130 | 131 | queueMyObj.enqueue(new MyObj(1, 2)) 132 | expect(queueMyObj.toString()).toBe('1|2') 133 | 134 | queueMyObj.enqueue(new MyObj(3, 4)) 135 | expect(queueMyObj.toString()).toBe('1|2,3|4') 136 | }) 137 | }) 138 | -------------------------------------------------------------------------------- /tests/datastructures/stack/stack.test.ts: -------------------------------------------------------------------------------- 1 | import Stack from 'core/datastructures/stack/stack' 2 | import { MyObj } from 'core/node' 3 | 4 | describe('Stack', () => { 5 | let stack: Stack 6 | beforeEach(() => { 7 | stack = new Stack() 8 | }) 9 | it('starts empty', () => { 10 | expect(stack.size()).toBe(0) 11 | expect(stack.isEmpty()).toBe(true) 12 | }) 13 | it('pushes elements', () => { 14 | stack.push(1) 15 | expect(stack.size()).toBe(1) 16 | stack.push(2) 17 | expect(stack.size()).toBe(2) 18 | stack.push(3) 19 | expect(stack.size()).toBe(3) 20 | expect(stack.isEmpty()).toBe(false) 21 | }) 22 | it('pops elements', () => { 23 | stack.push(1) 24 | stack.push(2) 25 | stack.push(3) 26 | expect(stack.pop()).toBe(3) 27 | expect(stack.pop()).toBe(2) 28 | expect(stack.pop()).toBe(1) 29 | expect(stack.pop()).toBe(undefined) 30 | }) 31 | it('implements LIFO logic', () => { 32 | stack.push(1) 33 | stack.push(2) 34 | stack.push(3) 35 | expect(stack.pop()).toBe(3) 36 | expect(stack.pop()).toBe(2) 37 | expect(stack.pop()).toBe(1) 38 | expect(stack.pop()).toBe(undefined) 39 | }) 40 | it('allows to peek at the top element in the stack without popping it', () => { 41 | expect(stack.peek()).toBe(undefined) 42 | stack.push(1) 43 | expect(stack.peek()).toBe(1) 44 | stack.push(2) 45 | expect(stack.peek()).toBe(2) 46 | stack.pop() 47 | expect(stack.peek()).toBe(1) 48 | }) 49 | it('returns the correct size', () => { 50 | expect(stack.size()).toBe(0) 51 | stack.push(1) 52 | expect(stack.size()).toBe(1) 53 | stack.push(2) 54 | expect(stack.size()).toBe(2) 55 | stack.push(3) 56 | expect(stack.size()).toBe(3) 57 | stack.clear() 58 | expect(stack.isEmpty()).toBe(true) 59 | stack.push(1) 60 | stack.push(2) 61 | stack.push(3) 62 | stack.pop() 63 | expect(stack.size()).toBe(2) 64 | stack.pop() 65 | expect(stack.size()).toBe(1) 66 | stack.pop() 67 | expect(stack.size()).toBe(0) 68 | stack.pop() 69 | expect(stack.size()).toBe(0) 70 | }) 71 | it('returns if it is empty', () => { 72 | expect(stack.isEmpty()).toBe(true) 73 | stack.push(1) 74 | expect(stack.isEmpty()).toBe(false) 75 | stack.push(2) 76 | expect(stack.isEmpty()).toBe(false) 77 | stack.push(3) 78 | expect(stack.isEmpty()).toBe(false) 79 | stack.clear() 80 | expect(stack.isEmpty()).toBe(true) 81 | stack.push(1) 82 | stack.push(2) 83 | stack.push(3) 84 | stack.pop() 85 | expect(stack.isEmpty()).toBe(false) 86 | stack.pop() 87 | expect(stack.isEmpty()).toBe(false) 88 | stack.pop() 89 | expect(stack.isEmpty()).toBe(true) 90 | stack.pop() 91 | expect(stack.isEmpty()).toBe(true) 92 | }) 93 | it('returns toString primitive types', () => { 94 | expect(stack.toString()).toBe('') 95 | 96 | stack.push(1) 97 | expect(stack.toString()).toBe('1') 98 | 99 | stack.push(2) 100 | expect(stack.toString()).toBe('1,2') 101 | 102 | stack.clear() 103 | expect(stack.toString()).toBe('') 104 | 105 | const stackString = new Stack() 106 | stackString.push('el1') 107 | expect(stackString.toString()).toBe('el1') 108 | 109 | stackString.push('el2') 110 | expect(stackString.toString()).toBe('el1,el2') 111 | }) 112 | 113 | it('returns toString objects', () => { 114 | const stackMyObj = new Stack() 115 | expect(stackMyObj.toString()).toBe('') 116 | 117 | stackMyObj.push(new MyObj(1, 2)) 118 | expect(stackMyObj.toString()).toBe('1|2') 119 | 120 | stackMyObj.push(new MyObj(3, 4)) 121 | expect(stackMyObj.toString()).toBe('1|2,3|4') 122 | }) 123 | it('clears the stack', () => { 124 | stack.clear() 125 | expect(stack.isEmpty()).toBe(true) 126 | stack.push(1) 127 | stack.push(2) 128 | stack.clear() 129 | expect(stack.isEmpty()).toBe(true) 130 | }) 131 | it('is palindrome', () => { 132 | const isPalindrome = (word: string): boolean => { 133 | for (let i = 0, len: number = word.length; i < len; ++i) { 134 | stack.push(word[i]) 135 | } 136 | let rword = '' 137 | while (stack.size() > 0) { 138 | rword += stack.pop() 139 | } 140 | return word === rword 141 | } 142 | expect(isPalindrome('racecar')).toBe(true) 143 | expect(isPalindrome('abedkhfbj')).toBe(false) 144 | }) 145 | it('hex converter', () => { 146 | const hexConverter = (decNumber: number, base: number): string => { 147 | let rem: number 148 | let binaryString = '' 149 | const digits = '0123456789ABCDEF' 150 | while (decNumber > 0) { 151 | rem = Math.floor(decNumber % base) 152 | stack.push(rem) 153 | decNumber = Math.floor(decNumber / base) 154 | } 155 | while (!stack.isEmpty()) { 156 | binaryString += digits[(stack as Stack).pop()] 157 | } 158 | return binaryString 159 | } 160 | expect(hexConverter(233, 2)).toBe('11101001') 161 | }) 162 | }) 163 | -------------------------------------------------------------------------------- /tests/datastructures/tree/adelsonVelskiiLandiTree.test.ts: -------------------------------------------------------------------------------- 1 | import AdelsonVelskiiLandiTree from 'core/datastructures/tree/adelsonVelskiiLandiTree' 2 | 3 | describe('AVLTree', () => { 4 | let tree: AdelsonVelskiiLandiTree 5 | 6 | beforeEach(() => { 7 | tree = new AdelsonVelskiiLandiTree() 8 | }) 9 | 10 | it('starts empty', () => { 11 | expect(tree.getRoot()).toEqual(undefined) 12 | }) 13 | 14 | it('inserts elements in the AVLTree', () => { 15 | expect(tree.getRoot()).toEqual(undefined) 16 | 17 | tree.insert(1) 18 | .insert(2) 19 | .insert(3) 20 | .insert(4) 21 | .insert(5) 22 | .insert(6) 23 | .insert(7) 24 | .insert(14) 25 | .insert(15) 26 | .insert(13) 27 | .insert(12) 28 | .insert(11) 29 | 30 | expect(tree.toArray()).toEqual([ 31 | 7, 32 | 4, 33 | 14, 34 | 2, 35 | 6, 36 | 12, 37 | 15, 38 | 1, 39 | 3, 40 | 5, 41 | 11, 42 | 13, 43 | ]) 44 | }) 45 | 46 | it('removes elements in the AVLTree', () => { 47 | tree.insert(1).insert(2).insert(3).insert(4).insert(5) 48 | 49 | expect(tree.toArray()).toEqual([2, 1, 4, 3, 5]) 50 | 51 | tree.remove(4) 52 | expect(tree.toArray()).toEqual([2, 1, 5, 3]) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /tests/datastructures/tree/binarySearchTree.test.ts: -------------------------------------------------------------------------------- 1 | import { BSTNode } from 'core/node' 2 | import BinarySearchTree from 'core/datastructures/tree/binarySearchTree' 3 | 4 | describe('BinarySearchTree', () => { 5 | let bst: BinarySearchTree 6 | 7 | beforeEach(() => { 8 | bst = new BinarySearchTree() 9 | }) 10 | 11 | it('starts empty', () => { 12 | expect(bst.getRoot()).toEqual(undefined) 13 | }) 14 | 15 | function assertNode( 16 | node: BSTNode, 17 | key: number, 18 | left: number, 19 | right: number, 20 | ) { 21 | if (key) { 22 | expect(node.key).toEqual(key) 23 | } else { 24 | expect(node).toEqual(key) 25 | return 26 | } 27 | 28 | if (left) { 29 | expect(node.left.key).toEqual(left) 30 | } else { 31 | expect(node.left).toEqual(left) 32 | } 33 | 34 | if (right) { 35 | expect(node.right.key).toEqual(right) 36 | } else { 37 | expect(node.right).toEqual(right) 38 | } 39 | } 40 | 41 | it('inserts elements in the BST', () => { 42 | expect(bst.getRoot()).toEqual(undefined) 43 | 44 | bst.insert(11) 45 | bst.insert(7) 46 | bst.insert(15) 47 | bst.insert(5) 48 | bst.insert(3) 49 | bst.insert(9) 50 | bst.insert(8) 51 | bst.insert(10) 52 | bst.insert(13) 53 | bst.insert(12) 54 | bst.insert(14) 55 | bst.insert(20) 56 | bst.insert(18) 57 | bst.insert(25) 58 | 59 | let node = bst.getRoot() 60 | assertNode(node, 11, 7, 15) 61 | 62 | node = node.left 63 | assertNode(node, 7, 5, 9) 64 | 65 | node = node.left 66 | assertNode(node, 5, 3, undefined) 67 | 68 | node = node.left 69 | assertNode(node, 3, undefined, undefined) 70 | 71 | node = bst.getRoot().left.left.right 72 | assertNode(node, undefined, undefined, undefined) 73 | 74 | node = bst.getRoot().left.right 75 | assertNode(node, 9, 8, 10) 76 | 77 | node = node.left 78 | assertNode(node, 8, undefined, undefined) 79 | 80 | node = bst.getRoot().left.right.right 81 | assertNode(node, 10, undefined, undefined) 82 | 83 | node = bst.getRoot().right 84 | assertNode(node, 15, 13, 20) 85 | 86 | node = node.left 87 | assertNode(node, 13, 12, 14) 88 | 89 | node = node.left 90 | assertNode(node, 12, undefined, undefined) 91 | 92 | node = bst.getRoot().right.left.right 93 | assertNode(node, 14, undefined, undefined) 94 | 95 | node = bst.getRoot().right.right 96 | assertNode(node, 20, 18, 25) 97 | 98 | node = node.left 99 | assertNode(node, 18, undefined, undefined) 100 | 101 | node = bst.getRoot().right.right.right 102 | assertNode(node, 25, undefined, undefined) 103 | }) 104 | 105 | it('verifies if element exists', () => { 106 | expect(bst.getRoot()).toEqual(undefined) 107 | }) 108 | 109 | it('removes a leaf', () => { 110 | expect(bst.getRoot()).toEqual(undefined) 111 | }) 112 | 113 | it('in order traverse', () => { 114 | const list: number[] = [10, 3, 18, 2, 4, 13, 21, 9, 8, 9] 115 | list.forEach(item => { 116 | bst.insert(item) 117 | }) 118 | const res: number[] = [] 119 | bst.inOrderTraverse((key: number): void => { 120 | res.push(key) 121 | }) 122 | expect(res.toString()).toStrictEqual('2,3,4,8,9,9,10,13,18,21') 123 | }) 124 | 125 | it('pre order traverse', () => { 126 | const list: number[] = [10, 3, 18, 2, 4, 13, 21, 9, 8, 9] 127 | list.forEach(item => { 128 | bst.insert(item) 129 | }) 130 | const res: number[] = [] 131 | bst.preOrderTraverse((key: number): void => { 132 | res.push(key) 133 | }) 134 | expect(res.toString()).toStrictEqual('10,3,2,4,9,8,9,18,13,21') 135 | }) 136 | 137 | it('post order traverse', () => { 138 | const list: number[] = [10, 3, 18, 2, 4, 13, 21, 9, 8, 9] 139 | list.forEach(item => { 140 | bst.insert(item) 141 | }) 142 | const res: number[] = [] 143 | bst.postOrderTraverse((key: number): void => { 144 | res.push(key) 145 | }) 146 | expect(res.toString()).toStrictEqual('2,8,9,9,4,3,13,21,18,10') 147 | }) 148 | 149 | it('breadth first search', () => { 150 | const list: number[] = [10, 3, 18, 2, 4, 13, 21, 9, 8, 9] 151 | list.forEach(item => { 152 | bst.insert(item) 153 | }) 154 | const res: number[] = [] 155 | bst.breadthFirstSearch((key: number): void => { 156 | res.push(key) 157 | }) 158 | expect(res.toString()).toStrictEqual('10,3,18,2,4,13,21,9,8,9') 159 | }) 160 | 161 | it('depth first search', () => { 162 | const list: number[] = [10, 3, 18, 2, 4, 13, 21, 9, 8, 9] 163 | list.forEach(item => { 164 | bst.insert(item) 165 | }) 166 | const res: number[] = [] 167 | bst.depthFirstSearch((key: number): void => { 168 | res.push(key) 169 | }) 170 | expect(res.toString()).toStrictEqual('10,3,2,4,9,8,9,18,13,21') 171 | }) 172 | }) 173 | -------------------------------------------------------------------------------- /tests/datastructures/tree/redBlackTree.test.ts: -------------------------------------------------------------------------------- 1 | import { RBNode } from 'core/node' 2 | import RedBlackTree, { Colors } from 'core/datastructures/tree/redBlackTree' 3 | 4 | describe('RedBlackTree', () => { 5 | let tree: RedBlackTree 6 | 7 | const assertNode = (node: RBNode, key: number, color: Colors) => { 8 | expect(node.color).toEqual(color) 9 | expect(node.key).toEqual(key) 10 | } 11 | 12 | beforeEach(() => { 13 | tree = new RedBlackTree() 14 | }) 15 | 16 | it('starts empty', () => { 17 | expect(tree.getRoot()).toEqual(undefined) 18 | }) 19 | 20 | it('inserts elements in the RedBlackTree', () => { 21 | expect(tree.getRoot()).toEqual(undefined) 22 | 23 | let node: RBNode 24 | 25 | tree.insert(1) 26 | assertNode(tree.getRoot(), 1, Colors.BLACK) 27 | 28 | tree.insert(2) 29 | assertNode(tree.getRoot(), 1, Colors.BLACK) 30 | assertNode(tree.getRoot().right, 2, Colors.RED) 31 | 32 | tree.insert(3) 33 | assertNode(tree.getRoot(), 2, Colors.BLACK) 34 | assertNode(tree.getRoot().right, 3, Colors.RED) 35 | assertNode(tree.getRoot().left, 1, Colors.RED) 36 | 37 | tree.insert(4) 38 | assertNode(tree.getRoot(), 2, Colors.BLACK) 39 | assertNode(tree.getRoot().left, 1, Colors.BLACK) 40 | assertNode(tree.getRoot().right, 3, Colors.BLACK) 41 | assertNode(tree.getRoot().right.right, 4, Colors.RED) 42 | 43 | tree.insert(5) 44 | assertNode(tree.getRoot(), 2, Colors.BLACK) 45 | assertNode(tree.getRoot().left, 1, Colors.BLACK) 46 | node = tree.getRoot().right 47 | assertNode(node, 4, Colors.BLACK) 48 | assertNode(node.left, 3, Colors.RED) 49 | assertNode(node.right, 5, Colors.RED) 50 | 51 | tree.insert(6) 52 | assertNode(tree.getRoot(), 2, Colors.BLACK) 53 | assertNode(tree.getRoot().left, 1, Colors.BLACK) 54 | node = tree.getRoot().right 55 | assertNode(node, 4, Colors.RED) 56 | assertNode(node.left, 3, Colors.BLACK) 57 | assertNode(node.right, 5, Colors.BLACK) 58 | assertNode(node.right.right, 6, Colors.RED) 59 | 60 | tree.insert(7) 61 | assertNode(tree.getRoot(), 2, Colors.BLACK) 62 | assertNode(tree.getRoot().left, 1, Colors.BLACK) 63 | node = tree.getRoot().right 64 | assertNode(node, 4, Colors.RED) 65 | assertNode(node.left, 3, Colors.BLACK) 66 | assertNode(node.right, 6, Colors.BLACK) 67 | assertNode(node.right.right, 7, Colors.RED) 68 | assertNode(node.right.left, 5, Colors.RED) 69 | 70 | tree.insert(8) 71 | assertNode(tree.getRoot(), 4, Colors.BLACK) 72 | node = tree.getRoot().left 73 | assertNode(node, 2, Colors.RED) 74 | assertNode(node.left, 1, Colors.BLACK) 75 | assertNode(node.right, 3, Colors.BLACK) 76 | node = tree.getRoot().right 77 | assertNode(node, 6, Colors.RED) 78 | assertNode(node.left, 5, Colors.BLACK) 79 | assertNode(node.right, 7, Colors.BLACK) 80 | assertNode(node.right.right, 8, Colors.RED) 81 | 82 | tree.insert(9) 83 | assertNode(tree.getRoot(), 4, Colors.BLACK) 84 | node = tree.getRoot().left 85 | assertNode(node, 2, Colors.RED) 86 | assertNode(node.left, 1, Colors.BLACK) 87 | assertNode(node.right, 3, Colors.BLACK) 88 | node = tree.getRoot().right 89 | assertNode(node, 6, Colors.RED) 90 | assertNode(node.left, 5, Colors.BLACK) 91 | assertNode(node.right, 8, Colors.BLACK) 92 | assertNode(node.right.left, 7, Colors.RED) 93 | assertNode(node.right.right, 9, Colors.RED) 94 | 95 | tree.insert(10) 96 | assertNode(tree.getRoot(), 4, Colors.BLACK) 97 | node = tree.getRoot().left 98 | assertNode(node, 2, Colors.BLACK) 99 | assertNode(node.left, 1, Colors.BLACK) 100 | assertNode(node.right, 3, Colors.BLACK) 101 | node = tree.getRoot().right 102 | assertNode(node, 6, Colors.BLACK) 103 | assertNode(node.left, 5, Colors.BLACK) 104 | assertNode(node.right, 8, Colors.RED) 105 | assertNode(node.right.left, 7, Colors.BLACK) 106 | assertNode(node.right.right, 9, Colors.BLACK) 107 | assertNode(node.right.right.right, 10, Colors.RED) 108 | }) 109 | 110 | it('removes elements in the RedBlackTree', () => { 111 | expect(tree.getRoot()).toEqual(undefined) 112 | tree.insert(1) 113 | .insert(2) 114 | .insert(3) 115 | .insert(4) 116 | .insert(5) 117 | .insert(6) 118 | .insert(7) 119 | .insert(8) 120 | expect(tree.toArray()).toEqual([4, 2, 6, 1, 3, 5, 7, 8]) 121 | 122 | tree.remove(5).remove(6).remove(11) 123 | expect(tree.toArray()).toEqual([4, 2, 7, 1, 3, 8]) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "baseUrl": ".", 5 | "outDir": "./dist/", 6 | "noImplicitAny": true, 7 | "module": "commonjs", 8 | "target": "ES2019", 9 | "allowJs": true, 10 | "alwaysStrict": true, 11 | "watch": true, 12 | "esModuleInterop": true, 13 | "sourceMap": true, 14 | "declaration": true, 15 | "importHelpers": true, 16 | "paths": { 17 | "@/*": ["src/*"], 18 | "src/*": ["src/*"], 19 | "tests/*": ["tests/*"], 20 | "core/*": ["src/core/*"], 21 | "static/*": ["static/*"] 22 | }, 23 | "typeRoots": ["node_modules/@types"], 24 | "lib": [ 25 | "esnext", 26 | "dom", 27 | "es5", 28 | "scripthost", 29 | "es2015.promise", 30 | "es2015", 31 | "es2016", 32 | "es2017", 33 | "es2018", 34 | "es2019", 35 | "es2020" 36 | ] 37 | }, 38 | "include": [ 39 | "src/*.*", 40 | "src/**/*.*", 41 | "src/**/**/*.*", 42 | "tests/**/**/*.*", 43 | "tests/**/**/*.*", 44 | "tests/**/**/*.*" 45 | ], 46 | "exclude": [ 47 | "node_modules", 48 | "**/*.spec.ts", 49 | "**/dist/**", 50 | "**/static/**", 51 | "**/config/**" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 首页 14 | 15 | 16 | 17 |
18 |

你好,我是本库的作者陈大鱼头

19 |

20 | 下面是我的公众号『鱼头的Web海洋』二维码,以及我个人微信二维码,有兴趣的可以扫码一起来分享技术以及谈天说地。 21 |

22 | qrcode 23 |

下面是我的一些项目地址,有兴趣可以保存书签哦

24 | 33 |
34 | 35 | 36 | --------------------------------------------------------------------------------