归并排序
归并排序(Merge Sort)是一种基于分治思想的有效、稳定、且采用递归实现的排序算法。
它的基本思想是将一个待排序的数组分成若干个子数组,分别对每个子数组进行排序,然后再将已排序的子数组合并成一个有序的数组。归并排序的时间复杂度为O(n log n),且具有稳定性,即不会改变相等元素的相对顺序。
以下是归并排序在C语言中的实现思路及代码示例:
思路
- 分解:将数组从中间分成两部分,递归地对这两部分分别进行归并排序。
- 合并:将两个已排序的部分合并成一个有序的数组。
步骤
- 确定中点:找到数组的中间位置,将数组分成两部分。
- 递归排序:对左右两部分分别进行归并排序。
- 合并:将两个已排序的部分合并到一个临时数组中,然后再复制回原数组。
时间复杂度分析
分解阶段:
在每一层递归中,数组被分成两半。
如果数组长度为 n,则需要进行 log n 层递归(因为每次都将数组大小减半)。
合并阶段:
在每一层递归中,需要合并所有子数组。
合并两个长度为 n 的子数组需要 O(n) 的时间。
由于每一层都有 n 个元素需要合并,所以每一层合并的总时间复杂度是 O(n)。
总时间复杂度:
由于有 log n 层递归,每层都需要 O(n) 的时间来合并子数组。
因此,总时间复杂度是 O(n log n)。
空间复杂度分析
归并排序需要额外的空间来存储临时子数组。
在最坏情况下,归并排序的空间复杂度是 O(n),因为需要创建一个与原数组大小相同的临时数组来存储合并后的结果。
然而,通过一些优化技术(如原地合并),可以在某些情况下降低空间复杂度,但这通常会使算法变得更加复杂且可能牺牲其稳定性。
稳定性分析
归并排序是稳定的排序算法,即它保持相等元素的相对顺序。
这是因为在合并过程中,当两个元素相等时,算法总是选择先遇到的元素放入结果数组中。
综上所述,归并排序的时间复杂度为 O(n log n),空间复杂度为 O(n),并且它是一个稳定的排序算法。这些特性使得归并排序在处理大型数据集时非常有效,尤其是当需要保持元素相对顺序时。
代码实例
#include<stdio.h>
#include<stdlib.h>
//遍历数组
void ergodicArray(int *array,int array_size) {
for(int i=0;i< array_size;i++)
printf("%d ", array[i]);
printf("\n");
}
//合并数组并排序
void merge(int* array, int left, int right,int mid) {
//两个数组的长度
int n1 = mid - left + 1;
int n2 = right - mid;
//动态分配临时数组
int* L = (int*)malloc(n1 * sizeof(int));
int* R = (int*)malloc(n2 * sizeof(int));
//拷贝数据到临时数组
for (int i = 0; i < n1; i++)
L[i] = array[left + i];
for (int j = 0; j < n2; j++)
R[j] = array[mid +1+j];
//合并临时数组到原数组并排序
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
array[k] = L[i];
i++;
k++;
}
else {
array[k] = R[j];
j++;
k++;
}
}
//拷贝剩余元素
while (i < n1) {
array[k] = L[i];
i++;
k++;
}
while (j < n2) {
array[k] = R[j];
j++;
k++;
}
//释放临时数组
free(L);
free(R);
}
//归并排序--递归--分解
void mergingSort(int* array, int left,int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergingSort(array, left, mid); // 前部
mergingSort(array, mid+1, right); //后部
merge(array,left,right,mid);//合并数组并排序
}
}
int main() {
int array[] = { 12,2,6,3,56,17,8 };
int array_size = sizeof(array) / sizeof(array[0]);
ergodicArray(array, array_size-1);
mergingSort(array, 0, array_size - 1);
ergodicArray(array, array_size - 1);
return 0;
}
归并排序的原地版本
归并排序的原地版本,即原地归并排序(In-place MergeSort),是一种在归并过程中不需要额外辅助数组的排序算法。传统的归并排序在合并两个有序子数组时,通常需要一个与原数组大小相同的临时数组来存储合并后的结果。然而,原地归并排序通过巧妙的算法设计,避免了这一额外的空间开销。
原地归并排序的核心思想
原地归并排序的核心思想是在原数组上进行归并操作,同时确保不会覆盖或丢失任何未处理的元素。这通常涉及到元素的交换和旋转(循环移动)操作。
原地归并排序的实现
步骤
- 分解:与传统归并排序一样,首先将数组递归地分解成单个元素的子数组。
- 合并:在合并两个有序子数组时,采用原地合并策略。
这通常涉及以下步骤:
- 找到两个子数组的第一个需要比较的元素。
- 通过比较和交换,将较小的元素移动到正确的位置。
- 使用旋转操作来移动元素,以确保不会覆盖未处理的元素。
- 重复:递归地执行上述合并步骤,直到整个数组有序。
原地归并排序的复杂度分析
时间复杂度:与传统归并排序相同,原地归并排序的时间复杂度也是 O(n log n)。尽管原地合并操作可能增加了一些常数因子,但总体上时间复杂度保持不变。
空间复杂度:原地归并排序的空间复杂度为 O(log n),这是由于递归调用栈所需的空间。在某些优化实现中,可以通过迭代方式进一步减少空间复杂度至 O(1),但这通常会使算法实现更加复杂。
注意事项
原地归并排序的实现通常比传统归并排序更复杂,且可能牺牲一些性能以换取空间效率。
在实际应用中,是否使用原地归并排序取决于具体场景的需求,例如内存限制和性能要求。
由于原地归并排序的具体实现可能因算法设计者的不同而有所差异,因此上述示例代码和复杂度分析仅提供了基本概念和框架。在实际编程中,需要根据具体需求来设计和实现原地归并排序算法