今天,为大家介绍三种常见的排序算法:冒泡排序、选择排序和归并排序。我将列举它们的用途,以及如何在 JavaScript 中实现它们。
排序介绍
当我们考虑排序时,我们会考虑数字的排序或按字母顺序对字符串进行排序,或者在现实世界中,我们可能会考虑按身高、年龄等对人进行排序。在面试环境中,当我们被问及关于排序,我们可以假设它是关于一个数字数组。通常,我们需要编写一些代码来获取这些数字,并将它们从最小到最大排序。
[3, 8, 5, 9, 1, 2] -> [1, 2, 3, 5, 8, 9]
如果您是 JavaScript 开发人员,可能会想到sort()
数组方法。
console.log([3, 8, 5, 9, 1, 2].sort());
// [ 1, 2, 3, 5, 8, 9 ]
根据您使用的浏览器,此内置sort()
方法的底层算法会有所不同。浏览器有不同的JavaScript 引擎,这是一种执行 JavaScript 代码的方式。例如,Chrome 使用V8 引擎,而 Mozilla Firefox 使用SpiderMonkey。Mozilla 使用归并排序,而 V8 使用Timsort(本文不讨论)。
我们如何在不使用内置方法的情况下实现我们自己的排序列表的sort()
方法?
让我们看一下一些常见的排序算法。
时间复杂度
查看最坏情况下的运行时间,我们发现冒泡排序和选择排序都有N^2
运行时间。这意味着对于添加到要排序的数字集合中的每个额外元素,算法将花费更多的时间来运行。这意味着冒泡排序和选择排序是用于大型数据集的糟糕算法。但是,如果您正在处理较小的数据集,并且您知道它仍将是一个小数据集,那么使用这些算法没有任何问题。
一般来说,当我们考虑排序算法时,我们通常会假设最坏的情况n*log(n)
。
使用 JavaScript 进行冒泡排序
冒泡排序是一种排序算法,它逐个元素地循环遍历输入列表,将当前元素与后面的元素进行比较,并在需要时交换它们的值。它实际上不是一个有用的算法,除了用于介绍排序算法的目的之外——由于它的简单性。
冒泡排序是使用 JavaScript 解决问题的更简单的方法之一。我们将从以下代码开始:
function bubbleSort(arr) {
// your code here
}
bubbleSort([3, 4, 9, 5, 1]);
查看输入,我们看到arr
,它是数组的缩写。arr
将是一个数字数组。与我们将在本文中实现的所有算法一样,我们的目标是将这个数字数组从最小到最大排序。
我们要采取的第一步是遍历数组。
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) {}
}
bubbleSort([3, 4, 9, 5, 1]);
我们使用经典for
循环来访问数组中每个位置的索引和元素。
接下来,我们要编写一个内部 for 循环,它将从索引 0 开始循环,一直循环到数组的长度。数组长度总是从索引偏移 1,因为数组的索引为 0,因此-1
将确保我们的代码不会在不应该迭代的地方迭代。
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {}
}
}
bubbleSort([3, 4, 9, 5, 1]);
在我们的for
循环中,我们将要编写一些逻辑来检查数组中的当前元素和下一个元素。
if (arr[j] > arr[j + 1]) {
const lesser = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = lesser;
}
if
声明内容如下:if the current element at the current index is greater than the next one
,我们将在此处交换值。
注意:最后一步不需要自己的代码块。当然,我们会想要return
排序的数组。检查下面的完整实现。
实施
function bubbleSort(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
const lesser = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = lesser;
}
}
}
// return the sorted array
return arr;
}
console.log(bubbleSort([3, 4, 9, 3, 1]));
使用 JavaScript 进行选择排序
选择排序是一种就地比较排序算法,具有n^2
时间复杂度,正如我们上面所说,这使得它在大型项目列表上效率低下。它以简单着称,并且在特定情况下确实比复杂算法具有性能提升。
选择排序动画。红色是电流最小值。黄色是排序列表。蓝色是当前项目。资料来源:维基百科。
为了了解如何实现这个算法,让我们稍微考虑一下。我们知道我们需要比较数组中的元素,所以我们可能需要两个for
循环。
该算法的第一步是for
在整个数组中运行一个外部循环:
function selectionSort(arr) {
for (let i = 0; i < arr.length; i++) {}
}
我们还想声明一个变量,indexMin
并将其分配给i
,假设它是数组中的最小元素。
function selectionSort(arr) {
for (let i = 0; i < arr.length; i++) {
let indexMin = i;
}
}
接下来,我们将设置另一个for循环来验证indexMin
确实是数组中的最小元素,但是if
我们看到一个元素更少,我们将通过存储它来记录它的索引indexMin
,如下所示:
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[indexMin]) {
indexMin = j;
}
}
我们算法的最后一步是检查是否indexMin
等于i
。请记住,我们假设这i
是上面的最低值。如果它们不相等,它们将被交换。
if (indexMin !== i) {
let lesser = arr[indexMin];
arr[indexMin] = arr[i];
arr[i] = lesser;
}
就是这样。我们有一个选择排序的实现。但不要忘记——最后一步是return
数组 ( arr
)。请参阅下面的完整实现。
实施
function selectionSort(arr) {
for (let i = 0; i < arr.length; i++) {
let indexMin = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[indexMin]) {
indexMin = j;
}
}
if (indexMin !== i) {
let lesser = arr[indexMin];
arr[indexMin] = arr[i];
arr[i] = lesser;
}
}
return arr;
}
console.log(selectionSort([3, 4, 9, 3, 1]));
使用 JavaScript 合并排序
Merge Sort 是John Von Neumann发明的一种算法,他是一位杰出的数学家、物理学家、计算机科学家、工程师和博学家。与上述两种算法不同,归并排序是一种高效的通用排序算法。它通过递归排序(我们在这篇 Fibonacci 文章中稍微介绍了递归)将数组分成多个较小的数组,这些数组经过排序然后组合它们。我们将使用两个独立的函数,它们都有不同的用途。
简而言之,归并排序:
- 将未排序的列表分成 n 个子列表,每个子列表包含一个元素(一个元素的列表被认为是已排序的)。
- 重复合并子列表以产生新的排序子列表,直到只剩下一个子列表。这将是排序列表。
归并排序的一个例子。资料来源:维基百科。
提示:合并排序是Cracking the Coding Interview 的作者Gayle Laakmann McDowell推荐了解的必备算法之一。
让我们看看如何在 JavaScript 中实现归并排序。
第 1 步:合并
主要功能mergeSort()
将进行递归调用,因此我们不会逐行输入此算法,而是要分别研究每个功能 - 因为代码很棘手且很难理解。
merge()
比较简单,我们先来看一下。
请注意,merge()
接受两个参数,left
和right
。left
并且right
是两个单独的排序数组值。本质上,我们merge()
函数的目标是获取这两个排序数组并将它们合并在一起。
您可能已经在考虑实现这一目标的方法。如果您想声明一个变量并将其设置为一个空数组,那么您就走对了!让我们看一下merge()
函数。
// merge()
function merge(left, right) {
const results = [];
while (left.length && right.length) {
if (left[0] < right[0]) {
results.push(left.shift());
} else {
results.push(right.shift());
}
}
return [...results, ...left, ...right];
}
稍微检查一下上面的代码块。这里没有发生太复杂的事情,函数也不包含太多代码。让我们尝试运行它。将代码保存到可以运行的地方,console.log(merge([3, 2, 1], [9, 2, 3]))
在函数后面添加。如果你运行这个,你会看到两个数组,left
和right
已经合并,返回一个单一的数组:[ 3, 2, 1, 9, 2, 3 ]
。
为了更好地理解我们的代码在上面做了什么,这些链接可能对您有用:
="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push">.push()方法
"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift">.shift()方法
- 传播语法
第二步:归并排序
到目前为止,我们已经实现了算法的一部分。是时候通过实施来完成它了mergeSort()
。
在您花一些时间了解该merge()
功能后,请查看下面的功能,mergeSort()
。这就是事情会变得混乱的地方。
// mergeSort()
function mergeSort(arr) {
if (arr.length === 1) {
return arr;
}
const center = Math.floor(arr.length / 2);
const left = arr.slice(0, center);
const right = arr.slice(center);
return merge(mergeSort(left), mergeSort(right));
}
首先,让我们实际测试一下我们的代码。
console.log(mergeSort([4, 3, 2, 1]))
->[ 1, 2, 3, 4 ]
它有效,但这是怎么回事?
如果我们放慢速度并稍微考虑一下,我们就会发现我们必须获取arr
参数,将其拆分为两个,然后将其传递给merge()
函数(但只有在没有任何东西可以拆分时)。
让我们看看这三个变量在做什么。
const center = Math.floor(arr.length / 2);
const left = arr.slice(0, center);
const right = arr.slice(center);
console.log(center, left, right);
// 2 [ 4, 3 ] [ 2, 1 ]
// 1 [ 4 ] [ 3 ]
// 1 [ 2 ] [ 1 ]
所以,正如我们上面所说,我们的代码应该将数组分成两半,直到它不能再这样做为止,此时数组将被传递给merge()
函数。由于数组中只有 4 个元素,因此它将在中间拆分一次,然后在left
和上再次拆分right
。我们将剩下[4, 3]
and [2, 1]
,它将作为 传递到 merge 中merge([4, 3], [2, 1])
。
递归本身就很混乱,我们知道这个算法可能会让你摸不着头脑。但需要一些时间来试验它。研究它,甚至记住它。
实施
function mergeSort(arr) {
if (arr.length === 1) {
return arr;
}
const center = Math.floor(arr.length / 2);
const left = arr.slice(0, center);
const right = arr.slice(center);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right) {
const results = [];
while (left.length && right.length) {
if (left[0] < right[0]) {
results.push(left.shift());
} else {
results.push(right.shift());
}
}
return [...results, ...left, ...right];
}
console.log(mergeSort([3, 4, 9, 3, 1]));
结论
这是一篇很长的文章,如果您已经读到这里,请拍拍自己的背!我们探索了三种不同的排序算法,并用 JavaScript 编写了它们。您可能还想查看许多其他排序算法,例如快速排序、插入排序等等。您可以在网络的其他区域探索它们。您还可以在右侧订阅我们的时事通讯,以便在我们编写更多有关 JavaScript 算法的文章时保持更新。