🥳每日一练-快速排序和优化-JS简单版

今天分享的内容是一种排序算法--插入排序。插入排序是一个基本难度的算法,相较于其他的更快的算法,像快速排序算法,有个优点,就是稳定。稳定是一个很重要的性质。

如果两个数值相等的元素,在排序之后,这两个元素的相对位置不会发生变化,那么这个算法就是稳定的。这是很重要的。

假设现在有这么一个场景,现在多条学生成绩的数据,最初是按照学生的姓名首字母排序的,现在按照学生的成绩排序,要求相同成绩的学生之间,依旧按照字母排序。如果排序不是稳定的,那么这种要求就难以保证,除非相同成绩的学生之间再来一次排序😄

什么是插入排序

插入排序是一种最简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 插入排序和冒泡排序一样,也有一种优化算法,叫做拆半插入。

具体来说,插入排序的实现思路如下:

  1. 将第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
  2. 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。
  3. 如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。(稳定性的考量)

实现快速排序

javascript 复制代码
const data = [23, 54, 2, 23, 1, 2, 65, 343, 9, 213];

const insertSort = (array) => {
    for (let i = 1; i < array.length; i++) {
        if (array[i - 1] > array[i]) { // 如果前一个元素大于当前元素
            const temp = array[i]; // 保存当前元素
            let j = i - 1;
            for (; j >= 0 && array[j] > temp; j--) { // 从右向左搜索放置项目的正确位置
                array[j + 1] = array[j]; // 将前一个元素复制到后一个元素上
            }
            array[j + 1] = temp; // 找到当前元素应处于的位置
        }
    }
};

这是一段插入排序的代码。插入排序就是从选择其中一个数,插入前面的有序数组。

代码中从数组中的第二个位置开始遍历,往前面的有序数组进行插入。假设遍历到了第 k 个, 第 k-1 个元素比第 k 个大,就需要发生数据的挪动。接下来的 for 循环就是找前面所有的比第 k 个元素大的元素,有一个算一个,全部往后移一位。虽然会覆盖第 k 个位置,不过没关系,第 k 个元素用 temp 保存了起来。等找完了,就将第 k 个元素,放到前面空出来的位置。这样有序数组的长度就增加了一。

接着 i++,继续遍历下一个元素,即将 k+1 个元素插入到前面的有序数组中

等将所有的元素都插入到有序数组中,array 数组自然就是有序的了

测试代码:

javascript 复制代码
insertSort(data);
console.log(data);
// [
//    1,  2,  2,   9,  23,
//   23, 54, 65, 213, 343
// ]

测试成功,输出结果是有序的

有个关键的地方需要注意:在判断array[j] > temp 中,>不能写成>=, 如果加了=,就会使得大小相同的元素的相对位置会发生变化,算法就不稳定了

优化

在插入时,需要找到第 k 个元素插入的位置。查找的对象是有序的,那就可以在此做文章了。

查找有序的数组,可以使用折半查找的算法。折半查找,又称二分查找,实现思路如下:

  1. 初始状态下,搜索区域为整个查找表,用 left 记录搜索区域内第一个元素的位置,用 right 记录搜索区域内最后一个元素的位置。
  2. 借助 ⌊ (left + right) / 2⌋ 公式,找到搜索区域内的中间元素。
  3. 如果中间元素等于目标元素,则查找成功;否则,根据中间元素与目标元素的大小关系,确定新的搜索区域,重复步骤 2 直到查找成功或失败。
javascript 复制代码
const halfFind = (array, value, left, right) => {
	if (left > right) return left;
	const mid = Math.floor((left + right) / 2);
	if (value < array[mid]) {
		right = mid - 1;
		return halfFind(array, value, left, right);
	}
	if (array[mid] <= value) {
		left = mid + 1;
		return halfFind(array, value, left, right);
	}
};

const insertSortHalf = (array) => {
	for (let i = 1; i < array.length; i++) {
		if (array[i - 1] > array[i]) {
			const temp = array[i];
			const mid = halfFind(array, temp, 0, i - 1);
			for (let j = i - 1; j >= mid; j--) {
				array[j + 1] = array[j];
			}
			array[mid] = temp;
		}
	}
};

这里定义了insertSortHalf,接收一个数组作为参数,函数中会利用折半查找对数组 array 进行排序。

排序的过程和上面一样,不同的是找插入位置的过程,假设已经利用halfFind找到了,就将 mid`` 到 i-1 的所有元素,都往后移动一位,然后将 第 k 个元素放到 mid 的位置上。

halfFind,作用是找到第 k 个元素插入的位置。过程很简单,代码也很简单,就不啰嗦了。和上面描述折半查找的步骤不同的,我们需要找的是**最后一个大于第 k 个元素的位置,而不是等于第 k 个元素的位置。**所以当array[mid] == value 时,我仍然对halfFind 进行递归调用,直到left > right,才返回 left

这个地方有些难理解,可以自己举个例子

这是和一般折半查找不同的地方需要注意。

算法复杂度分析

对于一个混乱序列的数组,遍历的每个数都要查找在前面有序数组的位置,查找和挪动元素的复杂度 n, 又因为有 n 个元素,所以插入排序的复杂度是 O(n^2);对于一个有序(升序)数组,查找和挪动元素的复杂度 1,又因为有 n 个元素,所以插入排序的复杂度是 O(n);对于一个有序(降序)的数组,是最差的情况,插入排序的复杂度是 O(n^2)

所以平均而言,插入排序的复杂度就是O(n^2)

总结

这篇文章分享了一个基础的排序算法--插入排序,讲解了两种版本的代码,两种版本都要注意算法的稳定性。文末分析了算法的复杂度,虽然介绍地有些粗糙,但大致过程就是这样了

你觉得这篇文章怎么样?我每天都会分享一篇算法小练习,喜欢就点赞+关注吧

相关推荐
Omics Pro6 分钟前
首个!外源天然产物综合性代谢图谱
数据库·人工智能·算法·机器学习·r语言
voidmort14 分钟前
3. 微调(Fine-tuning)与强化学习(RL)的核心思想
python·深度学习·算法
人道领域1 小时前
【LeetCode刷题日记】669.修剪二叉搜索树
开发语言·python·算法
2401_868534781 小时前
【无标题】
数据结构·r语言
Mr. zhihao2 小时前
Redis五大高级数据结构:原理-场景-底层-横向对比
数据结构·redis
QiLinkOS2 小时前
【从实验室到商业战场:发明专利如何重塑科技与企业的共生生态】
大数据·c语言·数据结构·c++·人工智能·单片机·算法
如此这般英俊2 小时前
手撕Claude Code—第一章 agent-loop
数据结构·人工智能·语言模型·自然语言处理
小白兔奶糖ovo3 小时前
【Leetcode】231. 2的幂
linux·算法·leetcode
丷丩3 小时前
MapLibre GL JS第19课:实时更新要素
前端·javascript·gis·map·mapbox·maplibre gl js
xiaoxiaoxiaolll3 小时前
《Light: Science & Applications》合并BIC实现80倍阈值单模运行:超紧凑光子晶体激光器新突破
人工智能·算法·机器学习