Web 文章首发于 两个程序猿 微信公众号,希望能和大家多多交流,共同进步~
目录
一.基本排序算法
- 冒泡排序
- 选择排序
- 插入排序
二. 基本排序算法的比较
本文会详细比较这几种排序的时间复杂度和空间复杂度,算法稳定性,并分析基本排序算法的运行时间。
注:
原地排序算法:
在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。
排序算法稳定性:
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
基本排序算法
1. 冒泡排序
冒泡排序只会操作相邻的两个数据。假设正在将一组数字按照升序排列,当左侧值大于右侧的值时,进行位置交换,较大的值会移动到数组右侧,较小的值会移动到数组左侧。重复 n 次, 就完成了 n 个数据的排序工作。
接下来分析一个简单的例子:
原始数据: 7 5 8 2 6 1 按从小到大的顺序排序
| 冒泡次数 | 结果 |
|---|---|
| 第1次冒泡 | 5 7 2 6 1 8 |
| 第2次冒泡 | 5 2 6 1 7 8 |
| 第3次冒泡 | 2 5 1 6 7 8 |
| 第4次冒泡 | 2 1 5 6 7 8 |
| 第5次冒泡 | 1 2 5 6 7 8 |
代码:
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortArray = function(nums) {
//nums.length - 1是因为最后一轮不需要排序
for(var i=0; i< nums.length-1; i++){
console.log("第" + (i+1) + "趟")
// nums.length - i是因为每一轮都能确定排序好一个数
for(var j=0; j< nums.length-1-i; j++){
if(nums[j]>nums[j+1]){
[nums[j], nums[j+1]] = [nums[j+1], nums[j]]
}
console.log("第" + (j+1) + "次" + nums)
}
}
return nums
};
有时,一些数组在冒泡未结束的时候,就已经是一个有序的数组,不需要在继续执行程序,所以冒泡排序还可以这样优化
优化冒泡:
/**
* @param {number[]} nums
* @return {number[]}
*/
function bubbleSort(nums) {
console.time('冒泡排序')
for(var i=0; i< nums.length-1; i++){
//console.log("第" + (i+1) + "趟")
// 定义一个变量,初始值为true
var flag = true
for(var j=0; j< nums.length-1-i; j++){
if(nums[j]>nums[j+1]){
[nums[j], nums[j+1]] = [nums[j+1], nums[j]]
flag = false // 有变量交换时,说明还不是有序
}
console.log("第" + (j+1) + "次" + nums)
}
// 如果数组已经有序,退出循环
if(flag){
break;
}
}
//console.timeEnd('冒泡排序')
return nums
};
时间复杂度:
若数组本身有序, 时间复杂度是
E = O(n)若数组顺序与正好与结果相反,时间复杂度是
E = O(n^2)
空间复杂度:
E = O(1)
冒泡排序是原地排序算法
冒泡排序是稳定的排序算法,因为当相邻的两个元素大小相等时,不做交换。所以这两个大小相等的元素在排序前后顺序没有变。
注意: 不稳定的算法在某种条件下可以变成稳定的算法,而稳定的算法在某种条件下也可以变成不稳定的算法。 例如:冒泡排序是稳定的算法, 若将判断条件改为 nums[j] >= nums[j+1],两个相等的元素就会交换位置,变成不稳定的算法。
2. 选择排序
选择排序从数组的开头开始,将第一个元素和其他元素进行比较。检查完所有元素后,最小的元素会被放到数组的第一个位置。然后算法会从 第二个位置继续和其他元素比较,完成排序。
原始数据: 7 5 8 2 6 1 按从小到大的顺序排序
| 排序次数 | 结果 |
|---|---|
| 第1次 | 1 5 8 2 6 7 |
| 第2次 | 1 2 8 5 6 7 |
| 第3次 | 1 2 5 8 6 7 |
| 第4次 | 1 2 5 6 8 7 |
| 第5次 | 1 2 5 6 7 8 |
| 第6次 | 1 2 5 6 7 8 |
/**
* @param {number[]} nums
* @return {number[]}
*/
function selectionSort(nums) {
//console.time('选择排序')
for (let i = 0; i < nums.length; i++) {
let min = Infinity;
let minIndex;
for (j = i; j < nums.length; j++) {
if (nums[j] < min) {
min = nums[j]
minIndex = j;
}
}
[nums[i], nums[minIndex]] = [nums[minIndex], nums[i]];
}
//console.timeEnd('选择排序')
return nums;
};
时间复杂度:
E = O(n^2)
空间复杂度:
E = O(1)
选择排序是原地排序算法
选择排序不是稳定的排序算法
3. 插入排序
插入排序类似于人类按数字或字母顺序对数据进行排序。将数据分为已排序区间和未排序区间。初始的已排序区间里的元素是数组的第一个元素,取未排序区间的元素,插入到已排序区间的合适位置。重复这个动作,直到数组变成有序。
原始数据: 7 5 8 2 6 1 按从小到大的顺序排序
/**
* @param {number[]} nums
* @return {number[]}
*/
function insertionSort(nums) {
//console.time('插入排序')
for (let i = 1; i < nums.length; i++) {
let temp = nums[i];
let j = i - 1;
for (; j >= 0; j--) {
if (temp >= nums[j]) break;
nums[j + 1] = nums[j]
console.log("移动",nums)
}
console.log(j)
nums[j + 1] = temp;
console.log("插入",nums)
}
// console.timeEnd('插入排序') 测试排序时间
return nums;
};
移动 [ 7, 7, 8, 2, 6, 1 ]
-1
插入 [ 5, 7, 8, 2, 6, 1 ]
1
插入 [ 5, 7, 8, 2, 6, 1 ]
移动 [ 5, 7, 8, 8, 6, 1 ]
移动 [ 5, 7, 7, 8, 6, 1 ]
移动 [ 5, 5, 7, 8, 6, 1 ]
-1
插入 [ 2, 5, 7, 8, 6, 1 ]
移动 [ 2, 5, 7, 8, 8, 1 ]
移动 [ 2, 5, 7, 7, 8, 1 ]
1
插入 [ 2, 5, 6, 7, 8, 1 ]
移动 [ 2, 5, 6, 7, 8, 8 ]
移动 [ 2, 5, 6, 7, 7, 8 ]
移动 [ 2, 5, 6, 6, 7, 8 ]
移动 [ 2, 5, 5, 6, 7, 8 ]
移动 [ 2, 2, 5, 6, 7, 8 ]
-1
插入 [ 1, 2, 5, 6, 7, 8 ]
时间复杂度:
若数组本身有序
E = O(n)若数组是倒序的
E = O(n^2)
空间复杂度:
E = O(1)
插入算法是原地排序算法
插入算法是稳定的排序算法
基本排序算法的比较
先写一个生成指定长度的随机数组,用来测试
function randomArray(numsLength) {
var arr = []
for (var i = 0; i < numsLength; ++i) {
arr[i] = Math.floor(Math.random() * (numsLength + 1));
}
return arr
}
测试代码:
当 numsLength = 100 时
var arr1 = randomArray(100)
var arr2 = arr1.slice()
var arr3 = arr2.slice()
insertionSort(arr1) 插入排序: 0.3701171875ms
bubbleSort(arr2) 冒泡排序: 2.2109375ms
selectionSort(arr3) 选择排序: 0.412841796875ms
当 numsLength = 1000 时
var arr4 = randomArray(1000)
var arr5 = arr4.slice()
var arr6 = arr5.slice()
insertionSort(arr4) 插入排序: 6.634765625ms
bubbleSort(arr5) 冒泡排序: 10.864013671875ms
selectionSort(arr6) 选择排序: 6.319091796875ms
当 numsLength = 10000 时
var arr7 = randomArray(10000)
var arr8 = arr7.slice()
var arr9 = arr8.slice()
insertionSort(arr7) 插入排序: 30.64404296875ms
bubbleSort(arr8) 冒泡排序: 201.574951171875ms
selectionSort(arr9) 选择排序: 106.9638671875ms
当 numsLength = 100000 时
var arr10 = randomArray(100000)
var arr11 = arr10.slice()
var arr12 = arr11.slice()
insertionSort(arr10) 插入排序: 2908.252197265625ms
bubbleSort(arr11) 冒泡排序: 24053.094970703125ms
selectionSort(arr12) 选择排序: 10498.163330078125ms
所以大致可以认为选择和插入排序要比冒泡排序快,插入排序是最快的。
接下来总结高级排序算法:希尔排序、归并排序、快速排序
