6.归并排序
6.1 基础知识
核心思想
归并排序采用分治法的思想,将一个大问题分解为小问题,先解决小问题,然后合并它们的解以得到原始问题的解。具体而言,归并排序将待排序数组分为两个子数组,分别排序后再合并成一个有序数组。可以通俗的理解为天下第一武道大会的形式。分淘汰赛,比完排序战斗力后就合成一个队了。一个个队再继续比,再合并队,直到比完。
算法步骤
- 初始状态:将整个数组递归地分为两半,直到每个子数组只包含一个元素。
- 递归排序:对每个子数组进行递归排序,直至每个子数组都有序。
- 合并操作:合并两个有序子数组,创建一个新的有序数组。这个过程不断重复,直到整个数组有序。
时间复杂度
归并排序的时间复杂度始终为 O(n log n),无论是最好、最坏还是平均情况。这是因为每次合并操作都需要线性时间,而递归树的深度是 log n。
空间复杂度
归并排序需要额外的空间来存储临时数组,其空间复杂度为 O(n)。这使得归并排序不是一个原地排序算法。
稳定性
归并排序是稳定的排序算法。相同元素的相对顺序在排序前后不会改变。
适用场景
由于其稳定性和一贯的 O(n log n) 时间复杂度,归并排序适用于各种规模的数据集。特别是在链表结构中,它比其他排序算法更易于实现。
代码步骤
- MergeSort 函数:接收一个数组进行归并排序。调用 MergeSortRecursive 函数传入数组和数组的起始终止索引,作为归并排序开始的递归入口。
- MergeSortRecursive 函数:接收数组和数组的起始终止索引。如果起始索引小于终止索引,说明有元素需要排序,把数组分成左右两半分别递归调用 MergeSortRecursive 函数得到排序后的左右数组,调用 Merge 函数对左右数组进行合并排序。如果起始索引不小于于终止索引,说明没有元素需要排序,只有一个元素了,直接返回只有该元素的数组。
- Merge 函数:合并左右数组并排序。定义长度是左右数组长度相加的结果数组。定义左右数组和结果数组指针索引。使用 while 循环,比较左右数组的元素,并将较小的元素放入合并数组,直到其中一个数组被完全处理,然后将另一个数组的剩余元素添加到合并数组中。
总结说明
归并排序在各个方面都表现出色,但其对额外空间的需求可能在某些情况下成为限制因素。在需要稳定性和较为均衡的性能时,归并排序是一个可靠的选择。然而,在对空间有严格限制的情况下,可能需要考虑其他排序算法。
6.2 思路框架
static void MergeSort(ref int[] array)
{
调用归并排序递归部分函数MergeSortRecursive,传入数组和起始终止索引,得到排序后的数组
}
static int[] MergeSortRecursive(int[] array, int left, int right)
{
如果左索引小于右索引
{
计算中间索引
对左半部分调用归并排序递归部分函数,传入数组、左索引和中间索引,得到左半部分排序后的数组
对右半部分调用归并排序递归部分函数,传入数组、中间索引+1和右索引,得到右半部分排序后的数组
调用合并函数Merge,合并排序后的左右数组
}
左右索引相等,返回当前元素
}
Merge(int[] leftArray, int[] rightArray)
{
初始化左、右、合并数组的索引
获取左、右数组的长度
初始化合并后的数组 mergedArray,长度为左右数组长度之和
循环直到左或右数组其中一个索引值大于该数组长度后结束
{
若左数组当前元素小于等于右数组当前元素
{
将左数组当前元素放入合并数组中
左数组索引向后移动一位
}
否则
{
将右数组当前元素放入合并数组中
右数组索引向后移动一位
}
合并数组索引向后移动一位
}
若左数组还有剩余元素,则将剩余元素复制到合并数组末尾
若右数组还有剩余元素,则将剩余元素复制到合并数组末尾
返回合并后的数组 mergedArray
}
6.3 纯净代码
static void MergeSort(ref int[] array)
{
array = MergeSortRecursive(array, 0, array.Length - 1);
}
static int[] MergeSortRecursive(int[] array, int left, int right)
{
if (left < right)
{
int middle = (left + right) / 2;
int[] leftSorted = MergeSortRecursive(array, left, middle);
int[] rightSorted = MergeSortRecursive(array, middle + 1, right);
return Merge(leftSorted, rightSorted);
}
return new int[] { array[left] };
}
static int[] Merge(int[] leftArray, int[] rightArray)
{
int leftIndex = 0;
int rightIndex = 0;
int leftLength = leftArray.Length;
int rightLength = rightArray.Length;
int mergedIndex = 0;
int[] mergedArray = new int[leftLength + rightLength];
while (leftIndex < leftLength && rightIndex < rightLength)
{
if (leftArray[leftIndex] <= rightArray[rightIndex])
{
mergedArray[mergedIndex] = leftArray[leftIndex];
leftIndex++;
}
else
{
mergedArray[mergedIndex] = rightArray[rightIndex];
rightIndex++;
}
mergedIndex++;
}
while (leftIndex < leftLength)
{
mergedArray[mergedIndex] = leftArray[leftIndex];
leftIndex++;
mergedIndex++;
}
while (rightIndex < rightLength)
{
mergedArray[mergedIndex] = rightArray[rightIndex];
rightIndex++;
mergedIndex++;
}
return mergedArray;
}
require("Lesson01_SortHelpTool")
-- 归并排序函数
function QuickSort(array)
array = QuickSortRecursive(array, 1, #array)
return array
end
function QuickSortRecursive(array, left, right)
if left < right then
local middle = left + math.floor((right - left) / 2)
local leftSorted = QuickSortRecursive(array, left, middle)
local rightSorted = QuickSortRecursive(array, middle + 1, right)
return Merge(leftSorted, rightSorted)
end
return {array[left]}
end
function Merge(leftArray, rightArray)
local leftIndex = 1;
local rightIndex = 1;
local leftLength = #leftArray;
local rightLength = #rightArray;
local mergedIndex = 1;
local mergedArray = {};
while leftIndex <= leftLength and rightIndex <= rightLength do
if(leftArray[leftIndex] < rightArray[rightIndex] ) then
mergedArray[mergedIndex] = leftArray[leftIndex]
leftIndex = leftIndex +1
else
mergedArray[mergedIndex] = rightArray[rightIndex]
rightIndex = rightIndex +1
end
mergedIndex = mergedIndex+1
end
while leftIndex <= leftLength do
mergedArray[mergedIndex] = leftArray[leftIndex]
leftIndex = leftIndex +1
end
while rightIndex <= rightLength do
mergedArray[mergedIndex] = rightArray[rightIndex]
rightIndex = rightIndex +1
end
return mergedArray
end
SortHelpTool.PrintArray(SortHelpTool.arrayToSort, "排序前")
SortHelpTool.arrayToSort = QuickSort(SortHelpTool.arrayToSort)
SortHelpTool.PrintArray(SortHelpTool.arrayToSort, "排序后")
6.4 带注释代码
using System;
using Lesson01_概述;
class Program
{
static void Main()
{
// 输出排序前的数组
Console.WriteLine("排序前的数组:");
SortHelpTool.PrintArray(SortHelpTool.arrayToSort);
// 调用归并排序函数
MergeSort(ref SortHelpTool.arrayToSort);
// 输出排序后的数组
Console.WriteLine("排序后的数组:");
SortHelpTool.PrintArray(SortHelpTool.arrayToSort);
}
// 归并排序函数
static void MergeSort(ref int[] array)
{
// 调用归并排序辅助函数
array = MergeSortRecursive(array, 0, array.Length - 1, 0);
}
// 归并排序的递归函数,返回吧左右数组合并排序后的数组
static int[] MergeSortRecursive(int[] array, int left, int right, int depth)
{
Console.WriteLine(new String('-', 2 * depth) + $"开始归并排序的递归部分函数,深度是{depth}");
// 如果左边索引小于右边索引,说明还有至少两个元素需要排序
if (left < right)
{
Console.WriteLine(
new String('-', 2 * depth) + $"左边索引小于右边索引,至少有两个元素需要排序,左边索引是{left},右边索引是{right},深度是{depth}");
// 计算中间位置,这将帮助将数组一分为二
int middle = (left + right) / 2;
// 递归对左半部分进行排序
Console.WriteLine(new String('-', 2 * depth) + $"开始递归对左半部分进行排序,左半部分开始索引是{left},终止索引是{middle},深度是{depth}");
int[] leftSorted = MergeSortRecursive(array, left, middle, depth + 1);
Console.WriteLine(new String('-', 2 * depth) + $"结束递归对左半部分进行排序,左半部分开始索引是{left},终止索引是{middle},深度是{depth}");
// 递归对右半部分进行排序
Console.WriteLine(new String('-', 2 * depth) +
$"开始递归对右半部分进行排序,右半部分开始索引是{middle + 1},终止索引是{right},深度是{depth}");
int[] rightSorted = MergeSortRecursive(array, middle + 1, right, depth + 1);
Console.WriteLine(new String('-', 2 * depth) +
$"结束递归对右半部分进行排序,右半部分开始索引是{middle + 1},终止索引是{right},深度是{depth}");
// 合并两个已排序的部分的数组并重新排序
return Merge(leftSorted, rightSorted, depth);
}
// 当左右索引相等时,返回只包含一个元素的数组,表示已排序
Console.WriteLine(
new String('-', 2 * depth) + $"左右索引相等,返回只包含一个元素的数组,表示已排序,索引是{left},值是{array[left]},深度是{depth}");
Console.WriteLine(new String('-', 2 * depth) + $"结束归并排序的递归部分函数,深度是{depth}");
return new int[] { array[left] };
}
// 合并两个已排序的部分的数组并重新排序函数
static int[] Merge(int[] leftArray, int[] rightArray, int depth)
{
Console.WriteLine(new String('-', 2 * depth) + $"开始合并两个已排序的部分的数组并重新排序函数,深度是{depth}");
Console.Write(new String('-', 2 * depth) + $"左数组是:");
SortHelpTool.PrintArray(leftArray);
Console.Write(new String('-', 2 * depth) + $"右数组是:");
SortHelpTool.PrintArray(rightArray);
// 初始化左子数组的索引
int leftIndex = 0;
// 初始化右子数组的索引
int rightIndex = 0;
// 获取左子数组的长度
int leftLength = leftArray.Length;
// 获取右子数组的长度
int rightLength = rightArray.Length;
// 初始化合并后数组的索引
int mergedIndex = 0;
// 创建一个新的数组,用于存储合并后的结果
int[] mergedArray = new int[leftLength + rightLength];
// 归并 leftArray 和 rightArray 到 mergedArray
while (leftIndex < leftLength && rightIndex < rightLength)
{
// 比较左右两个子数组的元素,将较小的元素放入合并后的数组
if (leftArray[leftIndex] <= rightArray[rightIndex])
{
mergedArray[mergedIndex] = leftArray[leftIndex];
leftIndex++;
}
else
{
mergedArray[mergedIndex] = rightArray[rightIndex];
rightIndex++;
}
// 移动合并后数组的索引
mergedIndex++;
}
// 将 leftArray 的剩余部分复制到 mergedArray
while (leftIndex < leftLength)
{
mergedArray[mergedIndex] = leftArray[leftIndex];
// 移动 leftArray 和 mergedArray 的索引
leftIndex++;
mergedIndex++;
}
// 将 rightArray 的剩余部分复制到 mergedArray
while (rightIndex < rightLength)
{
mergedArray[mergedIndex] = rightArray[rightIndex];
// 移动 rightArray 和 mergedArray 的索引
rightIndex++;
mergedIndex++;
}
Console.Write(new String('-', 2 * depth) + $"合并结束,深度是{depth},合并后的数组是:");
SortHelpTool.PrintArray(mergedArray);
Console.WriteLine(new String('-', 2 * depth) + $"结束归并排序的递归部分函数,深度是{depth}");
// 返回合并后的结果数组
return mergedArray;
}
}
6.5 运行结果
排序前的数组:
5 2 8 1 7 3 10 4 6 9
开始归并排序的递归部分函数,深度是0
左边索引小于右边索引,至少有两个元素需要排序,左边索引是0,右边索引是9,深度是0
开始递归对左半部分进行排序,左半部分开始索引是0,终止索引是4,深度是0
–开始归并排序的递归部分函数,深度是1
–左边索引小于右边索引,至少有两个元素需要排序,左边索引是0,右边索引是4,深度是1
–开始递归对左半部分进行排序,左半部分开始索引是0,终止索引是2,深度是1
—-开始归并排序的递归部分函数,深度是2
—-左边索引小于右边索引,至少有两个元素需要排序,左边索引是0,右边索引是2,深度是2
—-开始递归对左半部分进行排序,左半部分开始索引是0,终止索引是1,深度是2
——开始归并排序的递归部分函数,深度是3
——左边索引小于右边索引,至少有两个元素需要排序,左边索引是0,右边索引是1,深度是3
——开始递归对左半部分进行排序,左半部分开始索引是0,终止索引是0,深度是3
——–开始归并排序的递归部分函数,深度是4
——–左右索引相等,返回只包含一个元素的数组,表示已排序,索引是0,值是5,深度是4
——–结束归并排序的递归部分函数,深度是4
——结束递归对左半部分进行排序,左半部分开始索引是0,终止索引是0,深度是3
——开始递归对右半部分进行排序,右半部分开始索引是1,终止索引是1,深度是3
——–开始归并排序的递归部分函数,深度是4
——–左右索引相等,返回只包含一个元素的数组,表示已排序,索引是1,值是2,深度是4
——–结束归并排序的递归部分函数,深度是4
——结束递归对右半部分进行排序,右半部分开始索引是1,终止索引是1,深度是3
——开始合并两个已排序的部分的数组并重新排序函数,深度是3
——左数组是:5
——右数组是:2
——合并结束,深度是3,合并后的数组是:2 5
——结束归并排序的递归部分函数,深度是3
—-结束递归对左半部分进行排序,左半部分开始索引是0,终止索引是1,深度是2
—-开始递归对右半部分进行排序,右半部分开始索引是2,终止索引是2,深度是2
——开始归并排序的递归部分函数,深度是3
——左右索引相等,返回只包含一个元素的数组,表示已排序,索引是2,值是8,深度是3
——结束归并排序的递归部分函数,深度是3
—-结束递归对右半部分进行排序,右半部分开始索引是2,终止索引是2,深度是2
—-开始合并两个已排序的部分的数组并重新排序函数,深度是2
—-左数组是:2 5
—-右数组是:8
—-合并结束,深度是2,合并后的数组是:2 5 8
—-结束归并排序的递归部分函数,深度是2
–结束递归对左半部分进行排序,左半部分开始索引是0,终止索引是2,深度是1
–开始递归对右半部分进行排序,右半部分开始索引是3,终止索引是4,深度是1
—-开始归并排序的递归部分函数,深度是2
—-左边索引小于右边索引,至少有两个元素需要排序,左边索引是3,右边索引是4,深度是2
—-开始递归对左半部分进行排序,左半部分开始索引是3,终止索引是3,深度是2
——开始归并排序的递归部分函数,深度是3
——左右索引相等,返回只包含一个元素的数组,表示已排序,索引是3,值是1,深度是3
——结束归并排序的递归部分函数,深度是3
—-结束递归对左半部分进行排序,左半部分开始索引是3,终止索引是3,深度是2
—-开始递归对右半部分进行排序,右半部分开始索引是4,终止索引是4,深度是2
——开始归并排序的递归部分函数,深度是3
——左右索引相等,返回只包含一个元素的数组,表示已排序,索引是4,值是7,深度是3
——结束归并排序的递归部分函数,深度是3
—-结束递归对右半部分进行排序,右半部分开始索引是4,终止索引是4,深度是2
—-开始合并两个已排序的部分的数组并重新排序函数,深度是2
—-左数组是:1
—-右数组是:7
—-合并结束,深度是2,合并后的数组是:1 7
—-结束归并排序的递归部分函数,深度是2
–结束递归对右半部分进行排序,右半部分开始索引是3,终止索引是4,深度是1
–开始合并两个已排序的部分的数组并重新排序函数,深度是1
–左数组是:2 5 8
–右数组是:1 7
–合并结束,深度是1,合并后的数组是:1 2 5 7 8
–结束归并排序的递归部分函数,深度是1
结束递归对左半部分进行排序,左半部分开始索引是0,终止索引是4,深度是0
开始递归对右半部分进行排序,右半部分开始索引是5,终止索引是9,深度是0
–开始归并排序的递归部分函数,深度是1
–左边索引小于右边索引,至少有两个元素需要排序,左边索引是5,右边索引是9,深度是1
–开始递归对左半部分进行排序,左半部分开始索引是5,终止索引是7,深度是1
—-开始归并排序的递归部分函数,深度是2
—-左边索引小于右边索引,至少有两个元素需要排序,左边索引是5,右边索引是7,深度是2
—-开始递归对左半部分进行排序,左半部分开始索引是5,终止索引是6,深度是2
——开始归并排序的递归部分函数,深度是3
——左边索引小于右边索引,至少有两个元素需要排序,左边索引是5,右边索引是6,深度是3
——开始递归对左半部分进行排序,左半部分开始索引是5,终止索引是5,深度是3
——–开始归并排序的递归部分函数,深度是4
——–左右索引相等,返回只包含一个元素的数组,表示已排序,索引是5,值是3,深度是4
——–结束归并排序的递归部分函数,深度是4
——结束递归对左半部分进行排序,左半部分开始索引是5,终止索引是5,深度是3
——开始递归对右半部分进行排序,右半部分开始索引是6,终止索引是6,深度是3
——–开始归并排序的递归部分函数,深度是4
——–左右索引相等,返回只包含一个元素的数组,表示已排序,索引是6,值是10,深度是4
——–结束归并排序的递归部分函数,深度是4
——结束递归对右半部分进行排序,右半部分开始索引是6,终止索引是6,深度是3
——开始合并两个已排序的部分的数组并重新排序函数,深度是3
——左数组是:3
——右数组是:10
——合并结束,深度是3,合并后的数组是:3 10
——结束归并排序的递归部分函数,深度是3
—-结束递归对左半部分进行排序,左半部分开始索引是5,终止索引是6,深度是2
—-开始递归对右半部分进行排序,右半部分开始索引是7,终止索引是7,深度是2
——开始归并排序的递归部分函数,深度是3
——左右索引相等,返回只包含一个元素的数组,表示已排序,索引是7,值是4,深度是3
——结束归并排序的递归部分函数,深度是3
—-结束递归对右半部分进行排序,右半部分开始索引是7,终止索引是7,深度是2
—-开始合并两个已排序的部分的数组并重新排序函数,深度是2
—-左数组是:3 10
—-右数组是:4
—-合并结束,深度是2,合并后的数组是:3 4 10
—-结束归并排序的递归部分函数,深度是2
–结束递归对左半部分进行排序,左半部分开始索引是5,终止索引是7,深度是1
–开始递归对右半部分进行排序,右半部分开始索引是8,终止索引是9,深度是1
—-开始归并排序的递归部分函数,深度是2
—-左边索引小于右边索引,至少有两个元素需要排序,左边索引是8,右边索引是9,深度是2
—-开始递归对左半部分进行排序,左半部分开始索引是8,终止索引是8,深度是2
——开始归并排序的递归部分函数,深度是3
——左右索引相等,返回只包含一个元素的数组,表示已排序,索引是8,值是6,深度是3
——结束归并排序的递归部分函数,深度是3
—-结束递归对左半部分进行排序,左半部分开始索引是8,终止索引是8,深度是2
—-开始递归对右半部分进行排序,右半部分开始索引是9,终止索引是9,深度是2
——开始归并排序的递归部分函数,深度是3
——左右索引相等,返回只包含一个元素的数组,表示已排序,索引是9,值是9,深度是3
——结束归并排序的递归部分函数,深度是3
—-结束递归对右半部分进行排序,右半部分开始索引是9,终止索引是9,深度是2
—-开始合并两个已排序的部分的数组并重新排序函数,深度是2
—-左数组是:6
—-右数组是:9
—-合并结束,深度是2,合并后的数组是:6 9
—-结束归并排序的递归部分函数,深度是2
–结束递归对右半部分进行排序,右半部分开始索引是8,终止索引是9,深度是1
–开始合并两个已排序的部分的数组并重新排序函数,深度是1
–左数组是:3 4 10
–右数组是:6 9
–合并结束,深度是1,合并后的数组是:3 4 6 9 10
–结束归并排序的递归部分函数,深度是1
结束递归对右半部分进行排序,右半部分开始索引是5,终止索引是9,深度是0
开始合并两个已排序的部分的数组并重新排序函数,深度是0
左数组是:1 2 5 7 8
右数组是:3 4 6 9 10
合并结束,深度是0,合并后的数组是:1 2 3 4 5 6 7 8 9 10
结束归并排序的递归部分函数,深度是0
排序后的数组:
1 2 3 4 5 6 7 8 9 10
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com