1.冒泡排序
-
原理:相邻元素两两比较,将较大的元素逐步交换到右侧。
-
特点:
-
稳定排序
-
平均和最坏时间复杂度 O(n²)
-
空间复杂度 O(1)
-
let arr = [434, 342, 45, 45, 23, 4, 1234, 124]
function bubblesort(arr) {
let { length } = arr
for (let i = 0; i < length; i++) {
for (let j = 0; j < length - 1-i; j++) { //-i是因为第一层for执行完后最大的已经是最后一个了,
if (arr[j] > arr[j + 1]) {
swap(arr, j)
}
}
}
return arr
}
function swap(arr, j) {
// let temp = arr[j]
// arr[j] = arr[j + 1]
// arr[j + 1] = temp
// 或者
[arr[j],arr[j+1]]=[arr[j+1],arr[j]]
}
console.log(bubblesort(arr))
2.选择排序
-
原理:每次遍历选择最小(或最大)的元素,放到已排序序列的末尾。
-
特点:
-
不稳定排序
-
时间复杂度始终 O(n²)
-
空间复杂度 O(1)
-
let arr = [434, 342, 45, 45, 23, 4, 1234, 124]
function SelectionSort(arr) {
for (let i = 0; i < arr.length - 1; i++) {
let minIndex = i
for (let j = i; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
minIndex = j
}
}
if (minIndex !== i) {
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
}
}
console.log(arr)
}
SelectionSort(arr)
3.插入排序
-
原理:将未排序元素逐个插入到已排序序列的合适位置。
-
特点:
-
稳定排序
-
时间复杂度 O(n²),但接近有序数据时效率高
-
空间复杂度 O(1)
-
function insertSort(arr) {
let { length } = arr
for (let i = 1; i < length; i++) {
let temp = arr[i]
let j = i
while (j > 0 && arr[j - 1] > temp) {
arr[j] = arr[j - 1]
j--
}
arr[j]=temp
}
console.log(arr);
}
insertSort(arr)
4.归并排序(火狐 .sort原理)
原理:时间复杂度O(n log n)
-
分治(Divide):将数组递归地拆分为左右两部分,直到每个子数组只含一个元素。
-
合并(Merge):将两个已排序的子数组合并为一个有序数组。
-
稳定性:归并排序是稳定的(相同元素顺序不变),适合对对象数组按特定属性排序。
function mergeSort(arr) {
if (arr.length <= 1) return arr;
// 1. 分治:拆分数组
const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid));
const right = mergeSort(arr.slice(mid));
// 2. 合并:合并有序子数组
return merge(left, right);
}
function merge(left, right) {
console.log(left, right);
const result = [];
let i = 0, j = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
result.push(left[i]);
i++;
} else {
result.push(right[j]);
j++;
}
}
// 处理剩余元素
return result.concat(left.slice(i)).concat(right.slice(j));
}
// 示例
console.log(mergeSort(arr)); // [1, 2, 3, 4, 5, 8]
5.快速排序(谷歌 .sort原理)
快速排序(Quick Sort)是一种高效的排序算法,采用分治法(Divide and Conquer)策略。它的基本思想是通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据小,然后再递归地对这两部分数据进行快速排序,最终实现整个序列的有序化。平均时间复杂度:O(n log n)。不稳定排序
function quickSort(arr) {
if (arr.length <= 1) {
return arr; // 如果数组长度小于等于1,直接返回
}
// 选择基准值(这里选择数组的最后一个元素)
const pivot = arr[arr.length - 1];
const left = [];
const right = [];
// 分区操作(这里可以直接利用array.filter方法)
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] < pivot) {
left.push(arr[i]); // 比基准值小的放在左边
} else {
right.push(arr[i]); // 比基准值大的放在右边
}
}
// 递归排序左右子数组,并拼接结果
return [...quickSort(left), pivot, ...quickSort(right)];
}
// 示例
const arr = [3, 6, 8, 10, 1, 2, 1];
const sortedArr = quickSort(arr);
console.log(sortedArr); // 输出: [1, 1, 2, 3, 6, 8, 10]
6.计数排序
计数排序使用一个用来存储每个元素在原始数组中出现次数的临时数组。在所有元素都计数完成后,临时数组已排好序并可迭代以构建排序后的数组结果。时间复杂度:O(n + k)。稳定排序。
function countSort(arr) {
let { length } = arr
if (length < 2) {
return arr
}
let maxValue = Math.max(...arr)
console.log(maxValue)
let counts = new Array(maxValue + 1)
arr.forEach((e) => {
if (!counts[e]) {
counts[e] = 0
}
counts[e]++
})
let newarr = []
counts.forEach((e, i) => {
let index = e
while (index > 0) {
newarr.push(i)
index--
}
})
return newarr
}
7.桶排序
桶排序的基本思想是将待排序的数据分到若干个“桶”中,每个桶内的数据再分别排序(可以使用其他排序算法或递归使用桶排序),最后将所有桶中的数据依次合并,得到有序序列。
时间复杂度:平均为 O(n + k),最坏为 O(n²)(当所有数据都分配到同一个桶中时)。 空间复杂度:O(n + k),需要额外的桶空间。 稳定性:取决于桶内排序算法的稳定性。
//这个逻辑比较复杂
function bucketSort(arr, bucketSize = 5) {
if (arr.length === 0) {
return arr;
}
// 找到数组中的最小值和最大值
let minValue = arr[0];
let maxValue = arr[0];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < minValue) {
minValue = arr[i];
} else if (arr[i] > maxValue) {
maxValue = arr[i];
}
}
// 计算桶的数量
const bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
const buckets = new Array(bucketCount);
for (let i = 0; i < buckets.length; i++) {
buckets[i] = [];
}
// 将元素分配到各个桶中
for (let i = 0; i < arr.length; i++) {
const bucketIndex = Math.floor((arr[i] - minValue) / bucketSize);
buckets[bucketIndex].push(arr[i]);
}
// 对每个桶进行排序,这里使用插入排序
const sortedArray = [];
for (let i = 0; i < buckets.length; i++) {
insertionSort(buckets[i]); // 可以使用任何排序算法
sortedArray.push(...buckets[i]);
}
return sortedArray;
}
// 插入排序辅助函数
function insertionSort(arr) {
for (let i = 1; i < arr.length; i++) {
const key = arr[i];
let j = i - 1;
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
return arr;
}
// 测试示例
const array = [0.42, 0.32, 0.33, 0.52, 0.37, 0.47, 0.51];
console.log("排序前:", array);
console.log("排序后:", bucketSort(array));
8.基数排序
基数排序的基本思想是将待排序的数据按照位数进行排序,从最低位到最高位依次排序。每次排序时,使用稳定的排序算法(如计数排序)对当前位进行排序。
时间复杂度:O(d * (n + k)),其中 d 是最大位数,k 是基数(通常为 10)。 空间复杂度:O(n + k),需要额外的计数数组和输出数组。 稳定性:基数排序是稳定的排序算法。
// 基数排序
function radixSort(arr) {
const base = 10; // 设置基数为10,适用于十进制数字排序
let divider = 1; // 初始化除数为1,用于提取数字的个位数
let maxValue = Math.max(...arr); // 找到数组中的最大值,用于确定排序的轮数
// 当除数小于等于最大值时,继续进行排序
while (divider <= maxValue) {
// 创建10个桶,用于存储按当前位数分类的数字
const buckets = [...Array(10)].map(() => []);
// 遍历数组中的每个数字
for (let val of arr) {
// 计算当前位数的值,并将其放入相应的桶中
buckets[Math.floor(val / divider) % base].push(val);
}
// 打印当前轮次的桶状态(调试用)
console.log(buckets);
// 将所有桶中的数字按顺序合并回原数组
arr = [].concat(...buckets);
// 将除数乘以基数,以便在下一轮中处理下一位数
divider *= base;
}
// 返回排序后的数组
return arr;
}