本文使用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])
:在数组每个元素项上执行callback
12var 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
,后者只要有符合条件的就返回true
123456789function 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