很多前端人认为算法是后端才需要掌握的知识,但随着前端在行业的重要性越来越高,对其知识的储备要求也越来越高,不懂点算法都不好意思说自己是个好前端了。同时数据结构和算法的设计基本上决定了最终程序的好坏,编写程序的好坏,会直接影响到程序的性能优劣。如今的大环境里,算法已经成为了前端工程师发展路上不可或缺的技能之一。如果我们想未来更上一层楼,不再是只写业务代码的应用工程师,就离不开对 算法和数据结构的掌握, 了解一些基本的算法与数据结构可以在阅读源码的时候,更容易理解作者的思路。
数据结构
js 数据类型
- 基本类型(栈 stack) : Number、String 、Boolean、Null 和 Undefined , Symbol(es6 新增); 基本数据类型是按值访问 由高向低分配,栈内存最大是 8MB,(超出报栈溢出), String:是特殊的栈内存 (向高分配大小不定),程序员分配
- 引用类型(堆 heap) :Object 、Array 、Function 、Data;引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址(指针),向高分配,系统自动分配
一、堆栈空间分配区别:
- 栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
- 堆(操作系统): 一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收,分配方式倒是类似于链表。
二、堆栈缓存方式区别:
- 栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放;
- 堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
三、堆 、栈数据结构区别:
- 堆(数据结构):堆可以被看成是一棵树,如:堆排序;
- 栈(数据结构):一种先进后出的数据结构。
数据结构
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成;数据结构的基本操作的设置的最重要的准则是,实现应用程序与存储结构的独立(数据结构=数据的存储+算法)。
数据结构分类
- 逻辑结构:反映数据之间的逻辑关系;
- 存储结构:数据结构在计算机中的表示;
逻辑结构:
集合:结构中的数据元素除了同属于一种类型外,别无其它关系。(无逻辑关系) 线性结构 :数据元素之间一对一的关系(线性表) 树形结构 :数据元素之间一对多的关系(非线性) 图状结构或网状结构: 结构中的数据元素之间存在多对多的关系(非线性)
存储结构:
顺序存储数据结构 链式存储数据结构 索引存储数据结构 散列存储数据结构
算法
前端需要的算法并不像后端那么全面,但基本的知识还是要掌握的。
算法(Algorithm)是指用来操作数据、解决程序问题的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和时间却会有很大的区别。
主要从算法所占用的「时间」和「空间」两个维度去考量。
- 时间维度:是指执行当前算法所消耗的时间,我们通常用「时间复杂度」来描述。
- 空间维度:是指执行当前算法需要占用多少内存空间,我们通常用「空间复杂度」来描述。
因此,评价一个算法的效率主要是看它的时间复杂度和空间复杂度情况。然而,有的时候时间和空间却又是「鱼和熊掌」,不可兼得的,那么我们就需要从中去取一个平衡点。
时间复杂度
一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或「时间频度」。记为T(n)。
时间频度T(n)中,n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。但有时我们想知道它变化时呈现什么规律,为此我们引入时间复杂度的概念。算法的时间复杂度也就是算法的时间度量,记作:T(n) = O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称「时间复杂度」。
这种表示方法我们称为「 大O符号表示法 」,又称为渐进符号,是用于描述函数渐进行为的数学符号
常见的时间复杂度量级有:
- 常数阶 O ( 1 ) O(1) O(1)
- 线性阶 O ( n ) O(n) O(n)
- 平方阶 O ( n 2 ) O(n^2) O(n2)
- 立方阶 O ( n 3 ) O(n^3) O(n3)
- 对数阶 O ( l o g n ) O(logn) O(logn)
- 线性对数阶 O ( n l o g n ) O(nlogn) O(nlogn)
- 指数阶 O ( 2 n ) O(2^n) O(2n)
空间复杂度
空间复杂度(Space Complexity)是对一个算法在运行过程中临时占用存储空间大小的一个量度,同样反映的是一个趋势,一个算法所需的存储空间用f(n)表示。S(n)=O(f(n)),其中n为问题的规模,S(n)表示空间复杂度。
一个算法在计算机存储器上所占用的存储空间,包括存储算法本身所占用的存储空间,算法的输入输出数据所占用的存储空间和算法在运行过程中临时占用的存储空间这三个方面。
一般情况下,一个程序在机器上执行时,除了需要存储程序本身的指令、常数、变量和输入数据外,还需要存储对数据操作的存储单元。若输入数据所占空间只取决于问题本身,和算法无关,这样只需要分析该算法在实现时所需的辅助单元即可。若算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为O(1)。当一个算法的空间复杂度与n成线性比例关系时,可表示为 0 ( n ) 0(n) 0(n),类比时间复杂度。
空间复杂度比较常用的有:O(1)、O(n)、O(n²)
冒泡排序
冒泡排序分两种,一种小泡泡往上吐,一种大泡泡往上吐。也就是从小到大和从大到小。
冒泡排序就是从最开始的位置或结尾的位置反方向对比,如果比它大/小,就交换然后继续走,第一遍走完,最后一个位置是最大值或者最小值
function bubbleSort(arr) {
var len = arr.length;
for (var i = 0; i < len - 1; i++) {
for (var j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j+1]) { // 相邻元素两两对比
var temp = arr[j+1]; // 元素交换
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
快速排序
快速排序简称快排,基本上别人问你数据结构学过没,都会让你手写个快排看看。
快排就是一开始找个中介,然后把比它小的放左边,比它大的放右边,然后重新对中介两边的数据各自重新找个中介,如此循环。
function swap(items, firstIndex, secondIndex){
var temp = items[firstIndex];
items[firstIndex] = items[secondIndex];
items[secondIndex] = temp;
}
function partition(items, left, right) {
var pivot = items[Math.floor((right + left) / 2)],
i = left,
j = right;
while (i <= j) {
while (items[i] < pivot) {
i++;
}
while (items[j] > pivot) {
j--;
}
if (i <= j) {
swap(items, i, j);
i++;
j--;
}
}
return i;
}
function quickSort(items, left, right) {
var index;
if (items.length > 1) {
index = partition(items, left, right);
if (left < index - 1) {
quickSort(items, left, index - 1);
}
if (index < right) {
quickSort(items, index, right);
}
}
return items;
}
var items = [3,8,7,2,9,4,10]
var result = quickSort(items, 0, items.length - 1);
插入排序
插入排序它将数组分成“已排序”和“未排序”两部分,一开始的时候,“已排序”的部分只有一个元素,然后将它后面一个元素从“未排序”部分插入“已排序”部分,从而“已排序”部分增加一个元素,“未排序”部分减少一个元素。以此类推,完成全部排序。
function insertionSort(arr) {
var len = arr.length;
var preIndex, current;
for (var i = 1; i < len; i++) {
preIndex = i - 1;
current = arr[i];
while(preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex+1] = arr[preIndex];
preIndex--;
}
arr[preIndex+1] = current;
}
return arr;
}
选择排序
找到数组中最小的那个元素,将它和数组的第一个元素交换位置,
如果第一个元素就是最小的那么就和自己交换
然后,在剩下的数组中找到最小的元素,将它和数组的第二个元素交换
循环直到排序完成。
内循环只比较大小,交换代码在内循环之外\
每次交换都排定一个元素,交换的总次数为N
function selectionSort(arr) {
var len = arr.length;
var minIndex, temp;
for (var i = 0; i < len - 1; i++) {
minIndex = i;
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
希尔排序
希尔排序属于高级排序,因为它其实是利用了多个插入排序来实现。
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序
function shellsort(numbers) {
console.log("原数组:" + numbers)
var h = 1;
while (h < numbers.length / 3) {
h = (3 * h) + 1;
}
while (h >= 1) {
console.log("此时h:" + h)
for (var i = h; i < numbers.length; i++) {
for (var j = i; j >= h && numbers[j] < numbers[j - h]; j -= h) {
exchange(numbers, j, j - h);
console.log("此时数组:" + numbers)
}
}
h = --h / 3;
}
return numbers;
}
var nums = [2,3,4,3,1,5,7,122,341,-1]
console.log(shellsort(nums))
二分搜索
二分查找是基本功,而且需要注意的是,二分查找的前提是数组一开始就是有序的。
function binSearch(arr, data) {
arr = arr.sort(function(a, b) {
return a - b;
})
console.log(arr)
var upperBound = arr.length-1;
var lowerBound = 0;
while (lowerBound <= upperBound) {
var mid = Math.floor((upperBound + lowerBound) / 2);
console.log("Current midpoint: " + mid);
if (arr[mid] < data) {
lowerBound = mid + 1;
}
else if (arr[mid] > data) {
upperBound = mid - 1;
}
else {
return mid;
}
}
return -1;
}
var nums = [2,3,4,3,1,5,7,122,341,-1]
console.log(binSearch(nums,122))