js排序的时间复杂度_JavaScript实现十大排序算法
一 : 冒泡排序
人们开始学习排序算法时,通常都先学冒泡算法,因为它在所有排序算法中最简单。然而, 从运行时间的角度来看,冒泡排序是最差的一个,接下来你会知晓原因
冒泡排序比较所有相邻的两个项,如果第一个比第二个大,则交换它们。元素项向上移动至 正确的顺序,就好像气泡升至表面一样,冒泡排序因此得名。
export function bubbleSort(arr: number[]) {
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
;[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
}
}
}
return arr
}
冒泡排序每一轮(外层循环) 会选出一个最小 或者最大的数组 放到数组最后
所以N 个 数字 只用 选N -1 次 =>外层循环 i
内存循环 两两相比, 只用 比较 N - 1次 ,但是 外层循环已经找到的最值不用比较
所以 => 内层循环 j < arr.length - i -1
一幅图来描述 冒泡的工作流程
二 :选择排序
选择排序算法是一种原址比较排序算法。 他解决了冒泡 交换次数过多的毛病,在冒泡排序中 需要交换 O(N^2) 次 但 选择排序中 只用交换 O(N)次
选择排序大致的思路是找到数据结构中的最小值并 将其放置在第一位,接着找到第二小的值并将其放在第二位,以此类推。
export function selectionSort(arr: number[]) {
let min = 0
for (let i = 0; i < arr.length - 1; i++) {
min = i
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
min = j
}
}
if (i !== min) {
;[arr[i], arr[min]] = [arr[min], arr[i]]
}
}
return arr
}
选择排序的代码执行过程 如下图
三 :插入排序
插入排序的思想十分的重要, 学会了他你才能学习 希尔排序,而 希尔排序 又是排序算法 历史上的一个转折点 他打破了 排序算法 时间复杂度平均不会低于 O(N^2) 的理论。
插入排序每次排一个数组项,以此方式构建最后的排序数组。假定第一项已经排序了。接着, 它和第二项进行比较——第二项是应该待在原位还是插到第一项之前呢?这样,头两项就已正确 排序,接着和第三项比较(它是该插入到第一、第二还是第三的位置呢),以此类推
export function insertionSort(arr: number[]) {
for (let i = 1; i < arr.length; i++) {
let j = i
let temp = arr[i]
//插入操作 while (j > 0 && arr[j - 1] > temp) {
arr[j] = arr[j - 1]
j--
}
arr[j] = temp
}
return arr
}
插入排序的 过程 假设数组 [ 3 ,5 , 1, 4, 2]
一开始 把 3 当作局部有序 , 把 5 拎出来 和 局部有序 数列比较 并插入
// 第一次 : 5 > 3 合理 进入下一次循环
// 第二次 : 把 1 拎出来 插入 , 局部有序变为 : [ 3,5 ] 并比较 数组变为[1,3,5,4,2]
//第三次 : 把 4拎出来 插入 , 局部有序 变为 [1,3,5 ] 数组变为 [ 1,3,4,5, 2 ]
// 第四次 : 把2拎出来插入 局部有序 变为 [1,3,4,5 ] 数组变为 [ 1,2,3,4,5
//结束循环
四 : 希尔排序
希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”。
他是 第一个 将 排序算法 复杂度降低 到 O (N^2)之下的 . 但是他的复杂度 至今未被证明 猜测 效率 为 O (N^1.3)
他就是插入排序的一种进阶。只要你会了插入排序 你就能写出希尔排序
//复杂度为被证明,猜测 为 O (N ^1.3)export function shellSort(arr: number[]) {
//获取增量 let gap = Math.floor(arr.length / 2)
//增量等于1 即为 插入排序 原始。 一定会将数组排好 这个时候结束循环 while (gap >= 1) {
//进行插入排序 for (let i = gap; i < arr.length; i++) {
let j = i
let temp = arr[i]
while (j > gap - 1 && arr[j - gap] > temp) {
arr[j] = arr[j - gap]
j -= gap
}
arr[j] = temp
}
//缩小增量 gap = Math.floor(gap / 2)
}
return arr
}
注意: 插入排序 就是 进行 gap(增量) 为 1 时候的操作 ,
在希尔排序 中 初始增量 为数组长度一半 , 并依次缩小 .这是原版的做法
事实上 这个 增量 有别的计算方式,可以让 希尔排序 效率更高 接近O(N^4/5) 但是他不是我们学习 希尔排序 的重点 , 关于增量的证明 与改进 更多是数学方面的知识 我们基于面试 能够实现基本的希尔排序 就可以了 .
五 : 快速排序
快速排序也许是最常用的排序算法了。它的复杂度为 O(nlog(n)),且性能通常比其他复杂度 为 O(nlog(n))的排序算法要好。 快速排序 使用分而治之的方法 .
快速排序也是二十世纪 十大算法之一,感兴趣的朋友可以了解一下
我们重点学习 快速排序。
我们先看下阮一峰老师给出的快速排序算法:
function quickSort(arr) {
if (arr.length <= 1) return arr
let left = [],
right = []
//将中间值 取除 并在arr中移除 let middle = arr.splice(Math.floor(arr.length / 2), 1)[0]
arr.forEach((el) => (el >= middle ? right.push(el) : left.push(el)))
return quickSort1(left).concat(middle, quickSort1(right))
}
例如 数组 [ 5, 4 ,3, 2 ,1 ] 先取出 中间值 3 => [5,4,2,1]
然后 将数组一分为二 => 小于 3 的在左边 大于 3 的在右边
=> left [ 2 , 1 ] right[ 5, 4]
=>然后将 左右 数组 递归
=> [2,1] => middle :2 left[ 1 ] right [ ]
=> [5,4 ] => middle :5 left[ 4 ] right [ ]
=>最后 依次拼接数组 left+middle +right => [1,2,3,4,5]
注意: 上面的算法 使用了 splice 效率 非常的差,只是基于快速排序的思想, 写出的 比较容易理解的代码 我们来看下效率 更好 的快排 是如何做的
export function quickSort(arr: number[]) {
quick(arr, 0, arr.length - 1)
return arr
}
function quick(arr: number[], left: number, right: number) {
if (arr.length > 1) {
let index = partition(arr, left, right)
// 两半局部有序分开 递归排序 分而治之 if (left < index - 1) {
quick(arr, left, index - 1)
}
if (index < right) {
quick(arr, index, right)
}
}
}
function partition(arr: number[], left: number, right: number) {
//将数组 以pivot 为标准 划分为 两半 局部有序的 let pivot = arr[Math.floor((left + right) / 2)]
let i = left
let j = right
while (i <= j) {
while (arr[i] < pivot) {
i++
}
while (arr[j] > pivot) {
j--
}
if (i <= j) {
swap(arr, i, j)
i++
j--
}
}
return i
}
function swap(arr: number[], i: number, j: number) {
;[arr[i], arr[j]] = [arr[j], arr[i]]
}
这种实现思路 是利用 i j 两个指针 , 将 数组 以 pivot(枢纽) 分割成两半
左边 小于 pivot 右边大于pivot ,然后递归 分而治之 .
例如 数组 [ 3 , 6 , 5 , 7 , 4 , 1 , 8 , 2 , 9]
选取pivot : 4 , left : 3 right : 9
//left 会一直找到 >= 4 时停住
//right 会一直找到<= 4 停住
=> left =>6 , right => 2 这个时候 swap
[3,2,5,7,4,1,8,6,9] 继续循环
=>left =>5 , right =>1 调用 swap
[ 3,2,1,7,4,5,8,6,9]
=>left => 7 , right =>4 调用 swap
[3,2,1,4,7,5,8,6,9] 退出循环 ( i > j )
你会发现 在 4 的左边 都比 4 小
在 4 的右边都比 4 大 ,这个时候以4 为枢纽分开 递归即可
六 归并排序
归并排序也是一个可以实际使用的排序算法。 归并排序性能不错,其复杂度为 O(nlog(n))。
// JavaScript 的 Array 类定义了一个 sort 函数(Array.prototype.sort)用以 排序 JavaScript 数组(我们不必自己实现这个算法)。ECMAScript 没有定义用哪 个排序算法,所以浏览器厂商可以自行去实现算法。例如,Mozilla Firefox 使用归并排序作为 Array.prototype.sort 的实现,而 Chrome(V8 引擎)使用了 一个快速排序的变体
一张图 足以 说明 归并排序 的流程
export function mergeSort(arr: number[]) {
//分而治之 //先分 if (arr.length > 1) {
//将数组 分成两半 递归进行 直到 数组长度 小于等于 1 let middle = Math.floor(arr.length / 2)
let left = mergeSort(arr.slice(0, middle))
let right = mergeSort(arr.slice(middle, arr.length))
//然后 合并排序 arr = merge(left, right)
}
//将结果返回 return arr
}
function merge(left: number[], right: number[]) {
//将左右两个数组 合并 排序 //i 指向 左数组 j 指向右数组 let i = 0
let j = 0
//将结果有序的push 进 result 中 let result = []
while (i < left.length && j < right.length) {
//排序 result.push(left[i] < right[j] ? left[i++] : right[j++])
}
//合并 =>将 左右 数组 剩余的部分 concat return result.concat(i < left.length ? left.slice(i) : right.slice(j))
}
七 : 计数排序
计数排序是一个分布式排序 , 它是用来排序整数的优秀算法(它是一个整数排序算法),时间复杂度为 O(n+k),其中 k 是 临时计数数组的大小;但是,它确实需要更多的内存来存放临时数组。
这个排序 算法 的思路 就是 计数
=> 将每个数 当作索引 存进 数组(count)中,如果重复出现 则 索引对应的值 ++ ,
这个时候遍历我们的 的数组(count) ,他就排好序了。非常的简单
export function countingSort(arr: number[]) {
let count = [] //计数 let result = [] //结果 arr.forEach((el) => {
//将每个数字 以索引 存入 count中 if (!count[el]) {
count[el] = 0
}
count[el]++
})
count.forEach((el, i) => {
//将count 取出 if (el && el > 0) {
for (let j = 0; j < el; j++) {
result.push(i)
}
}
})
return result
}
你会发现 这个算法 只能存储 整数 且 需要消耗大量的空间, 但是在 有些时候利用此算法排序 非常的 好用 , 比如 按照年龄排序, 按照 成绩排序 .
即 量大 范围小(年龄 0 岁~ 150岁) 时 非常好用的算法 ,因为他的效率高 , 因为范围小 空间消耗也是 可以接受的。
八 : 基数排序
基数排序也是一个分布式排序算法,它是根据关键字排序的。
什么是关键字排序 ? 我们就按数组排序 为例 ,他先比较 每个数组的个位 按 计数排序处理, 再比较数组的 十位. 计数排序 处理 ..... 直到比较完最高位 就排好序了.
其实也就是 重复 进行我们的计数排序 ,来节省 空间的浪费
export function radixSort(arr: number[]) {
if (arr.length < 2) {
return arr
}
//找到最大值 let max = -Infinity
arr.forEach((el) => (el > max ? (max = el) : null))
//求他的位数 let digit = (max + '').length
//循环计数排序 let count = []
for (let i = 0; i < digit; i++) {
//按 个位排序, 十位排序 ,百位排序 .... arr.forEach((el) => {
let str = el + ''
let temp = +str[str.length - 1 - i]
if (isNaN(temp)) {
temp = 0
}
if (Array.isArray(count[temp])) {
count[temp].push(el)
} else {
count[temp] = [el]
}
})
arr = []
count.forEach((el) => {
if (Array.isArray(el)) {
el.forEach((e) => {
arr.push(e)
})
}
})
count = []
}
return arr
}
九 :堆排序
这个我在二叉堆里介绍过了。
十 : 桶排序
桶排序(也被称为箱排序)也是分布式排序算法,它将元素分为不同的桶(较小的数组), 再使用一个简单的排序算法,例如插入排序(用来排序小数组的不错的算法),来对每个桶进行 排序。然后,它将所有的桶合并为结果数组。
其实 上面的计数排序 基数排序 应该都属于 我们的桶排序,
但是桶排序 实际用的并不多 , 我们掌握 计数 和基数 两个变体 就好了.
感兴趣的可以去单独了解桶 排序 , 看看 他为什么 用的不多 。
十一 : 总结
然后我又基于 随机的数组 (十万个随机数)
,分别进行了上面的 排序算法 我们来看下他的耗时
你可以发现 冒泡 简直不能用, 等了24秒
最快 的计数排序 6 ms 就搞定了。。 但是耗费了大量的空间
官方自带的sort 在大部分情况下 速度还是慢于 我们的 快速排序
最后贴上单元测试。
import { createRandomArray } from '../util'
import { bubbleSort } from './01 冒泡排序'
import { selectionSort } from './02 选择排序'
import { insertionSort } from './03 插入排序'
import { shellSort } from './04 希尔排序'
import { quickSort } from './05 快速排序'
import { mergeSort } from './06 归并排序'
import { countingSort } from './07 计数排序'
import { radixSort } from './08 基数排序'
describe('排序算法测试', () => {
test('冒泡排序测试', () => {
const arr = createRandomArray(10)
const sortArr = Array.from(arr).sort((a, b) => a - b)
expect(bubbleSort(arr)).toEqual(sortArr)
})
test('选择排序测试', () => {
const arr = createRandomArray(10)
const sortArr = Array.from(arr).sort((a, b) => a - b)
expect(selectionSort(arr)).toEqual(sortArr)
})
test('插入排序测试', () => {
const arr = createRandomArray(10)
const sortArr = Array.from(arr).sort((a, b) => a - b)
expect(insertionSort(arr)).toEqual(sortArr)
})
test('希尔排序测试', () => {
const arr = createRandomArray(10)
const sortArr = Array.from(arr).sort((a, b) => a - b)
expect(shellSort(arr)).toEqual(sortArr)
})
test('快速排序', () => {
const arr = createRandomArray(10)
const sortArr = Array.from(arr).sort((a, b) => a - b)
expect(quickSort(arr)).toEqual(sortArr)
})
test('归并排序', () => {
const arr = createRandomArray(10)
const sortArr = Array.from(arr).sort((a, b) => a - b)
expect(mergeSort(arr)).toEqual(sortArr)
})
test('计数排序', () => {
const arr = createRandomArray(10)
const sortArr = Array.from(arr).sort((a, b) => a - b)
expect(countingSort(arr)).toEqual(sortArr)
})
test('基数排序', () => {
const arr = createRandomArray(10)
const sortArr = Array.from(arr).sort((a, b) => a - b)
expect(radixSort(arr)).toEqual(sortArr)
})
})
创建随机数组
'../util'
export function createRandomArray(size: number) {
const array = []
for (let i = 0; i < size; i++) {
array.push(Math.floor(Math.random() * 100))
}
return array
}
js排序的时间复杂度_JavaScript实现十大排序算法相关推荐
- JS的十大经典算法排序
更正(2018/5/31):如果看的是原文章的话,注意希尔排序少一个等号,在本博中,我已经添加上了,希尔排序(更正后)及以前的经测试都是正确的,后面的我正找时间持续研究验证中--(过程可能有点慢) 更 ...
- JS 实现十大排序算法
文章目录 前言 零.十大排序 一.冒泡排序(bubbleSort) 二.选择排序(selectionSort) 三.插入排序(insertSort) 四.希尔排序(shellSort) 五.归并排序( ...
- 十大排序算法JS实现以及复杂度分析
文章目录 十大排序算法概述 应用场景 代码实现 一.冒泡排序 二.选择排序 三.插入排序 四.希尔排序 五.归并排序 六.快速排序 七.堆排序 八.计数排序 九.桶排序 十.基数排序 十大排序算法概述 ...
- 十大排序算法(Java)
文章目录 十大排序算法(Java) 一.冒泡排序(Bubble Sort) 二.选择排序(Selection Sort) 三.堆排序(Heap Sort) 四.插入排序(Insertion Sort) ...
- 这或许是东半球分析十大排序算法最好的一篇文章
作者 | 不该相遇在秋天 转载自五分钟学算法(ID:CXYxiaowu) 前言 本文全长 14237 字,配有 70 张图片和动画,和你一起一步步看懂排序算法的运行过程. 预计阅读时间 47 分钟,强 ...
- 「干货总结」程序员必知必会的十大排序算法
点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:硬刚一周,3W字总结,一年的经验告诉你如何准备校招! 个人原创100W+访问量博客:点击前往,查看更多 绪论 身 ...
- 「归纳|总结」程序员必知必会的十大排序算法
微信搜一搜「bigsai」关注这个有趣的程序员 新人原创公众号,求支持一下!你的点赞三连肯定对我至关重要! 文章已收录在 我的Github bigsai-algorithm 欢迎star 本文目录 绪 ...
- 归并排序执行次数_十大排序算法,看这篇就够了
排序算法分类[1][2] 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序. 非比较类排序:不通过比较来决定元素间的相对次序,它可以 ...
- C 数据结构之十大排序
C 数据结构之十大排序 排序算法是<数据结构与算法>中最基本的算法之一. 排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳 ...
最新文章
- CPU 是如何理解 01 二进制的?
- 安装多个java后,java版本不对
- 【CodeForces - 151D】Quantity of Strings (字符串问题,思维推导,有坑)
- java 二叉树特点_疯狂java笔记之树和二叉树
- php-fpm的pool php-fpm慢执行日志 open_basedir php-fpm进程管理
- java对mysql的简单操作——增删改查的总结
- python2.7更新python3.6_python2.7升级到python3.6注意事项
- Jmeter查看结果树
- iPhone/iPad各种文件路径详解 帮助了解自己的iphone和ipad
- 电池SOH仿真系列-基于LSTM神经网络的电池SOH估算方法
- 超越宝典汽配汽修管理系统——“维修业务”模块功能实现
- Ubuntu20.04安装搜狗拼音
- 2022-2027年中国玩偶行业市场全景评估及发展战略规划报告
- Repeated DNA
- Caj文件怎么转换成pdf?Caj转pdf在线转换器推荐
- 关于三通道彩色图像的存储方式理解
- 【吴刚】个人网站设计初级入门标准视频教程-吴刚-专题视频课程
- 关于node启动:File exists: node_modules\.bin\babel-doctor不成功
- Connectify中文版必不可少的wifi软件
- Puppet应用配置的工作原理
热门文章
- Spring第三天,详解Bean的生命周期,学会后让面试官无话可说!
- Python时间转换:X秒 --> 时:分:秒
- 让Excel输入内容后自动加边框的方法,及其原理深度解析
- 基于百度云人脸融合API的python实现视频人像换脸
- 微信小程序集合2(外卖+商城半成品+教育商城+外卖商城+婚庆商店+简易抽奖+外卖购物车+小天气+打卡签到+游戏社区攻略)
- 市场营销书籍推荐,这些书帮你学好营销
- MATLAB绘制泰勒图(10个以上model)
- 如何判断你用的是左脑还是右脑!
- iOS Swift 判断手机机型 已更新 至iPhone12
- mysql 取消外键关联约束