小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
插入排序、选择排序、冒泡排序都是两层遍历,时间复杂度都为
O(n²);且都是操作原数组,无需新建额外数组内存空间,空间复杂度为O(1);
插入排序
-
插入排序的思路是把同一数组分为有序和无序两部分,每次遍历,把无序部分的插入到有序部分中。如有下面数组,绿色部分代表有序部分,紫色代表无序部分
此时把元素12依次与45、42、23一一比较,一一互换位置,最后12比11大停止,所以插入到11与23之间。 -
代码
let arr = [3, 1, 2, 4, 5, 6] const insertionSorter = function (arr) { // 默认数组第一个元素为有序的,所以从i=1开始为无序的 for(let i = 1; i < arr.length; i++) { // 外层循环为从i=1开始的每个无序元素 for(let j = i; j > 0; j--) { // 内层循环为单个无序元素与每个有序元素逐个比较,互换位置 if (arr[j] < arr[j - 1]) { let temp = arr[j - 1] arr[j - 1] = arr[j] arr[j] = temp } else { break } } } } insertionSorter(arr) // [1, 2, 3, 4, 5, 6] -
优化:元素互换位置的时候,每个元素都被访问了2次(
arr[j-1], arr[j]),比较消耗性能。从这个角度优化的方式是,使用赋值移动的方式,有如下数组:绿色部分为有序,紫色为无序,当前待插入的元素为12,使用变量temp临时存储,
12首先与42比较,42向右移动(赋值)
12与23比较,23向右移动(赋值),再与16比较,向右移动(赋值)
循环结束后,插入所在索引位置 j
所以使用赋值(元素只访问一次)取代元素替换的方式减少访问次数,提升性能const insertionSorter = function (arr) { for(let i = 1; i < arr.length; i++) { // 记录待插入的元素 let temp = arr[i] let j for(j = i; j > 0; j--) { if (temp < arr[j - 1]) { // 待插入元素temp依次与前一个元素比较 arr[j] = arr[j - 1] // 较大的元素向右移动,元素只被访问一次 } else { break } } // 内层循环结束后,找到元素插入的位置 arr[j] = temp } }
选择排序
- 选择排序思路是在数组中找到当前数组最小值元素位置,然后与数组第一位交换,接着在除第一个元素外剩余数组中同样找到当前数组最小值,然后与数组第二位交换,以此类推,最后完成排序
let arr = [3, 1, 2, 4, 5, 6] const selectionSorter = function (arr) { for(let i = 0; i < arr.length; i++) { // 外层循环控制每次交换位置 let minIndex = i // 设置初始最小值索引 for(let j = i + 1; j < arr.length; j++) { // 内层循环找到当前剩余元素最小值,所以剩余元素索引从 i+1 开始遍历 if (arr[j] < arr[minIndex]) { minIndex = j } } // 最小值与数组元素按i的顺序互换位置 let temp = arr[i] arr[i] = arr[minIndex] arr[minIndex] = temp } } selectionSorter(arr) // [1, 2, 3, 4, 5, 6] - 优化(待更新)
冒泡排序
- 冒泡排序的思路是数组中的元素要逐一的与其它元素比较,如果值比较大,则与其互换位置,然后再与下一个元素比较,如果还是更大,继续互换位置,以此类推,如果这个元素是最大的,那么此元素将“冒泡”到数组末尾,第一轮比较结束。按照这个逻辑,再把第二个元素依次与剩下的元素依次进行第二轮比较,然后第三轮,第四轮,直至数组元素遍历完,排序完成。
let arr = [3, 1, 2, 4, 5, 6] const bubleSorter = function (arr) { for(let round = 1; round < arr.length; round++) { // round 为轮次 let compareTimes = arr.length - round // 每轮比较的次数 for(let i = 0; i < compareTimes; i++) { if (arr[i] > arr[i+1]) { // 如果值更大,那么与后一元素互换位置 let temp = arr[i] arr[i] = arr[i+1] arr[i+1] = temp } } } } bubleSorter(arr) // [1, 2, 3, 4, 5, 6] - 优化:在一轮round遍历当中,如果没有执行一次元素位置,说明数组已经排序好了,则可提前终止遍历
const bubleSorter = function (arr) { for(let round = 1; round < arr.length; round++) { let hasSwap = false // 标记此轮冒泡未发生交换 let compareTimes = arr.length - round for(let i = 0; i < compareTimes; i++) { if (arr[i] > arr[i+1]) { let temp = arr[i] arr[i] = arr[i+1] arr[i+1] = temp hasSwap = true // 如果发生交换,则为 true } } if (!hasWeap) break // 退出遍历 } }
注
- 以数组长度为1000的乱序数组,从消耗时间角度,三种排序的性能高低排序分别是 插排 > 选排 > 冒泡排序
- 如果存在多个值相等的元素,使用选择排序后,原位置可能会发生变化,如[2,1,1,3],排序后[1,1,2,3],两个1的先后位置可能发生变化,而冒泡排序和插入排序不变