作者:前端实习生鲸落
Github:鲸落(github.com)
冒泡排序
基本思路
- 基本思路是通过两两比较相邻的元素并交换它们的位置,从而使整个序列按照顺序排列。
- 该算法一趟排序后,最大值总是会移到数组最后面,那么接下来就不用再考虑这个最大值。
- 一直重复这样的操作,最终就可以得到排序完成的数组。
实现步骤
- 从第一个元素开始,逐一比较相邻元素的大小。
- 如果前一个元素比后一个元素大,则交换位置。
- 在第一轮比较结束后,最大的元素被移动到了最后一个位置。
- 在下一轮比较中,不再考虑最后一个位置的元素,重复上述操作。
- 每轮比较结束后,需要排序的元素数量减一,直到没有需要排序的元素。
- 排序结束。
- 这个流程会一直循环,直到所有元素都有序排列为止。
代码实现
时间复杂度:O(n2)
function foo(arr){
const n = arr.length
// 循环未排序的部分
for (let i = 0; i < n - 1; i++) {
// 这里为什么是arr.length - 1:在下面我们有 j+1 的情况,不减1会越界
// arr.length - 1 - i:每一次排序过后,后面的数据就不用管他了
// 内层循环找到最大值,不断交换
for (let j = 0; j < n - 1 - i; j++) {
if(arr[j] > arr[j+1]){
// let temp = arr[j]
// arr[j] = arr[j+1]
// arr[j+1] = temp
// 下面是es6的语法,简写
[ arr[j], arr[j+1] ] = [ arr[j+1], arr[j] ]
}
}
}
return arr
}
// 性能优化版本:当我们在循环的时候,某一次循环,发现没有发生交换的时候,这个排序就已经结束了,不需要再继续下去消耗性能了
function foo(arr){
const n = arr.length
for (let i = 0; i < n-1; i++) {
let isSwapped = false
for (let j = 0; j < n-1-i; j++) {
if( arr[j] > arr[j+1] ){
[ arr[j], arr[j+1] ] = [ arr[j+1], arr[j] ]
isSwapped = true
}
}
if(!isSwapped) break
}
return arr
}
时间复杂度
-
在冒泡排序中,每次比较两个相邻的元素,并交换他们的位置,如果左边的元素比右边的元素大,则交换它们的位置。这样的比较和交换的过程可以用一个循环实现。
-
最好情况:O(n)
- 即待排序的序列已经是有序的。
- 此时仅需遍历一遍序列,不需要进行交换操作。
-
最坏情况:O(n^2)
- 即待排序的序列是逆序的。
- 需要进行n-1轮排序,每一轮中需要进行n-i-1次比较和交换操作。
-
平均情况:O(n^2)
- 即待排序的序列是随机排列的。
- 每一对元素的比较和交换都有1/2的概率发生,因此需要进行n-1轮排序,每一轮中需要进行n-i-1次比较和交换操作。
-
由此可见,冒泡排序的时间复杂度主要取决于数据的初始顺序。最坏情况下时间复杂度是O(n^2),不适用于大规模数据的排序。
选择排序
基本思路
- 首先在未排序的数列中找到最小元素,然后将其存放到数列的起始位置
- 接着,再从剩余未排序的元素中继续寻找最小元素,然后放到已排序序列的末尾
- 以此类推,直到所有元素均排序完毕。
实现流程
-
遍历数组,找到未排序部分的最小值
- 首先,将未排序部分的第一个元素标记为最小值
- 然后,从未排序部分的第二个元素开始遍历,依次和已知的最小值进行比较
- 如果找到了比最小值更小的元素,就更新最小值的位置
-
将未排序部分的最小值放置到已排序部分的后面
- 首先,用解构赋值的方式交换最小值和已排序部分的末尾元素的位置
- 然后,已排序部分的长度加一,未排序部分的长度减一
-
重复执行步骤1和2,直到所有元素都有序
实现步骤
时间复杂度:O(n2)
// 先找到最小的排到第一个
function foo(arr){
const n = arr.length
let minIndex = 0
// 从第一个开始,第0个是自己不需要找
for (let j = 1; j < n; j++) {
if(arr[minIndex] > arr[j]){
minIndex = j
}
}
//这里我们就可以找到最小值了
console.log(arr[minIndex]);
//交换,这个时候就最小值在第一个了
[ arr[0], arr[minIndex] ] = [ arr[minIndex], arr[0] ]
return arr
}
// 循环上述步骤
function foo(arr){
const n = arr.length
// 为什么是n-1,因为排到最后一个,一定是最大的
for (let i = 0; i < n - 1; i++) {
let minIndex = i
// 从第i+1个开始,前面已经排序过了
// 作用:找到最小值
for (let j = 1 + i; j < n; j++) {
if(arr[minIndex] > arr[j]){
minIndex = j
}
}
//小优化:不相等的时候交换
if(i !== minIndex){
[ arr[i], arr[minIndex] ] = [ arr[minIndex], arr[i] ]
}
}
return arr
}
时间复杂度
-
最好情况时间复杂度:O(n^2)
- 最好情况是指待排序的数组本身就是有序的。
- 在这种情况下,内层循环每次都需要比较n-1次,因此比较次数为n(n-1)/2,交换次数为0。
- 所以,选择排序的时间复杂度为O(n^2)。
-
最坏情况时间复杂度:O(n^2)
- 最坏情况是指待排序的数组是倒序排列的。
- 在这种情况下,每次内层循环都需要比较n-i-1次,因此比较次数为n(n-1)/2,交换次数也为n(n-1)/2。
- 所以,选择排序的时间复杂度为O(n^2)。
-
平均情况时间复杂度:O(n-2)
- 平均情况是指待排序的数组是随机排列的。
- 在这种情况下,每个元素在内层循环中的位置是等概率的,因此比较次数和交换次数的期望值都是n(n-1)/4。
- 所以,选择排序的时间复杂度为O(n^2)。
插入排序
基本思路
- 首先假设第一个数据是已经排好序的,接着取出下一个数据,在已经排好序的数据中从后往前扫描,找到比它小的数的位置,将该位置之后的数整体后移一个单位,然后再将该数据插入到该位置
- 不断重复上述操作,直到所有的数据都插入到已经排好序的数据中,排序完成。
实现步骤
- 1.首先,假设数组的第一个元素已经排好序了,因为它只有一个元素,所以可以认为是有序的。
- 2.然后,从第二个元素开始,不断与前面的有序数组元素进行比较。
- 3.如果当前元素小于前面的有序数组元素,则把当前元素插入到前面的合适位置。
- 4.否则,继续与前面的有序数组元素进行比较。
- 5.以此类推,直到整个数组都有序。
- 6.循环步骤2~5,直到最后一个元素。
代码实现
时间复杂度:O(n2)
// 内循环
function foo(arr){
const n = arr.length
// 假设数组的那么我们前面有三个牌已经排好了,我们应该去第4个牌来与前面进行比较
let newNum = arr[4]
// 不确定循环次数,还不知道在哪里插入。假设拿到第四个牌,那么我们前面有三个牌已经排好了
let j = 4 - 1
// 我们的找法是从后往前找的
while(arr[j] > newNum && j >=0 ){
//后移操作
arr[j+1] = arr[j]
j--
}
//循环结束,要么 j=-1,要么 arr[j] == newNum
// 把后面空的位置给newNum
arr[j+1] = newNum
return arr
}
// 循环上述步骤
function foo(arr){
const n = arr.length
// 从下标为1的开始,因为下标为0的第一个我们默认是排好序的
for (let i = 1; i < n; i++) {
let newNum = arr[i]
let j = i - 1
while(arr[j] > newNum && j >=0 ){
arr[j+1] = arr[j]
j--
}
arr[j+1] = newNum
}
return arr
}
时间复杂度
-
最好情况:O(n)
- 如果待排序数组已经排好序
- 那么每个元素只需要比较一次就可以确定它的位置,因此比较的次数为n-1,移动的次数为0。
- 所以最好情况下,插入排序的时间复杂度为线性级别,即o(n)。
-
最坏情况:O(n^2)
- 如果待排序数组是倒序排列的
- 那么每个元素都需要比较和移动i次,其中i是元素在数组中的位置。
- 因此比较的次数为n(n-1)/2,移动的次数也为n(n-1)/2。
- 所以最坏情况下,插入排序的时间复杂度为平方级别,即O(n^2)。
-
平均情况:O(n^2)
- 对于一个随机排列的数组,插入排序的时间复杂度也为平方级别,即O(n^2)。
补充
后续会补充归并排序、快排序、堆排序和希尔排序
其他文章推荐:
浏览器输入url会发生什么 - 巨详细完整版 - 掘金 (juejin.cn)
浏览器输入url会发生什么 - 巨详细完整版续集 - 掘金 (juejin.cn)
你真的了解宏任务与微任务的执行顺序吗?你确定是微任务先执行吗? - 掘金 (juejin.cn)