├── README.md ├── design-patterns ├── README.md ├── 代理模式 │ ├── README.md │ └── index.html ├── 适配器模式 │ ├── README.md │ └── index.html ├── 单例模式 │ ├── README.md │ └── index.html └── 工厂模式 │ ├── README.md │ └── index.html ├── .DS_Store ├── flutter ├── 1.png └── README.md ├── algorithm ├── .DS_Store ├── 图 │ └── README.md ├── 堆 │ ├── imgs │ │ ├── 1.png │ │ ├── 10.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ ├── 大顶堆.html │ ├── 堆.html │ └── RRADME.md ├── 树 │ ├── .DS_Store │ ├── imgs │ │ ├── 1.png │ │ ├── 2.png │ │ └── 3.png │ ├── 排序二叉树.html │ ├── README.md │ └── 完全二叉树.html └── 链表 │ ├── imgs │ ├── 5.png │ ├── 6.png │ ├── 1.webp │ ├── 2.webp │ ├── 3.webp │ ├── 4.webp │ └── 7.webp │ ├── 双向链表.html │ ├── 单向链表.html │ ├── 反向链表.html │ └── README.md └── gpu ├── 一维数组测试 ├── cpu1.html ├── gpu1.html ├── gpu2.html └── gpu3.html ├── README.md └── 二维数组测试 └── gpu1.html /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /design-patterns/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /design-patterns/代理模式/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/.DS_Store -------------------------------------------------------------------------------- /flutter/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/flutter/1.png -------------------------------------------------------------------------------- /algorithm/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/.DS_Store -------------------------------------------------------------------------------- /algorithm/图/README.md: -------------------------------------------------------------------------------- 1 | # 参考文章 2 | 3 | - [图的基本算法(BFS和DFS)](https://www.jianshu.com/p/70952b51f0c8) -------------------------------------------------------------------------------- /algorithm/堆/imgs/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/堆/imgs/1.png -------------------------------------------------------------------------------- /algorithm/堆/imgs/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/堆/imgs/10.png -------------------------------------------------------------------------------- /algorithm/堆/imgs/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/堆/imgs/2.png -------------------------------------------------------------------------------- /algorithm/堆/imgs/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/堆/imgs/3.png -------------------------------------------------------------------------------- /algorithm/堆/imgs/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/堆/imgs/4.png -------------------------------------------------------------------------------- /algorithm/堆/imgs/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/堆/imgs/5.png -------------------------------------------------------------------------------- /algorithm/堆/imgs/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/堆/imgs/6.png -------------------------------------------------------------------------------- /algorithm/堆/imgs/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/堆/imgs/7.png -------------------------------------------------------------------------------- /algorithm/堆/imgs/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/堆/imgs/8.png -------------------------------------------------------------------------------- /algorithm/堆/imgs/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/堆/imgs/9.png -------------------------------------------------------------------------------- /algorithm/树/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/树/.DS_Store -------------------------------------------------------------------------------- /algorithm/树/imgs/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/树/imgs/1.png -------------------------------------------------------------------------------- /algorithm/树/imgs/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/树/imgs/2.png -------------------------------------------------------------------------------- /algorithm/树/imgs/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/树/imgs/3.png -------------------------------------------------------------------------------- /algorithm/链表/imgs/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/链表/imgs/5.png -------------------------------------------------------------------------------- /algorithm/链表/imgs/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/链表/imgs/6.png -------------------------------------------------------------------------------- /algorithm/链表/imgs/1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/链表/imgs/1.webp -------------------------------------------------------------------------------- /algorithm/链表/imgs/2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/链表/imgs/2.webp -------------------------------------------------------------------------------- /algorithm/链表/imgs/3.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/链表/imgs/3.webp -------------------------------------------------------------------------------- /algorithm/链表/imgs/4.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/链表/imgs/4.webp -------------------------------------------------------------------------------- /algorithm/链表/imgs/7.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wscats/awesome/master/algorithm/链表/imgs/7.webp -------------------------------------------------------------------------------- /design-patterns/适配器模式/README.md: -------------------------------------------------------------------------------- 1 | # 结构型模式 2 | 3 | - 适配器模式主要解决两个接口之间不匹配的问题,不会改变原有的接口,而是由一个对象对另一个对象的包装。 4 | - 适配器模式符合开放封闭原则 5 | 6 | # 总结 7 | 8 | 类似一个过滤器,把接口经过一个代理函数去新增或者重写其他接口 -------------------------------------------------------------------------------- /flutter/README.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | 3 | [官网下载SDK](https://flutter.dev/docs/development/tools/sdk/releases?tab=macos#macos) 4 | 5 | 解压下载好的flutter文件,进入`flutter/bin`文件夹里面,运行以下命令检查是否安装成功。 6 | 7 | ```js 8 | ./flutter 9 | ``` 10 | 11 | -------------------------------------------------------------------------------- /design-patterns/单例模式/README.md: -------------------------------------------------------------------------------- 1 | - 1.单例模式的主要思想就是,实例如果已经创建,则直接返回 2 | 3 | ```js 4 | function creatSingleton() { 5 | var obj = null 6 | // 实例如已经创建过,直接返回 7 | if (!obj) { 8 | obj = xxx 9 | } 10 | return obj 11 | } 12 | ``` 13 | 14 | - 2.符合开放封闭原则 -------------------------------------------------------------------------------- /design-patterns/工厂模式/README.md: -------------------------------------------------------------------------------- 1 | # 工厂模式 2 | 3 | 工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象,用工厂方法代替new操作的一种模式。 4 | 5 | ```js 6 | class Creator { 7 | create(name) { 8 | return new Animal(name) 9 | } 10 | } 11 | 12 | class Animal { 13 | constructor(name) { 14 | this.name = name 15 | } 16 | } 17 | 18 | var creator = new Creator() 19 | 20 | var duck = creator.create('Duck') 21 | console.log(duck.name) // Duck 22 | 23 | var chicken = creator.create('Chicken') 24 | console.log(chicken.name) // Chicken 25 | ``` 26 | 27 | - 构造函数和创建者分离,对new操作进行封装 28 | - 符合开放封闭原则 29 | -------------------------------------------------------------------------------- /gpu/一维数组测试/cpu1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /gpu/README.md: -------------------------------------------------------------------------------- 1 | # gpu.js 2 | 3 | GPU,全称**Graphics Processing Unit**,即图像处理器,早期主要用于显示图像使用。因为图像处理主要偏简单的矩阵运算,逻辑判断等很少,因此`GPU`的设计跟`CPU`架构不一样,也因此做到一个`GPU`上可以有很多计算单元,可以进行大量并行计算。 4 | 5 | `gpu.js`这个库会把你写的`JS`编译成`GLSL`然后在`GPU`上执行,以达到加速的效果。并且,如果电脑不支持`GPU`,它还会当成普通的`JS`执行,更多详情请查看[gpu.js](https://github.com/gpujs/gpu.js)。 6 | 7 | # GPU VS CPU 8 | 9 | 一维数组计算 10 | 11 | |量级|GPU|CPU| 12 | |-|-|-| 13 | |10|约120ms|约0.6ms| 14 | |100|约125ms|约1.5ms| 15 | |1000|约125ms|约2ms| 16 | |10000|约145ms|约2.5ms| 17 | |100000|约150ms|约15ms| 18 | |1000000|约155ms|约150ms| 19 | |10000000|约250ms|约5000ms| 20 | |100000000|约250ms|约5000ms| 21 | |1000000000|约1000ms|卡死了...| 22 | 23 | 在计算百万级别以下的时候CPU更快,超过百万级别的话GPU越有优势 -------------------------------------------------------------------------------- /gpu/二维数组测试/gpu1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /design-patterns/工厂模式/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /design-patterns/适配器模式/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /gpu/一维数组测试/gpu1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /gpu/一维数组测试/gpu2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 14 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /design-patterns/单例模式/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 42 | 43 | -------------------------------------------------------------------------------- /design-patterns/代理模式/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /gpu/一维数组测试/gpu3.html: -------------------------------------------------------------------------------- 1 | 2 | GPU.js Random Examples 3 | 4 | 5 |

CPU Random

6 | 7 |

WebGL1 Random

8 | 9 |

WebGL2 Random

10 | 11 | 12 | 13 | 53 | 54 | -------------------------------------------------------------------------------- /algorithm/堆/大顶堆.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /algorithm/树/排序二叉树.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /algorithm/树/README.md: -------------------------------------------------------------------------------- 1 | 2 | # 度,节点和深度 3 | 4 | 结点所拥有的子树的个数称为该结点的度(Degree); 树中各结点度的最大值称为该树的度; 称度为m的树为m叉树。 5 | 6 | 也就是说二叉树的度为2,三叉树的度为3 7 | 8 | - 树中结点的最大度数没有限制,而二叉树结点的最大度数为2; 9 | 10 | - 树的结点无左、右之分,而二叉树的结点有左、右之分。 11 | 12 | |树|度| 13 | |-|-| 14 | |二叉树|2| 15 | |三叉树|3| 16 | |四叉树|4| 17 | |n叉树|n| 18 | 19 | 二叉树中每个元素都称为节点。 20 | 多少个元素多少节点。 21 | 根节点就是深度1,后面每一层加1。 22 | 23 | 24 | 25 | - 根节点:二叉树最顶层的节点 26 | - 分支节点:除了根节点以外且拥有叶子节点 27 | - 叶子节点:除了自身,没有其他子节点 28 | 29 | ## 二叉树的一些公式 30 | 31 | 1. 第n层的节点数最多为2n个节点 32 | 2. n层二叉树最多有`2^0 + ... + 2^n = 2^(n+1) - 1`个节点 33 | 3. 第一个非叶子节点:length/2 34 | 4. 一个节点的孩子节点:2n、2n+1 35 | 36 | ## 树和二叉树的三个主要差别 37 | 38 | - 树的节点个数至少为1,而二叉树的节点个数可以为0 39 | - 树中节点的最大度数(节点数量)没有限制,而二叉树的节点的最大度数为2 40 | - 树的节点没有左右之分,而二叉树的节点有左右之分 41 | 42 | ## 二叉树分类 43 | 44 | - 满二叉树:一棵深度为k且有2^k - 1个节点的二叉树称为满二叉树 45 | - 完全二叉树:完全二叉树是指最后一层左边是满的,右边可能满也可能不满,然后其余层都是满的二叉树称为完全二叉树(满二叉树也是一种完全二叉树) 46 | 47 | ## 排序二叉树 / 搜索二叉树 48 | 49 | 50 | 51 | 左子树小于根节点,右子树大于根节点,子树也满足这样的条件,这样的树叫做排序二叉树。 52 | ```js 53 | function BinaryTree() { 54 | // 节点 55 | var Node = function (key) { 56 | // 节点值 57 | this.key = key; 58 | // 左叶子 59 | this.left = null; 60 | // 右叶子 61 | this.right = null; 62 | }; 63 | // 根节点 64 | this.root = null; 65 | // 插入节点 66 | var insertNode = function (node, newNode) { 67 | // 对比值 新值小于根(该)节点值,放左边 68 | if (newNode.key < node.key) { 69 | // 左边如果为空直接放入 70 | if (node.left === null) { 71 | node.left = newNode; 72 | // 左边如果不为空把节点取出来继续往下面对比 73 | } else { 74 | insertNode(node.left, newNode); 75 | } 76 | } else { 77 | if (node.right === null) { 78 | node.right = newNode; 79 | } else { 80 | insertNode(node.right, newNode); 81 | } 82 | } 83 | }; 84 | this.insert = function (key) { 85 | // 创建节点 86 | var newnode = new Node(key); 87 | // 如果this.root为空,也就是第一个为根节点,否则插入节点 88 | if (this.root === null) { 89 | this.root = newnode; 90 | } else { 91 | insertNode(this.root, newnode); 92 | } 93 | }; 94 | } 95 | 96 | // 数组存放没放入排序二叉树的节点 97 | var nodes = [8, 3, 10, 1, 6, 14, 4, 7, 13]; 98 | var binaryTree = new BinaryTree; 99 | nodes.forEach(function (key) { 100 | binaryTree.insert(key); 101 | }); 102 | console.log(binaryTree); 103 | ``` 104 | 105 | 数据结构 106 | 107 | 108 | 109 | # 遍历 110 | 111 | - 中序遍历(inorder):先遍历左节点,再遍历自己,最后遍历右节点,输出的刚好是**有序的列表**是一种排序方案 112 | - 前序遍历(preorder):先自己,再遍历左节点,最后遍历右节点 113 | - 后序遍历(postorder):先左节点,再右节点,最后自己 114 | 115 | -------------------------------------------------------------------------------- /algorithm/链表/双向链表.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /algorithm/链表/单向链表.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /algorithm/堆/堆.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /algorithm/堆/RRADME.md: -------------------------------------------------------------------------------- 1 | # 堆 2 | 3 | - 堆的底层实际上是一棵完全二叉树。 4 | - 可以用数组实现 5 | - 每个的节点元素值不小于其子节点 - 最大堆 6 | - 每个的节点元素值不大于其子节点 - 最小堆 7 | 8 | # 堆的构建 9 | 10 | ## 大顶堆 11 | 12 | 从第一个非叶子节点开始依次对数组中的元素进行下沉操作 13 | 14 | 和孩子节点的最大值max比较 15 | 大于max — 不需要在下沉 16 | 小于max — 和max交换位置 - 继续和下一层孩子节点比较,直到队列末尾 17 | 18 | 19 | 20 | 21 | ## 堆排序算法 22 | 23 | - 将初始二叉树转化为大顶堆(heapify)(实质是从第一个非叶子结点开始,从下至上,从右至左,对每一个非叶子结点做shiftDown操作),此时根结点为最大值,将其与最后一个结点交换。 24 | - 除开最后一个结点,将其余节点组成的新堆转化为大顶堆(实质上是对根节点做shiftDown操作),此时根结点为次最大值,将其与最后一个结点交换。 25 | - 重复步骤2,直到堆中元素个数为1(或其对应数组的长度为1),排序完成。 26 | 27 | ### 步骤1: 28 | 29 | 初始化大顶堆,首先选取最后一个非叶子结点(我们只需要调整父节点和孩子节点之间的大小关系,叶子结点之间的大小关系无需调整)。设数组为arr,则第一个非叶子结点的下标为:`i = Math.floor(arr.length/2 - 1) = 1`,也就是数字4,如图中虚线框,找到三个数字的最大值,与父节点交换。 30 | 31 | 32 | 33 | 然后,下标 i 依次减1(即从第一个非叶子结点开始,从右至左,从下至上遍历所有非叶子节点)。后面的每一次调整都是如此:找到父子结点中的最大值,做交换。 34 | 35 | 36 | 37 | 这一步中数字6、1交换后,数字[1,5,4]组成的堆顺序不对,需要执行一步调整。因此需要注意,每一次对一个非叶子结点做调整后,都要观察是否会影响子堆顺序! 38 | 39 | 40 | 41 | 这次调整后,根节点为最大值,形成了一个大顶堆,将根节点与最后一个结点交换。 42 | 43 | ### 步骤2: 44 | 45 | 除开当前最后一个结点6(即最大值),将其余结点[4,5,3,1]组成新堆转化为大顶堆(注意观察,此时根节点以外的其他结点,都满足大顶堆的特征,所以可以从根节点4开始调整,即找到4应该处于的位置即可)。 46 | 47 | 48 | 49 | 50 | 51 | ### 步骤3: 52 | 53 | 接下来反复执行步骤2,直到堆中元素个数为1: 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 堆中元素个数为1, 排序完成。 62 | 63 | ### 代码实现 64 | 65 | ```js 66 | // 交换两个节点 67 | function swap(A, i, j) { 68 | let temp = A[i]; 69 | A[i] = A[j]; 70 | A[j] = temp; 71 | } 72 | 73 | // 将 i 结点以下的堆整理为大顶堆,注意这一步实现的基础实际上是: 74 | // 假设 结点 i 以下的子堆已经是一个大顶堆,shiftDown函数实现的 75 | // 功能是实际上是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面 76 | // 将写一个 for 循环,从第一个非叶子结点开始,对每一个非叶子结点 77 | // 都执行 shiftDown操作,所以就满足了结点 i 以下的子堆已经是一大 78 | //顶堆 79 | function shiftDown(A, i, length) { 80 | let temp = A[i]; // 当前父节点 81 | // j=0; i--) { 100 | shiftDown(A, i, A.length); 101 | } 102 | // 排序,每一次for循环找出一个当前最大值,数组长度减一 103 | for(let i = Math.floor(A.length-1); i>0; i--) { 104 | swap(A, 0, i); // 根节点与最后一个节点交换 105 | shiftDown(A, 0, i); // 从根节点开始调整,并且最后一个结点已经为当 106 | // 前最大值,不需要再参与比较,所以第三个参数 107 | // 为 i,即比较到最后一个结点前一个即可 108 | } 109 | } 110 | 111 | let Arr = [4, 6, 8, 5, 9, 1, 2, 5, 3, 2]; 112 | heapSort(Arr); 113 | alert(Arr); 114 | ``` 115 | 116 | ## 小顶堆 117 | 118 | 从第一个非叶子节点开始依次对数组中的元素进行下沉操作 119 | 120 | 和孩子节点的最小值min比较 121 | 小于min — 不需要在下沉 122 | 大于min — 和min交换位置(下沉) - 继续和下一层孩子节点比较,直到队列末尾 123 | 124 | ## 参考文档 125 | 126 | - [JS实现堆排序](https://segmentfault.com/a/1190000015487916?utm_source=tag-newest) 127 | 128 | 129 | -------------------------------------------------------------------------------- /algorithm/链表/反向链表.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /algorithm/树/完全二叉树.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /algorithm/链表/README.md: -------------------------------------------------------------------------------- 1 | # 栈、队列 2 | 3 | > 栈、队列进行存数数据,他们其实都是列表的一种,底层存储的数据的数据结构都是数组。 4 | 5 | # 链表 6 | 7 | 数组不总是最佳的数据结构,因为,在很多编程语言中,数组的长度都是固定的,如果数组已被数据填满,再要加入新的元素是非常困难的。而且,对于数组的删除和添加操作,通常需要将数组中的其他元素向前或者向后平移,这些操作也是十分繁琐的。 8 | 9 | 然而,JS中数组却不存在上述问题,主要是因为他们被实现了成了对象,但是与其他语言相比(比如C或Java),那么它的效率会低很多。 10 | 11 | 链表其实有许多的种类:单向链表、双向链表、单向循环链表和双向循环链表。 12 | 13 | ## 单向链表 14 | 15 | ### 链表结构图 16 | 17 | 18 | 19 | 链表是一组节点组成的集合,每个节点都使用一个对象的引用来指向它的后一个节点。 20 | 21 | 其中,data中保存着数据,next保存着下一个链表的引用。上图中,我们说 data2 跟在 data1 后面,而不是说 data2 是链表中的第二个元素。上图,值得注意的是,我们将链表的尾元素指向了 null 节点,表示链接结束的位置。 22 | 23 | ### 有头节点的链表 24 | 25 | 由于链表的起始点的确定比较麻烦,因此很多链表的实现都会在链表的最前面添加一个特殊的节点,称为 头节点,表示链表的头部。进行改造,链表就成了如下的样子: 26 | 27 | 28 | 29 | ### 插入节点 30 | 31 | 向链表中插入一个节点的效率很高,需要修改它前面的节点(前驱),使其指向新加入的节点,而将新节点指向原来前驱节点指向的节点即可。下面我将用图片演示如何在 data2 节点 后面插入 data4 节点。 32 | 33 | 34 | 35 | ### 删除节点 36 | 37 | 同样,从链表中删除一个节点,也很简单。只需将待删节点的前驱节点指向待删节点的,同时将待删节点指向null,那么节点就删除成功了。下面我们用图片演示如何从链表中删除 data4 节点。 38 | 39 | 40 | 41 | # 链表的设计 42 | 43 | 我们设计链表包含两个类,一个是 Node 类用来表示节点,另一个事 LinkedList 类提供插入节点、删除节点等一些操作。 44 | 45 | 46 | ## Node类 47 | 48 | Node类包含连个属性: element 用来保存节点上的数据,next 用来保存指向下一个节点的链接,具体实现如下: 49 | 50 | ```js 51 | //节点 52 | function Node(element) { 53 | this.element = element; //当前节点的元素 54 | this.next = null; //下一个节点链接 55 | } 56 | ``` 57 | 58 | ## LinkedList类 59 | 60 | LinkedList类提供了对链表进行操作的方法,包括插入删除节点,查找给定的值等。值得注意的是,它只有一个 61 | 属性,那就是使用一个 Node 对象来保存该链表的头节点。 62 | 63 | 它的构造函数的实现如下: 64 | 65 | ```js 66 | //链表类 67 | function LList () { 68 | this.head = new Node( 'head' ); //头节点 69 | this.find = find; //查找节点 70 | this.insert = insert; //插入节点 71 | this.remove = remove; //删除节点 72 | this.findPrev = findPrev; //查找前一个节点 73 | this.display = display; //显示链表 74 | } 75 | ``` 76 | 77 | head节点的next属性初始化为 null ,当有新元素插入时,next会指向新的元素。 78 | 79 | 接下来,我们来看看具体方法的实现。 80 | 81 | ### insert:向链表插入一个节点 82 | 83 | 我们先分析分析insert方法,想要插入一个节点,我们必须明确要在哪个节点的前面或后面插入。我们先来看看,如何在一个已知节点的后面插入一个节点。 84 | 85 | 在一个已知节点后插入新节点,我们首先得找到该节点,为此,我们需要一个 find 方法用来遍历链表,查找给定的数据。如果找到,该方法就返回保存该数据的节点。那么,我们先实现 find 方法。 86 | 87 | 88 | ### find:查找给定节点 89 | 90 | ```js 91 | //查找给定节点 92 | 93 | function find ( item ) { 94 | var currNode = this.head; 95 | while ( currNode.element != item ){ 96 | currNode = currNode.next; 97 | } 98 | return currNode; 99 | } 100 | ``` 101 | 102 | find 方法同时展示了如何在链表上移动。首先,创建一个新节点,将链表的头节点赋给这个新创建的节点,然后在链表上循环,如果当前节点的 element 属性和我们要找的信息不符,就将当前节点移动到下一个节点,如果查找成功,该方法返回包含该数据的节点;否则,就会返回null。 103 | 一旦找到了节点,我们就可以将新的节点插入到链表中了,将新节点的 next 属性设置为后面节点的 next 属性对应的值,然后设置后面节点的 next 属性指向新的节点,具体实现如下: 104 | 105 | ```js 106 | //插入节点 107 | 108 | function insert ( newElement , item ) { 109 | var newNode = new Node( newElement ); 110 | var currNode = this.find( item ); 111 | newNode.next = currNode.next; 112 | currNode.next = newNode; 113 | } 114 | ``` 115 | 116 | 现在我们可以测试我们的链表了。等等,我们先来定义一个 display 方法显示链表的元素,不然我们怎么知道对不对呢? 117 | 118 | ### display:显示链表 119 | 120 | 这个方法其实跟`find`很相似,只是寻找的时候要判断 next 是否 121 | ```js 122 | //显示链表元素 123 | 124 | function display () { 125 | var currNode = this.head; 126 | while ( !(currNode.next == null) ){ 127 | console.log( currNode.next.element ); 128 | currNode = currNode.next; 129 | } 130 | } 131 | ``` 132 | 133 | 实现原理同上,将头节点赋给一个新的变量,然后循环链表,直到当前节点的 next 属性为 null 时停止循环,我们循环过程中将每个节点的数据打印出来就好了。 134 | ```js 135 | var fruits = new LList(); 136 | 137 | fruits.insert('Apple' , 'head'); 138 | fruits.insert('Banana' , 'Apple'); 139 | fruits.insert('Pear' , 'Banana'); 140 | 141 | console.log(fruits.display()); // Apple 142 | // Banana 143 | // Pear 144 | ``` 145 | 146 | ### remove:从链表中删除一个节点 147 | 148 | 从链表中删除节点时,我们先要找个待删除节点的前一个节点,找到后,我们修改它的 next 属性,使其不在指向待删除的节点,而是待删除节点的下一个节点。那么,我们就得需要定义一个 findPrevious 方法遍历链表,检查每一个节点的下一个节点是否存储待删除的数据。如果找到,返回该节点,这样就可以修改它的 next 属性了。 findPrevious 的实现如下: 149 | ```js 150 | //查找带删除节点的前一个节点 151 | 152 | function findPrev( item ) { 153 | var currNode = this.head; 154 | while ( !( currNode.next == null) && ( currNode.next.element != item )){ 155 | currNode = currNode.next; 156 | } 157 | return currNode; 158 | } 159 | ``` 160 | 161 | 这样,remove 方法的实现也就迎刃而解了,而删除的本质就是改变链表中某一个元素的指向,让它指向下下一个元素 162 | ```js 163 | //删除节点 164 | 165 | function remove ( item ) { 166 | var prevNode = this.findPrev( item ); 167 | if( !( prevNode.next == null ) ){ 168 | prevNode.next = prevNode.next.next; 169 | } 170 | } 171 | ``` 172 | 我们接着写一段测试程序,测试一下 remove 方法: 173 | // 接着上面的代码,我们再添加一个水果 174 | ```js 175 | fruits.insert('Grape' , 'Pear'); 176 | console.log(fruits.display()); // Apple 177 | // Banana 178 | // Pear 179 | // Grape 180 | 181 | // 我们把香蕉吃掉 182 | 183 | fruits.remove('Banana'); 184 | console.log(fruits.display()); // Apple 185 | // Pear 186 | // Grape 187 | ``` 188 | 189 | Great!成功了,现在你已经可以实现一个基本的单向链表了。 190 | 191 | ## 单向链表结构 192 | 193 | 194 | 195 | ```js 196 | display: f, 197 | insert: f, 198 | find: f, 199 | head: { 200 | element: "head", // Head 201 | next: { 202 | element: "Apple" 203 | next: { 204 | element: "Banana" 205 | next: { 206 | element: "Pear" 207 | next: null 208 | } 209 | } 210 | } 211 | } 212 | ``` 213 | 214 | # 双向链表 215 | 216 | 要实现双向链表,首先需要给 Node 类增加一个 previous 属性: 217 | ```js 218 | //节点类 219 | 220 | function Node(element) { 221 | this.element = element; //当前节点的元素 222 | this.next = null; //下一个节点链接 223 | this.previous = null; //上一个节点链接 224 | } 225 | ``` 226 | 227 | 双向链表的 insert 方法与单链表相似,但需要设置新节点的 previous 属性,使其指向该节点的前驱,定义如下: 228 | ```js 229 | //插入节点 230 | function insert ( newElement , item ) { 231 | var newNode = new Node( newElement ); 232 | var currNode = this.find( item ); 233 | newNode.next = currNode.next; 234 | newNode.previous = currNode; 235 | currNode.next = newNode; 236 | } 237 | ``` 238 | 239 | 双向链表的删除 remove 方法比单链表效率高,不需要查找前驱节点,只要找出待删除节点,然后将该节点的前驱 next 属性指向待删除节点的后继,设置该节点后继 previous 属性,指向待删除节点的前驱即可。定义如下: 240 | //删除节点 241 | ```js 242 | function remove ( item ) { 243 | var currNode = this.find ( item ); 244 | if( !( currNode.next == null ) ){ 245 | currNode.previous.next = currNode.next; 246 | currNode.next.previous = currNode.previous; 247 | currNode.next = null; 248 | currNode.previous = null; 249 | } 250 | } 251 | ``` 252 | 253 | ## 双向链表结构 254 | 255 | 双向比单向列表多了一个`previous`属性 256 | 257 | 258 | ```js 259 | display: f, 260 | insert: f, 261 | find: f, 262 | head: { 263 | element: "head", // Head 264 | previous: null, 265 | next: { 266 | element: "Apple" 267 | previous: {element:"head",next:Node,previous:null} 268 | next: null 269 | } 270 | } 271 | ``` 272 | 273 | ## 反向链表 274 | 275 | 还有一些反向显示链表 dispReverse,查找链表最后一个元素 findLast 等方法,相信你已经有了思路,这里我给出一个基本双向链表的完成代码,供大家参考。 276 | 277 | ```js 278 | //节点 279 | 280 | function Node(element) { 281 | this.element = element; //当前节点的元素 282 | this.next = null; //下一个节点链接 283 | this.previous = null; //上一个节点链接 284 | } 285 | 286 | //链表类 287 | 288 | function LList () { 289 | this.head = new Node( 'head' ); 290 | this.find = find; 291 | this.findLast = findLast; 292 | this.insert = insert; 293 | this.remove = remove; 294 | this.display = display; 295 | this.dispReverse = dispReverse; 296 | } 297 | 298 | //查找元素 299 | 300 | function find ( item ) { 301 | var currNode = this.head; 302 | while ( currNode.element != item ){ 303 | currNode = currNode.next; 304 | } 305 | return currNode; 306 | } 307 | 308 | //查找链表中的最后一个元素 309 | 310 | function findLast () { 311 | var currNode = this.head; 312 | while ( !( currNode.next == null )){ 313 | currNode = currNode.next; 314 | } 315 | return currNode; 316 | } 317 | 318 | 319 | //插入节点 320 | 321 | function insert ( newElement , item ) { 322 | var newNode = new Node( newElement ); 323 | var currNode = this.find( item ); 324 | newNode.next = currNode.next; 325 | newNode.previous = currNode; 326 | currNode.next = newNode; 327 | } 328 | 329 | //显示链表元素 330 | 331 | function display () { 332 | var currNode = this.head; 333 | while ( !(currNode.next == null) ){ 334 | console.debug( currNode.next.element ); 335 | currNode = currNode.next; 336 | } 337 | } 338 | 339 | //反向显示链表元素 340 | 341 | function dispReverse () { 342 | var currNode = this.findLast(); 343 | while ( !( currNode.previous == null )){ 344 | console.log( currNode.element ); 345 | currNode = currNode.previous; 346 | } 347 | } 348 | 349 | //删除节点 350 | 351 | function remove ( item ) { 352 | var currNode = this.find ( item ); 353 | if( !( currNode.next == null ) ){ 354 | currNode.previous.next = currNode.next; 355 | currNode.next.previous = currNode.previous; 356 | currNode.next = null; 357 | currNode.previous = null; 358 | } 359 | } 360 | 361 | var fruits = new LList(); 362 | 363 | fruits.insert('Apple' , 'head'); 364 | fruits.insert('Banana' , 'Apple'); 365 | fruits.insert('Pear' , 'Banana'); 366 | fruits.insert('Grape' , 'Pear'); 367 | 368 | console.log( fruits.display() ); // Apple 369 | // Banana 370 | // Pear 371 | // Grape 372 | 373 | console.log( fruits.dispReverse() ); // Grape 374 | // Pear 375 | // Banana 376 | // Apple 377 | ``` 378 | 379 | 380 | 381 | ## 循环链表 382 | 383 | 循环链表和单链表相似,节点类型都是一样,唯一的区别是,在创建循环链表的时候,让其头节点的 next 属性执行它本身,即 384 | ```js 385 | head.next = head; 386 | ``` 387 | 388 | 这种行为会导致链表中每个节点的 next 属性都指向链表的头节点,换句话说,也就是链表的尾节点指向了头节点,形成了一个循环链表,如下图所示: 389 | 390 | 391 | 392 | 原理相信你已经懂了,循环链表这里就不贴代码了,相信你自己能独立完成! 393 | 394 | 至此,我们对链表有了比较深刻的认识,如果想让我们的链表更加健全,我们还可发挥自己的思维,给链表添加比如向前移动几个节点,向后移动几个节点,显示当前节点等方法,大家一起加油! 395 | 396 | 397 | # 参考文档 398 | 399 | - [JS中的算法与数据结构——链表(Linked-list)](https://www.jianshu.com/p/f254ec665e57) --------------------------------------------------------------------------------