归并排序
简介:
归并排序是成功应用分治技术的一个完美例子。
对于一个需要排序的数组 A [ 0.. n − 1 ] A[0..n-1] A[0..n−1],归并排序把它一分为二: A [ 0.. n 2 − 1 ] A[0..\frac{n}{2}-1] A[0..2n−1]和 A [ n 2 . . n − 1 ] A[\frac{n}{2}..n-1] A[2n..n−1],
并对每个子数组队规排序,然后把这两个排好序的子数组合并为一个有序数组。
演示:
通过归并排序对数组 8 , 3 , 2 , 9 , 7 , 1 , 5 , 4 8,3,2,9,7,1,5,4 8,3,2,9,7,1,5,4进行排序的操作过程。
C
// 2-路归并
void Merge(RcdType SR[], RcdType TR[], int i, int m, int n) {
// 将相邻的有序区间SR[i..m]和SR[m+1..n]归并为有序的TR[i..n]
int k, j; // i是最左边向右,j是中间向右,k是新数组整体从左到右填写
for (j = m + 1, k = i; i <= m && j <= n; ++k) {
// 将SR中记录按关键字从小到大地复制到TR中
if (SR[i].key <= SR[j].key) // "<="归并排序稳定,"<"归并排序不稳定
TR[k] = SR[i++];
else
TR[k] = SR[j++];
}
while (i <= m) TR[k++] = SR[i++]; // 将剩余的SR[i..m]复制到TR
while (j <= n) TR[k++] = SR[j++]; // 将剩余的SR[j..n]复制到TR
}
// 递归归并排序
void MSort(RcdType R1[], RcdType R2[], int i, int left, int right) {
// 对R1[left..right]归并排序,若i % 2 == 1,则排序后的记录存入R2[left..right],否则存入R1[left..right]
int middle;
if (left == right) {
if (i % 2)
R2[left] = R1[left];
} else {
middle = (left + right) / 2; // 将区间[left..right]平分为[left..m]和[m+1..right]
MSort(R1, R2, i + 1, left, middle); // 对区间[left..m]递归
MSort(R1, R2, i + 1, middle + 1, right);// 对区间[m+1..right]递归
if (i % 2) // 将R1[left..m]和R1[m+1..right]归并到R2[left..right]
Merge(R1, R2, left, middle, right);
else // 将R2[left..m]和R2[m+1..right]归并到R1[left..right]
Merge(R2, R1, left, middle, right);
}
}
// 对顺序表L作2-路归并排序
void MergeSort(RcdSqList &L) {
RcdType *R;
R = (RcdType *) malloc((L.length + 1) * sizeof(RcdType)); // 分配辅助空间
MSort(L.rcd, R, 0, 1, L.length); // 对L.rcd[1..L.length]归并排序
free(R);
}
Java
package sortdemo;
import java.util.Arrays;
/**
* Created by chengxiao on 2016/12/8.
*/
public class MergeSort {
public static void main(String []args){
int []arr = {9,8,7,6,5,4,3,2,1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int []arr){
int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
sort(arr,0,arr.length-1,temp);
}
private static void sort(int[] arr,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
}
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;//左序列指针
int j = mid+1;//右序列指针
int t = 0;//临时数组指针
while (i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid){//将左边剩余元素填充进temp中
temp[t++] = arr[i++];
}
while(j<=right){//将右序列剩余元素填充进temp中
temp[t++] = arr[j++];
}
t = 0;
//将temp中的元素全部拷贝到原数组中
while(left <= right){
arr[left++] = temp[t++];
}
}
}
小结:
归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为 O ( n ) O(n) O(n),而完全二叉树的深度为 ∣ l o g 2 n ∣ |log2n| ∣log2n∣。总的平均时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)。
而且,归并排序的最好,最坏,平均时间复杂度均为 O ( n l o g n ) O(nlogn) O(nlogn)。