js必会搜索算法

174 阅读1分钟

主要内容:

  • 顺序搜索
  • 二分搜索
  • 内插搜索
  • 随机算法

接下来,进入主题。

顺序搜索

顺序搜索(线性搜索)是最基本的搜索算法。其将每一个数据结构中的元素和我们要找的元素做比较。顺序搜索是最低效的一种搜索算法。

图示:

91529f0a577b4320e49c6decd0aa0a0b_b4394a9f98f14c98be3147fa6e8e3d53_from=pc.gif

代码实现:

/**
 * 顺序搜索
 * @param {array} array 查询数据源
 * @param {function} equalsFn 比较函数
 * @returns 符合条件的索引
 */
function linearSearch(array, equalsFn) {
	const length = array.length;
	for (let i = 0; i < length; i++) {
		if (equalsFn(array[i])) return i;
	}
	return -1;
}

const list = [
	{
		name: "孙悟空",
		age: 18,
	},
	{
		name: "唐僧",
		age: 19,
	},
	{
		name: "猪八戒",
		age: 20,
	},
	{
		name: "沙僧",
		age: 21,
	},
];

const idx = linearSearch(list, (v) => {
	return v.age === 18;
});
console.log(list[idx]);// {name: '孙悟空', age: 18}

二分搜索

二分搜索算法的原理和猜数字游戏类似,就是那个有人说“我正想着一个1~100的数”的游戏。我们每回应一个数,那个人就会说这个数是高了、低了还是对了。

这个算法要求被搜索的数据结构已排序。

图示:

d4611b7a5ddf2d8540796e9dc50f4bfe_82abee1ee7b1b12c56e4cc37a56f203b.gif

算法实现步骤:

  1. 选择数组的中间值。
  2. 如果选中值是待搜索值,那么算法执行完毕(值找到了)。
  3. 如果待搜索值比选中值要小,则返回步骤1并在选中值左边的子数组中寻找(较小)。
  4. 如果待搜索值比选中值要大,则返回步骤1并在选中值右边的子数组中寻找(较大)。

代码实现:

/**
 * 二分搜索
 * @param {array} array 查询数据源
 * @param {*} searchValue 查询值
 * @param {string} key 对象键名
 * @returns 符合条件的索引
 */
function binarySearch(array, searchValue, key) {
	let low = 0,
		high = array.length - 1;
	// low比high小时,计算得到中间项索引并取得中间项的值
	while (low < high) {
		const mid = Math.floor((low + high) / 2);
		const el = array[mid];
		const value = key ? el[key] : el;
		// 大于
		if (value > searchValue) {
            high = mid - 1;
		} else if (value < searchValue) {
            // 小于
			low = mid + 1;
		} else {
			// 等于
			return mid;
		}
	}
	// low比high大,该搜索项不存在
	return null;
}
// 已排序数组
const list = [
	{ name: "A", idx: 1 },
	{ name: "B", idx: 2 },
	{ name: "C", idx: 3 },
	{ name: "D", idx: 4 },
	{ name: "E", idx: 5 },
	{ name: "F", idx: 6 },
	{ name: "G", idx: 7 },
	{ name: "H", idx: 8 },
	{ name: "I", idx: 9 },
];

console.log(list[binarySearch(list, 2, "idx")]);// {name: 'B', idx: 2}

const data =[1,2,3,4,5,6,7,8,9]
console.log(data[binarySearch(data, 2)]);// 2

内插搜索

内插搜索是二分搜索的改良版。二分搜索总是检查中间位置上的值,而内插搜索可能会根据要搜索的值检查数组中的不同地方。

前提被搜索的数据结构已排序。

实现步骤:

  1. 使用position公式选中一个值。
  2. 如果这个值是待搜索值,那么算法执行完毕(值找到了)。
  3. 如果待搜索值比选中值要小,则返回步骤1并在选中值左边的子数组中寻找(较小)。
  4. 如果待搜索值比选中值要大,则返回步骤1并在选中值右边的子数组中寻找(较大)。

代码实现:

const Compare = {
	LESS_THAN: -1,
	BIGGER_THAN: 1,
	EQUALS: 0,
};

function defaultCompare(a, b) {
	if (a === b) {
		return Compare.EQUALS;
	}
	return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN;
}
function defaultEquals(a, b) {
	return a === b;
}
function defaultDiff(a, b) {
	return Number(a) - Number(b);
}

function lesserOrEquals(a, b, compareFn) {
	const comp = compareFn(a, b);
	return comp === Compare.LESS_THAN || comp === Compare.EQUALS;
}

function biggerOrEquals(a, b, compareFn) {
	const comp = compareFn(a, b);
	return comp === Compare.BIGGER_THAN || comp === Compare.EQUALS;
}

/**
 * 内插搜索
 * @param {array} array 查询数据源
 * @param {*} searchValue 查询值
 * @param {function} compareFn 比较大小
 * @param {function} equalsFn 是否相等
 * @param {function} diffFn 是否不同
 * @returns 符合条件的索引
 */
function interpolationSearch(
	array,
	searchValue,
	compareFn = defaultCompare,
	equalsFn = defaultEquals,
	diffFn = defaultDiff
) {
	let low = 0,
		high = array.length - 1,
		position = -1,
		delta = -1;

	while (
		low <= high &&
		biggerOrEquals(searchValue, array[low], compareFn) &&
		lesserOrEquals(searchValue, array[high], compareFn)
	) {
		delta =
			diffFn(searchValue, array[low]) / diffFn(array[high], array[low]);
		// 计算比较值的位置position。
		// 如果查找的值更接近array[high]则查找position位置旁更大的值
		// 如果查找的值更接近array[low]则查找position位置旁更小的值
		position = low + Math.floor((high - low) * delta);
		// 如果待搜索值找到了,则返回它的索引值
		if (equalsFn(array[position], searchValue)) return position;
		// 如果待搜索值小于当前位置的值,使用左边或右边的子数组重复这段逻辑
		if (compareFn(array[position], (searchValue = Compare.LESS_THAN))) {
			low = position + 1;
		} else {
			high = position - 1;
		}
	}
	// 该搜索项不存在
	return null;
}
// 已排序数组
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(data[interpolationSearch(data, 2)]); // 2

随机算法

原理: 迭代数组,从最后一位开始并将当前位置和一个随机位置进行交换。这个随机位置比当前位置小。这个算法可以保证随机过的位置不会再被随机一次(洗扑克牌的次数越多,随机效果越差)。

图示:

9c24b3863a135407f0dc2f551aa13b9c_caae93eb41813d8bdb77d0c1a5973ad8.gif

代码实现

function swap(array, a, b) {
	[array[a], array[b]] = [array[b], array[a]];
}

function shuffle(array) {
	for (let i = array.length-1; i > 0; i--) {
		const randomIdx = Math.floor(Math.random() * (i + 1));
		swap(array, i, randomIdx);
	}
	return array;
}
console.log(shuffle([1, 2, 3, 4, 5]));// [4, 2, 1, 5, 3]