2.2.12 #
解答 #
中文版的翻译比较难理解。
实际上就是另一种归并排序的实现方式。
先把数组分成若干个大小为 M 的块 。
对于每个块,用选择排序进行排序 。
随后遍历数组,将各个块归并起来。
归并时仅复制右侧数组就够了,然后倒着归并(从右到左),可以将额外空间降到 M。
具体归并流程如下:
- 复制右侧数组到
aux
,现在右侧数组a[hi]~a[mid+1]
中的元素可以被安全覆盖。 - 设定指针
i,j,k
,将数组a[mid]~a[0]
和aux[hi-mid-1]~aux[mid + 1]
归并到a[hi - 1]~a[0]
。
在这个流程中左侧数组的指针i
是不会大于归并的写入指针k
的。
最坏情况下,aux
用尽时 i == k
,左侧数组可以直接并上去。
代码 #
public class MergeSort : BaseSort
{
/// <summary>
/// 利用归并排序将数组按升序排序。
/// </summary>
/// <typeparam name="T">数组元素类型。</typeparam>
/// <param name="a">待排序的数组。</param>
public override void Sort<T>(T[] a)
{
Sort(a, 1);
}
/// <summary>
/// 利用分块法进行归并排序。
/// </summary>
/// <typeparam name="T">待排序的数组内容。</typeparam>
/// <param name="a">待排序的数组。</param>
/// <param name="m">分块大小。</param>
public void Sort<T>(T[] a, int m) where T : IComparable<T>
{
var blockNum = (a.Length + m - 1) / m;
var selection = new SelectionSort();
// 对块进行选择排序。
for (var i = 0; i < blockNum; i++)
{
var lo = i * m;
var hi = Math.Min((i + 1) * m - 1, a.Length - 1);
selection.Sort(a, lo, hi);
}
// 将各个块合并。
for (var i = 0; i < blockNum - 1; i++)
{
Merge(a, 0, (i + 1) * m - 1, Math.Min((i + 2) * m - 1, a.Length - 1));
}
}
/// <summary>
/// 将指定范围内的元素归并。
/// </summary>
/// <typeparam name="T">数组元素类型。</typeparam>
/// <param name="a">原数组。</param>
/// <param name="lo">范围起点。</param>
/// <param name="mid">范围中点。</param>
/// <param name="hi">范围终点。</param>
private void Merge<T>(T[] a, int lo, int mid, int hi) where T : IComparable<T>
{
var aux = new T[hi - lo + 1];
for (var k = lo; k <= hi; k++)
{
aux[k] = a[k];
}
int i = lo, j = mid + 1;
for (var k = lo; k <= hi; k++)
{
if (i > mid)
{
a[k] = aux[j];
j++;
}
else if (j > hi)
{
a[k] = aux[i];
i++;
}
else if (Less(aux[j], aux[i]))
{
a[k] = aux[j];
j++;
}
else
{
a[k] = aux[i];
i++;
}
}
}
}