前言
面试官: 你可以在白板手写一下快速排序/归并排序/二分查找算法吗?
Recursion:(函式) 中有呼叫自己 (Self Calling)
重复执行
- 重复执行一段程式,可用
- 循环 (Iteration)
- 递回 (Recursion)
Recursion算法
-
递回的要素:
- 递回关系式:找出问题共通的关系,以便反复呼叫自己
- 终止条件:递回结束的条件
-
呼叫函式前要将目前的变数值及状态 Push 到 Stack 中
- 参数值 (Parameter)
- 区域 / 暂存变数值 (Local Variable)
- 返回位址 (Return Address)
-
将控制权转移到呼叫的函式
-
呼叫函式后要将变数值及状态由 Stack 中 Pop 出来
直接递回 (Direct Recursion) | 间接递回 (Indirect Recursion) | 尾端递回 (Tail Recursion) |
---|---|---|
函式直接呼叫自己 | 函式呼叫另一函式,再由另一函式呼叫自己 | 直接递回的特例,函式在结束的前一行呼叫自己 |
- 每次找到null前:+1,不断调用(inorder(curr.left), inorder(curr.right))+1压入栈,不断+1
- 找到null后:持续与当前maximum比较
// 每次找到null前+1,找到null后就持续与当前 maximum 比较
var maxDepth = function(root) {
return inorder(root);
};
function inorder(curr){
if(curr == null){
return 0;
}
// 递归分解为左分支和右分支,直到null才回传比较
return Math.max(inorder(curr.left), inorder(curr.right))+1;
}
快速排序采用分治法 (Divide and Conquer)
排序
- 选定一个基准值 (Pivot)
- 将比基准值 (Pivot) 小的数值移到基准值左边,形成左子串列
- 将比基准值 (Pivot) 大的数值移到基准值右边,形成右子数列
分割(通过递归):将数列依基准值分成三部份
- 左子数列:比基准值小的数值
- 中子数列:基准值
- 右子数列:比基准值大的数值
function quickSort(arr) {
if(arr.length <2) return arr;
const [p, ...ary]= arr
const left = [], right = [];
ary.forEach(c => {
if(c < p) left.push(c)
else right.push(c)
})
return [...quickSort(left), p,...quickSort(right)]
}
合并排序法采用分治法 (Divide and Conquer)
分割(递归)
-
将数列对分成左子数列、右子数列
-
分别对左子数列、右子数列作上一个步骤⇒递回 (Recursive)
- 直到左子数列、右子数列被分割成只剩一个元素为止
- 将仅剩的一个元素作为递回的结果回传
-
对回传的左子数列、右子数列 依大小排列合并
-
将合并的结果作为递回的结果回传
合并:将左子数列及右子数列依大小合并成一个新的数列
- 若左子数列的数值都已填到新的数列⇒将右子数列中未填过的最小值填入新数列
- 若右子数列的数值都已填到新的数列⇒将左子数列中未填过的最小值填入新数列
- 将左子数列及右子数列中,未填过的最小值填到新的数列
/**
* postOrder searching:先分支,再排序
* @param {mergeSort (arr)} 取中间值,分支为 【左子数列】,【右子数列】,通过递回不断将资料分割 => [只要资料列只剩一个元素,就直接回传该资料列,进入Sorting()]
* @param {merge()}: 回传重新合并并排序过的资料列
* @returns
*/
function mergeSort (arr) {
if (arr.length === 1) return arr;
const splitIndex = Math.floor(arr.length / 2)
// 取得分开的【左子数列】【右子数列】
const left = arr.slice(0, splitIndex)
const right = arr.slice(splitIndex)
// 通过递回不断将资料分割
return Sorting(mergeSort(left), mergeSort(right))
}
/**
* @function 将【左子数列】【右子数列】排序 => 合并成一个数列
* @option 若【左子数列】的值都已填入新的数列 => 将【右子数列】中未填过的最小值填入新数列
* @option 若【右子数列】的值都已填入新的数列 => 将【左子数列】中未填过的最小值填入新数列
* @option 将【左子数列】【右子数列】未填过的最小值填到新的数列
* @return {Array} 排好序的数列
*/
function Sorting (left, right) {
let result = [];
// 比较【左子数列】【右子数列】的值
while (left.length && right.length) {
if (left[0] < right[0]) {
result.push(left.shift());
} else if (left[0] > right[0]) {
result.push(right.shift());
} else {
result.push(left.shift(), right.shift());
}
}
return [...result, ...left, ...right];
}
const example = [7, 8, 9, 10, 11, 12, 1, 6, 3, 2]
console.log(mergeSort(example));
搜寻的资料必须经过排序=> 二分搜索
对于已排序好的资料,利用已排序的特性来加快搜寻速度
-
Middle = ⌊(Left + Right)/2⌋, 将键值 key 与搜寻范围的中间资料 data [Middle] 作比对
- key = data [Middle]:找到
- key <data [Middle]:缩小搜寻范围 ⇒ Right = Middle-1
- key > data [Middle]:缩小搜寻范围 ⇒ Left = Middle+1
-
重复上步骤,直到找到资料或搜寻范围交叉 (找不到)
function binarySearch(data,key) {
let left = 0, right = data.length -1, middle
/**
* @param {left <= right}在左边界点小于右边界点的情况下设定中间值的位置
* @option 若寻找值和中间值刚好相同,直接回传中间值的索引位置,假如寻找值比中间值大时,让中间值后的一个元素位置指定 left,在符合条件的状况下重新执行 while loop,再次取到新的中间值,然后不断比较下去,直到找到和寻找值相同的值为止。
* @returns 若都没有找到值相同的值,回传-1
*/
while (left <= right) {
middle = Math.floor((left + right) / 2)
if (data[middle] == key) {
return middle
}else if (key > data[middle]){
left = middle + 1
}else {
right = middle - 1
}
}
return -1;
}