小哼的班上只有 5 个同学,这 5 个同学分别考了 5 分、3 分、5 分、2 分和 8 分,哎考得真是惨不忍睹(满分是 10 分)。接下来将分数进行从大到小排序,排序后是 8 5 5 3 2。你有没有什么好方法编写一段程序,让计算机随机读入 5 个数然后将这 5 个数从大到小输出?
const numList = [5, 3, 5, 8, 2];
const arr = Array(11).fill(0);
for (let i = 0; i < numList.length; i++) {
arr[numList[i]]++;
}
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr[i]; j++) {
console.log(i);
}
}
代码中第 4 行的循环一共循环了 m 次(m 为桶的个数),第 8 行和第 9 行一共循环了 m+n 次。所以整个排序算法一共执行了 m+m+n 次。我们用大写字母 O 来表示时间复杂度,最终桶排序的时间复杂度为 O(2m+n)。还有一点,在表示时间复杂度的时候,n 和 m 通常用大写字母即 O(M+N)。
将 8, 100, 50, 22, 15, 6, 1, 1000, 999, 0 十个数从小到大排列
const arr = [8, 100, 50, 22, 15, 6, 1, 1000, 999, 0];
function sort(arr) {
for (let j = 0; j < arr.length - 1; j++) {
for (let i = 0; i < arr.length - j; i++) {
if (arr[i] > arr[i + 1]) {
[arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
}
}
}
return arr;
}
console.log(sort(arr));
冒泡排序的核心部分是双重嵌套循环。不难看出冒泡排序的时间复杂度是 O(N 2)。这是一个非常高的时间复杂度。冒泡排序早在 1956 年就有人开始研究,之后有很多人都尝试过对冒泡排序进行改进,但结果却令人失望。如 Donald E. Knuth(中文名为高德纳,1974 年图灵奖获得者)所说:“冒泡排序除了它迷人的名字和导致了某些有趣的理论问题这一事实之外,似乎没有什么值得推荐的。”你可能要问:那还有没有更好的排序算法呢?不要走开,请看下节——快速排序。
将 8, 100, 50, 22, 15, 6, 1, 1000, 999, 0 十个数从小到大排列
const arr = [8, 100, 50, 22, 15, 6, 1, 1000, 999, 0];
function quickSort(arr) {
if (arr.length <= 1) return arr;
// 基准值下标
const numIndex = ~~(arr.length / 2);
// 基准值
const num = arr[numIndex];
// 基准值左侧
const left = [];
// 基准值右侧
const right = [];
arr.forEach((it, idx) => {
if (idx == numIndex) return;
it < num ? left.push(it) : right.push(it);
});
return [...quickSort(left), num, ...quickSort(right)];
}
console.log(quickSort(arr));
快速排序之所以比较快,是因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样只能在相邻的数之间进行交换,交换的距离就大得多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的,都是 O(N2),它的平均时间复杂度为 O (NlogN)。其实快速排序是基于一种叫做“二分”的思想。