一、排序算法
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数组求和
- 递归
- 常规循环
- reduce reducer逐个遍历数组元素,每一步都将当前元素的值与上一步的计算结果相加(上一步的计算结果是当前元素之前所有元素的总和)——直到没有更多的元素被相加。
function sum(arr){
return arr.reduce((prev,curr)=>prev+curr)
}
- forEach遍历
function sum(arr){
let s=0
arr.forEach((val)=>s+=val)
return s
}
- eval eval() 函数用来执行一个字符串表达式,并返回表达式的值。
function sum(arr){
return eval(arr.join('+'))
}
2. 精度问题的解决
- 0.1+0.2!==0.3
- 原因:浮点数转二进制可能出现无限循环的现象,再转为十进制时出现精度误差
- 解决方法:
- toFixed()控制小数点后位数
- 将其先转换成整数,再相加之后转回小数
- 使用es6新增的Number.EPSILON方法,这个方法表示js的最小精度,使用这个方法通常只是对0.1+0.2是否=0.3做判断,并不像前两种改变0.1+0.2的值
3.找出一个数组中出现次数超过一半的元素
三种解决方法:
- 将数组排序,排序后中间的元素就是我们要找的元素,然后再判断它出现的次数是不是超过了数组长度的一半。
- 将数组的元素存进map里,key为元素,value为个数。遍历数组的时候,判断value大小,如果value大于数组长度的一半则返回。
- 模仿快速排序的做法,当枢纽值为中间值时返回,并统计
4.数组去重
- 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))
- 双for循环,如果arr[i]===arr[j],则用splice(j,1)删掉后面的值
- 创建新的空数组,for循环,用indexOf判断新数组是否包含对应的值,不包含则push进新数组
- includes(同上)
- 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));