文章目录
-
- 冒泡排序
- 选择排序
- 插入排序
- 快速排序
- 归并排序
- 计数排序
- 基数排序
1.冒泡排序
冒泡排序是一种非常基础的排序算法,它通过重复遍历要排序的数列,比较每对相邻元素的大小,并在必要时交换它们的位置。这个过程会重复进行,直到没有需要交换的元素为止,这意味着数列已经完全排序。
时间复杂度: O(n^2)
const sort = (arr) => {
for(let i = 0; i < arr.length; i++) {
for(let j = i + 1; j < arr.length;j++) {
if(arr[i] > arr[j]) {
[arr[i],arr[j]] = [arr[j],arr[i]] // 交换两个元素位置
}
}
}
return arr
}
2.选择排序
选择排序是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完.
时间复杂度: O(n^2)
const sort = (arr) => {
for(let i = 0; i < arr.length - 1; i++) {
let minIndex = i;
for(let j = i + 1; j < arr.length; j++) {
if(arr[j] < arr[minIndex]) {
minIndex = j;
}
const temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
return arr;
}
3.插入排序
插入排序是一种简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要移动元素以腾出空间。
时间复杂度: O(n^2)
onst sort = (arr) => {
// 判断arr是否只有一个元素,是的话直接返回
if (arr.length <= 1) {
return arr;
}
for(let i = 1; i < arr.length; i++) {
let item = arr[i];
let j = i - 1;
while(j >= 0 && arr[j] > item) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = item;
}
return arr
}
4.快速排序
快速排序是一种高效的排序算法,采用分治法(Divide and Conquer)策略来把一个序列分为较小的两个子序列,然后递归地排序两个子序列。
时间复杂度:O(n log n)
const sort = (arr) => {
if(arr.length <= 1) return arr
const mid = Math.floor(arr.length / 2)
const midValue = arr.splice(mid,1)[0]
const left = []
const right = []
for(let i = 0 ;i < arr.length;i++) {
if(arr[i] < midValue) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return sort(left).concat(midValue,sort(right))
}
5.归并排序
归并排序是一种使用分治法的排序算法,它将数组分成更小的数组,直到每个子数组只有一个元素,然后开始合并这些子数组,直到最终得到一个有序的数组。
时间复杂度:O(n log n)
const sort = (arr) => {
// 判断数组元素是否小于2,如果小于,返回数组开始排序
if(arr.length < 2) return arr
// 获取数组中间索引
const mid = Math.floor(arr.length / 2);
const left = arr.slice(0,mid) // 左侧数组
const right = arr.slice(mid) //右侧数组
// 两个数组排合并为一个有序数组
const merge = (leftArr,rightArr) => {
let res = [] // 定义一个存储结果的数组
while(leftArr.length && rightArr.length ) {
// 判断左侧数组第一个元素是否小于右侧数组第一个元素
if(leftArr[0] <= rightArr[0]) {
res.push(leftArr.shift()) //
} else {
res.push(rightArr.shift())
}
}
return res.concat(leftArr).concat(rightArr)
}
// 递归调用并返回结果
return merge(sort(left),sort(right))
}
6.计数排序
计数排序是一种非比较型整数排序算法,它适用于当整数的取值范围不大时使用。这种算法的核心思想是使用一个额外的数组(计数数组)来统计原数组中每个元素出现的次数,然后根据这些计数来确定每个元素在排序后数组中的位置。
时间复杂度:O(n + k)
const sort = (arr) => {
const min = Math.min(...arr);
const max = Math.max(...arr);
const count = new Array(max - min + 1).fill(0);
let bias = 0;
for (let i = 0; i < arr.length; i++) {
count[arr[i] - min]++;
}
let index = 0;
let i = 0;
while (index < arr.length) {
if(count[i] !== 0) {
arr[index++] = i + min;
count[i]--;
} else {
i++;
}
}
return arr;
}
7.基数排序
归并排序是一种使用分治法策略的排序算法。它将原始数据分成更小的数组,递归地将这些小数组排序,然后将排序好的小数组合并成较大的有序数组,直到最终整个数组有序。
时间复杂度:O(nk)
const sort = (arr) => {
if(arr.length < 2 || arr === null) return arr
let max = Math.max(...arr) // 获取数组中的最大值
let maxDigit = 0 // 计算最大排序次数
while(max > 0) {
maxDigit++
max = Math.floor(max / 10)
}
const bucket = Array.from({length: 10}, () => []) // 创建10个桶
let mod = 10,div = 1 // 计算当前位的基数
for(let i = 0; i < maxDigit; i++) {
for(let j = 0 ; j < arr.length; j++) {
const item = arr[j]
const digit = Math.floor((item % mod) / div) // 获取当前位的数字
bucket[digit].push(item)
}
let index = 0
for(let i = 0; i < bucket.length; i++) {
while(bucket[i].length > 0) {
arr[index++] = bucket[i].shift()
}
}
mod *= 10
div *= 10
}
return arr
}
对比总结
以下是对冒泡排序、选择排序、插入排序、快速排序、归并排序、基数排序和计数排序的比较
排序算法 | 时间复杂度 | 空间复杂度 | 稳定性 | 是否基于比较 | 适用场景 |
---|---|---|---|---|---|
冒泡排序 | O(n^2) | O(1) | 稳定 | 是 | 小数据集或基本有序 |
选择排序 | O(n^2) | O(1) | 稳定 | 是 | 小数据集 |
插入排序 | O(n^2) | O(1) | 稳定 | 是 | 小数据集或部分有序 |
快速排序 | O(n log n) | O(log n) | 不稳定 | 是 | 大数据集,随机数据 |
归并排序 | O(n log n) | O(n) | 稳定 | 否 | 大数据集,有序数据 |
基数排序 | O(nk) | O(n+k) | 稳定 | 否 | 整数排序,k较小 |
计数排序 | O(n+k) | O(k) | 稳定 | 否 | 整数排序,k较小 |
- 时间复杂度:大多数排序算法在最好情况下可以达到O(n log n),但快速排序、归并排序和基数排序在平均情况下也能保持这一性能。冒泡排序、选择排序和插入排序在最坏情况下都是O(n^2)。
- 空间复杂度:原地排序算法(如冒泡排序、选择排序和插入排序)的空间复杂度为O(1),而归并排序和基数排序需要额外的空间。
- 稳定性:稳定的排序算法(如冒泡排序、选择排序、插入排序和归并排序)保持相等元素的原始顺序,这对于某些应用非常重要。
- 是否基于比较:基于比较的排序算法(如冒泡、选择和快速排序)通过元素之间的比较来确定顺序,而非基于比较的排序算法(如计数排序和基数排序)则不依赖于比较。
- 适用场景:每种排序算法都有其适用的场景。例如,快速排序适合大数据集,而计数排序和基数排序适合于整数且值的范围较小的情况