本文将逐一介绍常见的排序算法:
1 冒泡排序(O(n^2))
冒泡排序比较所有相邻的两个项,如果第一个比第二个大,则交换它们。元素项向上移动正确的顺序,就好像气泡升至表面一样,冒泡排序因此得名:
function bubbleSort(array) {
const {
length
} = array;
for (let i = 0; i < length; i++) {
for (let j = 0; j < length - 1; j++) {
if (array[j + 1] < array[j]) {
swap(array, j, j + 1);
}
}
}
return array;
}
function swap(array, a, b) {
const temp = array[a];
array[a] = array[b];
array[b] = temp;
}
使用如下代码来测试冒泡排序算法:
function createNonSortedArray(size) {
const array = [];
for (let i = size; i > 0; i--) {
array.push(i);
}
return array;
}
console.log(bubbleSort([9, 10, 3, 31, 0, 9, 10]))
let array = createNonSortedArray(5);
console.log(array);
console.log(bubbleSort(array));
2 选择排序(O(n^2))
选择排序是一种原址比较排序算法,选择排序大致思路是找到数据结构中的最小值并将其放到第一位,接着找到第二小的值并将其放在第二位,以此类推:
function selectionSort(array) {
const {
length
} = array;
let indexMin;
for (let i = 0; i < length - 1; i++) {
indexMin = i;
for (let j = i; j < length; j++) {
if (array[indexMin] > array[j]) {
indexMin = j;
}
}
if (i != indexMin) {
swap(array, i, indexMin);
}
}
return array;
}
3 插入排序(O(n^2))
默认 a[0] 为已排序数组中的元素,从 arr[1] 开始逐渐往已排序数组中插入元素,从后往前一个个比较,如果待插入元素小于已排序元素,则已排序元素往后移动一位,直到待插入元素找到合适的位置并插入已排序数组:
function insertionSort(array) {
const {
length
} = array;
let temp;
for (let i = 1; i < length; i++) {
let j = i;
temp = array[i];
while (j > 0 && array[j - 1] > temp) {
array[j] = array[j - 1];
j--;
}
array[j] = temp;
}
return array;
}
4 归并排序(O(nlog(n))
JavaScript的Array类定义了一个sort函数(Array.prototype.sort)用以排序JavaScript数组。ECMAScript没有定义用哪个排序算法,所以浏览器厂商可以自行去实现算法。例如Mozilla Firefox使用归并排序作为Array.prototype.sort的实现,而Chrome使用了一个快速排序的变体。
归并排序是一种分而治之算法,其思想是将原始数组切分成较小的数组,直到每个小数组只有一个位置,接着将小数组归并成较大的数组,直到最后只有一个排序完毕的大数组。
由于是分治法,归并排序也是递归的。我们要将算法分为两个函数:第一个负责将一个大数组分为多个小数组并调用用来排序的辅助函数:
function mergeSort(array) {
if (array.length > 1) {
const {
length
} = array;
const middle = Math.floor(length / 2);
const left = mergeSort(array.slice(0, middle));
const right = mergeSort(array.slice(middle, length));
array = merge(left, right);
}
return array;
}
归并排序将一个大数组转化为多个小数组直到其中只有一个项,由于算法是递归的,我们需要一个停止条件,在这里此条件是判断数组的长度是否为1。如果是则返回这个长度为1的数组,因为它已经排序了。
如果数组长度比1大,那么得将其分成小数组,为此首先得找到数组的中间位,找到后我们将数组分成两个小数组,分别叫做left和right。left数组由索引0至中间索引的元素组成,而right数组由中间索引至原始数组最后一个位置的元素组成。
下面步骤是调用merge函数,它负责合并和排序小数组来产生大数组,直到回到原始数组并已排序完成:
function merge(left, right) {
let i = 0;
let j = 0;
const result = [];
while (i < left.length && j < right.length) {
result.push(
left[i] < right[j] ? left[i++] : right[j++]
);
}
return result.concat(i < left.length ? left.slice(i) : right.slice(j));
}
merge函数接收两个数组作为参数,并将它们归并至一个大数组,排序发生在归并过程中。首先需要声明归并过程要创建的新数组以及用来迭代两个数组(left和right数组)所需的两个变量。迭代两个数组的过程中,我们比较来自left数组的项是否比来自right数组的项小,如果是,将该项从left数组添加至归并结果数组,并递增用于迭代数组的控制变量;否则从right数组添加项并递增用于迭代数组的控制变量。
接下来将left数组所有剩余的项添加到归并数组中,right数组也是一样。最后将归并数组作为结果返回。
5 快速排序(O(nlog(n))
function quickSort(arr, from, to) {
let i = from;
let j = to;
let key = arr[from];
if (from >= to) return;
while (i < j) {
while (arr[j] >= key && j > i) j--;
while (arr[i] <= key && j > i) i++;
swap(arr, i, j);
}
swap(arr, from, i);
quickSort(arr, from, i - 1);
quickSort(arr, i + 1, to);
function swap(arr, i, j) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
const arr = [3,2,1,0,-1,11,2,8]
quickSort(arr, 0, arr.length - 1);
console.log(arr);
6 基数排序(O(d*(n+r)))
设待排序的数组R[1…n],数组中最大的数是d位数,基数为r(如基数为10,即10进制,最大有10种可能,即最多需要10个桶来映射数组元素)。
处理一位数,需要将数组元素映射到r个桶中,映射完成后还需要收集,相当于遍历数组一遍,最多元素数为n,则时间复杂度为O(n+r)。所以,总的时间复杂度为O(d*(n+r))。
var counter = [];
// 定义一个函数 arr待排序数组 maxDigit数组中最大数的位数,例如[1,10,100]的maxDigit为3
function radixSort(arr, maxDigit) {
var mod = 10;
var dev = 1;
for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
// 把待排序的数组 arr 中的每一位整数,插入对应的容器
for (var j = 0; j < arr.length; j++) {
// 从个位开始,得到数组中每个数的每一位并保存在 bucket 变量中
// bucket 变量的值可能为 0 1 2 3 4 5 6 7 8 9
// 与之对应的 counter[bucket] 容器为 0 1 2 3 4 5 6 7 8 9
var bucket = parseInt((arr[j] % mod) / dev);
// 如果目前 bucket 变量的值对应的 counter[bucket] 容器还不存在(未初始化),
//则创建(初始化)一个新的空容器
if (counter[bucket] == null) {
counter[bucket] = [];
}
// 现在把这个 bucket 变量的值插入对应的 counter[bucket] 容器的尾部
counter[bucket].push(arr[j]);
}
// 把 counter[bucket] 容器里的数依次取出
var pos = 0;
for (var j = 0; j < counter.length; j++) {
// 定义一个变量 value 用于保存conter[j].shift
var value = null;
if (counter[j] != null) {
while ((value = counter[j].shift()) != null) {
arr[pos++] = value;
}
}
}
}
return arr;
}
console.log(radixSort([99, 15, 48, 75, 46, 37, 90, 100], 3));
console.log(radixSort([9, 0, 1, 2, 3, 98, 1], 2));