归并排序
数据科学家每天都在处理算法。 然而,数据科学学科作为一个整体已经发展成为一个不涉及复杂算法实现的角色。 尽管如此,从业者仍然可以从建立对算法的理解和知识库中受益。
在本文中,对排序算法归并排序进行了介绍、解释、评估和实现。 这篇文章的目的是为您提供有关合并排序算法的可靠背景信息,这些信息可以作为更复杂算法的基础知识。
尽管归并排序被认为并不复杂,但了解该算法将帮助您认识到在选择最有效的算法来执行与数据相关的任务时应考虑哪些因素。 John Von Neumann 创建于 1945 年,他使用分而治之的方法开发了归并排序算法。
分而治之
要理解归并排序算法,您必须熟悉分而治之范式以及递归的编程概念。 计算机科学领域内的递归是指为解决问题而定义的方法涉及在其实现主体中调用自身。
换句话说,该函数重复调用自身。
分而治之算法(合并排序是一种)在其方法中使用递归来解决特定问题。 分而治之算法将复杂问题分解为更小的子部分,其中定义的解决方案递归地应用于每个子部分。 然后分别解决每个子部分,并重新组合解决方案以解决原始问题。
算法设计的分而治之方法结合了三个主要元素:
- 将较大的问题分解为较小的子问题。 (划分)
- 递归利用函数来解决每个较小的子问题。 (征服)
- 最终解决方案是对较大问题的较小子问题的解决方案的组合。 (结合)
其他算法使用分而治之范式,例如快速排序、二分搜索和施特拉森算法。
归并排序
在按升序对列表中的元素进行排序的上下文中,合并排序方法将列表分成两半,然后遍历新的两半,不断将它们进一步细分为更小的部分。
随后,对较小的一半进行比较,并将结果组合在一起形成最终的排序列表。
步骤与实施
合并排序算法的实现是一个三步过程。 划分、征服和结合。
分而治之方法的划分部分是第一步。 此初始步骤将整个列表分成两个较小的部分。 然后,列表被进一步分解,直到它们不能再被分割,在每个减半的列表中只留下一个元素项。
归并排序第二阶段的递归循环关注的是列表的元素按特定顺序排序。 对于这种情况,初始数组按升序排序。
在下图中,您可以看到归并排序算法中涉及的除法、比较和组合步骤。
实现:
- 创建一个名为 merge_sort 的函数,它接受一个整数列表作为其参数。 以下所有说明均在此功能内。
- 首先将列表分成两半。 记录列表的初始长度。
- 检查记录的长度是否等于 1。如果条件评估为真,则返回列表,因为这意味着列表中只有一个元素。 因此,不需要划分列表。
- 获取元素个数大于1的列表的中点。使用Python语言时,
//
除法为无余数。 它将除法结果舍入到最接近的整数。 这也称为楼层划分。 - 使用中点作为参考点,将列表分成两半。 这是分而治之算法范式的划分方面。
- 在此步骤中利用递归来促进将列表划分为一半的组件。 变量“left_half”和“right_half”被分配给“merge_sort”函数的调用,接受初始列表的两半作为参数。
- “merge_sort”函数返回对一个函数的调用,该函数合并两个列表以返回一个组合的、排序的列表。
def merge_sort(list: [int]):list_length = len(list)if list_length == 1:return listmid_point = list_length // 2left_half = merge_sort(list[:mid_point])right_half = merge_sort(list[mid_point:])return merge(left_half, right_half)
- 创建一个“合并”函数,它接受两个整数列表作为其参数。 此函数包含分而治之算法范例的征服和组合方面。 以下所有步骤都在此函数体内执行。
- 将一个空列表分配给保存排序整数的变量“output”。
- 指针“i”和“j”分别用于索引左列表和右列表。
- 在 while 循环中,比较左右列表的元素。 每次比较后,输出列表被填充到两个被比较的元素中。 附加元素列表的指针递增。
- 要添加到排序列表的剩余元素是从当前指针值到相应列表末尾获得的元素。
def merge(left, right):output = []i = j = 0while (i < len(left) and j < len(right)):if left[i] < right[j]:output.append(left[i])i +=1else:output.append(right[j])j +=1output.extend(left[i:])output.extend(right[j:])return outputunsorted_list = [2, 4, 1, 5, 7, 2, 6, 1, 1, 6, 4, 10, 33, 5, 7, 23]
sorted_list = merge_sort(unsorted_list)
print(unsorted_list)
print(sorted_list)
性能和复杂性
大 O 表示法是根据算法的空间要求和执行时间来定义和组织算法性能的标准。
合并排序算法的时间复杂度在其最佳、最差和平均情况下是相同的。 对于大小为 n 的列表,归并排序算法完成的预期步数、最小步数和最大步数都是相同的。
正如本文前面所述,合并排序算法是一个三步过程:分治、合并。 “划分”步骤涉及列表中点的计算,无论列表大小如何,它都需要一个操作步骤。 因此,此操作的符号表示为 O(1)。
“解决”步骤涉及划分和递归求解子数组——符号 log n 表示这一点。 “组合”步骤包括将结果组合成最终列表; 此操作执行时间取决于列表大小并表示为 O(n)。
其平均、最佳和最差时间复杂度的合并排序表示法是 log n * n * O(1)
。 在大 O 表示法中,低阶项和常量可以忽略不计,这意味着归并排序算法的最终表示法是 O(n log n)
。 关于归并排序算法的详细分析,可以参考这篇文章。
评估
合并排序在对大型列表进行排序时表现良好,但在用于较小列表时,其运行时间比其他排序解决方案慢。 合并排序的另一个缺点是即使初始列表已经排序,它也会执行操作步骤。 在排序链表的用例中,归并排序是最快的排序算法之一。 合并排序可用于外部存储系统(如硬盘驱动器)内的文件排序。
要点
本文通过根据其组成操作和逐步过程对其进行分解来描述归并排序技术。
合并排序算法是常用的,与其他排序算法相比,该算法背后的直觉和实现相当简单。 本文包括归并排序算法在Python中的实现步骤。
您还应该知道,合并排序方法在不同情况下的执行时间的时间复杂度,在最佳、最差和平均情况下保持不变。 推荐在以下场景应用归并排序算法:
- 处理较大的数据集时,使用归并排序算法。 与其他排序算法相比,合并排序在小型数组上的表现不佳。
- 链表中的元素引用列表中的下一个元素。 这意味着在合并排序算法操作中,指针是可以修改的,使得元素的比较和插入具有恒定的时间和空间复杂度。
- 以某种形式确定数组未排序。 归并排序甚至会在已排序的数组上执行其操作,这是一种计算资源的浪费。
- 当考虑到数据的稳定性时,使用归并排序。 稳定排序涉及维护数组中相同值的顺序。 与未排序的数据输入相比,稳定排序中整个数组中相同值的顺序在排序后的输出中保持在相同的位置。