探秘前端JavaScript算法(一)

大家好,我是拾七,持续给大家补给前端干货🔥

在前端开发的世界里,数据结构和算法是我们的基石。数组、链表、栈、队列、树、图......这些基本的数据结构为我们提供了存储和组织数据的方式,而各种算法则是我们解决各种问题的思路和方法。

想象一下,当你面对一个巨大的数据集时,如何快速找到想要的数据?当你需要对列表或表格数据进行排序时,如何选择最合适的排序算法?当你想要实现流畅自然的动画效果时,如何选择最优的动画算法?前端算法就是帮助你解决这些问题的利器。

如何分析一个排序算法?

复杂度分析是整个算法学习的精髓。时间复杂度,表示一个算法执行所耗费的时间;空间复杂度,表示运行完一个程序所需耗费内存的大小。学习排序算法,除了学习它的算法原理、代码实现之外,更重要的是要学会如何评价、分析一个排序算法,从执行效率、内存消耗、稳定性三方面入手。

执行效率

首先,我们在分析排序算法的时间复杂度时,要分别给出最好情况、最坏情况、平均情况下的时间复杂度。除此之外,还要说出最好、最坏时间复杂度对应的要排序的原始数据是什么样的。

其二,我们知道,时间复杂度反应的是数据规模n很大的时候的一个增长趋势,所以它表示的时候会忽略系数、常数、低阶。但实际的软件开发中,排序的可能是10个、100个、1000个这样规模很小的数据,所以,在对同一阶时间复杂度的排序算法性能对比的时候,我们就要把系数、常数、低阶考虑进来。

其三,基于比较的排序算法的执行过程,会涉及两种操作,一种是元素比较大小,另一种是元素交换或移动。所以,如果我们在分析算法的执行效率的时候,应该把比较次数和交换次数也考虑进去。

内存消耗

这里看的是空间复杂度,需要了解内排序、外排序、原地排序。

内排序:所有排序操作都在内存完成。

外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行。

原地排序:原地排序算法,就是特指空间复杂度是O(1)的排序算法。

稳定性

稳定:如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变,比如:a原本在b前面,而a=b,排序之后,a仍然在b的前面。

不稳定:如果待排序的序列中存在值相等的元素,经过排序后,相等元素之间原有的先后顺序改变。比如:a原本在b前面,而a=b,排序之后,a仍然在b的后面。

排序算法 - 冒泡排序( Bubble Sort

1、冒泡排序只会操作相邻的两个数据

2、每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。

3、一次冒泡会让至少一个元素移动到它应该在的位置,重复n次,就完成了n个数据的排序工作。

css 复制代码
// 冒泡排序const bubbleSort = (arr) => {  console.time('改进前冒泡排序耗时');  const length = arr.length;  if (length <= 1) return;  for (let i = 0; i < length - 1; i++) {    for (let j = 0; j < length - i - 1; j++) {      if (arr[j] > arr[j + 1]) {        const temp = arr[j];        arr[j] = arr[j + 1];        arr[j + 1] = temp;      }    }  }  console.info('改进前', arr);  console.timeEnd('改进前冒泡排序耗时');};​​

问题:当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作。

perl 复制代码
// 冒泡排序(优化后)​const bubbleSort2 = (arr) => {  console.time('改进后冒泡排序时');  const length = arr.length;  if (length <= 1) return;  // i < length - 1是因为外层只需要length - 1次就排好了,第length次比较是多余的。  for (let i = 0; i < length - 1; i++) {    let hasChange = false; // 提前退出冒泡循环的标志位    // j<length -i -1是因为内层的length - i-1到length-1的位置已经排好了,不需要再比较一次    for (let j = 0; j < length - i - 1; j++) {      if (arr[j] > arr[j + 1]) {        const temp = arr[j];        arr[j] = arr[j + 1];        arr[j + 1] = temp;        hasChange = true; // 表示有数据交换      }    }    if (!hasChange) break;  }  console.info('改进后', arr);  console.timeEnd('改进后冒泡排序耗时');};
ruby 复制代码
// 测试const arr = [7, 8, 4, 5, 6, 3, 2, 1];bubbleSort(arr);// 改进前 arr : [1, 2, 3, 4, 5, 6, 7, 8]// 改进前冒泡排序耗时: 0.43798828125ms​const arr2 = [7, 8, 4, 5, 6, 3, 2, 1];bubbleSort2(arr2);// 改进后 arr : [1, 2, 3, 4, 5, 6, 7, 8]// 改进后冒泡排序耗时: 0.318115234375ms

分析

1、冒泡排序是原地排序算法吗?

冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为O(1),是一个原地排序的算法。

2、冒泡排序是稳定的排序算法吗?

在冒泡排序中,只有交换才可以改变两个元素的前后顺序。

为了保证冒泡排序算法的稳定性,当有相邻的两个元素大小相等的时候,我们不做交换,相同大小的数据在排序前后不会改变顺序。所以冒泡排序是稳定的排序算法。

3、冒泡排序的时间复杂度是多少?

最佳情况:T(n) = O(n), 数据已经是正序时

最差情况:T(n) = O(n^2),数据时反序时

平均情况:T(n) = O(n^2)

排序算法 - 插入排序( Insertion Sort

插入排序又分为直接插入排序、优化后的折半插入排序、希尔排序,我们通常说的插入排序是指直接插入排序。

一般人打扑克牌,整理牌的时候,都是按牌的大小(从小到大或者从大到小)整理牌的,那每摸一张新牌,就扫描自己的牌,把新牌插入到相应的位置。插入排序的工作原理:通过构建有序序列,对于未排序数据,在已排序列中从后向前扫描,找到相应位置并插入。

步骤

1、从第一个元素开始,该元素可以认为已经被排序;

2、取出下一个元素,在已经排序的元素序列中从后向前扫描;

3、如果该元素(已排序)大于新元素,将该元素移到下一位置;

4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;

5、将新元素插入到该位置后;

6、重复步骤2~5

css 复制代码
const insertSort = (arr) => {  const len = arr.length;  if (len <= 1) return;  let preIndex, current;  for (let i = 1; i < len; i++) {    preIndex = i - 1; // 待比较元素的下标    current = arr[i]; // 当前元素    while (preIndex >= 0 && arr[preIndex] > current) {      // 前置条件之一:待比较元素比当前元素大      arr[preIndex + 1] = arr[preIndex];      preIndex--; // 游标前移一位    }    if (preIndex + 1 != i) {      // 避免同一个元素赋值给自身      arr[preIndex + 1] = current; // 将当前元素插入预留空位      console.log('arr', arr);    }  }  return arr;};

分析

1、插入排序是原地排序算法吗 插入排序算法的运行不需要额外的存储空间,所以空间复杂度是O(1),所以,这是一个原地排序算法。

2、插入排序是稳定的排序算法吗?

在插入排序中,对于值相同的元素,我们可以选择将后面出现的元素,插入到前面出现元素的后面,这样就可以保持原有的前后顺序不变,所以插入排序是稳定的排序算法。

3、插入排序的时间复杂度是多少?

最佳情况:T(n) = O(n),数据已经是正序

最差情况:T(n) = O(n^2),数据反序

平均情况:T(n) = O(n^2)

折半插入排序是直接插入排序的升级版

鉴于插入排序第一部分为已排好序的数组,我们不必按顺序依次寻找插入点,只需比较它们的中间值与待插入元素的大小即可。

  • 取 0 ~ i-1 的中间点 ( m = (i-1) >> 1 ),array[i] 与 array[m] 进行比较,若 array[i] < array[m],则说明待插入的元素 array[i] 应该处于数组的 0 ~ m 索引之间;反之,则说明它应该处于数组的 m ~ i-1 索引之间。

  • 重复步骤 1,每次缩小一半的查找范围,直至找到插入的位置。

  • 将数组中插入位置后的元素全部后移一位

  • 在指定位置插入第i个元素。

    PS:x>>1是位运算中的右移运算,表示右移一位,等同于x除以2再取整,即x>>1 == Math.floor(x/2)

css 复制代码
// 折半插入const binaryInsertionSort = (array) => {  const len = array.length;  if (len <= 1) return;  let current, i, j, low, high, m;  for (i = 1; i < len; i++) {    low = 0;    high = i - 1;    current = array[i];​    while (low <= high) {      // 步骤 1 & 2 : 折半查找 x>>1 是位运算中的右移运算, 表示右移一位, 等同于 x 除以 2 再取整, 即 x>>1 == Math.floor(x/2) .      // 高位区:m ~ i-1  低位区:0 - m      m = (low + high) >> 1;      if (array[i] >= array[m]) {        low = m + 1;      } else {        high = m - 1;      }    }    for (j = i; j > low; j--) {      array[j] = array[j - 1];    }    array[low] = current;  }  return array;};​

还有更多算法,点关注收藏,持续更新中

相关推荐
Martin -Tang15 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发15 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
老码沉思录1 小时前
写给初学者的React Native 全栈开发实战班
javascript·react native·react.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂1 小时前
工程化实战内功修炼测试题
前端·javascript
爱吃生蚝的于勒1 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习