搜索算法
我们会发现 之前学习的 数据结构与算法 中的 BinarySearchTree 类的 search 方法(二叉搜索树中 )以及 LinkedList 类的 indexOf 方法( 链表中 )等, 都是搜索算法。当然,它们每一个都是根据其各自的数据结构来实现的。所以,我们已经熟悉两 个搜索算法了,只是还不知道它们“正式”的名称而已。
- 顺序搜索
顺序或线性搜索是最基本的搜索算法。它的机制是,将每一个数据结构中的元素和我们要找 的元素做比较。顺序搜索是最低效的一种搜索算法。
import { DOES_NOT_EXIST } from '../models/const'
export function sequentialSearch<T>(arr: T[], value: T) {
for (let i = 0; i < arr.length; i++) {
if (arr[i] === value) {
return i
}
}
return DOES_NOT_EXIST
}
DOES_NOT_EXIST 是一个常量 = -1 表示 没有找到该值
假定有数组[5, 4, 3, 2, 1]和待搜索值 3,下图展示了顺序搜索的示意图。
- 二分搜索
二分搜索算法的原理和猜数字游戏类似,就是那个有人说“我正想着一个 1~100 的数”的 游戏。我们每回应一个数,那个人就会说这个数是高了、低了还是对了。
这个算法要求被搜索的数据结构已排序。以下是该算法遵循的步骤。
(1) 选择数组的中间值。
(2) 如果选中值是待搜索值,那么算法执行完毕(值找到了)。
(3) 如果待搜索值比选中值要小,则返回步骤 1 并在选中值左边的子数组中寻找(较小)。
(4) 如果待搜索值比选中值要大,则返回步骤 1 并在选种值右边的子数组中寻找(较大)。
import { DOES_NOT_EXIST } from '../models/const'
export function binarySearch(arr: number[], value: number) {
//二分查找
let low = 0
let high = arr.length - 1
while (low <= high) {
const mid = Math.floor((low + high) / 2)
const element = arr[mid]
if (element === value) {
return mid
}
if (value > element) {
low = mid + 1
} else {
high = mid - 1
}
}
return DOES_NOT_EXIST
}
给定下图所示数组,让我们试试搜索 2。这些是算法将会执行的步骤。
- 内插搜索
内插搜索是改良版的二分搜索。二分搜索总是检查 mid 位置上的值,而内插搜索可能会根 据要搜索的值检查数组中的不同地方。
这个算法要求被搜索的数据结构已排序。以下是该算法遵循的步骤:
(1) 使用 position 公式选中一个值;(此公式的证明更多与数学相关)
(2) 如果这个值是待搜索值,那么算法执行完毕(值找到了);
(3) 如果待搜索值比选中值要小,则返回步骤 1 并在选中值左边的子数组中寻找(较小);
(4) 如果待搜索值比选中值要大,则返回步骤 1 并在选种值右边的子数组中寻找(较大)。
import { DOES_NOT_EXIST } from '../models/const'
export function interpolationSearch(arr: number[], value: number) {
let low = 0
let high = arr.length - 1
let position = -1
let delta = -1
while (low <= high && value >= arr[low] && value <= arr[high]) {
//delta 与position 是按照公式来计算的
delta = (value - arr[low]) / (arr[high] - arr[low])
position = low + Math.floor((high - low) * delta)
//剩下的与二分搜索思想一致。
if (arr[position] === value) {
return position
}
if (arr[position] < value) {
low = position + 1
} else {
high = position - 1
}
}
return DOES_NOT_EXIST
}
随机算法
之前我们学习了如何将一个数组进行排序以及怎样在排序后的数组中搜索元素。不过还有 一种场景是需要将一个数组中的值进行随机排列。现实中的一个常见场景是洗扑克牌。
- Fisher-Yates 随机
这个算法由 Fisher 和 Yates 创造,并由高德纳(Donald E. Knuth)在《计算机程序设计艺术》 系列图书①中推 , 是 随机数组的一种最有名的算法。
它的含义是迭代数组,从最后一位开始并将当前位置和一个随机位置进行交换。这个随机位 置比当前位置小。这样,这个算法可以保证随机过的位置不会再被随机一次(洗扑克牌的次数越 多,随机效果越差)。
export function shuffle(arr: number[]) {
for (let i = arr.length - 1; i > 0; i--) {
let index = Math.floor(Math.random() * (i + 1)) // 随机数满足区间 [0,i+1)
;[arr[i], arr[index]] = [arr[index], arr[i]] //交换两者值
}
return arr
}
为什么要逆序迭代 数组 ?
因为 维持 random 的区间 在 [0 , n ) 是最简单的,这样一定会和 arr [0 ~ n ]之间的值交换 所以要倒序 , 保证 已经随机过的值 不再随机.
下图展现了该算法的操作。