1. 基本思想
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
2. 代码实现(递归版)
void _MergeSort(int* a, int* tmp, int begin, int end)
{
if (end <= begin)
return;
int mid = (end + begin) / 2;
// [begin, mid][mid+1, end]
_MergeSort(a, tmp, begin, mid);
_MergeSort(a, tmp, mid+1, end);
// 归并到tmp数据组,再拷贝回去
// a->[begin, mid][mid+1, end]->tmp
int begin1 = begin, end1 = mid;
int begin2 = mid+1, end2 = end;
int index = begin;
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else
{
tmp[index++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = a[begin2++];
}
// 拷贝回原数组
memcpy(a + begin, tmp + begin, (end - begin + 1)*sizeof(int));
}
3. 代码实现(非递归版)
void MergeSortNor(int* a, int size, int left, int right)
{
int* tmp = (int*)malloc(sizeof(a[0]) * size);
//分组,先是一个和一个一组,然后两个和两个一组……
int gap = 1;
while (gap < size)
{
for (int i = 0; i < size; i += 2 * gap)
{
int index = i;
int begin1 = i;
int end1 = i + gap - 1;
int begin2 = i + gap;
int end2 = i + 2 * gap - 1;
//右区间不存在,不归并
if (begin2 > right)
{
break;
}
//右区间越界,调整一下
if (end2 > right)
{
end2 = right;
}
//开始归并
while (begin1 <= end1 && begin2 <= end2)
{
if (a[begin1] < a[begin2])
{
tmp[index++] = a[begin1++];
}
else
{
tmp[index++] = a[begin2++];
}
}
while (begin1 <= end1)
{
tmp[index++] = a[begin1++];
}
while (begin2 <= end2)
{
tmp[index++] = a[begin2++];
}
//拷贝回原数组,归一点,拷贝一点,这里可能有一部分没有归并到tmp数组,所以要控制边界,不然会拷贝随机值进原数组
for (int j = i; j <= end2; j++)
{
a[j] = tmp[j];
}
}
gap *= 2;
}
}
4. 时间和空间复杂度分析
时间复杂度:
归并操作的时间复杂度是O(n),其中n是待排序数组的长度。这是因为在归并操作中,我们需要将两个已排序的子数组合并成一个已排序的数组。合并两个子数组的过程需要遍历这两个数组并逐个比较元素,因此时间复杂度是线性的。
分割操作的时间复杂度取决于分割的过程。在归并排序中,我们将待排序数组递归地分割成两个子数组,直到每个子数组的长度为1。因此,分割操作的时间复杂度是O(log n),其中n是待排序数组的长度。
综上所述,归并排序的时间复杂度是O(n log n)。这使得归并排序成为一种高效的排序算法。
空间复杂度:
借助了临时数组进行归并操作,故空间复杂度为 O(n)
5. 特性
- 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
- 归并排序是稳定的。