本文使用JavaScript实现基础数据结构:数组、链表、栈、队列、树
数组
- 在数组的末尾插入/删除、更新、获取某个位置的元素,都是 O(1) 的时间复杂度
- 在数组的任何其它地方插入/删除元素,都是 O(n) 的时间复杂度
- 空间复杂度:O(n)
数组创建
|
|
常用数组方法
增加与删除
push():在数组末尾添加一个或多个元素,并返回数组操作后的长度unshift():在数组开头添加一个或多个元素,并返回数组的新长度pop():从数组移出最后一个元素,并返回该元素shift():从数组移出第一个元素,并返回该元素splice(start_index, upto_index)从数组提取一个片段,并作为一个新数组返回splice(index, count_to_remove, addElement1, addElement2, ...)从数组移出一些元素,(可选)并替换它们number.splice(2,2)删除从数组索引2开始2个元素number.splice(2,0,1,2,3)从数组索引5开始增加3个元素2,3,4

合并
concat():连接两个数组并返回一个新的数组

排序
reverse():颠倒数组元素的顺序,第一个变成最后一个,最后一个变成第一个sort():数组元素排序,也可以带一个回调函数来决定怎么比较数组元素


搜索
indexOf(searchElement[, fromIndex]):在数组中搜索searchElement并返回第一个匹配的索引lastIndexOf(searchElement[, fromIndex]:和indexOf差不多,但这是从结尾开始,并且是反向搜索

输出字符串
toString():把数组里所有元素输出为一个字符串,默认逗号分隔join(deliminator = ','):用指定分隔符把元素隔开,将数组的所有元素连接成一个字符串,默认逗号分隔

迭代器函数
forEach(callback[, thisObject]):在数组每个元素项上执行callback12var a = ['a', 'b', 'c'];a.forEach(function(element) { console.log(element);} );
map(callback[, thisObject]):遍历数组,并通过callback对数组元素进行操作,并将所有操作结果放入数组中并返回该数组123var a1 = ['a', 'b', 'c'];var a2 = a1.map(function(item) { return item.toUpperCase(); });console.log(a2); // logs A,B,C
filter(callback[, thisObject]):返回一个包含所有在回调函数上返回为true的元素的新数组,callback在这里担任的是过滤器的角色,当元素符合条件,过滤器就返回true,而filter则会返回所有符合过滤条件的元素123var a1 = ['a', 10, 'b', 20, 'c', 30];var a2 = a1.filter(function(item) { return typeof item == 'number'; });console.log(a2); // logs 10,20,30
every(callback[, thisObject]):当数组中每一个元素在callback上被返回true时就返回true,every其实类似filter,只不过它的功能是判断是不是数组中的所有元素都符合条件,并且返回的是布尔值1234567function isNumber(value){return typeof value == 'number';}var a1 = [1, 2, 3];console.log(a1.every(isNumber)); // logs truevar a2 = [1, '2', 3];console.log(a2.every(isNumber)); // logs false
some(callback[, thisObject]):只要数组中有一项在callback上被返回true,就返回true,类似every,不过前者要求都符合筛选条件才返回true,后者只要有符合条件的就返回true123456789function isNumber(value){return typeof value == 'number';}var a1 = [1, 2, 3];console.log(a1.some(isNumber)); // logs truevar a2 = [1, '2', 3];console.log(a2.some(isNumber)); // logs truevar a3 = ['1', '2', '3'];console.log(a3.some(isNumber)); // logs false
reduce(callback[, initialValue]):使用回调函数callback(firstValue, secondValue)把数组列表计算成一个单一值,对数组元素两两递归处理的方式把数组计算成一个值123var a = [10, 20, 30];var total = a.reduce(function(first, second) { return first + second; }, 0);console.log(total) // Prints 60
reduceRight(callback[, initalvalue]):和reduce()相似,但这从最后一个元素开始的,应该使用在那些需要把数组的元素两两递归处理,并最终计算成一个单一结果的算法
链表
- 在链表的首部插入/移除结点、获得链表首部的值,都是O(1)时间复杂度
- 获取/移除/插入任一结点、尾部结点,都是O(n)时间复杂度
单链表结构图


插入示意图

删除示意图

单链表实例
ES5实现:
|
|
用例测试:
|
|
双向链表结构图
双向链表和普通链表的区别在于,在链表中,一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素。

插入示意图

删除示意图

栈
- 栈满足后进先出 LIFO的原则
- 时间复杂度:压栈、出栈都是 O(1)
栈实例
ES5实现:
|
|
用例测试:
|
|
ES6语法:
|
|
队列
- 队列满足先进先出 FIFO 的原则
- 时间复杂度:出队列使用了数组的
shift()方法,故时间复杂度为 O(n);入队列采用了列表的push()方法,故时间复杂度为 O(1)
队列实例
ES5实现:
|
|
用例测试:
|
|
ES6语法:
|
|
循环队列
- 循环队列有一个最大长度
max_size,仍然采用数组实现。两个成员变量front和rear分别为队首元素和下一个入队的元素在列表中的索引。为了区别队列为空和队列为满,列表大小应为length = max_size + 1,列表中最多只能有max_size个队列元素 - 当进行入队操作时,先判断队列是否已满:(rear + 1) % length == front,一种方法是已满直接报错,另一种是若队列已满则扩容为原来的两倍。入队时,
rear = (rear + 1) % max_size - 进行出队操作时,先判断队列是否为空:front == rear,如果为空则报错。出队时,
front = (front + 1) % max_size - 获得当前队列长度:(rear - front + length) % length
- 使用循环队列的方法,由于入队和出队操作都是直接通过索引访问列表,所以时间复杂度都是 O(1)
树
- 结点、父结点、子结点、兄弟结点
- 层数、深度、高度、结点的度:结点的子树个数
- 满二叉树:除了叶结点,其它所有结点都有两个子结点(国内定义:除最后一层全为叶结点外,其它的层的每个结点都有两个子结点)
- 完全二叉树:除最后一层外,其它层全满,最后一层的叶结点必须从左到右填满
二叉树实例
ES5实现:
|
|
用例测试:
|
|
参考链接
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Indexed_collections
https://github.com/wolverinn/Iridescent/blob/master/Data%20Structure.md
https://github.com/wolverinn/Iridescent/blob/master/Data%20Structure%20code%20complete.ipynb