「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战」
前言
今天我们复习一下基础排序算法
- 冒泡排序
- 插入排序
- 选择排序
一步一步进行深入,然后会讲一下面试官会如何询问
排序算法
冒泡排序
步骤
- 1.从第一个元素开始,比较每两个相邻元素,如果前者大,就交换位置
- 2.每次遍历结束,能够找到该次遍历过的元素中的最大值
- 3.如果还有没排序过的元素,继续1
2022年5月26日思考 为什么要两层循环
- 第一层循环是告诉要开启新一轮冒泡,并把i传给第二层循环
- 第二层循环的意思就是这次循环要对比几个length-i个数
动画演示
基础写法
function bubbleSort(nums: number[]) {
const length = nums.length;
// 第一层循环可以使每次把最大的值,放到数组的最后面
for (let i = 0; i < length; i++){
// 第二层值只需要遍历len-i次,因为最大的值已经到数组最后面了
// 第二层循环是用来交换的
for (let j = 1; j < length - i; j++){
if (nums[j-1] > nums[j]) {
[nums[j-1], nums[j]] = [nums[j], nums[j-1]];
}
}
}
}
- 时间复杂度:O(n^2),两个for循环,并且最多都能执行n次,所以复杂度是O(n^2)
- 空间复杂度:O(1),没有把那个变量的空间进行扩展,所以空间复杂度为O(1)
我们考虑一下,如果题目给的数组就是有序的,那么我们还要循环两遍是不是有点多余,我们可以加一个标志位,来判断是不是这个数组本身就是有序的,以此来减少时间复杂度
进阶写法
增加标志位也叫哨兵法
function betterBubbleSort(arr) {
const len = arr.length
for(let i=0;i<len;i++) {
// 区别在这里,我们加了一个标志位
let flag = false
for(let j=0;j<len-1-i;j++) {
if(arr[j] > arr[j+1]) {
[arr[j], arr[j+1]] = [arr[j+1], arr[j]]
// 只要发生了一次交换,就修改标志位
flag = true
}
}
// 若一次交换也没发生,则说明数组有序,直接放过
if(flag == false) return arr;
}
return arr
}
- 此时最好情况的时间复杂度变为O(n)
选择排序
步骤
- 1.取出未排序部分的第一个元素,遍历该元素之后的部分并比较大小。对于第一次遍历,就是取出第一个元素
- 2.如果有更小的,与该元素交换位置
- 3.每次遍历都能找出剩余元素中的最小值并放在已排序部分的最后
动画演示
写法
const selectSort = (arr) => {
let length = arr.length;
//没有运算符“<”不能应用于 JavaScript 中的类型“未定义”错误,就像您在其他类型语言中发现的那样。因此,JavaScript 将带有运算符的不兼容类型评估为 false。
for (let i = 0; i < length; i++) {
// 初始化最小值为i
let min = i;
// 寻找未排序部分的最小值坐标
for (let j = i+1; j < length; j++) {
if (arr[min] > arr[j]) {
min = j;
}
}
// 最小值坐标如果不是原坐标的话,那么交换位置
if (min !== i) {
[arr[i], arr[min]] = [arr[min], arr[i]];
}
}
return arr;
};
-
时间复杂度:O(n^2),两个for循环,时间复杂度最大为O(n^2)
-
空间复杂度:O(1)
-
和冒泡排序差别:冒泡排序是把最大的冒泡排序到后面,选择排序是把最小的选择放到最前面
插入排序
步骤
- 1.将一组待排序的数据,分成2段,一段是“已经排序”了的数据,另一段是“未排序”的数据。
- 2.每次都从“未排序”的数据中取出一个元素,将这个元素插入到“已经排序”数据中的正确的位置(可能会涉及到原有元素的移动),那么插入后,“已经排序”区段中的数据依然是有序的,
- 3.只要这样不停的循环,直到所有的“未排序”的数据都已取完,则整个排序完成。
动画演示
写法
export const insertSort = (arr) => {
// 时间复杂度为O(n^2),所以两个循环
// 此处为什么i为1,因为要分成两段,未排序的从1开始,留出一个给已排序的
for (let i = 1; i < arr.length; i++) {
// 已排序部分倒着排序
// 如果当前的比前一个大,那么进行交换
// 此处要想明白第一次交换的是哪两个,是已排序的最后一个,和未排序的第一个进行交换
for (let j = i - 1; j >= 0; j--) {
// 每次都要用前面的和后面的对比
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
};
- 时间复杂度:O(n^2),外面的循环最大为n,里面的循环最大也为n,那么时间复杂度依然是O(n^2)
- 空间复杂度:O(1),这没啥好说的
面试如何提问
使用那种排序最快
- 如果数据规模小(n<50)的话,那么选择排序,插入排序都很快
- 如果有序的话,那么冒泡排序很适合
- 如果数据规模大的话,那么快速排序,归并排序更适合
总结
今天我们讲了冒泡排序,选择排序,插入排序,大家一定要认真学习,学习东西的时候一定要谨记,如何在学习的时候,让你百日之后依然能记得今天所学的内容,所以一定要学透,不要求快