算法背景
归并排序(Merge sort)是一种排序算法,它的目的是将一个无序的数组变成有序的。它采用分治法,将原数组不断地分成若干个小数组,直到每个小数组只有一个元素。然后把这些小数组按照顺序合并起来,最终得到一个有序的数组。
归并排序需要额外的空间来存放临时合并的数组,时间复杂度为O(nlogn),空间复杂度为O(n),属于稳定排序算法。
归并排序适用于大数据量排序,因为它具有较高的效率,在排序大数据量的时候比较常用。
归并排序是稳定排序算法,它能保证相同元素之间的顺序不变。
归并排序算法在排序大数据量的时候占有优势,在大数据量的排序场景中是一种非常有用的排序算法。
算法流程
归并排序是一种分治策略,将一个大的排序问题划分成小的子问题来解决。它的基本流程是:
输入:一个无序的整数数组
输出:一个有序的整数数组
算法核心部分的具体实现:
将整个数组分成两个子数组,分别对这两个子数组进行排序
将两个已经排好序的子数组进行合并,合并的时候需要比较两个子数组中的元素,将较小的元素先加入新数组中
重复步骤1和2,直到合并出最终有序数组
归并排序的时间复杂度为 O(nlogn),空间复杂度为 O(n)。因为归并排序需要额外的空间来存储新数组。
时间和空间复杂度
时间复杂度
归并排序是一种分治算法,它将一个数组分为两个子数组,分别对子数组进行排序,最后将排序好的子数组合并起来。
流程如下:
将数组平分成两个子数组
分别对子数组进行排序
将排序好的两个子数组合并
时间复杂度分析:
归并排序的时间复杂度为O(nlogn)。因为每次递归都会将数组平分成两个子数组,这样每次递归的时间复杂度就会减半,最后再将排序好的两个子数组合并,这一步的时间复杂度为O(n)。因此整个算法的时间复杂度为O(nlogn)。
总结:归并排序是一种高效的排序算法,它的时间复杂度为O(nlogn),是一种稳定的排序算法,适用于大规模数据的排序。
空间复杂度
归并排序的空间复杂度为 O(n),因为它需要一个辅助数组来存储归并之后的元素,但这个辅助数组的大小就是原始数组的大小。因此,它需要与数组大小线性相关的空间,而不是数组大小的平方或其他复杂的空间。
归并排序的优缺点
归并排序是一种排序算法,它的思路是将整个序列分成两个子序列,再将子序列分别排序,最后将排序后的子序列合并起来。
优点
时间复杂度为O(n log n),较快速排序算法
实现简单,可以递归实现
稳定排序,相同元素在排序前后相对位置不变
缺点
空间复杂度较高,需要额外的空间来存储子序列
数据规模较小时不如其他算法
适用场景
数据规模较大时,归并排序是一个很好的选择
数据稳定性要求较高时,归并排序是一个不错的选择
限制
空间限制较大时,归并排序不是很适合
数据规模较小时,其他算法更优秀
归并排序并不是在所有情况都是最优秀的排序算法,但是在数据规模较大时,稳定性要求较高,空间限制较小时是一个不错的选择.
示例代码
C++
#include <iostream>
using namespace std;
void merge(int arr[], int left, int mid, int right) {
// 计算两个子序列的长度
int n1 = mid - left + 1;
int n2 = right - mid;
// 创建两个辅助数组
int L[n1], R[n2];
// 拷贝数据到辅助数组
for (int i = 0; i < n1; i++) {
L[i] = arr[left + i];
}
for (int i = 0; i < n2; i++) {
R[i] = arr[mid + 1 + i];
}
// 合并两个子序列
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k++] = L[i++];
} else {
arr[k++] = R[j++];
}
}
// 拷贝剩余元素
while (i < n1) {
arr[k++] = L[i++];
}
while (j < n2) {
arr[k++] = R[j++];
}
}
void mergeSort(int arr[], int left, int right) {
if (left < right) {
// 找到中间位置
int mid = (left + right) / 2;
// 递归排序左半部分
mergeSort(arr, left, mid);
// 递归排序右半部分
mergeSort(arr, mid + 1, right);
// 合并两个子序列
merge(arr, left, mid, right);
}
}
int main() {
int arr[] = {5, 2, 9, 1, 7, 6, 8, 3, 4};
int n = sizeof(arr) / sizeof(arr[0]);
mergeSort(arr, 0, n - 1);
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
return 0;
}
这是一个归并排序算法的C++实现, mergeSort函数是主要的排序函数,它只有两个参数: arr是要排序的数组, left和right是数组的左右边界. 这个算法的思路是不断把整个序列分成两个子序列并对子序列进行排序, 一直到序列的长度为1为止.
具体实现中, 主要有三个函数:
mergeSort(int arr[], int left, int right) : 主要的排序函数, 递归分治,将序列分成两个子序列,分别对子序列进行排序,最后合并两个子序列
merge(int arr[], int left, int mid, int right) : 合并两个子序列的函数, 把两个子序列按照升序合并成一个序列
main() : 主函数, 测试代码
上面的代码中, arr是要排序的数组, left和right是数组的左右边界. 最后的结果会在输出中展示出来.
这个算法的时间复杂度是O(n log n),空间复杂度是O(n),归并排序不是一个原地排序算法,因为它需要额外的空间存储临时数组。在数据规模较大时,空间复杂度可能成为瓶颈。
这个算法的优点是稳定性较高,可以保证相同元素在排序前后相对位置不变,可以用于排序数据有要求的场景。缺点是空间复杂度较高,在数据规模较小时效率不如其他算法。
Python
def merge(left, right):
"""
合并两个有序数组,返回一个新的有序数组
"""
result = []
i = 0
j = 0
# 比较两个数组的元素,把小的元素添加到新数组中
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
# 如果左边数组还有剩余元素,添加到新数组
while i < len(left):
result.append(left[i])
i += 1
# 如果右边数组还有剩余元素,添加到新数组
while j < len(right):
result.append(right[j])
j += 1
return result
def merge_sort(arr):
"""
归并排序
"""
if len(arr) <= 1:
return arr
# 找到中间位置
mid = len(arr) // 2
# 递归排序左半部分
left = merge_sort(arr[:mid])
# 递归排序右半部分
right = merge_sort(arr[mid:])
# 合并两个子序列
return merge(left, right)
# 测试
arr = [5, 2, 9, 1, 7, 6, 8, 3, 4]
print(merge_sort(arr))
这是一个用 python 实现归并排序算法的示例,算法的核心是 merge_sort 函数,它通过递归把整个序列分成两个子序列,对子序列进行排序,最后通过 merge 函数合并两个子序列。
其中 merge 函数是合并两个子序列的函数,它会按照升序的顺序合并两个子序列,返回一个新的有序数组, 合并的过程是比较两个数组的元素, 选择较小的元素添加到新数组中.
merge_sort 函数是主要的排序函数, 通过递归的方式把整个序列分成两个子序列, 分别对子序列进行排序,最后通过 merge 函数合并两个子序列.
最后在main里面进行测试, 调用 merge_sort 函数, 排序后的结果会输出.
这个算法的时间复杂度是O(n log n),空间复杂度是O(n),同样不是一个原地排序算法,需要额外的空间存储临时数组。在数据规模较大时,空间复杂度可能成为瓶颈.
归并排序是一种稳定排序算法,具有很高的稳定性,对于数据有要求的场景是一个很好的选择. 但是在数据规模较小时,由于空间复杂度较高,效率不如其他算法.
JAVA
import java.util.Arrays;
public class MergeSort {
public static void merge(int[] arr, int left, int mid, int right) {
// 计算两个子序列的长度
int n1 = mid - left + 1;
int n2 = right - mid;
// 创建两个辅助数组
int[] L = new int[n1];
int[] R = new int[n2];
// 拷贝数据到辅助数组
for (int i = 0; i < n1; i++) {
L[i] = arr[left + i];
}
for (int i = 0; i < n2; i++) {
R[i] = arr[mid + 1 + i];
}
// 合并两个子序列
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k++] = L[i++];
} else {
arr[k++] = R[j++];
}
}
// 拷贝剩余元素
while (i < n1) {
arr[k++] = L[i++];
}
while (j < n2) {
arr[k++] = R[j++];
}
}
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
// 找到中间位置
int mid = (left + right) / 2;
// 递归排序左半部分
mergeSort(arr, left, mid);
// 递归排序右半部分
mergeSort(arr, mid + 1, right);
// 合并两个子序列
merge(arr, left, mid, right);
}
}
public static void main(String[] args) {
int[] arr = {5, 2, 9, 1, 7, 6, 8, 3, 4};
mergeSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
}
这是一个用 java 实现归并排序算法的示例,算法的核心是 mergeSort 函数,它通过递归把整个序列分成两个子序列,对子序列进行排序,最后通过 merge 函数合并两个子序列。
其中 merge 函数是合并两个子序列的函数,它会按照升序的顺序合并两个子序列.首先会计算出两个子序列的长度,然后创建两个辅助数组,将数据拷贝到辅助数组中,之后进行合并,合并时是按照从小到大的顺序进行比较,选择较小的元素放到新数组中,并且记录下标.
合并完成之后,如果有剩余的元素,就把它们拷贝到新数组中.
mergeSort 函数是主要的排序函数, 通过递归的方式把整个序列分成两个子序列, 分别对子序列进行排序,最后通过 merge 函数合并两个子序列.
最后在main里面进行测试, 调用 mergeSort 函数, 排序后的结果会输出.
这个算法的时间复杂度是O(n log n),空间复杂度是O(n),同样不是一个原地排序算法,需要额外的空间存储临时数组。
适用场景
归并排序是一种稳定的排序算法,它可以用来对数据进行排序,也可以用来合并两个有序的数组。
适用场景:
解决具体问题:
例子:
总的来说,归并排序是一种非常通用的算法,在很多场景下都可以使用,特别是对于大数据量和需要稳定排序的场景下都是非常好的选择.
正面例子
假设你有一个电商平台,需要对用户的订单数据进行排序。由于数据量非常大,超过了千万条订单,不能使用冒泡排序或者插入排序。你可以使用归并排序来解决这个问题。
你可以把这些订单按照时间戳分成若干个小的文件,对每一个文件使用归并排序算法进行排序。然后再把这些有序的文件合并起来,就能得到一个有序的订单列表。这样就能按照时间戳顺序排列所有订单。
由于归并排序是稳定排序算法,所以如果有多个订单的时间戳相同,它们的相对顺序不会改变。这样就能保证用户下单的先后顺序。
这样的实现方法不仅保证了排序的正确性,而且还能利用分治思想,把大量数据分成小块进行排序,减少内存和时间的消耗。这样就能在可接受的时间内对所有订单进行排序。
另外,由于归并排序是稳定算法,所以能保证相同时间戳的订单的相对顺序不变,这样更加符合实际的需求.
总的来说,这个例子说明了归并排序在大数据量的情况下的优秀表现,同时也说明了归并排序在稳定性上的优秀表现。
反面例子
假设你需要对一个很小的数组进行排序,比如只有5个元素。使用归并排序可能会带来额外的时间和空间的浪费,因为归并排序的时间复杂度是O(n log n),空间复杂度是O(n),而对于这么小的数组其实可以使用更简单的排序算法,如插入排序,冒泡排序等。
另外,归并排序不是一个原地排序算法,需要额外的空间存储临时数组,对于空间有限的系统来说,这可能是一个问题.
总的来说,对于小数据量的排序,归并排序可能并不是最优秀的选择,因为它带来了额外的时间和空间的浪费.