《排序算法系列6》归并排序

1 思想

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

2 举例

可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

合并相邻有序子序列

再来看看阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。

归并排序动态演示

 

3 代码实现

 1     public static void main(String[] args) {
 2         int[] arr = { 8, 4, 5, 7, 1, 3, 6, 2 };
 3         int[] temp = new int[arr.length];// 归并排序需要一个额外的空间
 4         mergeSort(arr, 0, arr.length - 1, temp);
 5         System.out.println("归并排序后的结果" + Arrays.toString(arr));
 6     }
 7     
 8     /**
 9        *  通过递归实现数组的分阶段  然后调用数组合并方法
10       * 从而实现归并排序的分而治之思想
11      * @param arr   需要排序的数组
12      * @param left  排序数组的开始索引
13      * @param right 排序数组的结束索引
14      * @param temp  辅助数组,用于接受排序的结果
15      * void
16      */
17     public static void mergeSort(int[] arr, int left, int right, int[] temp) {
18         if (left < right) {
19             int mid = (left + right) / 2;// 中间索引
20             // 向左递归进行分解
21             mergeSort(arr, left, mid, temp);
22             // 向右递归进行分解
23             mergeSort(arr, mid + 1, right, temp);
24             // 合并
25             merge(arr, left, mid, right, temp);
26         }
27     }
28 
29     /**
30      *  治阶段: 将分开的2个数组合并s 
31      * 
32      * @param arr   排序的原始数组
33      * @param left  左边有序数组
34      * @param mid   中间索引
35      * @param right 右边索引
36      * @param temp  中转的数组
37      */
38     public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
39         int i = left;// 左边有序序列的初始索引
40         int j = mid + 1;// 右边有序序列的初始索引
41         int t = 0; // 指向temp数组的当前索引
42 
43         // 步骤一:
44         // 先把左右两边的数据按照规则填充到temp数组
45         // 直接左右两边的数组 有一边处理完毕为止
46         while (i <= mid && j <= right) {
47             // 如果左边的有序序列的当前元素 小于等于右边有序序列的当前元素
48             // 把左边的元素添加到temp数组
49             if (arr[i] <= arr[j]) {
50                 temp[t] = arr[i];
51                 i++;
52                 t++;
53             } else {
54                 temp[t] = arr[j];
55                 j++;
56                 t++;
57             }
58         }
59 
60         // 步骤二:
61         // 把有剩余的数组一边的数据依次填充到temp
62         while (i <= mid) {
63             temp[t] = arr[i];
64             t++;
65             i++;
66         }
67         while (j <= right) {
68             temp[t] = arr[j];
69             t++;
70             j++;
71         }
72 
73         // 步骤三:
74         // 把temp拷贝到arr
75         //初始化t 
76         t = 0;
77         int tempLeft = left;
78         // 注意 并不是每次都拷贝所有 因为不需要拷贝temp 只需要拷贝当前的排好序的数组
79         while (tempLeft <= right) {
80             arr[tempLeft] = temp[t];
81             t++;
82             tempLeft++;
83         }
84     }

4 时间复杂度

假如n个元素使用归并排序的时间复杂度为T(n),那么由于归并排序使用的是分治思想,T(n)=2*T(n/2)+n,其中n就是两个子区间合并的时间复杂度,这个从合并函数可以看出来。可以推导出以下公式:

                        T(1) = C;   n=1 时,只需要常量级的执行时间,所以表示为 C。
                         T(n) = 2*T(n/2) + n; n>1

经过进一步推导,可以得到T(n)=2^k * T(n/2^k) + k * n,我们假设T(1)=T(n/2^k),也就是说当n/2^k个元素的进行归并排序,达到递归终止条件时,n/2^k=1,得到:k=logn

于是:

                                             T(n)=Cn+nlog2n

归并排序的时间复杂度就是O(nlogn),跟数组的有序度其实并没有什么关系,是非常稳定的时间复杂度。

 5 时间复杂度速度测试

 1   // 归并排序
 2     public static void main(String[] args) {
 3         speedTest(8000000);
 4     }
 5 
 6     /**
 7        * 创建一个随机数组 然后调用排序方法 得到时间
 8      * 
 9      * @param number 创建的随机数组个数
10      */
11     public static void speedTest(int number) {
12         int[] arr = new int[number];
13         int[] arr2 = new int[number];
14         for (int i = 0; i < arr.length; i++) {
15             arr[i] = (int) (Math.random() * 800000);
16         }
17 
18         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
19         Date date1 = new Date();
20         String time1 = simpleDateFormat.format(date1);
21         System.out.println("排序前的时间为:" + time1);
22 
23         // 调用上面的归并排序方法
24         mergeSort(arr, 0, arr.length-1, arr2);
25 
26         Date date2 = new Date();
27         String time2 = simpleDateFormat.format(date2);
28         System.out.println("排序后的时间为:" + time2);
29     }

归并排序速度测试结果

8万个数据测试结果大约需要不到1秒

 80万个数据测试结果大约需要不到

 800万个数据测试结果大约需要2秒

8000万个数据测试结果大约16秒

原文地址:https://www.cnblogs.com/wangxiucai/p/12679224.html