├── README.md └── src ├── BinSearch.js ├── BubbleSort.js ├── Dynamic.js ├── Graph.js ├── Greedy.js ├── Hash.js ├── InsertionSort.js ├── Link.js ├── List.js ├── MergeSort.js ├── Queue.js ├── QuickSort.js ├── SelectionSort.js ├── SeqSearch.js ├── Set.js ├── ShellSort.js ├── Stack.js ├── Tree.js └── index.html /README.md: -------------------------------------------------------------------------------- 1 | # 数据结构和算法 2 | 3 | ## 数据结构 4 | - [列表](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/List.js) 5 | - [栈](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/Stack.js) 6 | - [队列](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/Queue.js) 7 | - [链表](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/Link.js) 8 | - [散列](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/Hash.js) 9 | - [集合](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/Set.js) 10 | - [二叉树](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/Tree.js) 11 | - [图](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/Graph.js) 12 | 13 | ## 排序 14 | - [冒泡排序](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/BubbleSort.js) 15 | - [选择排序](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/SelectionSort.js) 16 | - [插入排序](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/InsertionSort.js) 17 | > 在排序数据较多时,插入排序最快,选择排序第二,冒泡排序最慢。 18 | 19 | ## 高级排序 20 | - [希尔排序](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/ShellSort.js) 21 | - [归并排序](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/MergeSort.js) 22 | - [快速排序](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/QuickSort.js) 23 | 24 | ## 检索 25 | - [顺序(线性)查找](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/SeqSearch.js) 26 | - [二分查找](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/BinSearch.js) 27 | 28 | ## 高级算法 29 | - [动态规划](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/Dynamic.js) 30 | - [贪心算法](https://github.com/ziyi2/data-structure-algorithm-procedure/blob/master/src/Greedy.js) 31 | 32 | ## 参考资料 33 | - [JavaScript Algorithms and Data Structures](https://github.com/trekhleb/javascript-algorithms) 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/BinSearch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-10-30 09:13:24 4 | * @Desc: 二分查找 5 | */ 6 | 7 | /** 8 | * 9 | 二分查找: 10 | 11 | 如果你要查找的数据是有序的,二分查找算法比顺序查找算法更高效。 12 | 13 | 1、将数组的第一个位置设置为下边界(0)。 14 | 15 | 2、将数组最后一个元素所在的位置设置为上边界(数组的长度减1)。 16 | 17 | 3、若下边界等于或小于上边界,则做如下操作: 18 | 19 | a. 将中点设置为数组长度除以2。 20 | 21 | b. 如果中点的元素小于查询的值,则将下边界设置为中点元素所在下标加1。 22 | 23 | c. 如果中点的元素大于查询的值,则将上边界设置为中点元素所在下标减1。 24 | 25 | d. 否则中点元素即为要查找的数据,可以进行返回。 26 | 27 | */ 28 | 29 | 30 | /** 31 | * @Author: zhuxiankang 32 | * @Date: 2018-10-31 08:37:11 33 | * @Desc: 二分法查找数据 34 | * @Parm: 35 | */ 36 | function binSearch(arr, data) { 37 | let upperBound = arr.length - 1 38 | let lowerBound = 0 39 | 40 | while(lowerBound <= upperBound) { 41 | let mid = Math.floor((upperBound + lowerBound) / 2) 42 | 43 | if(arr[mid] < data) { 44 | lowerBound = mid + 1 45 | } else if(arr[mid] > data) { 46 | upperBound = mid - 1 47 | } else { 48 | return mid 49 | } 50 | } 51 | 52 | return -1 53 | } 54 | 55 | 56 | /** 57 | * @Author: zhuxiankang 58 | * @Date: 2018-10-31 08:37:34 59 | * @Desc: 插入排序 60 | * @Parm: 61 | */ 62 | function insertionSort(arr) { 63 | var temp, inner 64 | for (var outer = 1; outer <= arr.length - 1; ++outer) { 65 | // 待插入的数据 66 | temp = arr[outer] 67 | inner = outer 68 | // 69 | // 找到需要插入的位置,其他数据往后移动,并为该位置提供空间 70 | while (inner > 0 && (arr[inner - 1] >= temp)) { 71 | arr[inner] = arr[inner - 1] 72 | --inner 73 | } 74 | 75 | // 将待插入数据插入适应位置 76 | arr[inner] = temp 77 | } 78 | } 79 | 80 | 81 | let arr = [65,1,6,3,8,2,9,1,11,22,33,7,89,65,37,215,13,8,897,23,55,55,55,78,899,212,45] 82 | insertionSort(arr) 83 | console.log(arr) 84 | console.log(binSearch(arr, 1)) 85 | 86 | 87 | /** 88 | * 89 | 计算重复数据: 90 | 91 | 当binSearch() 函数找到某个值时,如果在数据集中还有其他相同的值出现,那么该函数会定位在类似值的附近。 92 | 93 | 换句话说,其他相同的值可能会出现已找到值的左边或右边。 94 | 95 | 所以一个统计重复值的函数要怎么做才能确保统计到了数据集中出现的所有重复的值呢? 96 | 97 | 最简单的解决方案是写两个循环,两个都同时对数据集向下遍历,或者向左遍历,统计重复次数;然后,向上或向右遍历,统计重复次数。 98 | 99 | */ 100 | 101 | function binRepeatCount(arr, data) { 102 | let count = 0 103 | 104 | let position = binSearch(arr, data) 105 | 106 | if(position > -1) { 107 | ++ count 108 | 109 | let i = position 110 | 111 | while(arr[++ position] === data) { 112 | ++ count 113 | } 114 | 115 | while(arr[-- i] === data) { 116 | ++ count 117 | } 118 | } 119 | 120 | return count 121 | 122 | } 123 | 124 | 125 | console.log(binRepeatCount(arr, 65)) 126 | 127 | 128 | /** 129 | * 130 | 查找文本数据: 131 | 132 | 133 | 134 | */ 135 | 136 | 137 | let strArr = `The nationalism of Hamilton was undemocratic. The democracy of Jefferson was 138 | in the beginning, provincial. The historic mission of uniting nationalism and 139 | democracy was in the course of time given to new leaders from a region beyond 140 | the mountains, peopled by men and women from all sections and free from those 141 | state traditions which ran back to the early days of colonization. The voice 142 | of the democratic nationalism nourished in the West was heard when Clay of 143 | Kentucky advocated his American system of protection for industries; when 144 | Jackson of Tennessee condemned nullification in a ringing proclamation that 145 | has taken its place among the great American state papers; and when Lincoln 146 | of Illinois, in a fateful hour, called upon a bewildered people to meet the 147 | supreme test whether this was a nation destined to survive or to perish. And 148 | it will be remembered that Lincoln's party chose for its banner that earlier 149 | device--Republican--which Jefferson had made a sign of power. The "rail splitter" 150 | from Illinois united the nationalism of Hamilton with the democracy of Jefferson, 151 | and his appeal was clothed in the simple language of the people, not in the 152 | sonorous rhetoric which Webster learned in the schools.`.split(' ') 153 | 154 | // 注意这个数组里没有区分标点符号 155 | console.log(strArr) 156 | 157 | 158 | /** 159 | * @Author: zhuxiankang 160 | * @Date: 2018-10-30 08:54:57 161 | * @Desc: 线性查找数组的索引 162 | * @Parm: 163 | */ 164 | function findIndex(arr, data) { 165 | for(let i=0; i 0 & i % 10 == 0) { 144 | retstr += "\n"; 145 | } 146 | } 147 | return retstr 148 | } 149 | 150 | /** 151 | * @Author: zhuxiankang 152 | * @Date: 2018-10-19 19:19:13 153 | * @Desc: 交换数据 154 | * @Parm: 155 | */ 156 | CArray.prototype.swap = function(index1, index2) { 157 | let temp = this.data[index1] 158 | this.data[index1] = this.data[index2] 159 | this.data[index2] = temp 160 | } 161 | 162 | let arr = new CArray(100) 163 | arr.random() 164 | console.log(arr.show()) 165 | 166 | 167 | // 冒泡排序 168 | // --------------------------------------------------------------------- 169 | CArray.prototype.bubbleSort = function() { 170 | // 外部循环确定需要比较的最后一个值 171 | // 比如第一次一直要两两比较到最后一个(这样第一次内部循环计算出最大值放到最后一个) 172 | // 第二次一直要两两比较到倒数第二个(这样第二次内部循环计算出剩余的最大值放到倒数第二个) 173 | // ... 174 | for(let i=this.data.length; i>=2; i--) { 175 | for(let j=0; j this.data[j+1]) { 177 | this.swap(j, j+1) 178 | } 179 | } 180 | } 181 | } 182 | 183 | arr.bubbleSort() 184 | console.log(arr.show()) 185 | -------------------------------------------------------------------------------- /src/Dynamic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-11-01 08:53:16 4 | * @Desc: 动态规划 5 | */ 6 | 7 | 8 | /* 9 | 10 | 动态规划有时被认为是一种与递归相反的技术。 11 | 12 | 递归是从顶部开始将问题分解,通过解决掉所有分解出小问题的方式,来解决整个问题。 13 | 14 | 动态规划解决方案从底部开始解决问题,将所有小问题解决掉,然后合并成一个整体解决方案,从而解决掉整个大问题。 15 | 16 | 17 | 18 | 使用递归去解决问题虽然简洁,但效率不高。 19 | 20 | 包括JavaScript 在内的众多语言,不能高效地将递归代码解释为机器代码,尽管写出来的程序简洁,但是执行效率低下。 21 | 22 | 但这并不是说使用递归是件坏事,本质上说,只是那些指令式编程语言和面向对象的编程语言对递归的实现不够完善,因为它们没有将递归作为高级编程的特性。 23 | 24 | 25 | 26 | 许多使用递归去解决的编程问题,可以重写为使用动态规划的技巧去解决。 27 | 28 | 动态规划方案通常会使用一个数组来建立一张表,用于存放被分解成众多子问题的解。当算法执行完毕,最终的解将会在这个表中很明显的地方被找到。 29 | 30 | */ 31 | 32 | 33 | /** 34 | * @Author: zhuxiankang 35 | * @Date: 2018-11-01 09:04:27 36 | * @Desc: 使用递归计算斐波那契数列 37 | * @Parm: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, … 38 | */ 39 | function recursionFibonacci(n) { 40 | if(n < 2) { 41 | return n 42 | } 43 | 44 | return recursionFibonacci(n-1) + recursionFibonacci(n-2) 45 | } 46 | 47 | 48 | console.log(recursionFibonacci(8)) 49 | 50 | 51 | /** 52 | * @Author: zhuxiankang 53 | * @Date: 2018-11-01 09:09:55 54 | * @Desc: 使用动态规划计算斐波那契数列 55 | * @Parm: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, … 56 | */ 57 | 58 | function dynamicFibonacci(n) { 59 | 60 | // 如果是0或者1则直接返回 61 | if(n === 0 || n === 1) { 62 | return n 63 | } 64 | 65 | let arr = [] 66 | 67 | // 数组的大小是动态开辟的,随着n变化而变化 68 | arr[0] = 0 69 | arr[1] = 1 70 | 71 | for(let i=2; i<=n; i++) { 72 | arr[i] = arr[i-1] + arr[i-2] 73 | } 74 | 75 | return arr[n] 76 | } 77 | 78 | console.log(dynamicFibonacci(8)) 79 | 80 | 81 | 82 | console.time('递归耗时') 83 | console.log(recursionFibonacci(20)) 84 | console.timeEnd('递归耗时') 85 | 86 | console.time('动态规划耗时') 87 | console.log(dynamicFibonacci(20)) 88 | console.timeEnd('动态规划耗时') 89 | 90 | 91 | // 递归耗时: 2.30419921875ms 92 | // 动态规划耗时: 0.22900390625ms 93 | 94 | 95 | 96 | 97 | /* 98 | 99 | 寻找最长公共子串:例如,在单词“raven”和“havoc”中,最长的公共子串是“av”。 100 | 101 | 动态规划是适合解决这个问题的方案。这个算法使用一个二维数组存储两个字符串相同位置的字符比较结果。 102 | 103 | 初始化时,该数组的每一个元素被设置为0。 104 | 105 | 每次在这两个数组的相同位置发现了匹配,就将数组对应行和列的元素加1,否则保持为0。 106 | 107 | 按照这种方式,一个变量会持续记录下找到了多少个匹配项。 108 | 109 | 当算法执行完毕时,这个变量会结合一个索引变量来获得最长公共子串。 110 | 111 | */ 112 | 113 | 114 | function lcs(word1, word2) { 115 | let max = 0 116 | let index = 0 117 | 118 | let lcsArr = new Array(word1.length + 1) 119 | 120 | // 动态开辟二维数组大小 121 | for(let i=0; i<=word1.length + 1; i++) { 122 | for(let j=0; j<=word2.length + 1; j++) { 123 | lcsArr[i] = new Array(word2.length + 1) 124 | } 125 | } 126 | 127 | 128 | for(let i=0; i<=word1.length; i++) { 129 | for(let j=0; j<=word2.length; j++) { 130 | 131 | // 默认矩阵的横竖起始位置的重复都是0 132 | if(i==0 || j==0) { 133 | lcsArr[i][j] = 0 134 | console.log(`${i} - ${j} : `, lcsArr[i][j]) 135 | continue 136 | } 137 | 138 | // 如果当前位置的两个字符比对相等,则匹配数等于各自前一个字符匹配数 + 1 139 | if(word1[i-1] === word2[j-1]) { 140 | lcsArr[i][j] = lcsArr[i-1][j-1] + 1 141 | // 否则未匹配的话需要重新计算匹配数 142 | } else { 143 | lcsArr[i][j] = 0 144 | } 145 | 146 | // 获取最大的匹配数和匹配的最终位置 147 | if(max < lcsArr[i][j]) { 148 | max = lcsArr[i][j] 149 | index = j 150 | } 151 | } 152 | } 153 | 154 | let str = '' 155 | if(max === 0) { 156 | return '' 157 | } else { 158 | // 获取匹配的字符串 159 | for(let i = index - max; i<=max; i++) { 160 | str += word2[i] 161 | } 162 | } 163 | 164 | return str 165 | } 166 | 167 | 168 | // [0, 0, 0, 0, 0, 0, 0, 0, 0] 169 | // [0, 0, 1, 0, 0, 0, 0, 0, 0] 170 | // [0, 0, 0, 2, 1, 1, 1, 1, 0] 171 | // [0, 0, 0, 1, 3, 2, 2, 2, 0] 172 | // [0, 0, 0, 1, 2, 4, 3, 3, 0] 173 | // [0, 0, 0, 1, 2, 3, 5, 4, 0] 174 | // [0, 0, 0, 0, 0, 0, 0, 0, 5] 175 | // [0, 1, 0, 0, 0, 0, 0, 0, 0] 176 | 177 | // 查看斜角的规律 178 | 179 | lcs('a1111bc', 'ca11111b') 180 | 181 | // 如果不用动态规划的方法,那么需要使用暴力方式,。给出两个字符串A 和B,我们可以通过从A 的第一 182 | // 个字符开始与B 的对应的每一个字符进行比对的方式找到它们的最长公共子串。如果此时 183 | // 没有找到匹配的字母,则移动到A 的第二个字符处,然后从B 的第一个字符处进行比对, 184 | // 以此类推。 185 | 186 | 187 | 188 | /* 189 | 190 | 背包问题: 191 | 192 | 保险箱中有5 件物品,它们的尺寸分别是3、4、7、8、9,而它们的价值分别是4、5、10、11、13,且背包的容积为16, 193 | 194 | 那么恰当的解决方案是选取第三件物品和第五件物品,他们的总尺寸是16,总价值是23。 195 | 196 | */ 197 | 198 | 199 | /** 200 | * @Author: zhuxiankang 201 | * @Date: 2018-11-05 08:58:36 202 | * @Desc: 递归解决背包问题(这个问题暂时没搞清楚,后续回顾) 203 | * @Parm: 204 | */ 205 | function max(a, b) { 206 | return (a > b) ? a : b 207 | } 208 | 209 | 210 | function knapsack(capacity, size, value, n) { 211 | if(n === 0 || capacity === 0) { 212 | return 0 213 | } 214 | 215 | // 如果尺寸已经大于容量,则过滤该物品 216 | if(size[n-1] > capacity) { 217 | return knapsack(capacity, size, value, n - 1) 218 | } else { 219 | 220 | let data1 = value[n-1] + knapsack(capacity - size[n-1], size, value, n - 1) 221 | let data2 = knapsack(capacity, size, value, n - 1) 222 | 223 | return max(data1, data2) 224 | } 225 | } 226 | 227 | var value = [13, 4, 5, 10, 11] 228 | var size = [9, 3, 4, 7, 8] 229 | var capacity = 16 230 | console.log(knapsack(capacity, size, value, 5)) 231 | 232 | 233 | /** 234 | * @Author: zhuxiankang 235 | * @Date: 2018-11-05 08:58:36 236 | * @Desc: 动态规划解决背包问题 237 | * @Parm: 238 | */ 239 | function dKnapsack(capacity, size, value, n) { 240 | var k = [] 241 | 242 | for(let i=0; i<=capacity + 1; i++) { 243 | k[i] = [] 244 | } 245 | 246 | for(let i=0; i<=n; i++) { 247 | for(let j=0; j<=capacity; j++) { 248 | if(i===0 || j===0) { 249 | k[i][j] = 0 250 | } else if(size[i-1] <= j) { 251 | // k[i-1][j]是上一轮的值, 所以是和上一轮的值进行比较 252 | // value[i-1]是当前值,k[i-1][j-size[i-1]]的值是剩余容量的最大值 253 | // 所以value[i-1] + k[i-1][j-size[i-1]]是当前容量的值,继续和上一轮的最大值进行比较,从而获取当前轮的最大值 254 | k[i][j] = max(value[i-1] + k[i-1][j-size[i-1]], k[i-1][j]) 255 | // 如果容量超出了限制,则采用上一轮的同位置的值(k[i-1][j]是上一轮的值) 256 | } else { 257 | k[i][j] = k[i-1][j] 258 | } 259 | } 260 | } 261 | 262 | // 最后一个必须是最大值 263 | return k[n][capacity] 264 | } 265 | 266 | 267 | var value = [4, 5, 10, 11, 13] 268 | var size = [3, 4, 7, 8, 9] 269 | var capacity = 14 270 | var n = 5 271 | console.log(dKnapsack(capacity, size, value, n)) 272 | 273 | // [0, 0, 0, 0, 0, 0, 0, 0] 274 | // [0, 0, 0, 4, 4, 4, 4, 4] 275 | // [0, 0, 0, 4, 5, 5, 5, 9] 276 | // [0, 0, 0, 4, 5, 5, 5, 10] 例如这里的10是和上一轮的9进行比较,谁大就是填谁 277 | // [0, 0, 0, 4, 5, 5, 5, 10] 278 | // [0, 0, 0, 4, 5, 5, 5, 10] 279 | 280 | -------------------------------------------------------------------------------- /src/Graph.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-10-16 19:49:19 4 | * @Desc: 图 5 | */ 6 | 7 | 8 | /* 9 | 10 | 图的定义 11 | 12 | 图由"边"的集合以及"顶点"的集合组成。 13 | 14 | 边:由顶点对(顶点1,顶点2)定义。 15 | 顶点:有权重,也称为成本。 16 | 17 | 有序图:图的顶点是有序的,称之为有向图,有向图表明了顶点的流向。 18 | 无序图:图是无序的。 19 | 20 | 路径:由一系列顶点构成,路径中的所有顶点都由边链接。 21 | 路径的长度:用路径中的第一个顶点到最后一个顶点之间边的数量表示。 22 | 环:由指向自身顶点组成的路径称为环,环的长度称为0。 23 | 圈:至少有一条边的路径,且路径的第一个顶点和最后一个顶点相同。 24 | 简单圈:没有重复边或重复顶点的圈。 25 | 平凡圈:除了第一个和最后一个顶点外,路径的其他顶点有重复的圈。 26 | 27 | */ 28 | 29 | 30 | /* 31 | 32 | 图建模 33 | 34 | 交通流量建模:顶点可以表示街道的十字路口,边可以表示街道。 35 | 加权的边可以表示限速或者车道的数量,建模人员可以用这个系统来判最佳路线以及最有可能堵车的街道。 36 | 37 | 任何运输系统都可以用图来建模。例如航空公司可以用图来为飞行系统建模。将每个机场看成顶点,将经过两个顶点的每条航线看作一条边。 38 | 加权的边可以表示从一个机场到另一个机场的航班成本,或两个机场的距离,这取决于建模的对象是什么。 39 | 40 | 任何计算器网络同样经常用图来建模。 41 | 42 | */ 43 | 44 | 45 | /** 46 | * @Author: zhuxiankang 47 | * @Date: 2018-10-16 20:33:48 48 | * @Desc: 图类 49 | * @Parm: 50 | */ 51 | function Graph(v) { 52 | // 顶点数 53 | this.vertices = v 54 | // 边数 55 | this.edges = 0 56 | this.adj = [] 57 | for(let i=0; i ', i) 84 | for(j=0; j ', i) 184 | for(j=0; j= 10) { 35 | num = parseInt(remainAmt / 10) 36 | remainAmt = remainAmt % 10 37 | rmbs.push(num) 38 | } else { 39 | rmbs.push(0) 40 | } 41 | 42 | // 次优解,如果可以找零5元面值的,次优先使用5元面值 43 | if(remainAmt >= 5) { 44 | num = parseInt(remainAmt / 5) 45 | remainAmt = remainAmt % 5 46 | rmbs.push(num) 47 | } else { 48 | rmbs.push(0) 49 | } 50 | 51 | // 最差解,实在不行,则使用1元面值的硬币 52 | if(remainAmt >= 1) { 53 | num = parseInt(remainAmt / 1) 54 | rmbs.push(num) 55 | } else { 56 | rmbs.push(0) 57 | } 58 | 59 | return rmbs 60 | } 61 | 62 | 63 | console.log(makeChange(19)) // 1 1 4 64 | 65 | 66 | /** 67 | * 68 | * 背包问题 69 | * 70 | * 如果放入背包的物品从本质上说是连续的,那么就可以使用贪心算法来解决背包问题。 71 | * 72 | * 换句话说,该物品必须是不能离散计数的,比如布匹和金粉。如果用到的物品是连续的,那么可以简单地通过物品的单价除以单位体积来确定物品的价值。 73 | * 74 | * 在这种情况下的最优解是,先装价值最高的物品直到该物品装完或者将背包装满,接着装价值次高的物品,直到这种物品也装完或将背包装满,以此类推。 75 | * 76 | * 我们不能通过贪心算法来解决离散物品问题的原因,是因为我们无法将“半台电视”放入背包。 77 | * 78 | * 离散背包问题也称为“0-1”问题,因为你必须放入整个物品或者不放入。 79 | * 80 | * 这种类型的背包问题被称为部分背包问题。以下算法用于解决部分背包问题。 81 | * 82 | * (1) 背包的容量为W,物品的价格为v,重量为w。 83 | * 84 | * (2) 根据v/w 的比率对物品排序。 85 | * 86 | * (3) 按比率的降序方式来考虑物品。 87 | * 88 | * (4) 尽可能多地放入每个物品。 89 | */ 90 | 91 | 92 | /* 93 | 物品 A B C D 94 | 价格 50 140 60 60 95 | 尺寸 5 20 10 12 96 | 比率 10 7 6 5 97 | 98 | 因此A物品的价值最高,应该优秀考虑放入背包,B物品的价值次高,其次考虑放入背包.... 99 | */ 100 | 101 | function ksack(values, weights, capacity) { 102 | var load = 0 103 | var i = 0 104 | var w = 0 105 | while (load < capacity && i < 4) { 106 | if (weights[i] <= (capacity-load)) { 107 | w += values[i] 108 | load += weights[i] 109 | } 110 | else { 111 | var r = (capacity-load)/weights[i] 112 | w += r * values[i] 113 | load += weights[i] 114 | } 115 | ++i 116 | } 117 | return w 118 | } 119 | 120 | 121 | var items = ["A", "B", "C", "D"] 122 | var values = [50, 140, 60, 60] // 价值高的物品放在前面 123 | var weights = [5, 20, 10, 12] 124 | var capacity = 30 125 | console.log(ksack(values, weights, capacity)) 126 | -------------------------------------------------------------------------------- /src/Hash.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-10-10 08:47:12 4 | * @Desc: 散列 5 | */ 6 | 7 | /* 8 | 散列是一种常用的数据存储技术,散列后的数据可以快速的插入和使用。散列使用的数据结构叫做散列表,在散列上插入、删除和取用数据都非常快。 9 | 但是散列的查找操作效率低下(例如查找一组数据中的最大值和最小值,此时采用二叉查找树是一个非常好的选择)。 10 | 11 | 散列基于数组设计,数组的长度需要预先设定,如果需要可以随时增加。 12 | 使用散列表存储数据时,需要通过一个散列函数将键映射为一个数字,这个数字的范围是0到散列表的长度,因此散列是一种随机存储的数据结构。 13 | 14 | 理想情况下散列函数会将每一个键值映射为一个唯一的数组索引,但是键的数量无限,而数组的长度有限, 15 | 一个理想的目标是让散列函数尽量将键均匀的映射到数组中。 16 | 17 | 即使使用一个高效的散列函数,仍然存在将两个键映射成同一个值的可能,这种现象称为碰撞,当碰撞发生时,可以通过特殊的算法解决碰撞问题。 18 | */ 19 | 20 | 21 | /** 22 | * @Author: zhuxiankang 23 | * @Date: 2018-10-10 09:13:16 24 | * @Desc: 散列表 25 | * @Parm: 26 | */ 27 | function HashTable() { 28 | // 散列基于数组设计,数组的长度需要预先设定 29 | // 数组的长度应该是一个质数 30 | this.table = new Array(137) 31 | } 32 | 33 | 34 | /** 35 | * @Author: zhuxiankang 36 | * @Date: 2018-10-10 09:15:34 37 | * @Desc: 简单的可能产生碰撞的散列函数(除留余数法) 38 | * @Parm: 39 | */ 40 | HashTable.prototype.simpleHash = function (data) { 41 | var total = 0 42 | // 如果键是整型,则最简单的散列函数就是以数组的长度对键取余 43 | // 但是如果数组的长度是10,键值很容易是10的整数倍,那么取余的结果容易导致一致产生碰撞 44 | // 因此数组的长度最好是质数,取余的结果不容易产生碰撞 45 | // 如果键是随机的整数,则散列函数应该更为均匀的分布这些键,这种散列方式称为除留余数法 46 | // 在JavaScript中,键很容易是字符串类型,可以通过将字符串中的每一个字符的ASCII码相加的和除以数组的长度得到的余数作为散列值 47 | for(var i=0; i item === key) 349 | 350 | if(findIndex !== -1) { 351 | this.values[findIndex] = data 352 | return 353 | } 354 | 355 | 356 | while(this.table[pos]) { 357 | pos ++ 358 | index ++ 359 | 360 | if(pos >= this.table.length) { 361 | pos = 0 362 | } 363 | 364 | // 说明遍历整个散列表都没有可插值的空间 365 | if(index >= this.table.length) { 366 | break 367 | } 368 | } 369 | 370 | 371 | if(index !== this.table.length) { 372 | this.table[pos] = key 373 | this.values[pos] = data 374 | return true 375 | } 376 | 377 | return false 378 | } 379 | 380 | 381 | HashLineTable.prototype.get = function(key) { 382 | let findIndex = this.table.findIndex(item => item === key) 383 | return this.values[findIndex] 384 | } 385 | 386 | 387 | 388 | var hashLineTable = new HashLineTable() 389 | hashLineTable.put('ziyi2', 'ziyi2') 390 | hashLineTable.showDistro() 391 | 392 | hashLineTable.put('ziyi2', 'ziyi3') 393 | hashLineTable.showDistro() 394 | 395 | hashLineTable.put('2ziyi', 'ziyi3') 396 | hashLineTable.showDistro() 397 | 398 | console.log(hashLineTable.get('2ziyi')) 399 | 400 | console.log(hashLineTable.get('ziyi2')) -------------------------------------------------------------------------------- /src/InsertionSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-10-22 09:19:40 4 | * @Desc: 插入排序(不适合对于数据量比较大的排序应用,因为需要不断的挪移位置,适合数据量小的排序) 5 | */ 6 | 7 | /** 8 | 插入排序: 9 | 10 | 插入排序有两个循环。外循环将数组元素挨个移动,而内循环则对外循环中选中的元素及它后面的那个元素进行比较。 11 | 12 | 如果外循环中选中的元素比内循环中选中的元素小,那么数组元素会向右移动,为内循环中的这个元素腾出位置。 13 | 14 | 15 | 16 | 17 | 举例说明:要排序数组:let arr= [6,3,8,2,9,1] 18 | 19 | 第1趟排序: 20 | 21 | 默认待插入的数字是3,需要插入的数组是[6], 插入结果为[3,6] 22 | 23 | --------------------------------------------------------------------- 24 | 25 | 第2趟排序: 26 | 27 | 默认待插入的数字是8,需要插入的数组是[3,6], 插入结果为[3,6,8] 28 | 29 | --------------------------------------------------------------------- 30 | 31 | 第3趟排序: 32 | 33 | 默认待插入的数字是2,需要插入的数组是[3,6,8], 插入结果为[2,3,6,8] 34 | 35 | --------------------------------------------------------------------- 36 | 37 | 第4趟排序: 38 | 39 | 默认待插入的数字是9,需要插入的数组是[2,3,6,8], 插入结果为[2,3,6,8,9] 40 | 41 | 42 | --------------------------------------------------------------------- 43 | 44 | 第5趟排序: 45 | 46 | 默认待插入的数字是1,需要插入的数组是[2,3,6,8,9], 插入结果为[1,2,3,6,8,9] 47 | 48 | --------------------------------------------------------------------- 49 | 50 | 最终结果:1 2 3 6 8 9 51 | 52 | --------------------------------------------------------------------- 53 | 54 | 插入排序的时间复杂度是O(n2)。 55 | 56 | */ 57 | 58 | function insertionSort(arr) { 59 | var temp, inner 60 | for (var outer = 1; outer <= arr.length - 1; ++outer) { 61 | // 待插入的数据 62 | temp = arr[outer] 63 | inner = outer 64 | // 65 | // 找到需要插入的位置,其他数据往后移动,并为该位置提供空间 66 | while (inner > 0 && (arr[inner - 1] >= temp)) { 67 | arr[inner] = arr[inner - 1] 68 | --inner 69 | } 70 | 71 | // 将待插入数据插入适应位置 72 | arr[inner] = temp 73 | } 74 | } 75 | 76 | 77 | let arr = [6,3,8,2,9,1] 78 | insertionSort(arr) 79 | console.log(arr) -------------------------------------------------------------------------------- /src/Link.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-10-10 08:48:55 4 | * @Desc: 链表 5 | */ 6 | 7 | /* 8 | 数组长度固定,当数组数据填满时,需要加入新的元素会非常困难,在数组中添加和删除元素也非常麻烦。 9 | JavaScript中的数组主要问题在于它被实现成了对象,与其他语言的数组相比效率很低。 10 | 如果发现在实际使用时数组很慢,可以考虑使用链表代替它。当然如果需要随机方案,数组仍然是更好的选择。 11 | */ 12 | 13 | 14 | /** 15 | * @Author: zhuxiankang 16 | * @Date: 2018-10-08 20:14:28 17 | * @Desc: Node类 18 | * @Parm: 19 | */ 20 | function Node(element) { 21 | this.element = element 22 | // next是指向下一个节点的链接, 尾巴节点指向null 23 | this.next = null 24 | } 25 | 26 | 27 | /** 28 | * @Author: zhuxiankang 29 | * @Date: 2018-10-08 20:15:24 30 | * @Desc: 链表操作类 31 | * @Parm: 32 | */ 33 | function LinkList() { 34 | // 头节点 35 | this.head = new Node('head') 36 | } 37 | 38 | /** 39 | * @Author: zhuxiankang 40 | * @Date: 2018-10-08 20:18:28 41 | * @Desc: 在链表中查找节点 42 | * @Parm: 43 | */ 44 | LinkList.prototype.find = function(element) { 45 | var currentNode = this.head 46 | while(currentNode.element !== element) { 47 | currentNode = currentNode.next 48 | } 49 | return currentNode 50 | } 51 | 52 | /** 53 | * @Author: zhuxiankang 54 | * @Date: 2018-10-08 20:19:56 55 | * @Desc: 向链表插入节点 56 | * @Parm: 57 | */ 58 | LinkList.prototype.insert = function(element, after) { 59 | var newNode = new Node(element) 60 | var currentNode = this.find(after) 61 | // 新节点的下一个节点指向被插入节点的下一个节点 62 | console.log('current: ', currentNode) 63 | newNode.next = currentNode.next 64 | // 被插入节点的下一个节点指向新节点(新节点插入在被插入节点的后面) 65 | currentNode.next = newNode 66 | } 67 | 68 | /** 69 | * @Author: zhuxiankang 70 | * @Date: 2018-10-08 20:24:28 71 | * @Desc: 显示链表所有节点 72 | * @Parm: 73 | */ 74 | LinkList.prototype.display = function() { 75 | var currentNode = this.head 76 | while(!(currentNode.next === null)) { 77 | console.log(currentNode.next) 78 | currentNode = currentNode.next 79 | } 80 | } 81 | 82 | /** 83 | * @Author: zhuxiankang 84 | * @Date: 2018-10-08 20:32:14 85 | * @Desc: 查找当前节点的前一个节点 86 | * @Parm: 87 | */ 88 | LinkList.prototype.findPrevious = function(element) { 89 | var currentNode = this.head 90 | while(!(currentNode.next === null) && (currentNode.next.element !== element)) { 91 | currentNode = currentNode.next 92 | } 93 | return currentNode 94 | } 95 | 96 | 97 | /** 98 | * @Author: zhuxiankang 99 | * @Date: 2018-10-08 20:34:31 100 | * @Desc: 删除当前节点 101 | * @Parm: 102 | */ 103 | LinkList.prototype.remove = function(element) { 104 | delete this.find(element) 105 | var preNode = this.findPrevious(element) 106 | console.log('preNode: ', preNode) 107 | 108 | if(!(preNode.next === null)) { 109 | // 将删除的前一个节点指向删除的后一个节点 110 | preNode.next = preNode.next.next 111 | } 112 | } 113 | 114 | // var link = new LinkList() 115 | // link.insert('ziyi2', 'head') 116 | // console.log(link.find('ziyi2')) 117 | // link.insert('ziyi3', 'ziyi2') 118 | // console.log(link.find('ziyi3')) 119 | // link.insert('ziyi4', 'ziyi3') 120 | // console.log(link.find('ziyi4')) 121 | // link.display() 122 | 123 | // link.remove('ziyi3') 124 | // link.display() 125 | 126 | 127 | 128 | /** 129 | * @Author: zhuxiankang 130 | * @Date: 2018-10-09 08:29:20 131 | * @Desc: 可从后向前遍历的双向链表 132 | * @Parm: 133 | */ 134 | function DoublyNode(element) { 135 | this.element = element 136 | // 后继节点 137 | this.next = null 138 | // 前驱节点 139 | this.previous = null 140 | } 141 | 142 | 143 | /** 144 | * @Author: zhuxiankang 145 | * @Date: 2018-10-09 08:32:39 146 | * @Desc: 双向操作链表(可以反序遍历节点) 147 | * @Parm: 148 | */ 149 | function DoublyLinkList() { 150 | this.head = new DoublyNode('head') 151 | } 152 | 153 | /** 154 | * @Author: zhuxiankang 155 | * @Date: 2018-10-09 08:34:09 156 | * @Desc: 查找节点 157 | * @Parm: 158 | */ 159 | DoublyLinkList.prototype.find = function(element) { 160 | var currentNode = this.head 161 | while(currentNode.element !== element) { 162 | currentNode = currentNode.next 163 | } 164 | return currentNode 165 | } 166 | 167 | /** 168 | * @Author: zhuxiankang 169 | * @Date: 2018-10-09 08:33:17 170 | * @Desc: 插入节点 171 | * @Parm: 172 | */ 173 | DoublyLinkList.prototype.insert = function(element, after) { 174 | var newNode = new DoublyNode(element) 175 | var currentNode = this.find(after) 176 | newNode.next = currentNode.next 177 | newNode.previous = currentNode 178 | // 注意currentNode的前置节点仍然没有变化 179 | currentNode.next = newNode 180 | } 181 | 182 | 183 | /** 184 | * @Author: zhuxiankang 185 | * @Date: 2018-10-09 08:36:27 186 | * @Desc: 删除节点 187 | * @Parm: 188 | */ 189 | DoublyLinkList.prototype.remove = function(element) { 190 | var currentNode = this.find(element) 191 | delete this.find(element) 192 | while(!(currentNode.next === null)) { 193 | currentNode.previous.next = currentNode.next 194 | currentNode.next.previous = currentNode.previous 195 | currentNode.next = null 196 | currentNode.previous = null 197 | } 198 | } 199 | 200 | 201 | /** 202 | * @Author: zhuxiankang 203 | * @Date: 2018-10-09 08:40:07 204 | * @Desc: 查找最后一个节点 205 | * @Parm: 206 | */ 207 | DoublyLinkList.prototype.findLast = function() { 208 | var currentNode = this.head 209 | while(!(currentNode.next === null)) { 210 | currentNode = currentNode.next 211 | } 212 | return currentNode 213 | } 214 | 215 | 216 | /** 217 | * @Author: zhuxiankang 218 | * @Date: 2018-10-09 08:41:42 219 | * @Desc: 反序显示双向链表的元素 220 | * @Parm: 221 | */ 222 | DoublyLinkList.prototype.displayReverse = function() { 223 | var currentNode = this.findLast() 224 | while(!(currentNode.previous === null)) { 225 | console.log(currentNode) 226 | currentNode = currentNode.previous 227 | } 228 | } 229 | 230 | // var doublyLink = new DoublyLinkList() 231 | // doublyLink.insert('ziyi0', 'head') 232 | // doublyLink.insert('ziyi1', 'ziyi0') 233 | // doublyLink.insert('ziyi2', 'ziyi1') 234 | // doublyLink.remove('ziyi1') 235 | // doublyLink.displayReverse() 236 | 237 | 238 | /** 239 | * @Author: zhuxiankang 240 | * @Date: 2018-10-09 08:52:42 241 | * @Desc: 循环链表(可以反序遍历链表,不用付出额外代价创建双向链表,也可以从任何节点开始遍历链表) 242 | * @Parm: 243 | */ 244 | function LoopLinkList() { 245 | this.head = new Node() 246 | // 默认尾节点指向头节点 247 | this.head.next = this.head 248 | } 249 | 250 | 251 | /** 252 | * @Author: zhuxiankang 253 | * @Date: 2018-10-09 08:57:19 254 | * @Desc: 显示链表 255 | * @Parm: 256 | */ 257 | LoopLinkList.prototype.display = function() { 258 | var currentNode = this.head 259 | // 当循环到头节点时退出循环 260 | while(!(currentNode.next === null) && 261 | !(currentNode.next.element === 'head')) { 262 | console.log(currentNode.next) 263 | currentNode = currentNode.next 264 | } 265 | } 266 | 267 | 268 | -------------------------------------------------------------------------------- /src/List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-09-26 08:59:27 4 | * @Desc: 列表 5 | */ 6 | 7 | /* 8 | 列表在日常生活中经常被使用,例如待办事项列表、购物清单、榜单等。列表适合用于保存的元素不是很多的时候。 9 | 当不需要在一个很长的序列中查找元素,或者对齐进行排序时,列表非常有用,当然如果数据结构非常复杂,列表作用就不大了。 10 | */ 11 | 12 | function List() { 13 | this.listSize = 0 14 | this.listPos = 0 15 | this.list = [] 16 | } 17 | 18 | List.prototype.append = function(list) { 19 | this.list[this.listSize ++] = list 20 | } 21 | 22 | List.prototype.find = function(list) { 23 | for(var i=0; i -1) { 34 | this.list.splice(foundAt, 1) 35 | -- this.listSize 36 | return true 37 | } 38 | return false 39 | } 40 | 41 | List.prototype.length = function() { 42 | return this.listSize 43 | } 44 | 45 | List.prototype.toString = function() { 46 | return this.list.join(',') 47 | } 48 | 49 | List.prototype.insertBefore = function(list, after) { 50 | var insertAt = this.find(after) 51 | if(insertAt > -1) { 52 | this.list.splice(insertAt, 0, list) 53 | ++ this.listSize 54 | return true 55 | } 56 | return false 57 | } 58 | 59 | 60 | List.prototype.insertAfter = function(list, before) { 61 | var insertAt = this.find(before) 62 | if(insertAt > -1) { 63 | this.list.splice(insertAt + 1, 0, list) 64 | ++ this.listSize 65 | return true 66 | } 67 | return false 68 | } 69 | 70 | List.prototype.clear = function() { 71 | delete this.list 72 | this.list = [] 73 | this.listSize = this.listPos = 0 74 | } 75 | 76 | List.prototype.contains = function(list) { 77 | for(var i=0; i 0) { 96 | -- this.listPos 97 | } 98 | } 99 | 100 | List.prototype.next = function() { 101 | if(this.listPos < this.listSize - 1) { 102 | ++ this.listPos 103 | } 104 | } 105 | 106 | List.prototype.currPos = function() { 107 | return this.listPos 108 | } 109 | 110 | List.prototype.moveTo = function(pos) { 111 | this.listPos = pos 112 | } 113 | 114 | List.prototype.getList = function() { 115 | return this.list[this.listPos] 116 | } 117 | 118 | 119 | let list = new List() 120 | list.append('ziyi2') 121 | list.append('ziyi3') 122 | console.log(list) 123 | console.log(list.find('ziyi2')) 124 | console.log(list.toString()) 125 | console.log(list.length()) 126 | list.insertBefore('ziyi1', 'ziyi2') 127 | list.insertBefore('ziyi23', 'ziyi3') 128 | console.log(list.toString()) 129 | list.insertAfter('ziyi4', 'ziyi3') 130 | console.log(list.toString()) 131 | list.insertAfter('ziyi223', 'ziyi2') 132 | console.log(list.toString()) 133 | list.clear() 134 | console.log(list.length()) 135 | console.log(list.contains('ziyi2')) 136 | list.append('ziyi1') 137 | console.log(list.contains('ziyi1')) 138 | list.append('ziyi2') 139 | list.append('ziyi3') 140 | list.append('ziyi4') 141 | list.front() 142 | console.log(list.getList()) 143 | list.end() 144 | console.log(list.getList()) 145 | list.prev() 146 | console.log(list.getList()) 147 | console.log(list.length()) 148 | console.log(list.toString()) 149 | for(list.front(); list.currPos() < list.length() - 1; list.next()) { 150 | console.log(list.currPos()) 151 | console.log(list.getList()) 152 | } -------------------------------------------------------------------------------- /src/MergeSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-10-22 08:49:39 4 | * @Desc: 归并排序 5 | */ 6 | 7 | /** 8 | * 归并排序的概念:https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sorting/merge-sort 9 | * 10 | * 归并排序的命名来自它的实现原理: 11 | * 12 | * 把一系列排好序的子序列合并成一个大的完整有序序列。从理论上讲,这个算法很容易实现。 13 | * 14 | * 我们需要两个排好序的子数组,然后通过比较数据大小,先从最小的数据开始插入,最后合并得到第三个数组。 15 | * 16 | * 然而,在实际情况中,归并排序还有一些问题,当我们用这个算法对一个很大的数据集进行排序时,我们需要相当 大的空间来合并存储两个子数组。 17 | * 18 | * 就现在来讲,内存不那么昂贵,空间不是问题,因此值得我们去实现一下归并排序,比较它和其他排序算法的执行效率。 19 | * 20 | * 21 | * 22 | * 举例说明:要排序数组:let arr= [6,3,8,2,9,1,5] 23 | * 24 | * 第一趟(step = 1) [6]左 [3]右 [8]左 [2]右 [9]左 [1]右 [5]轮空 25 | * 26 | * 第二趟(step = 2) [3,6]左 [2,8]右 [1,9]左 [5]右 27 | * 28 | * 第三趟(step = 4) [2,3,6,8]左 [1,9,5]右 29 | * 30 | * 第四趟(step = 8) [1,2,3,5,6,8,9] 31 | */ 32 | 33 | /** 34 | * @Author: zhuxiankang 35 | * @Date: 2018-10-25 09:05:32 36 | * @Desc: 归并排序 37 | * @Parm: arr -> 需要被排序的数组 38 | */ 39 | function mergeSort (arr) { 40 | // 只有一个数组元素不需要排序 41 | if(arr.length < 2) { 42 | return 43 | } 44 | 45 | let left, right, step = 1 46 | 47 | // 如果step超过了数组长度,那么不需要拆分了 48 | // 例如以上示例中的第四趟,step = 8,但是数组长度只有7,因此已经排好序了,不需要在遍历了 49 | while(step < arr.length) { 50 | left = 0 51 | right = step 52 | 53 | // 第一次step = 1,将一个数组拆分被只有一个元素的多个数组 54 | // 第二次step = 2, 将拆分的只有一个元素的数组合并成排好序的只有2个元素的多个数组 55 | // ... 56 | 57 | // 注意 58 | // 这里考虑的是左右数组元素个数一致的情况 59 | while(right + step <= arr.length) { 60 | mergeArrays(arr, left, left+step, right, right+step) 61 | left = right + step 62 | right = left + step 63 | } 64 | 65 | 66 | // 这里考虑的是左右数组元素个数不一致的情况 67 | if(right < arr.length) { 68 | mergeArrays(arr, left, left+step, right, arr.length) 69 | } 70 | 71 | // 第一次step = 1, 72 | // 第二次step = 2, 因此进行两两合并,只是合并的每个数组长度是1 73 | // 第三次step = 4, 仍然进行两两合并,只是合并的每个数组长度是2 74 | step *= 2 75 | } 76 | } 77 | 78 | /** 79 | * @Author: zhuxiankang 80 | * @Date: 2018-10-25 09:19:37 81 | * @Desc: 合并数组 82 | * @Parm: arr -> 需要被排序的数组 83 | * leftStart -> 合并的左起始地址 84 | * leftEnd -> 合并的左结束地址 85 | * rightStart -> 合并的右起始地址 86 | * rightEnd -> 合并的右结束地址 87 | */ 88 | function mergeArrays(arr, leftStart, leftEnd, rightStart, rightEnd) { 89 | let rightArr = new Array(rightEnd - rightStart + 1) 90 | let leftArr = new Array(leftEnd - leftStart + 1) 91 | let k = rightStart 92 | 93 | // 对需要排序的数组按照step进行数组拆分,拆分成一个个小数组 94 | 95 | for(let i=0; i [].concat(3, []) = [3] 46 | 47 | // 同理递归right [9] 48 | 49 | // [3].concat(6, [9]) = [3,6,9] 50 | 51 | // 可见在数据比较小的时候递归执行的比较多,消耗了性能 52 | return quickSort(left).concat(base, quickSort(right)) 53 | } 54 | 55 | 56 | let arr = [6,3,9] 57 | let arr1 = quickSort(arr) 58 | console.log(arr1) 59 | -------------------------------------------------------------------------------- /src/SelectionSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-10-22 08:49:39 4 | * @Desc: 选择排序 5 | */ 6 | 7 | /** 8 | 选择排序: 9 | 10 | 选择排序从数组的开头开始,将第一个元素和其他元素进行比较。检查完所有元素后,最小的元素会被放到数组的第一个位置。 11 | 12 | 然后算法会从第二个位置继续。这个过程一直进行,当进行到数组的倒数第二个位置时,所有的数据便完成了排序。 13 | 14 | 15 | 举例说明:要排序数组:let arr= [6,3,8,2,9,1] 16 | 17 | 第1趟排序(次数 6 - 1 = 5): 18 | 19 | 以第1个元素为起始位置,比较5次确定最小值1,插入到第1个位置:1,6,3,8,2,9 20 | 21 | --------------------------------------------------------------------- 22 | 23 | 第2趟排序(次数 6 - 2 = 4): 24 | 25 | 以第2个元素为起始位置,比较4次确定最小值2,插入到第2个位置:1,2,6,3,8,9 26 | 27 | --------------------------------------------------------------------- 28 | 29 | 第3趟排序(次数 6 - 3 = 3): 30 | 31 | 以第3个元素为起始位置,比较3次确定最小值3,插入到第3个位置:1,2,3,6,8,9 32 | 33 | --------------------------------------------------------------------- 34 | 35 | 第4趟排序(次数 6 - 4 = 2): 36 | 37 | 以第4个元素为起始位置,比较2次确定最小值6,插入到第4个位置:1,2,3,6,8,9 38 | 39 | 40 | --------------------------------------------------------------------- 41 | 42 | 第5趟排序(次数 6 - 5 = 1): 43 | 44 | 以第5个元素为起始位置,比较1次确定最小值8,插入到第5个位置:1,2,3,6,8,9 45 | 46 | --------------------------------------------------------------------- 47 | 48 | 最终结果:1 2 3 6 8 9 49 | 50 | --------------------------------------------------------------------- 51 | 52 | 选择排序的时间复杂度是O(n2)。 53 | 54 | */ 55 | 56 | 57 | 58 | // 辅助数组类 59 | // --------------------------------------------------------------------- 60 | 61 | /** 62 | * @Author: zhuxiankang 63 | * @Date: 2018-10-19 19:08:59 64 | * @Desc: 辅助数组类 65 | * @Parm: 66 | */ 67 | function CArray(number) { 68 | this.data = [] 69 | for(let i=0; i 0 & i % 10 == 0) { 110 | retstr += "\n"; 111 | } 112 | } 113 | return retstr 114 | } 115 | 116 | /** 117 | * @Author: zhuxiankang 118 | * @Date: 2018-10-19 19:19:13 119 | * @Desc: 交换数据 120 | * @Parm: 121 | */ 122 | CArray.prototype.swap = function(index1, index2) { 123 | let temp = this.data[index1] 124 | this.data[index1] = this.data[index2] 125 | this.data[index2] = temp 126 | } 127 | 128 | let arr = new CArray(100) 129 | arr.random() 130 | console.log(arr.show()) 131 | 132 | 133 | // 选择排序 134 | // --------------------------------------------------------------------- 135 | CArray.prototype.selectionSort = function() { 136 | for(let i=0,len=this.data.length; i this.data[j]) { 143 | min = j 144 | } 145 | } 146 | 147 | this.swap(i, min) 148 | } 149 | } 150 | 151 | arr.selectionSort() 152 | console.log(arr.show()) -------------------------------------------------------------------------------- /src/SeqSearch.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-10-30 08:50:30 4 | * @Desc: 线性(顺序)查找 5 | */ 6 | 7 | 8 | /** 9 | * 线性查找: 10 | * 11 | * 对于查找数据来说,最简单的方法就是从列表的第一个元素开始对列表元素逐个进行判断,直到找到了想要的结果,或者直到列表结尾也没有找到。 12 | * 13 | * 这种方法称为顺序查找,有时也被称为线性查找。它属于暴力查找技巧的一种,在执行查找时可能会访问到数据结构里的所有元素。 14 | * 15 | */ 16 | 17 | 18 | /** 19 | * @Author: zhuxiankang 20 | * @Date: 2018-10-30 08:54:46 21 | * @Desc: 线性查找元素 22 | * @Parm: 23 | */ 24 | function find(arr, data) { 25 | for(let i=0; i0) { 95 | swap(arr, i, i-1) 96 | } 97 | return true 98 | } 99 | } 100 | return false 101 | } 102 | 103 | 104 | function swap(arr, index1, index2) { 105 | let temp = arr[index1] 106 | arr[index1] = arr[index2] 107 | arr[index2] = temp 108 | } 109 | 110 | 111 | /** 112 | * 使用自组织数据: 113 | * 114 | * 另外一种自组织数据的方法是:将找到的元素移动到数据集的起始位置,但是如果这个元素已经很接近起始位置,则不会对它的位置进行交换。 115 | * 116 | * 要实现这个目标,我们只对距离数据集起始位置一定范围外的元素进行交换。 117 | * 118 | * 我们只需要定义哪些是离数据集起始位置足够近的元素,通过这个来决定是否需要将元素移动到接近数据集的起始位置。 119 | * 120 | * 再次参照“80-20原则”,我们可以确定以下原则:仅当数据位于数据集的前20% 元素之外时,该数据才需要被重新移动到数据集的起始位置。 121 | */ 122 | 123 | function find(arr, data) { 124 | for(let i=0; i arr.length * 0.2) { 128 | swap(arr, i, 0) 129 | } 130 | 131 | return true 132 | } 133 | } 134 | return false 135 | } -------------------------------------------------------------------------------- /src/Set.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-10-12 08:43:07 4 | * @Desc: 集合 5 | * @Parm: 6 | */ 7 | 8 | /* 9 | 集合是一种包含不同元素的数据结构,集合中的元素称为成员。 10 | 11 | 集合的特性: 12 | 1、集合中的成员是无序的。 13 | 2、集合不允许相同成员存在。 14 | 15 | 16 | 集合的类型: 17 | 1、空集:不包含任何成员的集合。 18 | 2、全集:包含一切可能成员的集合。 19 | 3、子集:A集合中所有的成员都属于B集合,则称A集合是B集合的子集。 20 | 4、并集:将两个成员的集合进行合并得到新集合。 21 | 5、交集:两个集合中共同存在的成员组成的新的集合。 22 | 6、补集:属于一个集合而不属于另一个集合的成员组成的集合。 23 | */ 24 | 25 | 26 | /** 27 | * ES6中集合的应用 28 | */ 29 | 30 | const s = new Set() 31 | s.add('ziyi2') 32 | s.add('ziyi3') 33 | s.add('ziyi4') 34 | console.log(s.add('ziyi4')) 35 | console.log([...s]) 36 | console.log(s.size) 37 | 38 | // 去除数组重复成员 39 | console.log([...new Set([1,1,1,1,4,4,4,5,5,5,6,6,7,7,7,7])]) 40 | 41 | 42 | // new Set也可以接受类数组对象作为参数,例如Dom元素集 43 | 44 | // 将Set结构转化成数组 45 | console.log([...s]) 46 | console.log(Array.from(s)) 47 | 48 | /** 49 | * @Author: zhuxiankang 50 | * @Date: 2018-10-12 08:59:38 51 | * @Desc: 数组去重 52 | * @Parm: 53 | */ 54 | function dedupe(arr) { 55 | return Array.from(new Set(arr)) 56 | } 57 | 58 | console.log(dedupe([1,1,1,1,2,2,2,2,3,3,3])) 59 | 60 | // 遍历方法 61 | // keys() 62 | // values() 由于Set结构没有键名,只有键值,所以keys和values方法的行为完全一致 63 | // entries() 返回键值对的遍历器 64 | // forEach() 65 | 66 | for(let value of s.values()) { 67 | console.log(value) 68 | } 69 | 70 | // Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的values方法。 71 | // 这意味着,可以省略values方法,直接用for...of循环遍历 Set。 72 | for(let value of s) { 73 | console.log(value) 74 | } 75 | 76 | 77 | s.forEach((value, key) => console.log(`${key} : ${value}`)) 78 | 79 | // 扩展运算符(...)内部使用for...of循环,所以也可以用于 Set 结构。 80 | console.log(...s) 81 | console.log([...s]) 82 | 83 | /** 84 | * @Author: zhuxiankang 85 | * @Date: 2018-10-12 09:08:19 86 | * @Desc: 数组去重 87 | * @Parm: 88 | */ 89 | function bestDedupe(arr) { 90 | return [...new Set(arr)] 91 | } 92 | 93 | // 数组的map和filter方法也可以直接用于map 94 | // 用Set可以很容易实现并集、交集和补集 95 | 96 | let a = new Set([1, 2, 3]) 97 | let b = new Set([4, 3, 2]) 98 | 99 | // 并集 100 | let union = new Set([...a, ...b]) 101 | console.log(union) 102 | 103 | // 交集 104 | let intersect = new Set([...a].filter(x => b.has(x))) 105 | console.log(intersect) 106 | 107 | // 补集 108 | let difference = new Set([...a].filter(x => !b.has(x))) 109 | console.log(difference) 110 | 111 | 112 | /** 113 | * ES5中集合的模拟实现 114 | */ 115 | 116 | function Es5Set() { 117 | this.data = [] 118 | } 119 | 120 | 121 | /** 122 | * @Author: zhuxiankang 123 | * @Date: 2018-10-12 09:14:16 124 | * @Desc: 添加元素 125 | * @Parm: 126 | */ 127 | 128 | Es5Set.prototype.add = function(element) { 129 | if(this.data.indexOf(element) < 0) { 130 | this.data.push(element) 131 | return true 132 | } 133 | return false 134 | } 135 | 136 | 137 | /** 138 | * @Author: zhuxiankang 139 | * @Date: 2018-10-12 09:16:39 140 | * @Desc: 删除元素 141 | * @Parm: 142 | */ 143 | Es5Set.prototype.remove = function(element) { 144 | var pos = this.data.indexOf(element) 145 | if(pos > -1) { 146 | this.data.splice(pos, 1) 147 | return true 148 | } 149 | return false 150 | } 151 | 152 | /** 153 | * @Author: zhuxiankang 154 | * @Date: 2018-10-12 09:17:48 155 | * @Desc: 显示集合 156 | * @Parm: 157 | */ 158 | Es5Set.prototype.show = function () { 159 | console.log(this.data) 160 | } 161 | 162 | 163 | let set = new Es5Set() 164 | set.add('ziyi2') 165 | set.add('ziyi3') 166 | set.add('ziyi4') 167 | set.show() 168 | set.remove('ziyi3') 169 | set.show() 170 | 171 | // 除此之外也可以手动实现has、union(并集)、intersect(交集)、subset(子集)、size、difference(补集)等方法。 172 | -------------------------------------------------------------------------------- /src/ShellSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-10-22 08:49:39 4 | * @Desc: 希尔排序 5 | */ 6 | 7 | /** 8 | 希尔排序: 9 | 10 | 在插入排序的基础上左了改善,核心理念和插入排序不同,它会比较距离较远的元素,而非相邻元素。 11 | 12 | 和比较相邻元素不同,这种方案可以使离正确位置很远的元素更快的回到合适的位置。 13 | 14 | 当开始用这个算法遍历数据集时,所有元素之间的距离会不断减小,直到处理到数据集的末尾,这时算法比较的就是相邻元素了。 15 | 16 | 希尔排序的工作原理是,通过定义一个间隔序列来表示在排序过程中进行比较的元素之间有多远的间隔。 17 | 18 | 我们可以动态定义间隔序列,不过对于大部分的实际应用场景,算法要用到的间隔序列可以提前定义好。 19 | 20 | 有一些公开定义的间隔序列,使用它们会得到不同的结果。 21 | 22 | 在这里我们用到了Marcin Ciura 在他2001 年发表的论文“Best Increments for theAverage Case of Shell Sort”(http:bit.ly/1b04YFv,2001)中定义的间隔序列。 23 | 24 | 这个间隔序列是:701, 301, 132, 57, 23, 10, 4, 1。 25 | 26 | 27 | 希尔排序: 28 | 29 | 举例说明:要排序数组:let arr= [6,3,8,2,9,1,5], 间隔序列:5,3,1 30 | 31 | 32 | 第一趟排序(间隔为5): 33 | 34 | 第一次排序:将1插入到[6]中:1,3,8,2,9,6,5 35 | 36 | 第二次排序:将5插入到[3]中,位置不变:1,3,8,2,9,6,5 37 | 38 | 第一趟总共进行了2次比较, 排序结果:1,3,8,2,9,6,5 39 | 40 | 注:这里第一次排序插入的概念,先将[6,3,8,2,9,1,5]中的6移动到1所在的位置变成[6,3,8,2,9,6,5],然后将6插入到1的位置,变成[1,3,8,2,9,6,5] 41 | 42 | --------------------------------------------------------------------- 43 | 44 | 第二趟排序(间隔为3): 45 | 46 | 第一次排序:将2插入到[1]中,位置不变:1,3,8,2,9,6,5 47 | 48 | 第二次排序:将9插入到[3]中,位置不变:1,3,8,2,9,6,5 49 | 50 | 第三次排序:将6插入到[8]中:1,3,6,2,9,8,5 51 | 52 | 第四次排序:将5插入到[1,2]中,位置不变:1,3,6,2,9,8,5 53 | 54 | 第二趟总共进行了4次比较, 排序结果:1,3,6,2,9,8,5 55 | 56 | --------------------------------------------------------------------- 57 | 58 | 第三趟排序(间隔为1): 59 | 60 | 第一次排序:将3插入到[1]中,位置不变:1,3,6,2,9,8,5 61 | 62 | 第二次排序:将6插入到[1,3]中,位置不变:1,3,6,2,9,8,5 63 | 64 | 第三次排序:将2插入到[1,3,6]中:1,2,3,6,9,8,5 65 | 66 | 第四次排序:将9插入到[1,2,3,6]中,位置不变:1,2,3,6,9,8,5 67 | 68 | 第五次排序:将8插入到[1,2,3,6,9]中:1,2,3,6,8,9,5 69 | 70 | 第六次排序:将5插入到[1,2,3,6,8,9]中:1,2,3,5,6,8,9 71 | 72 | 第三趟总共进行了N-1次插入操作, 排序结果:1,2,3,5,6,8,9 73 | 74 | */ 75 | 76 | 77 | // 辅助数组类 78 | // --------------------------------------------------------------------- 79 | 80 | /** 81 | * @Author: zhuxiankang 82 | * @Date: 2018-10-19 19:08:59 83 | * @Desc: 辅助数组类 84 | * @Parm: 85 | */ 86 | function CArray(number) { 87 | this.data = [] 88 | for(let i=0; i 0 & i % 10 == 0) { 131 | retstr += "\n"; 132 | } 133 | } 134 | return retstr 135 | } 136 | 137 | /** 138 | * @Author: zhuxiankang 139 | * @Date: 2018-10-19 19:19:13 140 | * @Desc: 交换数据 141 | * @Parm: 142 | */ 143 | CArray.prototype.swap = function(index1, index2) { 144 | let temp = this.data[index1] 145 | this.data[index1] = this.data[index2] 146 | this.data[index2] = temp 147 | } 148 | 149 | 150 | /** 151 | * @Author: zhuxiankang 152 | * @Date: 2018-10-23 09:20:23 153 | * @Desc: 设置间隔序列 154 | * @Parm: 155 | */ 156 | CArray.prototype.setGaps = function(arr) { 157 | this.gaps = arr 158 | } 159 | 160 | 161 | /** 162 | * @Author: zhuxiankang 163 | * @Date: 2018-10-23 09:20:45 164 | * @Desc: 希尔排序 165 | * @Parm: 166 | */ 167 | CArray.prototype.shellSort = function() { 168 | // 遍历间隔序列 169 | for(let g=0; g=gap && this.data[j-gap] > temp; j-=gap) { 184 | this.data[j] = this.data[j - gap] 185 | } 186 | 187 | this.data[j] = temp 188 | 189 | } 190 | } 191 | } 192 | 193 | 194 | let arr = new CArray(6) 195 | arr.data = [6,3,8,2,9,1,5] 196 | arr.shellSort() 197 | 198 | 199 | /** 200 | 动态计算间隔序列: 201 | 202 | Robert Sedgewick定义了一个含有动态计算间隔序列的希尔排序 203 | 204 | */ 205 | 206 | 207 | /** 208 | * @Author: zhuxiankang 209 | * @Date: 2018-10-24 09:26:07 210 | * @Desc: 计算动态间隔序列 211 | * @Parm: 212 | */ 213 | 214 | function dynamicShell(length) { 215 | var n = length 216 | var h = 1 217 | 218 | while(h=1) { 223 | // 这里使用动态间隔对数据进行希尔排序处理 224 | console.log(h) 225 | h = (h-1)/3 226 | } 227 | } 228 | 229 | dynamicShell([6,3,8,2,9,1,5].length) // 间隔序列 [4,1] 230 | dynamicShell(100) // 间隔序列 [40,13,4,1] -------------------------------------------------------------------------------- /src/Stack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-09-27 09:08:27 4 | * @Desc: 栈 5 | */ 6 | 7 | /* 8 | 栈是一种先进后出的高效数据结构,因为数据只能在栈顶添加或删除,这样的操作很快,而且容易实现。 9 | */ 10 | 11 | 12 | function Stack() { 13 | this.top = 0 14 | this.stack = [] 15 | } 16 | 17 | Stack.prototype.push = function(element) { 18 | this.stack[this.top++] = element 19 | } 20 | 21 | Stack.prototype.pop = function() { 22 | if(this.top) { 23 | let stack = this.stack[--this.top] 24 | this.stack.splice(this.top, 1) 25 | return stack 26 | } 27 | } 28 | 29 | Stack.prototype.peek = function() { 30 | return this.stack[this.top - 1] 31 | } 32 | 33 | Stack.prototype.clear = function() { 34 | if(this.top) this.stack.splice(0, this.top) 35 | this.top = 0 36 | } 37 | 38 | Stack.prototype.length = function() { 39 | return this.top 40 | } 41 | 42 | 43 | let stack = new Stack() 44 | stack.push({name: 'ziyi2'}) 45 | console.log(stack.stack) 46 | console.log(stack.top) 47 | 48 | stack.pop() 49 | console.log(stack.stack) 50 | console.log(stack.top) 51 | 52 | stack.pop() 53 | console.log(stack.stack) 54 | console.log(stack.top) 55 | 56 | stack.push({name: 'ziyi2'}) 57 | stack.push({name: 'ziyi3'}) 58 | console.log(stack.peek()) 59 | 60 | stack.clear() 61 | console.log(stack.stack) 62 | console.log(stack.top) 63 | 64 | 65 | stack.push({name: 'ziyi2'}) 66 | stack.push({name: 'ziyi3'}) 67 | console.log(stack.length()) 68 | 69 | 70 | 71 | /** 72 | * @Author: zhuxiankang 73 | * @Date: 2018-09-27 08:40:35 74 | * @Desc: 进制转化(只适合2至9进制) 75 | * @Parm: num -> 被转化的数字 76 | * base -> 进制数 77 | */ 78 | function mulBase(num, base) { 79 | let s = new Stack() 80 | do { 81 | s.push(num % base) 82 | num = Math.floor(num / base) 83 | } while(num) 84 | 85 | let converted = '' 86 | 87 | while(s.length()) { 88 | converted += s.pop() 89 | } 90 | 91 | return converted 92 | } 93 | 94 | // 将10转化为2进制 95 | console.log(mulBase(10, 2)) 96 | // 将10转化为8进制 97 | console.log(mulBase(10, 8)) 98 | 99 | 100 | /** 101 | * @Author: zhuxiankang 102 | * @Date: 2018-09-27 09:00:48 103 | * @Desc: 递归阶乘 104 | * @Parm: 105 | */ 106 | 107 | function factorial(n) { 108 | return n ? n* factorial(n-1) : 1 109 | } 110 | console.log(factorial(4)) 111 | 112 | 113 | // 使用栈模拟 114 | function stackFactorial(n) { 115 | let s = new Stack() 116 | let num = n -- 117 | while(n) { 118 | s.push(n--) 119 | } 120 | while(s.length()) { 121 | num *= s.pop() 122 | } 123 | return num 124 | } 125 | console.log(stackFactorial(4)) 126 | -------------------------------------------------------------------------------- /src/Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: zhuxiankang 3 | * @Date: 2018-09-27 09:08:27 4 | * @Desc: 二叉树 5 | */ 6 | 7 | 8 | /* 9 | 二叉树 10 | 11 | 根节点:一颗树最上面的节点称为根节点(没有父节点的节点称为根节点)。 12 | 叶子节点:没有任何子节点的节点称为叶子节点。 13 | 14 | 二叉树中的任何节点的子节点个数不超过2个。 15 | 16 | 树中任何一层的节点可以都看做是子树的根。 17 | 18 | 树可以分为几个层次,根节点是第0层,它的子节点是第1层,子节点的子节点是第2层。 19 | 20 | 深度:定义树的层数就是树的深度。 21 | 键: 每个节点的都有与之相关的值,该值被称为键。 22 | 23 | 左子节点:父节点的左侧子节点(节点的键相对与父节点较小的保存在左节点中) 24 | 右子节点:父节点的右侧子节点。(节点的键相对与父节点较大的保存在右节点中) 25 | 26 | */ 27 | 28 | 29 | 30 | 31 | /** 32 | * @Author: zhuxiankang 33 | * @Date: 2018-10-15 19:58:30 34 | * @Desc: 树节点 35 | * @Parm: 36 | */ 37 | function Node(data, left, right) { 38 | this.data = data 39 | this.left = left 40 | this.right = right 41 | } 42 | 43 | 44 | /** 45 | * @Author: zhuxiankang 46 | * @Date: 2018-10-15 19:58:41 47 | * @Desc: 显示树节点的数据 48 | * @Parm: 49 | */ 50 | Node.prototype.show = function() { 51 | return this.data 52 | } 53 | 54 | 55 | /** 56 | * @Author: zhuxiankang 57 | * @Date: 2018-10-15 19:59:39 58 | * @Desc: 二叉查找树 59 | * @Parm: 60 | */ 61 | function BST() { 62 | this.root = null 63 | } 64 | 65 | 66 | /** 67 | * @Author: zhuxiankang 68 | * @Date: 2018-10-15 20:00:32 69 | * @Desc: 插入节点 70 | * @Parm: 71 | */ 72 | BST.prototype.insert = function(data) { 73 | let newNode = new Node(data, null, null) 74 | // 1. 当前二叉树没有根节点,则创建的是根节点 75 | if(this.root === null) { 76 | this.root = newNode 77 | return 78 | } 79 | 80 | var currentNode = this.root 81 | var parent 82 | 83 | while(true) { 84 | parent = currentNode 85 | // 如果数据小于当前节点,则插入的节点应该是当前节点的左子节点 86 | if(data < currentNode.data) { 87 | currentNode = currentNode.left 88 | // 当前左子节点已经存在,则继续遍历该节点 89 | // 如果当前节点没有左子节点,则待插入的节点就是当前节点的左子节点 90 | if(currentNode === null) { 91 | parent.left = newNode 92 | break 93 | } 94 | } else { 95 | currentNode = currentNode.right 96 | // 和左节点同理 97 | if(currentNode === null) { 98 | parent.right = newNode 99 | break 100 | } 101 | } 102 | } 103 | } 104 | 105 | 106 | let bst = new BST() 107 | 108 | bst.insert(23) 109 | bst.insert(45) 110 | bst.insert(16) 111 | bst.insert(37) 112 | bst.insert(3) 113 | bst.insert(99) 114 | bst.insert(22) 115 | 116 | 117 | /* 118 | 二叉树遍历 119 | 120 | 中序:按照节点上的键值,按照升序方式访问BST上的所有节点。 121 | 先序:先访问根节点,然后以同样的方式访问左子树和右子树。 122 | 后序:先访问叶子节点,从左子树到右子树,再到根节点。 123 | 124 | */ 125 | 126 | 127 | 128 | /** 129 | * @Author: zhuxiankang 130 | * @Date: 2018-10-15 20:28:40 131 | * @Desc: 中序遍历 132 | * @Parm: 133 | */ 134 | BST.prototype.inOrder = function(node) { 135 | // 当前遍历的节点存在,则继续遍历 136 | if(node !== null) { 137 | // 先遍历左子树的左子节点 138 | this.inOrder(node.left) 139 | // 如果左子节点是叶子节点则打印该叶子节点的键值 140 | console.log(node.show()) 141 | // 同理遍历右子节点 142 | this.inOrder(node.right) 143 | } 144 | } 145 | 146 | 147 | bst.inOrder(bst.root) 148 | 149 | 150 | 151 | /** 152 | * @Author: zhuxiankang 153 | * @Date: 2018-10-15 20:54:23 154 | * @Desc: 先序遍历 155 | * @Parm: 156 | */ 157 | BST.prototype.preOrder = function(node) { 158 | if(node !== null) { 159 | console.log(node.show()) 160 | this.preOrder(node.left) 161 | this.preOrder(node.right) 162 | } 163 | } 164 | 165 | bst.preOrder(bst.root) 166 | 167 | /** 168 | * @Author: zhuxiankang 169 | * @Date: 2018-10-15 20:57:41 170 | * @Desc: 后序遍历 171 | * @Parm: 172 | */ 173 | BST.prototype.postOrder = function(node) { 174 | if(node !== null) { 175 | this.postOrder(node.left) 176 | this.postOrder(node.right) 177 | console.log(node.show()) 178 | } 179 | } 180 | 181 | bst.postOrder(bst.root) 182 | 183 | 184 | /** 185 | * @Author: zhuxiankang 186 | * @Date: 2018-10-15 21:01:18 187 | * @Desc: 二叉树查找 - 查找最大值 188 | * @Parm: 189 | */ 190 | BST.prototype.getMax = function(root) { 191 | var currentNode = root 192 | while(currentNode.right !== null) { 193 | currentNode = currentNode.right 194 | } 195 | return currentNode 196 | } 197 | 198 | 199 | /** 200 | * @Author: zhuxiankang 201 | * @Date: 2018-10-15 21:01:57 202 | * @Desc: 二叉树查找 - 查找最小值 203 | * @Parm: 204 | */ 205 | BST.prototype.getMin = function(root) { 206 | var currentNode = root 207 | while(currentNode.left !== null) { 208 | currentNode = currentNode.left 209 | } 210 | return currentNode 211 | } 212 | 213 | console.log(bst.getMax(bst.root)) 214 | console.log(bst.getMin(bst.root)) 215 | 216 | 217 | /** 218 | * @Author: zhuxiankang 219 | * @Date: 2018-10-15 21:04:52 220 | * @Desc: 二叉树查找 - 查找特定值 221 | * @Parm: 222 | */ 223 | 224 | BST.prototype.get = function(data) { 225 | var currentNode = this.root 226 | while(currentNode !== null) { 227 | if(currentNode.data === data) { 228 | return currentNode 229 | // 当前节点的值比查找值小,则需要查看当前节点的右子节点 230 | } else if(currentNode.data < data) { 231 | currentNode = currentNode.right 232 | } else { 233 | currentNode = currentNode.left 234 | } 235 | } 236 | return null 237 | } 238 | 239 | 240 | console.log(bst.get(3)) 241 | 242 | 243 | /** 244 | * @Author: zhuxiankang 245 | * @Date: 2018-10-16 08:54:56 246 | * @Desc: 二叉树删除节点 247 | * @Parm: 248 | */ 249 | BST.prototype.remove = function(node, data) { 250 | if(data == null) { 251 | return null 252 | } 253 | 254 | if(data === node.data) { 255 | // 当前待删除的节点没有子节点 256 | if(node.left === null && node.right === null) { 257 | // 设置指向待删除节点的父节点指向null 258 | return null 259 | } 260 | 261 | // 当前待删除的节点没有左节点 262 | if(node.left === null) { 263 | // 设置指向待删除节点的父节点指向待删除节点的右节点 264 | return node.right 265 | } 266 | 267 | // 当前待删除的节点没有右节点 268 | if(node.right === null) { 269 | // 设置指向待删除节点的父节点指向待删除节点的左节点 270 | return node.left 271 | } 272 | 273 | // 当前待删除的节点既有左节点也有右节点 274 | // 寻找右子树中最小键值的节点,放置待删除节点位置 275 | // 因为右子树最小键值的节点肯定比左子树最大键值的节点大,比右子树中的所有节点小 276 | // 符合二叉树键值小与父节点的节点放在左子节点,键值大与父节点的节点放在右子节点的定律 277 | let tempNode = this.getMin(node.right) 278 | // 将最小键放置在待删除节点的位置 279 | node.data = tempNode.data 280 | // 删除被移动的节点 281 | node.right = this.remove(node.right, tempNode.data) 282 | // 需要注意返回值会被递归使用 283 | return node 284 | 285 | // 需要删除的节点在左子节点中 286 | } else if(data < node.data) { 287 | node.left = this.remove(node.left, data) 288 | // 需要注意返回值会被递归使用 289 | return node 290 | // 需要删除的节点在右子节点中 291 | } else { 292 | node.right = this.remove(node.right, data) 293 | // 需要注意返回值会被递归使用 294 | return node 295 | } 296 | } 297 | 298 | 299 | console.log(bst.root) 300 | 301 | console.log(bst.remove(bst.root, 45)) 302 | console.log(bst.root) -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | --------------------------------------------------------------------------------