🥳每日一练-快速排序和优化-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)

总结

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

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

相关推荐
铁皮饭盒2 小时前
Bun 哪比 Node.js 快?
javascript·后端
JieE2129 小时前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
candyTong12 小时前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构
_柳青杨16 小时前
深入理解 JavaScript 事件循环
前端·javascript
Jack2017 小时前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法
小小杨树19 小时前
读懂色彩:拍照调色不再难
算法·计算机视觉·配色
大家的林语冰1 天前
ES5 凉凉,Babel 8 正式发布,默认不再编译为 ES5 和 CJS......
前端·javascript·前端工程化
weedsfly1 天前
异步编程全景与事件循环——彻底搞懂 JS 执行机制
前端·javascript
用户1733598075371 天前
纯前端 PDF 数字签名实战:Vue 3 + pdf-lib 在浏览器里完成签名嵌入
前端·javascript
JieE2121 天前
LeetCode 226. 翻转二叉树|JS 递归超详细拆解,二叉树入门经典题
javascript·算法