├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .vscode └── launch.json ├── _config.yml ├── backtracking ├── EightQueens.js └── tests │ ├── EightQueens.test.js │ └── snapshots │ ├── EightQueens.test.js.md │ └── EightQueens.test.js.snap ├── docs ├── cn │ ├── arrays.adoc │ ├── backtracking.adoc │ ├── complexity.adoc │ ├── divide_and_conquer.adoc │ ├── dynamic_programming.adoc │ ├── graph.adoc │ ├── greedy.adoc │ ├── hash_table.adoc │ ├── heap.adoc │ ├── linked_list.adoc │ ├── main.adoc │ ├── queues.adoc │ ├── recursion.adoc │ ├── search.adoc │ ├── sorting.adoc │ ├── stack.adoc │ └── tree.adoc ├── images │ ├── Treedatastructure.png │ └── Trie_example.svg └── index.adoc ├── heap ├── BinaryHeap.js ├── IndexHeap.js └── index.js ├── index.js ├── index.test.js ├── knapsack ├── knapsackBacktracking.js ├── knapsackDynamic.js └── tests │ ├── knapsackBacktracking.test.js │ ├── knapsackDynamic.test.js │ └── snapshots │ ├── knapsackBacktracking.test.js.md │ └── knapsackBacktracking.test.js.snap ├── linked_list ├── LinkedList.js ├── index.js └── tests │ ├── LinkedList.test.js │ └── snapshots │ ├── LinkedList.test.js.md │ └── LinkedList.test.js.snap ├── package-lock.json ├── package.json ├── queue └── queue.js ├── readme.md ├── search ├── binary-search │ └── binarySearch.js └── linear-search │ └── linearSearch.js ├── sorting ├── bubbleSort │ └── index.js ├── heapSort │ └── index.js ├── insertionSort │ └── index.js ├── mergeSort │ └── index.js ├── quickSort │ └── index.js └── selectionSort │ ├── index.js │ └── test.js ├── stack └── stack.js ├── utils ├── index.js ├── searchUtils.js └── sortTestHelp.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*test.js 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "standard", 3 | "rules": { 4 | "space-before-function-paren":[0] 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "type": "node", 10 | "request": "launch", 11 | "name": "启动程序", 12 | "program": "${workspaceFolder}\\index.test.js" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /backtracking/EightQueens.js: -------------------------------------------------------------------------------- 1 | class EightQueens { 2 | constructor() { 3 | this.result = [] 4 | this.solutions = [] 5 | } 6 | 7 | solveEightQueens() { 8 | this.queensRecursive(0) 9 | } 10 | 11 | /** 12 | * A recursive method to solve Eight Queen problem. 13 | * @param row 14 | * @returns {boolean} 15 | */ 16 | queensRecursive(row) { 17 | if (row === 8) { 18 | // this.solutions.push() 19 | return true 20 | } 21 | for (let column = 0; column < 8; column++) { 22 | if (this.isSafe(row, column)) { 23 | this.result[row] = column 24 | this.queensRecursive(row + 1) 25 | } 26 | } 27 | } 28 | 29 | /** 30 | * Check if an queen can placed on the position. 31 | * @param {number} row 32 | * @param {number} column 33 | * @returns {boolean} 34 | */ 35 | isSafe(row, column) { 36 | let leftUp = column - 1 37 | let rightUp = row - 1 38 | 39 | for (let i = row - 1; i >= 0; i--) { 40 | if (this.result[i] === column) return false 41 | if (leftUp >= 0 && this.result[i] === leftUp) { 42 | return false 43 | } 44 | if (rightUp < 8 && this.result[i] === rightUp) { 45 | return false 46 | } 47 | leftUp-- 48 | rightUp-- 49 | } 50 | return true 51 | } 52 | } 53 | 54 | module.exports = { 55 | EightQueens 56 | } 57 | -------------------------------------------------------------------------------- /backtracking/tests/EightQueens.test.js: -------------------------------------------------------------------------------- 1 | import { EightQueens } from '../EightQueens' 2 | import test from 'ava' 3 | 4 | test('Create Eight Queens instance', t => { 5 | const eightQueens = new EightQueens() 6 | t.snapshot(eightQueens) 7 | }) 8 | 9 | test('Test the solve method of Eight Queens.', t => { 10 | const eightQueens = new EightQueens() 11 | eightQueens.solveEightQueens() 12 | t.is(eightQueens.result.length, 8, 'The solved is incorrectly!') 13 | }) 14 | -------------------------------------------------------------------------------- /backtracking/tests/snapshots/EightQueens.test.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `backtracking/tests/EightQueens.test.js` 2 | 3 | The actual snapshot is saved in `EightQueens.test.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## Create Eight Queens instance 8 | 9 | > Snapshot 1 10 | 11 | EightQueens { 12 | result: [], 13 | solutions: [], 14 | } 15 | -------------------------------------------------------------------------------- /backtracking/tests/snapshots/EightQueens.test.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MasterShu/JavaScript-DataStructures-Algorithms/66f895bcf9d2da73a580a71c6cab0b62ea633223/backtracking/tests/snapshots/EightQueens.test.js.snap -------------------------------------------------------------------------------- /docs/cn/arrays.adoc: -------------------------------------------------------------------------------- 1 | [arrays] 2 | == 数组 3 | 数组应该算是人人都知道的吧。 4 | 5 | === 这就是数组 6 | 7 | 数组其作为编程语言的数据类型,被很多语言支持的。然而不仅如此,它还是一种基础的数据结构。 8 | 9 | - 线性表结构 10 | - 用一组连续的内存空间,来存储一组具有相同类型的数据 11 | 12 | 根据上述数组的特性就可得,连续的空间意味着你可以 *随机访问*,其元素访问的时间复杂度为 latexmath:[O(1)]。 13 | 但是同时也会带来一些局限性。 14 | 15 | - 插入需要调整其插入位置之后的所有数据 16 | - 同理,删除也是会存在类似的情况。 17 | 18 | 数组的插入和删除平均时间复杂度就在 latexmath:[O(n)]。 19 | 20 | 这么看来,数组还是蛮简单的哈。没错,是这样。不过你别骄傲,数组就是基础。如果连这都不会,接下能做的 21 | 可能真的就是从入门到放弃了。 22 | 23 | === 数组你还应该 24 | 25 | 数组是一次直接申请到一组连续的空间,这就意味着,当空间不足时,你就会需要扩展你的空间了,怎么扩展, 26 | 重新申请一组双倍大小的连续空间(当然这儿的双倍是大家常用手段)。 27 | 28 | 当然,你以为这就完了,你还要考虑到,当一个操作需要扩展是,也会涉及到转移数据的问题,一次性的大量数据 29 | 迁移,你觉得会不会带来新的问题呢? 30 | 31 | 大家都知道,CPU 都会对数组的操作进行优化,这就意味着,有些时候相同的条件下,数组会比其他数据结构更 32 | 具有优势。 33 | 34 | === 真的够了吗? 35 | 36 | 接下来说一些不是很常见的话题,或许这些你也都知道。别急,你可以直接跳过了(虽然你不知道我要说什么,自信 37 | 就完了)。 38 | 39 | 数组其实还是有很多理解的,比方我再刚开始介绍数组时提到的,它是编程语言的数据类型,还是数据结构。 40 | 这就意味着,编程语言的数据类型是由数据结构实现的。可是也会存在数据类型由散列表、链表、搜索树或其他 41 | 数据结构实现。有没有感觉有点儿不一样了,说好的不是这样的啊。 42 | 43 | 在算法的理解中,它更应该是一种理论上计算机科学模型,专注于数组的基本性质上。这样的话,你就能理解某些 44 | 编程语言上的数组和上述的描述会有点儿出入了。 45 | 46 | *二维数组*,是不是没想到,数学上,我们称之为 *矩阵*。 47 | *多维数组*,这个是不是就更不好理解了,嘿嘿。 48 | *动态数组*,这个是不是也懵逼的。 49 | 50 | 其实像上面说的,都是对一维数组,也就是上面说的 *数组* 的扩展,你现在不明白没关系(因为目前我也没有 51 | 很通透的熟悉完所有的数组),随着后面的展开,你会更加了解和熟悉的。数组是很基础的数据结构, 52 | 它的特性一定要了然如胸,不然后面的数据结构和算法的内容会有点儿吃力,这是不好的现象。 53 | 54 | 因为本系列的课程主要都是围绕着 JavaScript 的数据结构和算法的,所以可能本系列对应的代码实现和前面 55 | 文本描述的会有一些出入,请参考刚才说的,你就明白是为什么。 56 | -------------------------------------------------------------------------------- /docs/cn/backtracking.adoc: -------------------------------------------------------------------------------- 1 | [backtracking_algorithm] 2 | == 回溯算法 3 | 回溯算法的思想很简单,就像是摸着石头过河一样,需要你不断的探索,不对了就回来继续探索前行。 4 | 5 | 说起来很简单,理解起来也简单,不过用起来你就不会觉得简单了。 6 | 7 | 我们就从上面简单的描述来分析一下回溯算法的一些特性: 8 | 9 | - 可以直接猜到,这个是个暴力算法。 10 | - 至少要求他可以分为多个步骤来解答。 11 | - 感觉和贪心算法很想,不过这个是有进有退。 12 | - 它是摸着石头过河,每一步如何,都是事后才知道。 13 | - 就方法来看,最差的情况下,需要摸完所有的石头,而且还没找到解。 14 | 15 | 16 | === 实现一个回溯算法 17 | 18 | 这个只用嘴巴来说的话,太好理解了,以至于用的时候一脸懵逼。基本上学习这个都是以 *八皇后问题* 来入手的。 19 | 接下来,我们也实现一下吧。 20 | -------------------------------------------------------------------------------- /docs/cn/complexity.adoc: -------------------------------------------------------------------------------- 1 | [complexity] 2 | == 复杂度 3 | 大 O 来表示复杂度。 4 | 5 | 粗略的来分类,可以衡量算法复杂度的两个指标。时间复杂度和空间复杂度。 6 | 7 | 复杂度的量级分类。可以简单的分为多项式量级和非多项式量级。*非多项式量级* 的复杂度有: latexmath:[$O(2^n)$] 8 | 和 latexmath:[$O(n!)$]。当规模 n 越大,求解的时间会急剧增长,故这种复杂度的算法基本上处于尴尬的地位。 9 | 10 | 常量 11 | 对数 12 | 线性 13 | 对数线性 14 | 平方/立方/k次方 15 | 指数 16 | 阶乘 17 | 18 | 常见的空间复杂度 latexmath:[$O(1)$],latexmath:[$O(n)$], latexmath:[$O(n^2)$] 19 | 20 | 然而,仅仅是时间复杂度是不够的,我们还要能细化到这种程度。 21 | 22 | - 最好情况时间复杂度 23 | - 最坏情况时间复杂度 24 | - 平均时间复杂度 25 | - 均摊时间复杂度 26 | 27 | -------------------------------------------------------------------------------- /docs/cn/divide_and_conquer.adoc: -------------------------------------------------------------------------------- 1 | [divide_and_conquer] 2 | == 分治算法 3 | 分治算法,简而言之就是 *分而治之*。 4 | 5 | 说起来很简单啦,就是把一个大的复杂的问题拆分成多个相似的子问题,直到子问题成为一个小的简单的问题,然后 6 | 就可以直接求解,并且往回合并,从而得到最终的解。 7 | 8 | 从上面的描述我们就能很自然的想到 *递归* 这个算法。嗯,其实也很好理解,递归是直接可以解决问题的方案, 9 | 这个分值算法,更多的体现在分治思想上,如何处理问题,分治,怎么实现分治,使用递归算法吧。嗯,这就很好 10 | 理解了。 11 | 12 | 不过这儿你需要明白一点儿,*分治算法* 看似很好理解,不过千万不要小看它,它的细节一点儿要了解透才行。 13 | 接下来就看看它在实践中需要注意的东西吧。 14 | 15 | - 问题的可拆解性 16 | - 子问题的可拆解性 17 | - 子问题的可求解性 18 | - 子问题解的可合并性 19 | 20 | 以上三点看似很简单,是因为它们被我总结过了。很多时候,概念就已经给我们答案,不过还是要牢记这些东西。 21 | 22 | [implement_divideAndConquer] 23 | === 实现一个分治算法 24 | 25 | 分治算法我们之前也有接触过,最简单的就是 *二分查找* 了,还有就是 *归并排序* 和 *快速排序*。 26 | 27 | 至此,你已经了解完分治算法的精髓了。我们就来看看那些可以应用分治算法的经典问题吧。 28 | 29 | - 二分查找 30 | - 归并排序 31 | - 快速排序 32 | - 大整数乘法 33 | - 距离最近的点 34 | - 矩阵乘法 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/cn/dynamic_programming.adoc: -------------------------------------------------------------------------------- 1 | [dynamic_programming] 2 | == 动态规划 3 | 动态规划多用来解决复杂问题, 4 | -------------------------------------------------------------------------------- /docs/cn/graph.adoc: -------------------------------------------------------------------------------- 1 | [graph] 2 | == 图 3 | 图也是一种非线性表数据结构。 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/cn/greedy.adoc: -------------------------------------------------------------------------------- 1 | [greedy_algorithm] 2 | == 贪心算法 3 | 贪心算法是在局部寻找最优解,以达到整体的最优解。 4 | 5 | 从上述简单的说明就可以看出来,这个是有漏洞在里面的,希望你不要纠结这个先,我们接着往下看,你便明了了。 6 | 7 | 我们来简单总结一下它的特性和条件: 8 | 9 | - 问题可以被拆解为若干子问题 10 | - 每个子问题有至少一个解 11 | - 所有子问题的解,可以得到一个最优解 12 | - 每个子问题的最优解,可以合成原来问题的解 13 | 14 | 上面的细节我们也能看出来,贪心算法的使用范围并不大,因为每个子问题都是相对独立的,所以想要保证最终解是 15 | 最优解,确实是有点儿贪心了,这就不现实了。 16 | 17 | 不过它也有一些可以解决的问题,如 *最小生成树*、*哈夫曼编码*等。 18 | 19 | 虽然贪心算法在其他问题中不能直接提供解答,但是贪心算法的这个思想确实很直接学习的。 20 | 21 | 下面,我们就以一个实例来介绍贪心算法的具体使用: 22 | 23 | *找钱*,现在顾客需要你找给他 46 元,我们以贪心算法来推导一下过程。 24 | 25 | - 首先,小于46的最大面额是20,那就先找零一个20. 26 | - 接下来剩余26,少于26的最大面额是20,再找零一张20. 27 | - 然后剩余6,小于6的最大面额是5,那就找零一张5. 28 | - 最后剩余1,那就只有1. 29 | 30 | 以上,就找零结束了,找零方案就是两张20,一张5,一张1。这个就是每次都是最优解,然后所有子问题合成问题 31 | 的最优解。 32 | -------------------------------------------------------------------------------- /docs/cn/hash_table.adoc: -------------------------------------------------------------------------------- 1 | [hash_table] 2 | == 哈希表 3 | 哈希表我们也叫它 *散列表*,是一种将键映射到值的数据结构。 4 | 5 | 哈希表是用哈希函数来将 *键* *映射* 到小范围的存储表(数组或桶或者槽)中。嘿嘿,这么看是不是觉超 6 | 简答,没错,我也这么认为,这个时候你就要小心的。如果存储表空间不足怎么办,能不能保证不会重复映射。 7 | 8 | 接下,我们就详细的来看看你要注意的东西。 9 | 10 | [hash_function] 11 | === 哈希函数 12 | 13 | hash(key) 这就是一个哈希函数。你以为这就完了,你肯定已经明白它是用来计算键(key)的 hash 值。 14 | 不过你一定要知道的是,一个好的哈希函数需要做到什么。 15 | 16 | - 极速计算,那必须是 latexmath:[O(1)] 级别的哈。 17 | - 属于同一组的所有值都将映射到同一组中。即 key1 === key2 时,hash(key1) === hash(key2)。 18 | - 需要分成不同组的值不会映射到同一组。即 key1 !== key2 时,hash(key1) !== hash(key2)。 19 | - 尽可能的减少碰撞, 20 | - 尽可能均匀的分配。 21 | 22 | 知道上面东西后,你就会发现,其实还是有问题存在的,散列冲突还是会存在的。常用的解决方案有两大类 23 | 24 | [open_addressing] 25 | === 开放寻址法 26 | 开放寻址法的核心思想是出现冲突,我们就重新探测一块空闲位置。其具体的操作方法包括 *线性探测*、 27 | *二次探测* 和 *双重散列*。 28 | 29 | *线性探测* 是在我们 *插入* 数据是发现位置已经被占用后,从当前的位置往后查找,直至找到空闲位置为止。 30 | 其实仔细想想你也会发现,随着空闲位置的变少,每次插入就可能找啊找的,多浪费时间;*查找* 更是坑爹不说, 31 | 你如果发现查找的不是你要的说明是个冲突位置,需要你挨个往后找,遇到空闲位置还找不到说明没有; 32 | 再说下 *删除* 操作,你直接删除留下的空位,可能在查找时误以为空位置就停下来,这需要你做个删除的标记, 33 | 可以让查找时继续查找。 34 | 35 | *二次探测* 其和线性探测思路一样,不过就是你探测的步长不一样,每次都是二次平方的步长。 36 | 37 | *双重散列* 是使用多个哈希函数,在出现冲突时,再用第二个哈希函数,直至找到空闲空间为止。 38 | 39 | 其实你也能看出来,当剩余的空间比较少时,冲突的概率就会大大的提高。我们可以用 *装载因子* 来表示空位 40 | 41 | 散列表的装载因子 = 表中的元素个数 / 散列表的长度 42 | 43 | [chaining] 44 | === 链表法 45 | 列表法是解决散列冲突的常用方法。 46 | 47 | 其操作比起 *开放寻址法* 更加的简单易用。来看一下具体的步骤。在遇到冲突时,在其位置后以链表的形式插入。 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/cn/heap.adoc: -------------------------------------------------------------------------------- 1 | [heap] 2 | == 堆 3 | 堆是一种特殊的树。 4 | 5 | - 堆是一个完全的二叉树。 6 | - 堆中每一个节点的值都必须大于等于其子树中每一个节点的值。(当然,小于等于每个节点的值也是可以的) 7 | 8 | 因为堆是一个完全二叉树,所以其存储是可以直接使用数组来存储的。 9 | 10 | 这个我们也叫做 *二叉堆*。其中分为 *大顶堆* 和 *小顶堆*。 11 | -------------------------------------------------------------------------------- /docs/cn/linked_list.adoc: -------------------------------------------------------------------------------- 1 | [linked-list] 2 | == 链表 3 | 链表相比于数组是稍复杂的数据结构。 4 | 5 | 链表的特点: 6 | 7 | - 线性表 8 | - 不连续的空间存储 9 | 10 | 链表作为和数组相似的数据结构,其都是线性表。但是有个很大的不同,那就是在于存储空间的不连续性。可能你会觉得这个没啥,我们稍微详细的解释一下,你就会明白了。 11 | 12 | 链表 是通过在单个节点上标注下一个节点的位置来把所有的元素链接在一起。所以,存储上并不需要连续的空间,这样就不会出现数组需要 1G 空间,明明剩余 1.2G,却提示空间不足的情况。不过从表述也能看出,它会把单个节点变大,毕竟还要存储下一个节点的位置(双向链表还要存上一个节点位置)。 13 | 链表的拥有很多种结构,这儿就挑一些一一展开介绍。 14 | 15 | *单链表*:最简单的链表,也是最常用的链表。链表的插入与删除操作都是 latexmath:[$O(1)$]。 16 | 找到需要插入的位置就需要进行遍历,平均时间复杂度为 latexmath:[$O(1)$]。 17 | 18 | *循环链表*:循环链表就是在普通单链表的基础上,对收尾进行相连,让尾节点的 next 指向头节点。这儿提供一个 19 | 可以用循环链表来解决问题的例子,Josephus problem。 20 | 21 | *双向链表*:双向链表意味着删除一个节点,就可以直接获取前序节点,操作时间复杂度就是 latexmath:[$O(1)$]。 22 | -------------------------------------------------------------------------------- /docs/cn/main.adoc: -------------------------------------------------------------------------------- 1 | = 数据结构与算法 2 | Master Shu 3 | :doctype: book 4 | :toc: 5 | :icons: 6 | :numbered: 7 | 8 | include::complexity.adoc[] 9 | include::arrays.adoc[] 10 | include::linked_list.adoc[] 11 | include::stack.adoc[] 12 | include::queues.adoc[] 13 | include::recursion.adoc[] 14 | include::sorting.adoc[] 15 | -------------------------------------------------------------------------------- /docs/cn/queues.adoc: -------------------------------------------------------------------------------- 1 | [queues] 2 | == 队列 3 | 应该说队列是日常生活中接触最多的。 4 | 5 | 你买东西结账要排队,等公交要排队,取钱要排队。这个对,就可以理解为队列了。 6 | 7 | 队列的操作只有两个,入队和出队。 8 | 9 | 根据场景也可以看出队列: 10 | 11 | - 线性表 12 | - 操作受限 13 | 14 | 队列和栈一样,也是可以使用数组或者链表来实现。并且队列也是分为多种不同的特性队列。 15 | 16 | *循环队列* 可以理解为一个环形的队列,不需要进行数据搬迁,而是不断调整头部和尾部就可以一直使用了。 17 | 18 | *阻塞队列* 这个接触过 Redis 的应该很快就能理解。就是 *出队* 和 *入队* 时增加限制条件,以此来阻塞队列。 19 | 20 | *并发队列* 这个可以结合着阻塞队列来理解,为了在并发时保证线程安全。 21 | -------------------------------------------------------------------------------- /docs/cn/recursion.adoc: -------------------------------------------------------------------------------- 1 | [recursion] 2 | == 递归算法 3 | 递归是计算机科学中的重要概念。 4 | 5 | 递归是众多算法和数据结构的基础。但是对于新手来说,就是有点儿呵呵了~ 6 | 7 | 有人这么解释过难理解的情况,`当人们试图想清楚整个递和归过程的做法,实际上是进入了一个思维误区。 8 | 很多时候,我们理解起来比较吃力,主要原因就是自己给自己制造了这种理解障碍。 9 | 编写递归代码的关键是,只要遇到递归,我们就把它抽象成一个递推公式, 10 | 不用想一层层的调用关系,不要试图用人脑去分解递归的每个步骤。` 11 | 12 | 那么接下来,让我们来理解一下递归的知识点。 13 | 14 | 编写递归代码,需要我们考虑以下几个问题。 15 | 16 | - 一个简单的基本案例(basic case) 17 | - 一组递推关系(recurrence relation)。这就需要我们考虑一个问题的结果与其子问题的结果之间的关系。 18 | 19 | 可以说熟练以上两个操作,就已经可以完成递归的计算和操作了。因为是基础的算法,所以必须的熟练,我提议 20 | 至少练习一百道递归相关的算法题。说再多不如亲子动手实践一下。 21 | 22 | 在这个基础上,接下来要说的是提升一点儿等级的东西。 23 | 24 | *边界问题* 这儿主要想要表达的是堆栈溢出的问题,这个是要注意的,每一次函数调用都会压入新的栈, 25 | 只有在函数返回的时候才会出栈。倘若规模很大的话,不难想象堆栈溢出的惨象。 26 | 27 | *重复计算* 重复计算这个会带来很大的资源浪费,不过别怕,我们可以利用记忆化的技术来避免重复计算。`记忆化 是一种优化技术,主要用于加快计算机程序的速度,方法是存储昂贵的函数调用的结果, 28 | 并在相同的输入再次出现时返回缓存的结果。 (来源: 维基百科)` 29 | -------------------------------------------------------------------------------- /docs/cn/search.adoc: -------------------------------------------------------------------------------- 1 | [search] 2 | == 搜索 3 | 4 | [linear_search] 5 | === 线性搜索 6 | 7 | 线性搜索的时间复杂度为 latexmath:[$O(n)$],就是按照顺序一个个检查,直到找到我们需要的元素为止。 8 | 最好的情况是 latexmath:[$O(1)$],最坏就是 latexmath:[$O(n)$]。其实这个不需要过多篇幅介绍。 9 | 10 | [binary_search] 11 | === 二分查找 12 | 13 | 二分查找是很好用的一种算法。时间复杂度为 latexmath:[$O(logn)$]。接来下,我们就从一个例子开始学习吧。 14 | 从一堆文件中找到你要的那个时间点的文件,当然这首先是按照顺序来排列的文件。我们首先从中间的文件看起, 15 | 然后就可以确定是在哪一叠,接下来就在那一叠重复刚才的查到操作。直至找到为止。 16 | 17 | 怎么样,是不是很好操作,也很容易掌握。而且速度也快。不过这也是在具备条件的前提下进行,比方说数组已经 18 | 是 *有序数组*,并且可以 *随机访问* 任意节点,如果是链表实现的数据,你就没办法这么操作了。 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/cn/sorting.adoc: -------------------------------------------------------------------------------- 1 | [sorting] 2 | == 排序 3 | 排序是你必须了解的算法。 4 | 5 | 排序可以说是众多操作的基础了。而且排序的算法也很多。这儿我也就挑一些算法来展开。 6 | 7 | 通过排序算法的学习,你会发现对之前介绍的时间复杂度、空间复杂度这些概念有更多也更深刻的认识, 8 | 从今天开始,它们就不再是冷冰冰的概念了,而是你随时备在手边的武器。 9 | 10 | 首先,我们来结合排序算法来回顾一下以前的关于复杂度的学习。 11 | 12 | *时间复杂度* 最好时间复杂度,最坏时间复杂度,平均时间复杂度。对于同样时间复杂度排序算法,如果知道 13 | 原本的数据接近有序,是不是就对选择有不同的影响了。 14 | 15 | *空间复杂度* 关于排序,我们会使用很多原地排序算法。即是 latexmath:[$O(1)$] 的空间复杂度。 16 | 17 | *稳定性* 在排序前相同值得元素不会在排序会被改变,这就是稳定性。 18 | 19 | 除了以上的内容,其实还有你需要关心的内容,比方说以上实现都一样,就可以考虑到单次操作的消耗了时间或者 20 | 空间了。 21 | 22 | [bubble_sort] 23 | === 冒泡排序 24 | 我们首先从冒泡排序学起吧。 25 | 26 | 用文字描述一下先,冒泡排序是通过操作相邻的两个元素进行比较,然后把较大那个换到后面,依次类推,完成一次 27 | 机可以确认出最大的数,并且已经移动到最后的位置,然后再循环操作一次,就可以选出第二大...。直至完全排序 28 | 完成。 29 | 30 | 通过文字描述就可以得出,基本上需要循环操作两次,就可以粗略的得出平均时间复杂度为 latexmath:[$O(1)] 31 | ,每次操作的空间是有限的,那就肯定是 latexmath:[$O(1)$]。只是比较大的元素会被交换,意味着这肯定是 32 | 个稳定排序。 33 | 34 | 以上的仅仅是凭借着第一感觉来分析的,不过现在也够了。 35 | 36 | [insertion_sort] 37 | === 插入排序 38 | 39 | 首先在需要排序的数据上,可以理解为两个区间。一边是有序的一边是需要排序的。初始状态是只有一个有序的 40 | 元素,无论大小。然后,从第二个元素提取出来开始进行比较,如果比前一个小,前面的就后移,把该位置空置留给需要的 41 | 元素,如果不比它不小,就把元素查到元素的后面,这样,有序的区间就会增加一个元素,需要排序的区间就会 42 | 少一个元素。依次类推,直至循环比较到最后一个元素,完成排序过程。 43 | 44 | 通过文字描述,我们就能猜得出来,这个空间复杂度为 latexmath:[$O(1)$], 并且它是一个稳定的排序。 45 | 然后我们来看时间复杂度,如果原本的数据就是有序的情况,那么我们只需要循环遍历比较一次依次,最好的时间 46 | 复杂度为 latexmath:[$O(n)$],当然如果全部无序,也就是说数据是倒序的话,那每个循环子项都需要进行大量 47 | 的移动和比较,最差的时间复杂度就位 latexmath:[$O(n^2)$]。平均复杂度为 latexmath:[$O(n^2)$]。 48 | 49 | [selection_sort] 50 | === 选择排序 51 | 52 | 选择排序的做法是在一个需要排序的数据中,循环操作数据长度对应的次数。第一次,用第一个元素和接下的所有 53 | 元素一一比较,比较出最小的那个,进行位置交换,把最小的那个第一个元素,第二次循环用第二个元素进行依次 54 | 循环比较,得出最小的元素,进行位置替换。依次循环到最后一个元素,完成排序操作。 55 | 56 | 通过文字描述,可以看出,这个也是个空间复杂度为 latexmath:[$O(1)$] 的原地排序算法。不过每次都需要 57 | 比较意味着不管是最好还是最坏以及平均时间复杂度都为 latexmath:[$O(n^2)$]。而且通过进行单个的元素 58 | 比较交换可以看出,这个是无法保证稳定性的。 59 | 60 | [merge_sort] 61 | === 归并排序 62 | 63 | 归并排序这儿我们要学一个新的概念,分治思想。 64 | 65 | 其关键就是两步操作,第一步,一分为二的进行排序,*递归实现* 直至递归到无法无法分解为止。第二步, 66 | 归并,通过一个临时数组,来进行比较需要归并的左右两个有序数据,标记一个中点,然后比较两侧的第 67 | 一个元素,接下来就再比较两边剩下的第一个元素,直至两边数据完全排序。最后不断递归回调直至完成排序。 68 | 69 | 递归相比较冒泡排序等是略微复杂一点儿的,因为从这儿开始就打上了组合拳。 70 | 71 | 接下我们简单分析一下复杂度。从临时增加的数组可以看出,他的空间需要和需要排序是数组是一般长的,故 72 | 为 latexmath:[$O(n)$],从排序的方式可以看出来,这十个稳定排序。时间复杂度就不好直接看出来, 73 | 那么简单的分析一下,分解过程是 latexmath:[$O(logn)$],而归并排序的过程是 latexmath:[$O(n)$], 74 | 这种情况下就得出为 latexmath:[$O(nlogn)$]。从分解和排序的过程来看,最好和最坏的时间复杂度都一样。 75 | 咦,一点儿都不严谨。 76 | 77 | [quick_sort] 78 | === 快速排序 79 | 快速排序常被简称为快排,例如、三路快排。 80 | 81 | 快速排序和归并排序还是有很多的相似之处的,我们可以类比着来学习。那么开始吧: 82 | 83 | 快排也是采用分治和递归的方式来解题,这个也是关键的两步走。第一步,分。选择一个随机点儿进行分区, 84 | 这个点儿的左边为小于它的存在,右边为大于它的存在。第二步,*递归实现* 通过以上的划分点儿,递归 85 | 操作两边的两个分区继续进行分解排序。循环执行直至无法分解。最后完成排序。 86 | 87 | 会发现其实两种和归并相比,这个还是区别挺明显的。因为是随机的生成区分点,这就意味着最差的情况下, 88 | 时间复杂度会退化到 latexmath:[$O(n^2)$],不过大部分情况下时间复杂度还是为 89 | latexmath:[$O(nlogn)$],但是优点也很明显,它可以做到原地排序,不过却做不到保持有序。 90 | 91 | [bucket_sort] 92 | === 桶排序 93 | 94 | 就是利用“桶”来进行排序,其操作就是把需要排序的数据放到几个有序的桶里,每个桶单独排序。完成后再 95 | 依次取出,组成的序列就是完成排序的数据了。 96 | 97 | 听着很简单的样子,我们简单分析一下。桶的时间复杂度为 latexmath:[$O(n)$],不过这是理想情况, 98 | 前提就是对需要排序的数据有要求,比方说,有的桶里数据多,有的桶里数据少,数据平均分布才能达到要 99 | 求。 100 | 101 | [counting_sort] 102 | === 计数排序 103 | 104 | 计数排序我们可以简单的划分为三个步骤。第一步,创建一个临时的二维数组。其一维的元素就是需要排序 105 | 的数据范围值。第二步,进行计数并将对应的数据插入到临时数组对应的位置中。第三步,对把临时数组的 106 | 元素按照顺序赋值给我们需要排序的数组,完成排序。 107 | 108 | 计数排序对需要排序的数据存在一定的要求,否则需要排序的数据范围广,还存在小数、负数之类的。所以要 109 | 明白计数排序的使用场景,多用在小范围的整数排序。 110 | 111 | [radix_sort] 112 | === 基数排序 113 | 114 | 基数排序,我们也分为三个步骤来做。第一步,确认需要排序的数据的位数 w,以及单个基数的比较范围 k, 115 | 注意,位数不满足的以 0 来补位。第二步,创建临时空间,空间内分为 k 个子空间,在最低位按照范围 116 | k 的顺序插入到临时空间 。(这就和计算排序的计数过程是一样的) 第三步,依次从地位到高位重复步骤二, 117 | 直至完成排序。 118 | 119 | 计数排序,也是非比较排序,其时间复杂度可以理解为 latexmath:[$O(n)$],但是需要注意的是,如果 k 120 | 够大,甚至说和需要排序的长度一般,那时间复杂度就是 latexmath:[$O(n^2)$]。同时这也是稳定排序, 121 | 前提是每一位的排序都是有序。 122 | 123 | [sorting_summary] 124 | === 排序小结 125 | 126 | 简单小结一下,排序算法是很多算法的基础。这个是一定要熟练掌握的。比方说搜索,那是在有序的基础上来 127 | 做的,否则你肯定要一个个比较了。而且,没有最好的算法,只有更适合的算法。撇开场景谈算法无异于纸上 128 | 谈兵,所以解题的关键第一步还是对题的分析。 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /docs/cn/stack.adoc: -------------------------------------------------------------------------------- 1 | [stack] 2 | == 栈 3 | 4 | 栈也是一种相当常见的数据结构。 5 | 6 | 简单的来说,你可以这么来理解栈,他就像是叠盘子一样,用的时候从上面拿,少了就往上面放,当然,这就是打个 7 | 比方,能形象的理解就好。 8 | 9 | 从上的比喻我们也能看出来,其实栈也是比较容易理解的。而且我们也可以很容易的得出这么个想法: 10 | 11 | - 栈也是线性表 12 | - 它操作是受限的 13 | - 操作也仅有一进一出 14 | - 后来者居上,也就是先到后出,后到先出 15 | - 单个端点的操作 16 | 17 | 至此,关于栈的基本解释就结束了。相信基本上都明白一二了。 18 | 19 | === 栈的应用 20 | 21 | 如果想更深入的了解栈,你就必须得用起来才行,或者说,至少知道别人是怎么用的。接下来我会介绍一些应用 22 | 场景,感兴趣可以自行了解哈。 23 | 24 | - 函数调用栈,不错,这个只要你接触到编程,就一定会了解到这个的。如果有小伙伴感兴趣,后续会以 JavaScript 25 | 为例,来介绍一下实际运行。 26 | - 表达式求值,这也是本人接触到栈的时候写过的第一个关于栈的应用代码。 27 | -------------------------------------------------------------------------------- /docs/cn/tree.adoc: -------------------------------------------------------------------------------- 1 | [tree] 2 | == 树 3 | 树是一种抽象数据类型,它是由 n 个有限节点组成一个具有层次关系的结合。 4 | 5 | 这个还是看图比较直观好了解。数据结构中的树和现实中的树可以说是很相似的,不过是是位置相反的,根在上 6 | 估计这也是符合咱们的使用习惯吧。 7 | 8 | image:../images/Treedatastructure.png[树] 9 | 10 | 从图上可以看出来,它可以简单的理解为树是由 *节点* 和 *节点间的关系* 构成。接下来,我们细致的了解一下。 11 | 最顶端的是根,也就是 *根节点*,唯独它是特殊的,它没有父节点。剩下的所有节点都是存在 *父节点* 的,而且有且仅有 12 | 一个父节点。这么来看,你是不是对树这种结构就容易理解了。 13 | 14 | 接下来再说一下关于树的几个常用概念: 15 | 16 | - 高度:对于任意节点 n,n 的高度为从 n 到叶子节点的最长路径。树的高度就是根节点的高度。 17 | - 深度:对于任意节点 n,n 的深度就是从根节点到 n 节点的唯一路径长。根节点的深度为0。 18 | - 层级:对于任意节点 n,n 的深度 + 1。 19 | 20 | 关于更多可以参考一下 https://zh.wikipedia.org/zh-cn/%E6%A0%91_(%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84)[Wiki] 21 | 22 | [binary_tree] 23 | === 二叉树 24 | 相传每年都有两棵树上会挂很多人,一颗是高数,另一颗是二叉树,随着时间的推移,这一理论已不可求证,不过 25 | 二叉树的威名算是流传下来了。 26 | 27 | 顾名思义,二叉树,就是只有两个叉,一个左叉,一个右叉。对应的分别是 *左子节点* 和 *右子节点*。以上 28 | 是最直观的理解,不过你也不要钻牛角尖,是不是每个节点都必须有两个子节点啊,那岂不是子子孙孙无穷尽也, 29 | 难道连叶子节点也不要了吗? 30 | 31 | [trie] 32 | === 字典树 33 | *字典树*,还可称为*单词查找树*。 34 | 35 | 字典树一个很大的应用场景就是在搜索引擎的输入联想提示。还是来看图说话吧。 36 | 37 | image::../images/Trie_example.svg[字典树] 38 | 39 | 我们来看下它的特点, 40 | 41 | - 根节点是一个不存储任何数据的节点 42 | - 是有序的数据 43 | -------------------------------------------------------------------------------- /docs/images/Treedatastructure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MasterShu/JavaScript-DataStructures-Algorithms/66f895bcf9d2da73a580a71c6cab0b62ea633223/docs/images/Treedatastructure.png -------------------------------------------------------------------------------- /docs/images/Trie_example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 13 | 19 | 24 | 25 | 31 | 36 | 37 | 38 | 43 | 51 | 59 | 67 | 75 | 83 | 91 | 99 | 107 | i 116 | t 125 | e 134 | o 143 | n 152 | n 161 | n 170 | a 179 | t 188 | i 197 | in 206 | inn 215 | te 224 | tea 233 | ten 242 | to 251 | 3 260 | 12 269 | 9 278 | 7 287 | 5 296 | 11 305 | 309 | 313 | 316 | 320 | 324 | 325 | 328 | 332 | 336 | 337 | 345 | 353 | 361 | 369 | 377 | 385 | 388 | 392 | 396 | 397 | ted 406 | d 415 | A 424 | 432 | 440 | A 449 | 15 458 | 4 467 | 468 | -------------------------------------------------------------------------------- /docs/index.adoc: -------------------------------------------------------------------------------- 1 | = DATA STRUCTURES AND ALGORITHMS 2 | Master Shu 3 | :doctype: book 4 | :toc: 5 | :icons: 6 | :numbered: 7 | 8 | 9 | -------------------------------------------------------------------------------- /heap/BinaryHeap.js: -------------------------------------------------------------------------------- 1 | class BinaryHeap { 2 | constructor () { 3 | this.data = [] 4 | this.count = 0 5 | } 6 | 7 | get size () { 8 | return this.count 9 | } 10 | 11 | /** 12 | * 构建二叉堆 - 通过数组数组 13 | * @param {array} array 原始数组 14 | */ 15 | build (array) { 16 | this.count = array.length 17 | for (let i = 0; i < array.length; i++) { 18 | this.data[i + 1] = array[i] 19 | } 20 | for (let i = 0; i < parseInt(this.count / 2); i++) { 21 | this.__shiftDown(i) 22 | } 23 | } 24 | 25 | print () { 26 | console.log(this.data) 27 | } 28 | 29 | isEmpty () { 30 | return this.count === 0 31 | } 32 | 33 | insert (item) { 34 | this.data[this.count + 1] = item 35 | this.count += 1 36 | this.__shiftUp(this.count) 37 | } 38 | 39 | shiftMax () { 40 | if (this.count < 0) { 41 | return null 42 | } 43 | const key = 1 44 | const result = this.data[key] 45 | const tempData = this.data[key] 46 | this.data[key] = this.data[this.count] 47 | this.data[this.count] = tempData 48 | this.count-- 49 | this.__shiftDown(key) 50 | return result 51 | } 52 | 53 | __shiftUp (k) { 54 | while (k > 1 && this.data[parseInt(k / 2)] < this.data[k]) { 55 | [this.data[parseInt(k / 2)], this.data[k]] = [this.data[k], this.data[parseInt(k / 2)]] 56 | k = parseInt(k / 2) 57 | } 58 | } 59 | 60 | __shiftDown (n) { 61 | while (2 * n <= this.count) { 62 | let i = 2 * n 63 | if (i + 1 <= this.count && this.data[i + 1] > this.data[i]) { 64 | i += 1 65 | } 66 | if (this.data[n] >= this.data[i]) { 67 | break 68 | } 69 | [this.data[i], this.data[n]] = [this.data[n], this.data[i]] 70 | n = i 71 | } 72 | } 73 | } 74 | 75 | module.exports = { 76 | BinaryHeap 77 | } 78 | -------------------------------------------------------------------------------- /heap/IndexHeap.js: -------------------------------------------------------------------------------- 1 | class IndexHeap { 2 | constructor() { 3 | this.data = [] 4 | this.count = 0 5 | this.indexes = [] 6 | this.reverse = [] 7 | } 8 | 9 | get size() { 10 | return this.count 11 | } 12 | 13 | /** 14 | * 构建二叉堆 - 通过数组数组 15 | * @param {array} array 原始数组 16 | */ 17 | build(array) { 18 | this.count = array.length 19 | for (let i = 0; i < array.length; i++) { 20 | this.data[i + 1] = array[i] 21 | } 22 | for (let i = 0; i < parseInt(this.count / 2); i++) { 23 | this.__shiftDown(i) 24 | } 25 | } 26 | 27 | print() { 28 | console.log(this.data) 29 | } 30 | 31 | isEmpty() { 32 | return this.count === 0 33 | } 34 | 35 | insert(i, item) { 36 | i += 1 37 | this.data[this.count + 1] = item 38 | this.indexes[this.count + 1] = i 39 | this.reverse[i] = this.count + 1 40 | this.count += 1 41 | this.__shiftUp(this.count) 42 | } 43 | 44 | shiftMax() { 45 | if (this.count < 0) { 46 | return null 47 | } 48 | const key = 1 49 | const result = this.data[this.indexes[key]] 50 | const tempData = this.indexes[key] 51 | this.indexes[key] = this.indexes[this.count] 52 | this.indexes[this.count] = tempData 53 | this.reverse[this.indexes[key]] = key 54 | this.reverse[this.indexes[this.count]] = 0 55 | this.count-- 56 | this.__shiftDown(key) 57 | return result 58 | } 59 | 60 | shiftMaxIndex() { 61 | if (this.count < 0) { 62 | return null 63 | } 64 | const key = 1 65 | const result = this.indexes[key] - 1 66 | const tempData = this.indexes[key] 67 | this.indexes[key] = this.indexes[this.count] 68 | this.indexes[this.count] = tempData 69 | this.reverse[this.indexes[key]] = key 70 | this.reverse[this.indexes[this.count]] = 0 71 | this.count-- 72 | this.__shiftDown(key) 73 | return result 74 | } 75 | 76 | /** 77 | * 获取子元素 78 | * 根据索引 79 | * @param {any} i 索引index 80 | * @returns 元素 81 | * @memberof IndexHeap 82 | */ 83 | getItem(i) { 84 | return this.data[i + 1] 85 | } 86 | 87 | /** 88 | * 改变子元素 89 | * 根据索引改变元素 90 | * @param {any} i 索引 91 | * @param {any} item 新的元素 92 | * @memberof IndexHeap 93 | */ 94 | change(i, item) { 95 | i += 1 96 | this.data[i] = item 97 | for (let j = 1; j <= this.count; j++) { 98 | if (i === this.indexes[j]) { 99 | this.__shiftUp(j) 100 | this.__shiftDown(j) 101 | return 102 | } 103 | } 104 | } 105 | 106 | changeItem(i, newItem) { 107 | i += 1 108 | this.data[i] = newItem 109 | let j = this.reverse[i] 110 | this.__shiftUp(j) 111 | this.__shiftDown(j) 112 | } 113 | 114 | __shiftUp(k) { 115 | while (k > 1 && this.data[this.indexes[parseInt(k / 2)]] < this.data[this.indexes[k]]) { 116 | [this.indexes[parseInt(k / 2)], this.indexes[k]] = [this.indexes[k], this.indexes[parseInt(k / 2)]] 117 | this.reverse[this.indexes[parseInt(k / 2)]] = parseInt(k / 2) 118 | this.reverse[this.indexes[k]] = k 119 | k = parseInt(k / 2) 120 | } 121 | } 122 | 123 | __shiftDown(n) { 124 | while (2 * n <= this.count) { 125 | let i = 2 * n 126 | if (i + 1 <= this.count && this.data[this.indexes[i + 1]] > this.data[this.indexes[i]]) { 127 | i += 1 128 | } 129 | if (this.data[this.indexes[n]] >= this.data[this.indexes[i]]) { 130 | break 131 | } 132 | [this.indexes[i], this.indexes[n]] = [this.indexes[n], this.indexes[i]] 133 | this.reverse[this.indexes[n]] = n 134 | this.reverse[this.indexes[i]] = i 135 | n = i 136 | } 137 | } 138 | } 139 | 140 | module.exports = { 141 | IndexHeap 142 | } 143 | -------------------------------------------------------------------------------- /heap/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./BinaryHeap') 2 | module.exports = require('./IndexHeap') 3 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const selection = require('./sorting/selectionSort/index') 2 | const quick = require('./sorting/quickSort/index') 3 | const insertion = require('./sorting/insertionSort/index') 4 | const merge = require('./sorting/mergeSort/index') 5 | const heap = require('./heap') 6 | const heapSort = require('./sorting/heapSort') 7 | const bubbleSort = require('./sorting/bubbleSort') 8 | 9 | module.exports = { 10 | ...selection, 11 | ...quick, 12 | ...insertion, 13 | ...merge, 14 | ...heap, 15 | ...heapSort, 16 | ...bubbleSort 17 | } 18 | -------------------------------------------------------------------------------- /index.test.js: -------------------------------------------------------------------------------- 1 | 2 | const { randArray, testSort, randNearlyArr } = require('./utils/index') 3 | const { selectionSort, insertionSortPlus, mergeSort, mergeSortBU, quickSort, quickSortRepetition, quickSort3Ways, BinaryHeap, heapSort, IndexHeap, bubbleSort } = require('./index') 4 | 5 | const numOfTimes = 10000 6 | 7 | const arr = randArray(0, 100000, numOfTimes) 8 | const arr2 = randNearlyArr(numOfTimes, 100) 9 | 10 | testSort('selectionSort', selectionSort, arr, numOfTimes) 11 | // testSort('insertionSort', insertionSortPlus, arr, numOfTimes) 12 | // testSort('mergeSort', mergeSort, arr, numOfTimes) 13 | 14 | testSort('mergeSortBU', mergeSortBU, arr, numOfTimes) 15 | testSort('quickSort', quickSort, arr, numOfTimes) 16 | testSort('bubbleSort', bubbleSort, arr, numOfTimes) 17 | 18 | // testSort('quickSort3Ways', quickSort3Ways, arr, numOfTimes) 19 | // testSort('heapSort', heapSort, arr, numOfTimes) 20 | 21 | // const hello = new IndexHeap() 22 | 23 | // for (let index = 0; index < 10; index++) { 24 | // const randKey = parseInt(Math.random() * 1000) 25 | // hello.insert(index, randKey) 26 | // } 27 | 28 | // // hello.print() 29 | // while (!hello.isEmpty()) { 30 | // console.log(hello.shiftMax()) 31 | // } 32 | -------------------------------------------------------------------------------- /knapsack/knapsackBacktracking.js: -------------------------------------------------------------------------------- 1 | class KnapsackBacktracking { 2 | /** 3 | * @param {array} weightList 4 | * @param {number} weightLoading 5 | */ 6 | constructor(weightList, weightLoading) { 7 | this.result = 0 8 | this.materialsWeight = weightList 9 | this.numbers = weightList.length 10 | this.weightLoading = weightLoading 11 | } 12 | 13 | getMaxWeight() { 14 | this.loadRecursive(0, 0) 15 | return true 16 | } 17 | 18 | /** 19 | * 20 | * @param {number} materialIndex 21 | * @param {number} currentWeight 22 | * @returns {boolean} 23 | */ 24 | loadRecursive(materialIndex, currentWeight) { 25 | // Check if the knapsack is full or material is done. 26 | if (currentWeight === this.weightLoading || materialIndex === this.numbers) { 27 | // Update the load of knapsack if current load is big than stored result. 28 | if (currentWeight > this.result) { 29 | this.result = currentWeight 30 | } 31 | return true 32 | } 33 | 34 | // Do not loading index of material 35 | this.loadRecursive(materialIndex + 1, currentWeight) 36 | // Loading index of material if the added weight is not overload. 37 | if (currentWeight + this.materialsWeight[materialIndex] <= this.weightLoading) { 38 | this.loadRecursive(materialIndex + 1, currentWeight + this.materialsWeight[materialIndex]) 39 | } 40 | } 41 | } 42 | 43 | module.exports = { 44 | KnapsackBacktracking 45 | } 46 | -------------------------------------------------------------------------------- /knapsack/knapsackDynamic.js: -------------------------------------------------------------------------------- 1 | class KnapsackDynamic { 2 | constructor(weightList = [], weightLoading = 0, valueList = []) { 3 | this.states = [] 4 | this.materialsWeight = weightList 5 | this.numbers = weightList.length 6 | this.weightLoading = weightLoading 7 | this.values = valueList 8 | } 9 | 10 | /** 11 | * 12 | * @returns {number} 13 | */ 14 | getMaxWeight() { 15 | this.states[0] = true 16 | if (this.materialsWeight[0] <= this.weightLoading) { 17 | this.states[this.materialsWeight[0]] = true 18 | } 19 | return this.dynamicCount() 20 | } 21 | 22 | /** 23 | * 24 | * @returns {number} 25 | */ 26 | dynamicCount() { 27 | for (let i = 1; i < this.numbers; i++) { 28 | for (let j = this.weightLoading - this.materialsWeight[i]; j >= 0; j--) { 29 | if (this.states[j] === true) { 30 | this.states[j + this.materialsWeight[i]] = true 31 | } 32 | } 33 | } 34 | 35 | for (let i = this.weightLoading; i >= 0; i--) { 36 | if (this.states[i] === true) { 37 | return i 38 | } 39 | } 40 | return 0 41 | } 42 | } 43 | 44 | module.exports = { 45 | KnapsackDynamic 46 | } 47 | -------------------------------------------------------------------------------- /knapsack/tests/knapsackBacktracking.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { KnapsackBacktracking } from '../knapsackBacktracking' 3 | 4 | test('Create empty instance of KnapsackBacktracking.', t => { 5 | const knapsackBacktracking = new KnapsackBacktracking([], 0) 6 | t.snapshot(knapsackBacktracking) 7 | t.pass() 8 | }) 9 | 10 | test('Counting max loading.', t => { 11 | const knapsackBacktracking = new KnapsackBacktracking([3, 5, 11, 7, 1], 21) 12 | knapsackBacktracking.getMaxWeight() 13 | t.is(knapsackBacktracking.result, 21) 14 | }) 15 | 16 | -------------------------------------------------------------------------------- /knapsack/tests/knapsackDynamic.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { KnapsackDynamic } from '../knapsackDynamic' 3 | 4 | test('Create empty instance of KnapsackDynamic', t => { 5 | const knapsackDynamic = new KnapsackDynamic([], 0) 6 | t.snapshot(knapsackDynamic) 7 | t.pass() 8 | }) 9 | 10 | test('Max loading', t => { 11 | const knapsackDynamic = new KnapsackDynamic([3, 5, 1, 2, 7], 16) 12 | const result = knapsackDynamic.getMaxWeight() 13 | t.is(result, 16) 14 | }) 15 | -------------------------------------------------------------------------------- /knapsack/tests/snapshots/knapsackBacktracking.test.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `knapsack/tests/knapsackBacktracking.test.js` 2 | 3 | The actual snapshot is saved in `knapsackBacktracking.test.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## Create empty instance of KnapsackBacktracking. 8 | 9 | > Snapshot 1 10 | 11 | KnapsackBacktracking { 12 | materialsWeight: [], 13 | numbers: 0, 14 | result: 0, 15 | weightLoading: 0, 16 | } 17 | -------------------------------------------------------------------------------- /knapsack/tests/snapshots/knapsackBacktracking.test.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MasterShu/JavaScript-DataStructures-Algorithms/66f895bcf9d2da73a580a71c6cab0b62ea633223/knapsack/tests/snapshots/knapsackBacktracking.test.js.snap -------------------------------------------------------------------------------- /linked_list/LinkedList.js: -------------------------------------------------------------------------------- 1 | class LinkedList { 2 | constructor() { 3 | /** 4 | * @type {LinkedListNode} 5 | */ 6 | this.head = null 7 | /** 8 | * @type {LinkedListNode} 9 | */ 10 | this.tail = null 11 | /** 12 | * The size of Linked list 13 | * @type {number} 14 | */ 15 | this.count = 0 16 | } 17 | 18 | /** 19 | * Building Linked list by array. 20 | * @param {array} array 21 | */ 22 | build(array) { 23 | this.count = array.length 24 | for (let i = 0; i < array.length; i++) { 25 | const newNode = new LinkedListNode(array[i]) 26 | this.__insertToEnd(newNode) 27 | } 28 | } 29 | 30 | /** 31 | * Append element to the end of the linked list 32 | * @param value 33 | * @returns {LinkedList} 34 | */ 35 | append(value) { 36 | const newNode = new LinkedListNode(value) 37 | this.__insertToEnd(newNode) 38 | this.count++ 39 | return this 40 | } 41 | 42 | /** 43 | * Delete element when it equal the pass value. 44 | * @param value 45 | * @returns {null} 46 | */ 47 | delete(value) { 48 | if (!this.head) { 49 | return null 50 | } 51 | 52 | let deleteNode = null 53 | 54 | // Delete element when position is head. 55 | while (this.head && this.head.value === value) { 56 | deleteNode = this.head 57 | this.head = this.head.next 58 | this.count-- 59 | } 60 | 61 | let currentNode = this.head 62 | // Delete element when item's position is between head and tail. 63 | if (currentNode !== null) { 64 | while (currentNode.next) { 65 | if (value === currentNode.next.value) { 66 | deleteNode = currentNode.next 67 | currentNode.next = currentNode.next.next 68 | this.count-- 69 | } else { 70 | currentNode = currentNode.next 71 | } 72 | } 73 | } 74 | 75 | // Delete element when item's position is tail. 76 | if (value === this.tail.value) { 77 | this.tail = currentNode 78 | this.count-- 79 | } 80 | 81 | return deleteNode 82 | } 83 | 84 | deleteNode(nodeItem) { 85 | if (this.count < 1) { 86 | return null 87 | } 88 | 89 | if (this.count === 1) { 90 | if (this.head === nodeItem) { 91 | this.head = this.tail = null 92 | this.count = 0 93 | return nodeItem 94 | } 95 | return null 96 | } 97 | 98 | const currentNode = this.head 99 | while (currentNode.next) { 100 | if (currentNode.next === nodeItem) { 101 | currentNode.next = currentNode.next.next 102 | return nodeItem 103 | } 104 | } 105 | 106 | return null 107 | } 108 | 109 | /** 110 | * 111 | * @param {LinkedListNode} newNode 112 | * @private 113 | */ 114 | __insertToEnd(newNode) { 115 | if (!this.head) { 116 | this.head = newNode 117 | this.tail = newNode 118 | return 119 | } 120 | this.tail.next = newNode 121 | this.tail = newNode 122 | } 123 | } 124 | 125 | class LinkedListNode { 126 | constructor(value, next = null) { 127 | this.value = value 128 | /** 129 | * @type {LinkedListNode} 130 | */ 131 | this.next = next 132 | } 133 | } 134 | 135 | module.exports = { 136 | LinkedList, 137 | LinkedListNode 138 | } 139 | -------------------------------------------------------------------------------- /linked_list/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./LinkedList') 2 | -------------------------------------------------------------------------------- /linked_list/tests/LinkedList.test.js: -------------------------------------------------------------------------------- 1 | import { LinkedList } from '../index' 2 | import test from 'ava' 3 | import { LinkedListNode } from '../LinkedList' 4 | 5 | test('Create new linked list', t => { 6 | const linkedList = new LinkedList() 7 | t.snapshot(linkedList) 8 | t.pass() 9 | }) 10 | 11 | test('Create new linked list node', t => { 12 | const linkedListNode = new LinkedListNode(9) 13 | t.snapshot(linkedListNode) 14 | }) 15 | 16 | test('Build a new linked list', t => { 17 | const linkedList = new LinkedList() 18 | const testArr = [3, 5, 7, 9, 10, 13] 19 | linkedList.build(testArr) 20 | t.snapshot(linkedList) 21 | t.is(linkedList.count, testArr.length) 22 | }) 23 | 24 | test('Append element to linked list', t => { 25 | const linkedList = new LinkedList() 26 | 27 | linkedList.append(3) 28 | const headElement = new LinkedListNode(3) 29 | t.deepEqual(linkedList.head, headElement) 30 | t.deepEqual(linkedList.tail, headElement) 31 | 32 | linkedList.append(5) 33 | linkedList.append(9) 34 | 35 | t.is(linkedList.tail.value, 9) 36 | t.notDeepEqual(linkedList.tail, headElement) 37 | }) 38 | 39 | test('Delete linked list node by value', t => { 40 | const linkedList = new LinkedList() 41 | linkedList.build([3, 5, 1, 9, 7]) 42 | linkedList.delete(1) 43 | t.is(linkedList.count, 4) 44 | let headElement = linkedList.head 45 | if(headElement.value === 1) { 46 | t.fail('Delete fail') 47 | } 48 | while(headElement.next) { 49 | if (headElement.next.value === 1) { 50 | t.fail('Delete fail') 51 | } 52 | headElement = headElement.next 53 | } 54 | }) 55 | -------------------------------------------------------------------------------- /linked_list/tests/snapshots/LinkedList.test.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `linked_list/tests/LinkedList.test.js` 2 | 3 | The actual snapshot is saved in `LinkedList.test.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## Build a new linked list 8 | 9 | > Snapshot 1 10 | 11 | LinkedList { 12 | count: 6, 13 | head: LinkedListNode { 14 | next: LinkedListNode { 15 | next: LinkedListNode { 16 | next: LinkedListNode { 17 | next: LinkedListNode { 18 | next: LinkedListNode { 19 | next: null, 20 | value: 13, 21 | }, 22 | value: 10, 23 | }, 24 | value: 9, 25 | }, 26 | value: 7, 27 | }, 28 | value: 5, 29 | }, 30 | value: 3, 31 | }, 32 | tail: LinkedListNode { 33 | next: null, 34 | value: 13, 35 | }, 36 | } 37 | 38 | ## Create new linked list 39 | 40 | > Snapshot 1 41 | 42 | LinkedList { 43 | count: 0, 44 | head: null, 45 | tail: null, 46 | } 47 | 48 | ## Create new linked list node 49 | 50 | > Snapshot 1 51 | 52 | LinkedListNode { 53 | next: null, 54 | value: 9, 55 | } 56 | -------------------------------------------------------------------------------- /linked_list/tests/snapshots/LinkedList.test.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MasterShu/JavaScript-DataStructures-Algorithms/66f895bcf9d2da73a580a71c6cab0b62ea633223/linked_list/tests/snapshots/LinkedList.test.js.snap -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algorithm", 3 | "description": "Data Structures and Algorithms with JavaScript 利用 JavaScript 来学数据结构和算法算法 ", 4 | "version": "1.0.0", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "ava" 8 | }, 9 | "keywords": [ 10 | "algorithm" 11 | ], 12 | "author": "MasterShu", 13 | "devDependencies": { 14 | "ava": "^2.4.0", 15 | "eslint": "^6.6.0", 16 | "eslint-config-standard": "^14.1.0", 17 | "eslint-plugin-import": "^2.18.2", 18 | "eslint-plugin-node": "^10.0.0", 19 | "eslint-plugin-promise": "^4.2.1", 20 | "eslint-plugin-standard": "^4.0.1" 21 | }, 22 | "license": "ISC", 23 | "dependencies": { 24 | "seedrandom": "^3.0.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /queue/queue.js: -------------------------------------------------------------------------------- 1 | class Queue { 2 | constructor() { 3 | this.data = [] 4 | this.head = 0 5 | this.count = 0 6 | } 7 | 8 | /** 9 | * Building Queue of array by array 10 | * @param {Array} array 11 | */ 12 | build(array) { 13 | this.data = array 14 | this.count = array.length 15 | } 16 | 17 | /** 18 | * Get Queue's length. 19 | * @returns {number} 20 | */ 21 | get size() { 22 | return this.count 23 | } 24 | 25 | /** 26 | * @returns {boolean} 27 | */ 28 | isEmpty() { 29 | return !this.count 30 | } 31 | 32 | /** 33 | * Add a new element to the end. 34 | * @param item 35 | */ 36 | enqueue(item) { 37 | this.data[this.head + this.count] = item 38 | this.count++ 39 | } 40 | 41 | /** 42 | * The element in front of the queue will removed and returned. 43 | * @returns {null|*} 44 | */ 45 | dequeue() { 46 | if (this.count <= 0) return null 47 | const element = this.data[this.head] 48 | this.head++ 49 | this.count-- 50 | return element 51 | } 52 | } 53 | 54 | module.exports = { 55 | Queue 56 | } 57 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 算法 2 | 3 | ```bash 4 | ├── bubble sort 冒泡排序 5 | ├── heap 堆结构 6 | ├── heap sort 堆排序 7 | ├── insertion sort 插入排序 8 | ├── merge sort 归并排序 9 | ├── quick sort 快速排序排序 10 | ├── selection sort 选择排序 11 | ├── utils 工具 12 | │ ├── index.js 模型类文件 13 | │ ├── sortTestHelp.js 测试排序 14 | ``` 15 | 16 | ## bubble sort 17 | 18 | > 冒泡排序 19 | 冒泡排序总的平均时间复杂度为 20 | 21 | ```math 22 | O(N^2) 23 | ``` 24 | 25 | 但是在有序的情况下, 就可以达到 26 | ```math 27 | O(N) 28 | ``` 29 | 30 | ## selection sort 31 | 32 | > 快速排序 33 | 34 | ## insertion sort 35 | 36 | > 稳定排序 37 | 38 | ## merge sort 39 | 40 | > 归并排序 41 | > 稳定排序 42 | 43 | 使用 二分法查分排序, 然后逐步归并, 然而在归并过程中, 就需要临时空间来做归并处理. 44 | 45 | ## quick sort 46 | 47 | > 快速排序法 48 | > 非稳定排序 49 | 50 | 在大量重复值的情况下, 性能堪忧? 不过普通情况下, 效率优于归并排序. 51 | 52 | ### 三路快速排序 53 | 54 | 当存在大量重复值的情况下, 三路快速排序可以提高排序速度, 并且在普通情况下, 拥有不错的效率. 55 | 56 | ## heap sort 57 | 58 | > 堆排序 59 | > 非稳定排序 60 | 61 | 这个并不是效率高的排序法, 但是特定的场景使用却是极好的. 62 | 63 | ### Index Max Heap 64 | 65 | > 最大索引堆 66 | -------------------------------------------------------------------------------- /search/binary-search/binarySearch.js: -------------------------------------------------------------------------------- 1 | import { isEqual, lessThan } from '../../utils' 2 | 3 | /** 4 | * Binary search implementation. 5 | * @param {array} arraySorted 6 | * @param seekingItem 7 | * @returns {number} 8 | */ 9 | const binarySearch = (arraySorted, seekingItem) => { 10 | // When array is null, we needn't search. 11 | if (arraySorted.length < 1) { 12 | return -1 13 | } 14 | // When array is only one element, we just make sure this element is we need or not. 15 | if (arraySorted.length === 1) { 16 | if (isEqual(arraySorted[0], seekingItem)) { 17 | return 0 18 | } 19 | return -1 20 | } 21 | 22 | let indexStart = 0 23 | let indexEnd = arraySorted.length - 1 24 | 25 | while (indexStart <= indexEnd) { 26 | // We need calculate the index of the middle element. 27 | const indexMiddle = indexStart + Math.floor((indexEnd - indexStart) / 2) 28 | 29 | // When the element of the middle is right. 30 | if (isEqual(arraySorted[indexMiddle], seekingItem)) { 31 | return indexMiddle 32 | } 33 | 34 | // Choose a zone half of the array. 35 | if (lessThan(arraySorted[indexMiddle], seekingItem)) { 36 | // Right 37 | indexStart = indexMiddle + 1 38 | } else { 39 | // Left 40 | indexEnd = indexMiddle - 1 41 | } 42 | } 43 | 44 | // We are not found the element of arrays. 45 | return -1 46 | } 47 | 48 | module.exports = { 49 | binarySearch 50 | } 51 | -------------------------------------------------------------------------------- /search/linear-search/linearSearch.js: -------------------------------------------------------------------------------- 1 | import { isEqual } from '../../utils' 2 | 3 | /** 4 | * Linear search implementation. 5 | * 6 | * @param {array} array 7 | * @param {*} seekingItem 8 | * @returns {number} 9 | */ 10 | function linearSearch(array, seekingItem) { 11 | const arrayLength = array.length 12 | for (let i = 0; i < arrayLength; i++) { 13 | if (isEqual(array[i], seekingItem)) { 14 | return i 15 | } 16 | } 17 | return -1 18 | } 19 | 20 | module.exports = { 21 | linearSearch 22 | } 23 | -------------------------------------------------------------------------------- /sorting/bubbleSort/index.js: -------------------------------------------------------------------------------- 1 | const bubbleSort = (arr, n) => { 2 | const [...newArr] = arr 3 | let tempExchangVal 4 | while (n > 0) { 5 | for (let j = 0; j < n - 1; j++) { 6 | if (newArr[j] > newArr[j + 1]) { 7 | tempExchangVal = newArr[j] 8 | newArr[j] = newArr[j + 1] 9 | newArr[j + 1] = tempExchangVal 10 | } 11 | } 12 | n-- 13 | } 14 | return newArr 15 | } 16 | 17 | module.exports = { 18 | bubbleSort 19 | } 20 | -------------------------------------------------------------------------------- /sorting/heapSort/index.js: -------------------------------------------------------------------------------- 1 | const heapSort = (arr, n) => { 2 | const newArr = [...arr] 3 | // heapify 4 | for (let i = parseInt((n - 1) / 2); i >= 0; i--) { 5 | __shiftDown(newArr, n, i) 6 | } 7 | 8 | for (let i = (n - 1); i > 0; i--) { 9 | [newArr[0], newArr[i]] = [newArr[i], newArr[0]] 10 | __shiftDown(newArr, i, 0) 11 | } 12 | return newArr 13 | } 14 | 15 | const __shiftDown = (arr, n, k) => { 16 | while ((2 * k + 1) < n) { 17 | let i = 2 * k + 1 18 | if (i + 1 < n && arr[i + 1] > arr[i]) { 19 | i += 1 20 | } 21 | if (arr[k] >= arr[i]) { 22 | break 23 | } 24 | [arr[i], arr[k]] = [arr[k], arr[i]] 25 | k = i 26 | } 27 | } 28 | 29 | module.exports = { 30 | heapSort 31 | } 32 | -------------------------------------------------------------------------------- /sorting/insertionSort/index.js: -------------------------------------------------------------------------------- 1 | const insertionSort = (arr, n) => { 2 | const [...newArr] = arr 3 | for (let i = 1; i < n; i++) { 4 | // 寻找插入位置 5 | for (let j = i; j > 0 && newArr[j] < newArr[j - 1]; j--) { 6 | [newArr[j], newArr[j - 1]] = [newArr[j - 1], newArr[j]] 7 | } 8 | } 9 | return newArr 10 | } 11 | 12 | // 升级版 13 | const insertionSortPlus = (arr, n) => { 14 | const [...newArr] = arr 15 | for (let i = 0; i < n; i++) { 16 | const e = newArr[i] 17 | let j 18 | for (j = i; j > 0 && newArr[j - 1] > e; j--) { 19 | newArr[j] = newArr[j - 1] 20 | } 21 | newArr[j] = e 22 | } 23 | return newArr 24 | } 25 | 26 | /** 27 | * 插入排序 28 | * @param {array} arr 需要排序的数组 29 | * @param {number} l 左侧 30 | * @param {number} r 右侧 31 | */ 32 | const insertionSortRange = (arr, l, r) => { 33 | for (let i = l + 1; i <= r; i++) { 34 | const e = arr[i] 35 | let j 36 | for (j = i; j > l && arr[j - 1] > e; j--) { 37 | arr[j] = arr[j - 1] 38 | } 39 | arr[j] = e 40 | } 41 | } 42 | 43 | module.exports = { 44 | insertionSort, 45 | insertionSortPlus, 46 | insertionSortRange 47 | } 48 | -------------------------------------------------------------------------------- /sorting/mergeSort/index.js: -------------------------------------------------------------------------------- 1 | import { insertionSortRange } from '../insertionSort' 2 | 3 | const mergeSort = (arr, n) => { 4 | const [...newArr] = arr 5 | __mergeSort(newArr, 0, n - 1) 6 | return newArr 7 | } 8 | 9 | const mergeSortBU = (arr, n) => { 10 | const [...newArr] = arr 11 | for (let sz = 1; sz < n; sz += sz) { 12 | for (let i = 0; (i + sz) < n; i += sz + sz) { 13 | // 对 arr[i...i+sz -1] 和 arr[i+sz...i+2*sz-1] 进行归并 14 | __merge(newArr, i, i + sz - 1, Math.min.apply(null, [i + sz + sz - 1, n - 1])) 15 | } 16 | } 17 | return newArr 18 | } 19 | 20 | // 递归使用归并排序, 对 arr[l...r]的范围进行排序 21 | const __mergeSort = (arr, l, r) => { 22 | // if (l >= r) { 23 | // return 24 | // } 25 | if ((r - l) <= 100) { 26 | insertionSortRange(arr, l, r) 27 | return 28 | } 29 | const mid = (l + r) / 2 30 | __mergeSort(arr, l, mid) 31 | __mergeSort(arr, mid + 1, r) 32 | __merge(arr, l, mid, r) 33 | } 34 | 35 | // 将 arr[l...mid] 和 arr[mid+1...r] 两部分进行归并 36 | const __merge = (arr, l, mid, r) => { 37 | const aux = [] 38 | for (let i = l; i <= r; i++) { 39 | aux[i - l] = arr[i] 40 | } 41 | let i = l 42 | let j = mid + 1 43 | for (let k = l; k <= r; k++) { 44 | if (i > mid) { 45 | arr[k] = aux[j - l] 46 | j++ 47 | } else if (j > r) { 48 | arr[k] = aux[i - l] 49 | i++ 50 | } else if (aux[i - l] < aux[j - l]) { 51 | arr[k] = aux[i - l] 52 | i++ 53 | } else { 54 | arr[k] = aux[j - l] 55 | j++ 56 | } 57 | } 58 | } 59 | 60 | module.exports = { 61 | mergeSort, 62 | mergeSortBU 63 | } 64 | -------------------------------------------------------------------------------- /sorting/quickSort/index.js: -------------------------------------------------------------------------------- 1 | const { insertionSortRange } = require('../insertionSort') 2 | 3 | // 快速排序 4 | const quickSort = (arr, n) => { 5 | const newArr = [...arr] 6 | __quickSort(newArr, 0, n - 1) 7 | return newArr 8 | } 9 | 10 | /** 11 | * 快速排序 - 针对多重复键值的 返回新的数组 12 | * @param {array} arr 需要排序的数组 13 | * @param {number} n 排序的长度 14 | */ 15 | const quickSortRepetition = (arr, n) => { 16 | const newArr = [...arr] 17 | __quickSortRepetition(newArr) 18 | return newArr 19 | } 20 | 21 | /** 22 | * 三路快速排序 - 返回新的排序完数组 23 | * @param {array} arr 需要排序的数组 24 | * @param {number} n 需要排序数组的长度 25 | */ 26 | const quickSort3Ways = (arr, n) => { 27 | const newArr = [...arr] 28 | // sran() 29 | __quickSort3Ways(newArr, 0, n - 1) 30 | return newArr 31 | } 32 | 33 | const __quickSort = (arr, l, r) => { 34 | // 注意, 调用一个小范围内大数量的数据循环次数多的情况下, 极易出现栈溢出 35 | if (r - l <= 200) { 36 | insertionSortRange(arr, l, r) 37 | return 38 | } 39 | const p = __partition(arr, l, r) 40 | __quickSort(arr, l, p - 1) 41 | __quickSort(arr, p + 1, r) 42 | } 43 | 44 | const __quickSortRepetition = (arr, l, r) => { 45 | // 注意, 调用一个小范围内大数量的数据循环次数多的情况下, 极易出现栈溢出 46 | if (r - l <= 200) { 47 | insertionSortRange(arr, l, r) 48 | return 49 | } 50 | const p = __partitionRepetition(arr, l, r) 51 | __quickSortRepetition(arr, l, p - 1) 52 | __quickSortRepetition(arr, p + 1, r) 53 | } 54 | 55 | const __quickSort3Ways = (arr, l, r) => { 56 | // 注意, 调用一个小范围内大数量的数据循环次数多的情况下, 极易出现栈溢出 57 | if (r - l <= 200) { 58 | insertionSortRange(arr, l, r) 59 | return 60 | } 61 | const [lt, gt] = __partition3Ways(arr, l, r) 62 | __quickSort3Ways(arr, l, lt - 1) 63 | __quickSort3Ways(arr, gt, r) 64 | } 65 | 66 | /** 67 | * 对 arr[l...r] 部分进行 partition 操作 68 | * @param {Array} arr 需要排序的数组 69 | * @param {Number} l 左边 70 | * @param {Number} r 右边 71 | * @return {Number} 返回 p, 使得 arr[l...p - 1] < arr[p]; arr[p+1...r] > arr[p] 72 | */ 73 | const __partition = (arr, l, r) => { 74 | const randKey = Math.floor(Math.random() * (r - l + 1)) + l 75 | 76 | const tmpItem = arr[l] 77 | arr[l] = arr[randKey] 78 | arr[randKey] = tmpItem 79 | 80 | const v = arr[l] 81 | 82 | let j = l 83 | for (let i = l + 1; i <= r; i++) { 84 | if (arr[i] < v) { 85 | [arr[j + 1], arr[i]] = [arr[i], arr[j + 1]] 86 | j++ 87 | } 88 | } 89 | 90 | [arr[l], arr[j]] = [arr[j], arr[l]] 91 | 92 | return j 93 | } 94 | 95 | const __partitionRepetition = (arr, l, r) => { 96 | const randKey = Math.floor(Math.random() * (r - l + 1)) + l 97 | 98 | const tmpItem = arr[l] 99 | arr[l] = arr[randKey] 100 | arr[randKey] = tmpItem 101 | 102 | const v = arr[l] 103 | 104 | // arr[l+1...i) <= v; arr(j...r] >= v 105 | let i = l + 1 106 | let j = r 107 | while (true) { 108 | while (arr[i] < v && arr[i] < v) { 109 | i++ 110 | } 111 | while (arr[j] > v && arr[j] > v) { 112 | j-- 113 | } 114 | if (i > j) { 115 | break 116 | } 117 | [arr[i], arr[j]] = [arr[j], arr[i]] 118 | i++ 119 | j-- 120 | } 121 | 122 | [arr[l], arr[j]] = [arr[j], arr[l]] 123 | return j 124 | } 125 | 126 | const __partition3Ways = (arr, l, r) => { 127 | const randKey = Math.floor(Math.random() * (r - l + 1)) + l 128 | 129 | const tmpItem = arr[l] 130 | arr[l] = arr[randKey] 131 | arr[randKey] = tmpItem 132 | 133 | const v = arr[l] 134 | 135 | let lt = l 136 | let gt = r + 1 137 | let i = l + 1 138 | while (i < gt) { 139 | if (arr[i] < v) { 140 | [arr[i], arr[lt + 1]] = [arr[lt + 1], arr[i]] 141 | lt++ 142 | i++ 143 | } else if (arr[i] > v) { 144 | [arr[i], arr[gt - 1]] = [arr[gt - 1], arr[i]] 145 | gt-- 146 | } else { 147 | i++ 148 | } 149 | } 150 | [arr[l], arr[lt]] = [arr[lt], arr[l]] 151 | return [lt, gt] 152 | } 153 | 154 | module.exports = { 155 | quickSort, 156 | quickSortRepetition, 157 | quickSort3Ways 158 | } 159 | -------------------------------------------------------------------------------- /sorting/selectionSort/index.js: -------------------------------------------------------------------------------- 1 | // 选择排序 2 | const selectionSort = (arr, n) => { 3 | const [...newArr] = arr 4 | for (let i = 0; i < n; i++) { 5 | let minIndex = i 6 | for (let j = i + 1; j < n; j++) { 7 | if (newArr[j] < newArr[minIndex]) { 8 | minIndex = j 9 | } 10 | } 11 | [newArr[i], newArr[minIndex]] = [newArr[minIndex], newArr[i]] 12 | } 13 | 14 | return newArr 15 | } 16 | 17 | module.exports = { 18 | selectionSort 19 | } 20 | -------------------------------------------------------------------------------- /sorting/selectionSort/test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { randArray } from '../../utils'; 3 | 4 | test('Test selection sort', t => { 5 | let testArr = randArray(1, 10000, 1000) 6 | let testArrLength = testArr.length 7 | t.assert(testArrLength, 1000) 8 | }) 9 | -------------------------------------------------------------------------------- /stack/stack.js: -------------------------------------------------------------------------------- 1 | class Stack { 2 | constructor() { 3 | /** 4 | * @type {*[]} 5 | */ 6 | this.data = [] 7 | this.count = 0 8 | } 9 | 10 | /** 11 | * Get stack size 获取栈的大小 12 | * @returns {number} 13 | */ 14 | get size () { 15 | return this.count 16 | } 17 | 18 | /** 19 | * Building Stack of array by array 20 | * @param {Array} array 21 | */ 22 | build(array) { 23 | this.count = array.length 24 | this.data = array 25 | } 26 | 27 | /** 28 | * Add new element to the top. 29 | * @param item 30 | */ 31 | push(item) { 32 | this.data[this.count] = item 33 | this.count++ 34 | } 35 | 36 | /** 37 | * The most top element of the stack is removed from the stack and returned. 38 | * @returns {null|T} 39 | */ 40 | pop() { 41 | if (this.count === 0) { 42 | return null 43 | } 44 | const element = this.data.pop() 45 | this.count-- 46 | return element 47 | } 48 | } 49 | 50 | module.exports = { 51 | Stack 52 | } 53 | -------------------------------------------------------------------------------- /utils/index.js: -------------------------------------------------------------------------------- 1 | const utils = require('./searchUtils') 2 | const help = require('./sortTestHelp') 3 | 4 | module.exports = { 5 | ...utils, 6 | ...help 7 | } 8 | -------------------------------------------------------------------------------- /utils/searchUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Make sure two elements are equal. 3 | * @param a 4 | * @param b 5 | * @returns {boolean} 6 | */ 7 | function isEqual(a, b) { 8 | return a === b 9 | } 10 | 11 | /** 12 | * Make sure variable a is less than b. 13 | * @param a 14 | * @param b 15 | * @returns {boolean} 16 | */ 17 | function lessThan(a, b) { 18 | return a - b < 0 19 | } 20 | 21 | module.exports = { 22 | isEqual, 23 | lessThan 24 | } 25 | -------------------------------------------------------------------------------- /utils/sortTestHelp.js: -------------------------------------------------------------------------------- 1 | const seedrandom = require('seedrandom') 2 | 3 | /** 4 | * Generator a random array by seed 5 | * @param {number} min 6 | * @param {number} max 7 | * @param {number} n 8 | * @returns {[]} 9 | */ 10 | function randArray(min, max, n) { 11 | if (min >= max) { 12 | throw 'Error' // eslint-disable-line 13 | } 14 | // 随机种子 15 | const rng = seedrandom(Date.now()) 16 | const arr = [] 17 | for (let i = 0; i < n; i++) { 18 | arr[i] = Math.floor(rng() * (max - min + 1) + min) 19 | } 20 | return arr 21 | } 22 | 23 | const randNearlyArr = (n, numOfTimes) => { 24 | const arr = [] 25 | for (let i = 0; i < n; i++) { 26 | arr[i] = i 27 | } 28 | const rng = seedrandom(Date.now()) 29 | for (let i = 0; i < n; i++) { 30 | const posX = Math.floor(rng() * n) 31 | const posY = Math.floor(rng() * n) 32 | arr[posX] = [arr[posY], arr[posY] = arr[posX]][0] 33 | // [arr[posX], arr[posY]] = [arr[posY], arr[posX]] 34 | } 35 | return arr 36 | } 37 | 38 | // 测试排序函数 39 | const testSort = (sortName, sort, arr, n) => { 40 | const startTime = getTime() 41 | const newArr = sort(arr, n) 42 | const endTime = getTime() 43 | if (!isSorted(newArr, n)) { 44 | throw 'Error' // eslint-disable-line 45 | } 46 | console.log(sortName, 'use time ', endTime - startTime, ' sort ', n) 47 | } 48 | 49 | // 验证排序是否成功 50 | const isSorted = (arr, n) => { 51 | for (let i = 0; i < n; i++) { 52 | if (arr[i] > arr[i + 1]) { 53 | console.error(`is ${i} value ${arr[i]} greater is ${i + 1} value ${arr[i + 1]}`) 54 | return false 55 | } 56 | } 57 | return true 58 | } 59 | 60 | // 获取当前时间 61 | const getTime = () => { 62 | return Date.now() 63 | } 64 | 65 | module.exports = { 66 | randArray, 67 | testSort, 68 | randNearlyArr 69 | } 70 | --------------------------------------------------------------------------------