大家好呀, 我是小九九的爸爸,我给自己立了个flag,要在8月前写完10大排序算法,这不,前些天写完了冒泡排序、选择排序、插入排序。这次决定将这3个算法合入到一篇文章了,加深一下它们的理解。
一、关联性
这三个算法都是基于比较的原地排序算法,具体如下:
| 算法 | 如何比较 |
|---|---|
| 冒泡 | 2次遍历,每次遍历都要确定本轮数据中的最大值,并将最大值依次放到数组的最后一位 |
| 选择 | 2次遍历,每次遍历都要确定本轮数据中的最小值,并将最小值依次放到数组的第一位 |
| 插入 | 2次遍历,从第2项开始,依次比较之前的数据,直到将当前项插入到所有前项里的合适位置 |
二、代码实操
2.1、冒泡(从小大)
中心思想:2次遍历,每次遍历都要确定本轮数据中的最大值,并将最大值依次放到数组的最后一位。
这里有个问题,而且我认为比较重要,为什么要二次遍历?
我们试一下只用一次遍历,且原地改变的方式,能不能完成数据的排序。
let arr = [...]
for (let index = 0; index < ar.length; index++){
if (arr[index] > arr[index+1]){
[ arr[index], arr[index+1] ] = [ arr[index+1], arr[index] ];
}
}
return arr
上面这种写法,只能得出最大值,但是无法得出第二大值,第三大值,因此无法给数组排序。
所以我们才需要2层遍历,最外层的遍历就是为了得出第N大值。最内层的遍历就是为了得出每一轮的最大值。
let arr = [...];
// 其实难点在于为什么要二次遍历,上面已经解释过了
for (let x = 0; x < arr.length; x++){
for (let y = 0; y < arr.length - x; y++){
if (arr[y] > arr[y+1]){
// 发生交换
[ arr[y], arr[y+1] ] = [ arr[y+1], arr[y] ];
}
}
}
return arr;
2.2、选择(从小到大)
中心思想:2次遍历,每轮遍历都要得出本轮的最小值,并将最小值分别放到前面。
其实选择排序就是冒泡排序的逆向流程。双层遍历的目的跟冒泡一样,一层遍历只能确定最小值,但是不能确定第N小值。
let data = [...];
for (let x = 0; x < data.length; x++){
let minIndex = x;
for (let y = minIndex + 1; y < data.length; y++){
if (data[y] < data[minIndex]){
minIndex = y;
}
}
[ data[x], data[minIndex] ] = [ data[minIndex], data[x] ];
}
return data;
2.3、插入(从小到大)
中心思想:从第二项开始,将本数据项与前面的数据进行比较,直到插入合适的位置。
因为我们的最终想要的数据是从小到大排序,所以什么是合适的位置呢?
其实就是找到自己是第几小值而已。
let data = [...];
for (let x = 1; x < data.length; x++){
let curIndex = x;
while(curIndex - 1 >= 0 && data[curIndex] < data[curIndex - 1]){
[ data[curIndex], data[curIndex - 1] ] = [ data[curIndex - 1], data[curIndex] ];
curIndex--;
}
}
return data;
三、最后
好啦,本次排序文章就分享到这啦,大家会看到我并没有分析这3种排序的优缺点,是因为我觉着优缺点是针对场景而言的,而这三种排序在遇到最坏的情况下,时间复杂度都是O(N^2)。也就是说,对这三种排序而言,没有银弹,一种适用,另外2种必然也满足要求。
那么,我们下期再见啦。希望我说的对你有帮助~~