你只管努力,其他交给时间,时间会证明一切。
文章标记颜色说明:
- 黄色:重要标题
- 红色:用来标记结论
- 绿色:用来标记一级论点
- 蓝色:用来标记二级论点
决定开一个算法专栏,希望能帮助大家很好的了解算法。主要深入解析每个算法,从概念到示例。
我们一起努力,成为更好的自己!
今天第3讲,讲一下排序算法的归并排序(Merge Sort)
排序算法是很常见的一类问题,主要是将一组数据按照某种规则进行排序。
以下是一些常见的排序算法:
冒泡排序(Bubble Sort)
插入排序(Insertion Sort)
选择排序(Selection Sort)
归并排序(Merge Sort)
快速排序(Quick Sort)
堆排序(Heap Sort)
归并排序(Merge Sort)是一种基于分治思想的排序算法,它将待排序的数组分成两部分,分别对这两部分递归地进行排序,最后将两个有序子数组合并成一个有序数组。它的时间复杂度为 O(nlogn)。
归并排序的基本思路是将待排序的数组分成两个部分,分别对这两部分进行排序,然后将排好序的两部分合并成一个有序数组。这个过程可以用递归来实现。具体的实现步骤如下:
分解:将待排序的数组不断分成两个子数组,直到每个子数组只有一个元素为止。
合并:将相邻的两个子数组合并成一个有序数组,直到最后只剩下一个有序数组为止。
合并的过程中,需要用到一个辅助数组来暂存合并后的有序数组。具体来说,假设待合并的两个有序数组分别为 A 和 B,它们的长度分别为 n 和 m,合并后的有序数组为 C,那么合并的过程可以按如下步骤进行:
定义三个指针 i、j 和 k,分别指向数组 A、B 和 C 的起始位置。
比较 A[i] 和 B[j] 的大小,将小的元素放入 C[k] 中,并将对应指针向后移动一位。
重复步骤 2,直到其中一个数组的元素全部放入 C 中。
将另一个数组中剩余的元素放入 C 中。
归并排序的优点是稳定性好,即对于相等的元素,在排序前后它们的相对位置不会改变。缺点是需要额外的空间来存储辅助数组。
以下是一个示例,演示了如何使用归并排序对一个数组进行排序:
假设要对数组
[5, 2, 4, 6, 1, 3]
进行排序。
首先将数组分成两部分:
[5, 2, 4]
和[6, 1, 3]
。对左右两部分分别递归调用归并排序。对于左半部分,继续进行分解,将其分成两部分:
[5]
和[2, 4]
。对于右半部分,也进行相同的操作,将其分成两部分:[6]
和[1, 3]
。对于
[5]
和[2, 4]
,由于它们的长度都小于等于 1,因此直接返回它们本身。对于[6]
和[1, 3]
,同样返回它们本身。接下来将排好序的左右两部分合并成一个有序数组。对于左半部分,由于它只有一个元素,因此可以直接将其作为有序数组。对于右半部分,需要将
[1, 3]
进行排序,排序后得到[1, 3, 6]
。将排好序的左右两部分合并成一个有序数组。对于左半部分,指针 i 指向其起始位置,即 0;对于右半部分,指针 j 指向其起始位置,即 0。比较左右两部分的元素大小,发现左半部分的第一个元素 5 大于右半部分的第一个元素 1,因此将 1 添加到新的数组 sorted_arr 中,并将右半部分的指针 j 向后移动一位。此时,sorted_arr 的内容为
[1]
。接着比较左半部分的第二个元素 2 和右半部分的第一个元素 3,发现左半部分的元素较小,因此将 2 添加到 sorted_arr 中,并将左半部分的指针 i 向后移动一位。此时,sorted_arr 的内容为[1, 2]
。接着继续比较左右两部分的元素大小,将它们依次添加到 sorted_arr 中。最终得到排好序的数组[1, 2, 3, 5, 6]
。因此,对于输入的数组
[5, 2, 4, 6, 1, 3]
,使用归并排序后得到的排好序的数组为[1, 2, 3, 4, 5, 6]
。
归并排序的时间复杂度为 O(nlogn),其中 n 是待排序数组的长度。
这个复杂度可以通过分治的思想来解释。
首先将待排序的数组分成两部分,对每一部分递归调用归并排序,然后将两部分合并成一个有序数组。
每次递归调用都将数组的长度减半,因此需要进行 logn 次递归调用。在每个递归层次中,需要将两个有序数组合并成一个有序数组,这一过程需要线性时间 O(n)。因此,归并排序的总时间复杂度为 O(nlogn)。
归并排序的空间复杂度为 O(n),其中 n 是待排序数组的长度。在排序过程中,需要使用一个辅助数组来存储合并后的有序数组。
这个辅助数组的长度等于待排序数组的长度,因此归并排序的空间复杂度为 O(n)。如果实现中使用链表来存储数据,空间复杂度可以降低为 O(1)。
归并排序的应用场景比较广泛,主要适用于以下几种情况:
对于大规模的数据排序:归并排序的时间复杂度为 O(nlogn),相比于其他排序算法如冒泡排序、插入排序等,它在处理大规模数据时更加高效。
对于稳定排序的需求:归并排序是一种稳定排序算法,即对于相等的元素,在排序前后它们的相对位置不会改变。
对于需要保证排序稳定性的需求:归并排序是一种基于比较的排序算法,不依赖于数据的初始状态,具有较好的稳定性。
对于需要多路排序的需求:归并排序可以轻松地扩展到多路排序,即将待排序的数组分成多个子数组,对每个子数组分别进行归并排序,然后将它们合并成一个有序数组。
对于需要外部排序的需求:归并排序可以应用于外部排序,即在排序过程中将数据存储在外部存储器中,而不是在内存中。在外部排序中,需要使用多路归并排序来合并不同的子文件。
总的来说,归并排序是一种高效、稳定的排序算法,适用于大规模数据的排序、需要保证排序稳定性的需求以及外部排序等场景。
以下是使用 Python 实现归并排序的完整代码:
def merge_sort(arr):
if len(arr) <= 1:
return arr
# 将数组分成两个部分
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
# 对左右两部分分别递归调用归并排序
left_half = merge_sort(left_half)
right_half = merge_sort(right_half)
# 合并左右两部分
return merge(left_half, right_half)
def merge(left_half, right_half):
i = j = 0
merged = []
# 比较左右两部分的元素,将较小的元素添加到 merged 中
while i < len(left_half) and j < len(right_half):
if left_half[i] < right_half[j]:
merged.append(left_half[i])
i += 1
else:
merged.append(right_half[j])
j += 1
# 将左右两部分中剩余的元素添加到 merged 中
merged += left_half[i:]
merged += right_half[j:]
return merged
这个实现使用了两个函数,一个是
merge_sort()
函数,用于进行递归调用,另一个是merge()
函数,用于合并两个有序数组。下面对这两个函数进行详细讲解:
merge_sort()
函数
def merge_sort(arr):
if len(arr) <= 1:
return arr
# 将数组分成两个部分
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
# 对左右两部分分别递归调用归并排序
left_half = merge_sort(left_half)
right_half = merge_sort(right_half)
# 合并左右两部分
return merge(left_half, right_half)
```
这个函数使用递归的方式对数组进行排序。对于输入的数组,首先判断其长度是否小于等于 1,如果是,则直接返回该数组。否则,将数组分成两个部分,分别对左半部分和右半部分递归调用 `merge_sort()` 函数。最后,将排好序的左右两部分合并成一个有序数组,并将其作为结果返回。需要注意的是,此处的 `merge()` 函数是在 `merge_sort()` 函数中调用的,因为只有在递归到最底层时才会对单个元素进行排序,而在其他情况下需要将数组分成两部分进行递归调用。
merge()
函数
def merge(left_half, right_half):
i = j = 0
merged = []
# 比较左右两部分的元素,将较小的元素添加到 merged 中
while i < len(left_half) and j < len(right_half):
if left_half[i] < right_half[j]:
merged.append(left_half[i])
i += 1
else:
merged.append(right_half[j])
j += 1
# 将左右两部分中剩余的元素添加到 merged 中
merged += left_half[i:]
merged += right_half[j:]
return merged
```
这个函数用于合并两个有序数组。在函数内部,使用两个指针 i 和 j 分别指向左右两部分的起始位置,以及一个新的数组 merged 来存储合并后的结果。合并的过程中,不断比较左右两部分的元素大小,并将较小的元素加入 merged 中。最后,将左右两部分中剩余的元素添加到 merged 中,最终返回 merged。
在实现归并排序时,需要注意以下几点:
判断数组长度是否小于等于 1:这一步是递归调用的终止条件,防止出现无限递归的情况。
将数组分成两部分:需要使用 Python 的切片操作来实现,将数组分成左右两部分。
对左右两部分进行递归调用:将左右两部分作为参数传入
merge_sort()
函数并进行递归调用,直到数组长度小于等于 1。合并两个有序数组:使用
merge()
函数将排好序的左右两部分合并成一个有序数组。
在使用上述代码实现归并排序时,可以通过以下代码测试:
arr = [3, 5, 1, 9, 7, 2, 8, 4, 6]
print(merge_sort(arr)) # 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9]
这个例子中,将一个无序的数组作为输入,调用
merge_sort()
函数进行排序,并输出排好序的结果。总的来说,这个实现是一种简单而清晰的归并排序实现方式,适合初学者学习和理解。虽然这个实现的时间复杂度为 O(nlogn),但其空间复杂度为 O(n),因为在合并过程中需要额外的空间来存储排好序的元素,因此在处理大规模数据时可能会占用较多的内存。
以下是使用 Java 实现归并排序的代码:
public class MergeSort { public static void main(String[] args) { int[] arr = {3, 5, 1, 9, 7, 2, 8, 4, 6}; mergeSort(arr, 0, arr.length - 1); System.out.println(Arrays.toString(arr)); // 输出 [1, 2, 3, 4, 5, 6, 7, 8, 9] } public static void mergeSort(int[] arr, int left, int right) { if (left >= right) { return; } int mid = (left + right) / 2; mergeSort(arr, left, mid); mergeSort(arr, mid + 1, right); merge(arr, left, mid, right); } public static void merge(int[] arr, int left, int mid, int right) { int[] temp = new int[right - left + 1]; int i = left, j = mid + 1, k = 0; while (i <= mid && j <= right) { if (arr[i] < arr[j]) { temp[k++] = arr[i++]; } else { temp[k++] = arr[j++]; } } while (i <= mid) { temp[k++] = arr[i++]; } while (j <= right) { temp[k++] = arr[j++]; } for (int m = 0; m < temp.length; m++) { arr[left + m] = temp[m]; } } }
这个实现也使用了两个函数,一个是
mergeSort()
函数,用于进行递归调用,另一个是merge()
函数,用于合并两个有序数组。下面对这两个函数进行详细讲解:
mergeSort()
函数public static void mergeSort(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
这个函数使用递归的方式对数组进行排序。
对于输入的数组和左右下标,首先判断左下标是否大于等于右下标,如果是,则直接返回。否则,将数组分成两个部分,分别对左半部分和右半部分递归调用 `mergeSort()` 函数。
最后,将排好序的左右两部分合并成一个有序数组,并将其作为结果返回。需要注意的是,此处的 `merge()` 函数是在 `mergeSort()` 函数中调用的,因为只有在递归到最底层时才会对单个元素进行排序,而在其他情况下需要将数组分成两部分进行递归调用。
merge()
函数public static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
if (arr[i] < arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (int m = 0; m < temp.length; m++) {
arr[left + m] = temp[m];
}
}
这个函数用于合并两个有序数组。在函数内部,使用两个指针 i 和 j 分别指向左右两部分的起始位置,以及一个新的数组 temp 来存储合并后的结果。
合并的过程中,不断比较左右两部分的元素大小,并将较小的元素加入 temp 中。
最后,将左右两部分中剩余的元素添加到 temp 中,最终将 temp 中的元素复制回原数组中。
需要注意的是,在复制回原数组时,需要计算出每个元素在原数组中的位置。
这个归并排序的实现是比较基础的,但是足以演示归并排序的算法思想和实现过程。当然,实际应用中可能需要对代码进行一些优化,比如可以对小数组使用插入排序来提高效率,或者使用迭代的方式来避免递归调用带来的额外开销。
图书名称:
- 精通Hadoop3
- pandas1.X实例讲解
- 人人都离不开的算法——图解算法应用
- Python数据清洗
618,清华社 IT BOOK 多得图书活动开始啦!活动时间为 2023 年 6 月 7 日至 6 月 18 日,清华社为您精选多款高分好书,涵盖了 C++、Java、Python、前端、后端、数据库、算法与机器学习等多个 IT 开发领域,适合不同层次的读者。全场 5 折,扫码领券更有优惠哦!快来京东点击链接 IT BOOK 多得,查看详情吧!
活动链接:IT BOOK
图书数量:本次送出 3 本 !!!⭐️⭐️⭐️
活动时间:截止到 2023-06-15 12:00:00抽奖方式:
- 评论区随机挑选小伙伴!
留言内容,以下方式都可以:
- 文章高质量评论+【你想要的书名】
- 要相信,所有的美好都是为了迎接美好,所有的困难都会为努力让道+【你想要的书名】
参与方式:关注博主、点赞、收藏,评论区留言
获奖名单
中奖名单:请关注博主动态
名单公布时间:2023-06-15 下午