前置知识
栈 Stack
堆栈, 又称为 栈 或者堆叠, 是计算机科学中的一种数据结构, 而且这种数据结构, 只能在栈顶对数据进行 插入 和 取出(删除), 后进先出 LIFO Last in First Out
可以参考现实生活中的例子:比如弹夹,最后进去的子弹, 最先被发射出去
调用栈 Call Stack
MDN: 调用栈是解释器(比如浏览器中的 JavaScript 解释器)追踪函数执行流的一种机制。当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。
- 每调用一个函数,解释器就会把该函数添加进调用栈并开始执行。
- 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
- 当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码。
- 当分配的调用栈空间被占满时,会引发“堆栈溢出”错误
function greeting () {
sayHi()
}
function sayHi () {}
greeting()
- 当 解释器 调用 greeting 函数时, 会将 greeting 压入到栈中, 并且执行
- 当 解释器 又调用 sayHi 函数时, 会将 sayHi 压入到 栈中, 并且执行
- 等 sayHi 函数 执行完毕后, 会从 栈 中被 清除出去
- 执行 greeting 的函数
递归
所谓的递归,指的是函数定义中使用函数自身的方法, 换句话说就是 自己调用自己
小时候有一个故事: 从前有座山, 山上有座庙,庙里住着一个老和尚和小和尚, 有一天老和尚给小和尚讲故事, 故事内容是: 从前有座山, 山上有座庙,庙里住着一个老和尚和小和尚, 有一天老和尚给小和尚讲故事... 就这样无限的循环
但是 我们的递归, 它并非无休止的循环下去, 而是需要中止的。 所以在编写递归函数的时候, 得告诉它什么时候终止递归. 也就是基线条件 和 递归条件
基线条件: 告诉函数不再自我调用 递归条件: 函数自我调用
欧几里德算法
欧几里德算法 又称为 辗转相除法, 计算两个正整数的a, b的最大公约数; 比如: 计算 A = 270, B = 192 的最大公约数
-
第一步:
-
第二步:
-
第三步:
-
第四步: 那么 A = 270, B = 192 的最大公约数就是: 6
快速排序
分而治之(Divide and Conquer 简称 D&C): 将一个序列分为 较大 和 较小两个子序列, 然后递归排序两个子序列
步骤:
- 挑选 基准值
- 将序列 根据 挑选出来的 基准值, 进行 分成 较大 和 较小两个子序列
- 递归 两个子序列, 按照1-2-3的顺序 执行下去
时间复杂度
[1, 2, 3, 4, 5, 6, 7, 8, 9]
, 每次都以第一个元素 为基准值, 那么调用的高度为8次 即 ,但是 如果每次都以 中间的元素 作为 基准值, 那么 调用高度 为4次 即 , 每一层都要循环遍历, 所需的时间
最好时间复杂度:
最差时间复杂度:
图解
代码实现(Typescript版)
function quickSort (arr: Array<Number>): Array<Number> {
if (arr.length < 2) return arr // 0 或者 1个
const lessArr: Array<Number> = []
const greaterAfr: Array<Number> = []
const base = arr[0]
for (let i = 1; i < arr.length; i++) {
if (arr[i] < base) {
lessArr.push(arr[i])
} else{
greaterAfr.push(arr[i])
}
}
return [...quickSort(lessArr), base, ...quickSort(greaterAfr)]
}
const arr:Array<Number> = [8, 5, 2, 6, 9, 3, 1, 4, 0, 7]
console.log(quickSort(arr)) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]