合并排序是一种经典的“分而治之”排序算法。 您应该永远不必编写一个,因为当标准库类已经为您完成时,您这样做就很愚蠢了。 但是,展示Scala编程技术的一些特性很有用。 首先,快速回顾一下合并排序。 这是一个分而治之的算法。 元素列表分为越来越小的列表。 当列表具有一个元素时,将被视为已排序。 然后将其与旁边的列表合并。 如果没有更多列表要合并,则认为原始数据集已排序。 现在,让我们看看如何使用Java中的命令式方法来做到这一点。
public void sort(int[] values) {
int[] numbers = values;
int[] auxillaryNumbers = new int[values.length];
mergesort(numbers, auxillaryNumbers, 0, values.length - 1);
}
private void mergesort(int [] numbers, int [] auxillaryNumbers, int low, int high) {
// Check if low is smaller then high, if not then the array is sorted
if (low < high) {
// Get the index of the element which is in the middle
int middle = low + (high - low) / 2;
// Sort the left side of the array
mergesort(numbers, auxillaryNumbers, low, middle);
// Sort the right side of the array
mergesort(numbers, auxillaryNumbers, middle + 1, high);
// Combine them both
// Alex: the first time we hit this when there is min difference between high and low.
merge(numbers, auxillaryNumbers, low, middle, high);
}
}
/**
* Merges a[low .. middle] with a[middle..high].
* This method assumes a[low .. middle] and a[middle..high] are sorted. It returns
* a[low..high] as an sorted array.
*/
private void merge(int [] a, int[] aux, int low, int middle, int high) {
// Copy both parts into the aux array
for (int k = low; k <= high; k++) {
aux[k] = a[k];
}
int i = low, j = middle + 1;
for (int k = low; k <= high; k++) {
if (i > middle) a[k] = aux[j++];
else if (j > high) a[k] = aux[i++];
else if (aux[j] < aux[i]) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
public static void main(String args[]){
...
ms.sort(new int[] {5, 3, 1, 17, 2, 8, 19, 11});
...
}
}
讨论中…
- 辅助数组用于实现排序。 要排序的元素被复制到其中,然后一旦排序就被复制回去。 重要的是,此阵列只能创建一次,否则创建的大量阵列可能会降低性能。 merge方法不必创建辅助数组,但是由于它会更改对象,因此意味着merge方法具有副作用。
- 合并排序big(O)性能为N logN。
现在让我们来看看Scala解决方案。
def mergeSort(xs: List[Int]): List[Int] = {
val n = xs.length / 2
if (n == 0) xs
else {
def merge(xs: List[Int], ys: List[Int]): List[Int] =
(xs, ys) match {
case(Nil, ys) => ys
case(xs, Nil) => xs
case(x :: xs1, y :: ys1) =>
if (x < y) x::merge(xs1, ys)
else y :: merge(xs, ys1)
}
val (left, right) = xs splitAt(n)
merge(mergeSort(left), mergeSort(right))
}
}
关键讨论点:
- 这是相同的分而治之的思想。
- splitAt函数用于每次将数据分成一个元组。 对于每次递归,这将创建一个新的元组。
- 然后使用本地函数合并来执行合并。 局部函数是一个有用的功能,因为它们有助于促进封装并防止代码膨胀。
- 不论mergeSort()还是merge()函数都有任何副作用。 他们不更改任何对象。 他们创建(并丢弃)对象。
- 因为没有跨合并的迭代传递数据,所以不需要传递开始和结束的指针,而这些指针可能会变得非常麻烦。
- 此合并递归使用模式匹配在这里效果很好。 不仅存在数据列表的匹配,而且发生匹配时,数据列表也被分配给变量:
-
x
表示左侧列表中的顶部元素 -
xs1
其余列表 -
y
表示右边列表中的顶部元素 -
ys1
表示右边列表中的其余数据
-
这样可以轻松比较顶部的元素,并传递剩余的日期进行比较。 Java中是否可以使用迭代方法? 当然。 但这将更加复杂。 您没有任何模式匹配,也无法像Scala那样通过使对象变为val或var来声明对象是不可变的。 在Java中,如果以命令式样式完成对象遍历循环的迭代而更改代码,那么阅读此问题的代码总是比较容易的。 但是Scala的功能性递归方法可能非常简洁。 因此,在这里我们看到一个示例,该示例说明了Scala如何使更容易实现良好,干净,简洁的递归并使功能性方法变得更加可行。
翻译自: https://www.javacodegeeks.com/2013/05/how-could-scala-do-a-merge-sort.html