前言
今天我们来看看快速排序
请输入一个数组,采取快速排序的方法对数组进行升序排序
请看示例1:
示例 1 :
输入:
arr1 = [1, 6, 2, 7, 3, 8, 4, 9, 5]
输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]
示例 2 :
输入:
arr2 = [-2, 2, -3, 1]
输出: [-3, -2, 1, 2]
示例 3 :
输入:
arr3 = [2, 2, 2, 2]
输出:[2, 2, 2, 2]
解题思路
1.找到中间位置midValue(二分法思想)
2.遍历数组,小于midValue的值放在left,否则放在right(小于中间值的在左,大于中间值的在右)
3.继续递归,最后concat拼接,返回数组(递归结束条件是传入的数组为空)
方法一(使用slice)
export function quickSort2(arr : number[]): number[] {
// 递归跳出递归循坏的条件(分别建立两个数组)
// 一个存储在每次递归中小于这个中间值的数,一个存储在每次递归中大于这个中间值的数
const length = arr.length
if (length === 0) return arr
// 向上取整,取得中间值索引号
const midIndex = Math.floor(length / 2)
// 根据索引号取得中间值,记住一定要加[0],因为只是数组后面无法参与大小于逻辑判断
const midValue = arr.slice(midIndex, midIndex + 1)[0]
const left: number[] = []
const right: number[] = []
for (let i = 0; i < length; i++) {
const n = arr[i]
// 为什么一定要设置if (n < midValue)?
if (i !== midIndex) {
if (n < midValue) {
// 小于 midValue ,则放在 left
left.push(n)
} else {
// 大于 midValue ,则放在 right
right.push(n)
}
}
}
return quickSort2(left).concat(
[midValue],
quickSort2(right)
)
}
为什么上述代码中一定要设置if (n < midValue)? 如果改成
if (n < midValue) {
// 小于 midValue ,则放在 left
left.push(n)
} else if(n > midValue) {
// 大于 midValue ,则放在 right
right.push(n)
}
那么当数组元素都为一样时,left和right便不会接收到任何元素,那么最后返回的数组中,只会出现一个中间值元素
return quickSort2(left).concat(
[midValue],
quickSort2(right)
)
// [ 2 ]
方法二(使用splice)
export function quickSort1(arr: number[]): number[] {
const length = arr.length
if (length === 0) return arr
const midIndex = Math.floor(length / 2)
const midValue = arr.splice(midIndex, 1)[0]
const left: number[] = []
const right: number[] = []
// 注意:这里不用直接用 length ,而是用 arr.length 。因为 arr 已经被 splice 给修改了
for (let i = 0; i < arr.length; i++) {
const n = arr[i]
if (n < midValue) {
// 小于 midValue ,则放在 left
left.push(n)
} else {
// 大于 midValue ,则放在 right
right.push(n)
}
}
return quickSort1(left).concat(
[midValue],
quickSort1(right)
)
}
算法时间度分析
先给出我们的结论:方法一和方法二的时间复杂度基本接近,都为O(n * log(n)),我们再来逐步分析原因
1.有二分数组的算法,时间复杂度为O(log(n))
if (n < midValue) {
// 小于 midValue ,则放在 left
left.push(n)
} else if(n > midValue) {
// 大于 midValue ,则放在 right
right.push(n)
}
2.其次算法中含有一个常规for循环嵌套时间复杂度O(n)
3.因此最后的复杂度两者都为O(n * log(n))
只有推论是站不住脚的,因此我们会给出相关的性能测试结果
为什么splice()没有很明显影响性能
一般地,在算法中,都要尽量使用像slice()这种不改变原数组的方法,而非splice()这种会修改原数组的方法,但是在我们这回的算法中
- 首先,算法本身时间复杂度就高达O(n * log(n)),splice()与slice()之间的性能不同,就被过高的时间复杂度所掩盖
- 其次,splice()方法是在二分法之后进行的,二分会快速削弱数量级
- 最后,当单独比较splice()与slice()之间的性能时,会发现它们的性能差距十分明显
测试环节
性能测试
/ // 性能测试
// const arr1 = \[]
// for (let i = 0; i < 10 \* 10000; i++) {
// arr1.push(Math.floor(Math.random() \* 1000))
// }
// console.time('quickSort1')
// quickSort1(arr1)
// console.timeEnd('quickSort1') // 74ms
// const arr2 = \[]
// for (let i = 0; i < 10 \* 10000; i++) {
// arr2.push(Math.floor(Math.random() \* 1000))
// }
// console.time('quickSort2')
// quickSort2(arr2)
// console.timeEnd('quickSort2') // 82ms
通过性能测试我们可以看到,使用splice与slice方法的性能十分接近,符合我们对时间复杂度的推测
单元测试
TS本身就自带类型约束,因此我们不需要考虑数组中,有元素不为number类型的情况
import { quickSort1, quickSort2 } from './quick-sort.copy'
describe('快速排序', () => {
it('正常情况', () => {
const arr = \[1, 6, 2, 7, 3, 8, 4, 9, 5]
const res = quickSort2(arr)
expect(res).toEqual(\[1, 2, 3, 4, 5, 6, 7, 8, 9])
})
it("有负数", () => {
const arr = \[-2, 2, -3, 1]
const res= quickSort2(arr)
expect(res).toEqual(\[-3, -2, 1, 2])
})
it("数组元素一致", () => {
const arr = [2, 2, 2, 2]
const res= quickSort2(arr)
expect(res).toEqual([2, 2, 2, 2])
})
it('空数组', () => {
const res = quickSort2([])
expect(res).toEqual([])
})
})
测试所需要的环境变量:
{
"name": "interview-js-code",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest --detectOpenHandles",
"dev": "cross-env NODE_ENV=development webpack serve --config build/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js",
"build:analyzer": "cross-env NODE_ENV=production_analyzer webpack --config build/webpack.prod.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.13.14",
"@babel/preset-env": "^7.13.12",
"@types/jest": "^27.0.2",
"autoprefixer": "^10.2.5",
"babel-jest": "^27.3.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.3",
"css-loader": "^5.2.0",
"html-webpack-plugin": "^5.3.1",
"jest": "^27.3.0",
"less": "^4.1.1",
"less-loader": "^8.0.0",
"postcss-loader": "^5.2.0",
"style-loader": "^2.0.0",
"ts-loader": "^8.1.0",
"typescript": "^4.2.3",
"url-loader": "^4.1.1",
"webpack": "^5.30.0",
"webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.7.3",
"ts-jest": "^27.0.7"
}
}