1.基本排序算法
1.1 冒泡排序
//比较相邻的数据,左大于右时互换
let arr = [10,8,3,2,2,4,9,5,4,3]
function bubbleSort(arr){
let i = arr.length;
while(i>0){
//两两比较交换,保证最大的值下沉到最后
for(let j=0;j<=i-1;j++){
if(arr[j] > arr[j+1]){
let temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
i--
}
}
//时间复杂度 O(n2),空间复杂度O(1)
1.2 选择排序
//选出最小的放在数组第一个位置,依次...
function selectionSort(arr){
let min,temp;
let len = arr.length;
let i = 0;
while(i<len-1){
min = i
//找出最小的值
for(let j=i+1;j<len;j++){
if(arr[min] > arr[j]){
min = j
}
}
//将最小的值放在当前位置
let temp = arr[i]
arr[i] = arr[min]
arr[min] = temp
i++
}
}
//时间复杂度O(n2),空间复杂度O(1)
1.3 插入排序
//从第二个值开始,比较当前值和上一个值的大小如果大于,当前值左移,直到找道插入位置或循环到左侧
function insertionSort(arr){
let temp,j;
for(let i=1;i<arr.length;i++){
temp = arr[i];
j = i
while(j>0 && arr[j-1] >= temp){
arr[j] = arr[j-1]
j--
}
arr[j] = temp
}
}
//时间复杂度O(n2),空间复杂度O(1)
1.4 希尔排序
在插入排序的基础上做了很大的改善,首先比较距离较远的元素,遍历之后元素距离减小。通过定义一个间隔序列来表示在排序过程中进行比较的元素之间有多远的间隔。多次插入排序会打乱其稳定性,所以shell排序是不稳定的。
shell排序的间隔序列可以进行动态计算
//计算间隔
function shellSort(arr){
let N = arr.length;
let h = 1;
while(h<N/3){
h = 3*h+1
}//1,4,13,40...
while(h>=1){
for(let i=h;i<N;i++){
let j = i;
while(j>=h&&arr[j-h]>arr[j]){
let temp = arr[j-h]
arr[j-h] = arr[j]
arr[j] = temp
j -= h
}
}
h = (h-1)/3
}
}
//时间复杂度O(n1.3-2),空间复杂度O(1)
1.5 归并排序
归并排序是将排好的子序列合并成大的有序序列,归并排序是稳定的排序。
一般来说归并排序可以使用递归来实现,但是在JS总递归深度太深了,所以还是采用非递归的方法。
首先分解为有序子序列,在进行合并时需要开辟新的空间作为临时容器。
function mergeSort(arr){
if(arr.length<2) return
let len = arr.length;
let step = 1;//步长
let left,right;
while(step<len){
left = 0;
right = step;
while(right + step <= len){
mergeArr(arr,left,right,step);
left = right + step;
right = left + step;
}
if(right < len){
mergeArr(arr,left,right,step);
}
step *= 2
}
}
function mergeArr(arr,left,right,step){
// 右边界检测
let end = right + step;
if(right + step > arr.length) {
end = arr.length
}
//左右数组
let leftArr = arr.slice(left,left+step);
leftArr.push(Infinity)//边界
let rightArr = arr.slice(right,end);
rightArr.push(Infinity)//边界
let l = 0,r = 0;//左右指针
for(let i=left;i<end;i++){
if(leftArr[l] < rightArr[r]){
arr[i] = leftArr[l];
l++
}else{
arr[i] = rightArr[r];
r++
}
}
}
//时间复杂度O(nlogn),空间复杂度O(n)
1.6 快速排序
通过递归将数据依次分解为包含较小元素和较大元素的不同子序列,分而治之。
适用于大型数据集合,小数据集时性能反而会下降。快速排序是不稳定的。
//选择基准值,分为大数组和小数组,通过递归
function quickSort(arr){
if(arr.length ==0) return []
let great = [];
let less = [];
let baseLine = arr[0];
for(let i=1;i<arr.length;i++){
if(arr[i] > baseLine){
great.push(arr[i])
}else{
less.push(arr[i])
}
}
return quickSort(great).concat(baseLine,quickSort(less))
}
//时间复杂度O(nlogn),空间复杂度O(n)
1.7 sort
在旧版V8引擎中,sort函数实现为在数组长度小于10位时,调用的是插入排序,超过10位,使用的是快速排序。快速排序是不稳定。
在新版的V8引擎中,快速排序被舍弃,采用了一种混合排序的算法TimSort,在数据量小的子数组中使用插入排序,再使用归并排序将有序的子数组进行合并排序,时间复杂度O(nlogn)。源码地址
具体实现步骤为:
1.判断数组长度,小于2直接返回,不排序。
2.开始循环。
3.找出一个有序子数组,我们称之为“run”,长度为 currentRunLength 。
4.计算最小合并序列长度 minRunLength (这个值会根据数组长度动态变化,在32~64之间)。
5.比较 currentRunLength 和 minRunLength ,如果 currentRunLength >= minRunLength ,否则采用插入排序补足数组长度至 minRunLength ,将 run 压入栈 pendingRuns 中。
6.每次有新的 run 被压入 pendingRuns 时保证栈内任意3个连续的 run(run0, run1, run2)从下至上满足run0>run1+run2 && run1>run2 ,不满足的话进行调整直至满足。
7.如果剩余子数组为0,结束循环。
8.合并栈中所有 run,排序结束。
2.检索算法
2.1顺序查找
暴力查找,按照列表顺序线性查找
function seqSearch(arr,data){
for(let i=0;i<arr.length;i++){
if(arr[i] == data){
return true
}
}
return false
}
2.2 二分查找
如果数组是有序的,二分查找就比顺序查找更有效。
function bindSearch(arr,data){
let upper = arr.length-1;
let lower = 0;
while(lower <= upper){
let mid = Math.floor((upper+lower)/2);
if(arr[mid] < data){
lower = mid + 1
}else if(arr[mid] > data){
upper = mid - 1
}else{
return mid
}
}
return -1
}