归并排序是一种有效的排序算法,采用分治法(Divide and Conquer)的思想来进行排序。其基本步骤包括:
- 分割(Divide) :将数组分成两个子数组,分别对两个子数组进行排序。
- 合并(Conquer) :将两个已经排序的子数组合并成一个有序的数组。
归并排序的时间复杂度为O(n log n),并且适用于大多数排序任务。
function mergeSort(arr) {
if (arr.length <= 1) {
return arr; // 如果数组只有一个元素,返回该数组
}
// 找到数组的中间位置
const middleIndex = Math.floor(arr.length / 2);
// 使用slice方法将数组分为左右两个子数组
const leftArray = arr.slice(0, middleIndex);
const rightArray = arr.slice(middleIndex);
// 递归对左右子数组进行排序,并合并结果
return merge(mergeSort(leftArray), mergeSort(rightArray));
}
function merge(leftArray, rightArray) {
const sortedArray = []; // 用于存储合并后的有序数组
let leftIndex = 0;
let rightIndex = 0;
// 同时遍历左右两个数组,比较元素并排序
while (leftIndex < leftArray.length && rightIndex < rightArray.length) {
if (leftArray[leftIndex] < rightArray[rightIndex]) {
sortedArray.push(leftArray[leftIndex]);
leftIndex++; // 左数组元素已使用,移动左指针
} else {
sortedArray.push(rightArray[rightIndex]);
rightIndex++; // 右数组元素已使用,移动右指针
}
}
// 将剩余的元素添加到有序数组中(如果有多余的元素)
return sortedArray.concat(leftArray.slice(leftIndex)).concat(rightArray.slice(rightIndex));
}
// 使用例子
const unsortedArray = [38, 27, 43, 3, 9, 82, 10];
const sortedArray = mergeSort(unsortedArray);
console.log(sortedArray); // 输出: [3, 9, 10, 27, 38, 43
思路解释:
- 分割:利用递归将数组不断地一分为二,直到每个数组只剩一个元素时停止分割。
- 合并过程:在递归返回时,调用
merge函数,该函数接受两个有序的子数组,并将它们合并成为一个有序数组。 - 合并方法中的比较与合并:遍历左右数组,同时从数组头部开始对比,将较小的元素放入
sortedArray中,这样保证合并后的数组依然有序。
通过这种递归的分割与合并,最终可以获得一个升序排列的数组。这种算法特别适合处理大规模数据,因为它的复杂度较低且比较稳定。
基数排序(Radix Sort)是一种非比较型的排序算法,它通过将数字分成不同的位(digits)进行排序。在排序过程中,基数排序通常采用“从最低位到最高位”(LSD, Least Significant Digit)的方法,一次处理每一位,并利用一个稳定的排序算法(如计数排序)进行排序。
基数排序的步骤:
- 找出最大的数字:确定待排序数组中数字的最大位数。
- 从最低位开始排序:对每个位进行排序,依次从最低位(单位)到最高位。
- 使用稳定的排序算法:在每一位上应用稳定的排序算法(如计数排序),确保在按当前位排序时,先排序的相同数字保持相对位置。
function getMax(arr) {
return Math.max(...arr); // 返回数组中的最大值
}
function countingSortForRadix(arr, place) {
const output = new Array(arr.length); // 输出数组
const count = new Array(10).fill(0); // 计数数组(假设使用0-9的数字)
// 计算每个数字在当前位的出现次数
for (let i = 0; i < arr.length; i++) {
const digit = Math.floor((arr[i] / place) % 10);
count[digit]++;
}
// 修改count数组,使它包含每个数字的位置
for (let i = 1; i < count.length; i++) {
count[i] += count[i - 1];
}
// 构建输出数组
for (let i = arr.length - 1; i >= 0; i--) {
const digit = Math.floor((arr[i] / place) % 10);
output[count[digit] - 1] = arr[i];
count[digit]--;
}
// 将已排序的数组填回原数组
for (let i = 0; i < arr.length; i++) {
arr[i] = output[i];
}
}
function radixSort(arr) {
// 1. 找到最大值
const max = getMax(arr);
// 2. 对每一位进行排序
for (let place = 1; Math.floor(max / place) > 0; place *= 10) {
countingSortForRadix(arr, place);
}
return arr;
}
// 使用示例
const unsortedArray = [170, 45, 75, 90, 802, 24, 2, 66];
const sortedArray = radixSort(unsortedArray);
console.log(sortedArray); // 输出: [2, 24, 45, 66, 75, 90, 170, 802]
思路解释:
-
找出最大值:使用
getMax函数找到输入数组中的最大数字,以确定需要排序的位数。 -
逐位排序:在
radixSort函数中,使用循环对每一位进行排序。从最低位开始,每次乘以10,直到处理完所有的位。 -
计数排序:
countingSortForRadix函数作为辅助函数,执行基于特定位的计数排序。主要分为几个步骤:- 先计算每个数字在当前位的出现次数,存储在
count数组中。 - 修改
count数组,计算每个数字的最终位置。 - 从后向前填充
output数组,以确保排序的稳定性(相同元素的相对位置保持不变)。 - 最后,将
output数组的内容复制回原数组中。
- 先计算每个数字在当前位的出现次数,存储在
基数排序适用于处理整数和字符串,尤其是在位数不多的情况下性能优越。其时间复杂度为O(d * (n + k)),其中d是数字的位数,n是待排序的元素数量,k是数字范围。