归并排序
前言
目前我掌握的排序算法有冒泡排序、插入排序、堆排序、快速排序,这几个排序算法的过程和思想包括时间复杂度我都能快速的讲出个1234来,唯独对归并排序理解的不够深入,这里重新学习一下归并排序
分治思想
将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,然后再合并这些子问题的解建立原问题的解,归并排序完全遵循分治模式:
- 分解:分解待排序的n个元素的序列成各具n/2个元素的两个子序列
- 解决:使用归并排序递归地排序两个子序列
- 合并:合并两个已排序的子序列以产生已排序的答案
归并排序算法
归并排序算法的关键操作是“合并”步骤中两个已排序序列的合并。我们通过调用一个辅助过程merge(A, p, q, r, T)来完成合并,其中A是一个数组,p,q,r是数组下标,满足p <= q < r, T是一个辅助数组空间。该过程假设子数组A[p..q]和A[q+1..r]都已排好序。它合并这两个子数组形成单一的已排好的子数组并代替当前的子数组A[p..r]n
过程merge需要O(n)的时间,其中n = r - p + 1是待合并元素的总数。
实现代码:
/** * 将有二个有序数列a[first...mid]和a[mid...last]合并 */ void merge(int *arr, int first, int mid, int end, int *temp) { int i, j, k, m, n; i = first; j = mid + 1; k = 0; while (i <= mid && j <= end) { if (arr[i] <= arr[j]) { temp[k] = arr[i]; } else { temp[k] = arr[j]; } k ++; } while (i <= mid) { temp[k ++] = arr[i ++]; } while (j <= end) { temp[k ++] = arr[j ++]; } // 对arr重新赋值,合并 for (i = 0; i < k; i ++) arr[first + i] = temp[i]; }
现在,我们可以过程merge作为归并排序算法中的一个子程序来用。下面的过程mereg_sort(A, p, r)排序子数组A[p...r]中的元素。若p >= r,则该子数组最多有一个元素,所以已经排好序。否则,分解步骤简单地计算一个下标q,将A[p...r]分成两个子数组A[p...q]和A[q...r],前者包含n / 2个元素,后者包含n / 2个元素
/** * 归并排序递归过程 */ void merge_sort(int *arr, int begin, int end, int *temp) { int mid; if (begin < end) { mid = (begin + end) / 2; merge_sort(arr, begin, mid, temp); merge_sort(arr, mid + 1, end, temp); merge(arr, begin, mid, end, temp); } }
写一个基础的归并排序代码,要求可以接收n组测试数据:
#include <stdio.h> #include <stdlib.h> /** * 将有二个有序数列a[first...mid]和a[mid...last]合并 */ void merge(int *arr, int first, int mid, int end, int *temp) { int i, j, k; i = first; j = mid + 1; k = 0; while (i <= mid && j <= end) { if (arr[i] <= arr[j]) { temp[k] = arr[i ++]; } else { temp[k] = arr[j ++]; } k ++; } while (i <= mid) { temp[k ++] = arr[i ++]; } while (j <= end) { temp[k ++] = arr[j ++]; } // 对arr重新赋值,合并 for (i = 0; i < k; i ++) arr[first + i] = temp[i]; } /** * 归并排序递归过程 */ void merge_sort(int *arr, int begin, int end, int *temp) { int mid; if (begin < end) { mid = (begin + end) / 2; merge_sort(arr, begin, mid, temp); merge_sort(arr, mid + 1, end, temp); merge(arr, begin, mid, end, temp); } } int main() { int i, n, *arr, *temp; while (scanf("%d", &n) != EOF) { arr = (int *)malloc(sizeof(int) * n); temp = (int *)malloc(sizeof(int) * n); for (i = 0; i < n; i ++) { scanf("%d", &arr[i]); } merge_sort(arr, 0, n - 1, temp); for (i = 0; i < n; i ++) printf("%d ", arr[i]); printf("\n"); free(arr); free(temp); } return 0; }
截图:
过程分析(手写分析过程,图片未压缩):
时间复杂度
归并排序的时间复杂度为O(nlgn),因为每次合并需要O(n)的时间复杂度,总共需要进行lg次(可以考虑成树的层数),因此为n * lgn