前端常见算法

164 阅读4分钟

一、排序算法

1. 快速排序

将数组分成左右两部分,让一部分中的每个值始终大于另一部分中的每个值,然后在左右两部分循环上述操作,最终达成排序的目的。

  • 创建新数组的写法
function quickSort(arr){
    let length=arr.length;
    if(!Array.isArray(arr)||length<2){
        return arr
    }
    let index=Math.floor((arr.length-1)/2)
    let pivot=arr.splice(index,1)[0]
    let left=[],right=[]
    for(let i=0,i<length,i++){
        if(arr[i]<=pivot){
            left.push(arr[i])
        }else{
            right.push(arr[i])
        }
    }
    return quickSort(left).concat(arr.splice(index,1),quickSort(left))
}
  • 不创建新数组的写法
function quickSort(arr,start,end){
    let length=arr.length;
    //如果不是数组或数组长度小于2,则不用排序,直接返回
    if(!Array.isArray(arr)||length<2)return;
    let start=0,end=length-1;
    // 将数组划分为两部分,并返回右部分的第一个元素的索引值
    let index=divide(arr,start,end)
    quickSort(arr[arr,start,index-1]) // 递归排序左半部分
    quickSort(arr[arr,index,end]) // 递归排序右半部分
    function divide(arr,start,end){
        let pivot=arr[start] // 取第一个值为枢纽值,获取枢纽值的大小
        // 当 start 等于 end 指针时结束循环
        while(start<end){
            // 当 end 指针指向的值大等于枢纽值时,end 指针向前移动
            while(arr[end]>pivot&&start<end){end--}
            // 将比枢纽值小的值交换到 start 位置
            arr[start]=arr[end]
            // 移动 start 值,当 start 指针指向的值小于枢纽值时,start 指针向后移动
            while(arr[start]<pivot&&start<end){start--}
            // 将比枢纽值大的值交换到 end 位置,进入下一次循环
            arr[end]=arr[start]
        }
        // 将枢纽值交换到中间点
        arr[start]=pivot
        // 返回中间索引值
        return start
    }
}

2.归并排序

是利用归并的思想实现的排序方法,该算法采用经典的分治策略。递归的将数组两两分开直到只包含一个元素,然后 将数组排序合并,最终合并为排序好的数组。

function mergeSort(array) {
    let length = array.length;
    // 如果不是数组或者数组长度小于等于 0,直接返回,不需要排序
    if (!Array.isArray(array) || length === 0) return;
    if (length === 1) {
        return array;
    }
    let mid = parseInt(length >> 1), // 找到中间索引值
    left = array.slice(0, mid), // 截取左半部分
    right = array.slice(mid, length); // 截取右半部分
    return merge(mergeSort(left), mergeSort(right)); // 递归分解后,进行排序合并
    }
function merge(leftArray, rightArray) {
    let result = [],leftLength = leftArray.length,rightLength = rightArray.length,il = 0,ir = 0;
    // 左右两个数组的元素依次比较,将较小的元素加入结果数组中,直到其中一个数组的元素全部加入完则停止
    while (il < leftLength && ir < rightLength) {
        if (leftArray[il] < rightArray[ir]) {
            result.push(leftArray[il++]);
        } else {
            result.push(rightArray[ir++]);
        }
    }
    // 如果是左边数组还有剩余,则把剩余的元素全部加入到结果数组中。
    while (il < leftLength) {
        result.push(leftArray[il++]);
    }
    // 如果是右边数组还有剩余,则把剩余的元素全部加入到结果数组中。
    while (ir < rightLength) {
        result.push(rightArray[ir++]);
    }
    return result;
}

3.堆排序

堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行 交换,此时末尾就为最大值。然后将剩余 n-1 个元素重新构造成一个堆,这样会得到 n 个元素的次小值。如此反复执行, 便能得到一个有序序列了。

function heapSort(array) {
    let length = array.length;
    // 如果不是数组或者数组长度小于等于 1,直接返回,不需要排序
    if (!Array.isArray(array) || length <= 1) return;
    buildMaxHeap(array); // 将传入的数组建立为大顶堆
    // 每次循环,将最大的元素与末尾元素交换,然后剩下的元素重新构建为大顶堆
    for (let i = length - 1; i > 0; i--) {
        swap(array, 0, i);
        adjustMaxHeap(array, 0, i); // 将剩下的元素重新构建为大顶堆
    }
    return array;
}
function adjustMaxHeap(array, index, heapSize) {
    let iMax,iLeft,iRight;
    while (true) {
        iMax = index; // 保存最大值的索引
        iLeft = 2 * index + 1; // 获取左子元素的索引
        iRight = 2 * index + 2; // 获取右子元素的索引
        // 如果左子元素存在,且左子元素大于最大值,则更新最大值索引
        if (iLeft < heapSize && array[iMax] < array[iLeft]) {
            iMax = iLeft;
        }
        // 如果右子元素存在,且右子元素大于最大值,则更新最大值索引
        if (iRight < heapSize && array[iMax] < array[iRight]) {
            iMax = iRight;
        }
        // 如果最大元素被更新了,则交换位置,使父节点大于它的子节点,同时将索引值跟新为被替换的值,继续检查它的子树
        if (iMax !== index) {
            swap(array, index, iMax);
            index = iMax;
        } else {
            // 如果未被更新,说明该子树满足大顶堆的要求,退出循环
            break;
        }
    }
}
// 构建大顶堆 
function buildMaxHeap(array) {
    let length = array.length,
    iParent = parseInt(length >> 1) - 1; // 获取最后一个非叶子点的元素
    for (let i = iParent; i >= 0; i--) {
        adjustMaxHeap(array, i, length); // 循环调整每一个子树,使其满足大顶堆的要求
    }
}
// 交换数组中两个元素的位置 
function swap(array, i, j) {
    let temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

4、冒泡排序

冒泡排序的基本思想是,对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素“浮”到顶端, 最终达到完全有序。

function bubbleSort(arr) {
    if (!Array.isArray(arr) || arr.length <= 1) return;
    let lastIndex = arr.length - 1;
    while (lastIndex > 0) { // 当最后一个交换的元素为第一个时,说明后面全部排序完毕
    let flag = true, k = lastIndex;
    for (let j = 0; j < k; j++) {
        if (arr[j] > arr[j + 1]) {
            flag = false;
            lastIndex = j; // 设置最后一次交换元素的位置
            [arr[j], arr[j+1]] = [arr[j+1], arr[j]];
        }
    }
    if (flag) break;
    }
}

时间复杂度:

排序方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性复杂性
堆排序O(nlog2n)O(nlog2n)O(nlog2n)O(1)不稳定较复杂
冒泡排序O(n2)O(n2)O(n)O(1)稳定简单
快速排序O(nlog2n)O(n2)O(nlog2n)O(nlog2n)不稳定较复杂
归并排序O(nlog2n)O(nlog2n)O(nlog2n)O(n)稳定较复杂

二、糖(小的实用场景)

1. js数组求和

  1. 递归
  2. 常规循环
  3. reduce reducer逐个遍历数组元素,每一步都将当前元素的值与上一步的计算结果相加(上一步的计算结果是当前元素之前所有元素的总和)——直到没有更多的元素被相加。
function sum(arr){
    return arr.reduce((prev,curr)=>prev+curr)
}
  1. forEach遍历
function sum(arr){
    let s=0
    arr.forEach((val)=>s+=val)
    return s
}
  1. eval eval() 函数用来执行一个字符串表达式,并返回表达式的值。
function sum(arr){
    return eval(arr.join('+'))
}

2. 精度问题的解决

  • 0.1+0.2!==0.3
  • 原因:浮点数转二进制可能出现无限循环的现象,再转为十进制时出现精度误差
  • 解决方法:
  1. toFixed()控制小数点后位数
  2. 将其先转换成整数,再相加之后转回小数
  3. 使用es6新增的Number.EPSILON方法,这个方法表示js的最小精度,使用这个方法通常只是对0.1+0.2是否=0.3做判断,并不像前两种改变0.1+0.2的值

3.找出一个数组中出现次数超过一半的元素

三种解决方法:

  1. 将数组排序,排序后中间的元素就是我们要找的元素,然后再判断它出现的次数是不是超过了数组长度的一半。
  2. 将数组的元素存进map里,key为元素,value为个数。遍历数组的时候,判断value大小,如果value大于数组长度的一半则返回。
  3. 模仿快速排序的做法,当枢纽值为中间值时返回,并统计

4.数组去重

  1. set
var arr = [1,1,8,8,12,12,15,15,16,16];
function unique (arr) {
  return Array.from(new Set(arr))
}

console.log(unique(arr))

  1. 双for循环,如果arr[i]===arr[j],则用splice(j,1)删掉后面的值
  2. 创建新的空数组,for循环,用indexOf判断新数组是否包含对应的值,不包含则push进新数组
  3. includes(同上)
  4. filter
var arr = [1, 1, 8, 8, 12, 12, 15, 15, 16, 16];
function unlink(arr) {
    return arr.filter(function (item, index, arr) {
        //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
        return arr.indexOf(item, 0) === index;
    });
}
console.log(unlink(arr));