#####归并排序(MERGE-SORT)基本思想
归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
这样说可能也不好理解,那这里看一个归并排序的排序过程。
如上图,先将要排序的数据分为两部分,然后将这两部分再继续分。。。直到每一部分只有一个数据,这时 分的过程。
接着进行 治的过程,这里的治指的是将两部分数据按照从小到大的顺序进行合并,合并完成也就排序完毕。
在合并的时候需使用另一个临时的数组来辅助合并,比如合并(0,4)、(1,3)两组数据,要先比较0和1的大小,将小的(0)存入temp,接着比较1和4的大小,将1存入temp,然后比较4和3的大小,将3存入temp,最后将4保存到temp就将这两部分合并完成了,所以归并排序需要一个额外的空间temp,temp长度就是待排序数组的长度。
代码实现
/**
* @author chenzhiyuan
* @date 2019-09-05 23:31
*/
public class MergeSort {
public static void main(String[] args) {
int[] nums = new int[]{10, 2, 11, 0, 20, 8, 90, 22, 1, 66};
for (int num : nums) {
System.out.print(num + "\t");
}
System.out.println();
mergeSort(nums, 0, nums.length - 1, new int[nums.length]);
for (int num : nums) {
System.out.print(num + "\t");
}
}
/**
* 合并数据
* left、mid、right解释:用三个变量来标识要合并的两部分数据的下标,
* 即left到mid是左边的那部分,mid+1到right是右边的部分
*
* @param nums 排序的数组
* @param left
* @param mid
* @param right
* @param temp 临时数组
*/
public static void merge(int[] nums, int left, int mid, int right, int[] temp) {
int i = left;
int j = mid + 1;
// 记录temp数组的下标
int t = 0;
// 进行合并
while (i <= mid && j <= right) {
if (nums[i] < nums[j]) {
temp[t++] = nums[i++];
} else {
temp[t++] = nums[j++];
}
}
// 上面的while执行完毕并不能保证所有的数据都存到了temp数组
// 比如合并(1,2,3,)和(4,5,6),上面的while执行完后,(4,5,6)一个都没有存入到temp
// 所以还需要接下来的这两步
while (i <= mid) {
temp[t++] = nums[i++];
}
while (j <= right) {
temp[t++] = nums[j++];
}
// 将temp中排序好的数据放到到原数组
t = 0;
for (int k = left; k <= right; k++) {
nums[k] = temp[t++];
}
}
// 归并排序
public static void mergeSort(int[] nums, int left, int right, int[] temp) {
if (left >= right) {
return;
}
int mid = (left + right) / 2;
// 对左边的部分进行归并排序
mergeSort(nums, left, mid, temp);
// 对右边的部分进行归并排序
mergeSort(nums, mid + 1, right, temp);
// 合并左右两部分
merge(nums, left, mid, right, temp);
}
}
结果
10 2 11 56 0 8 90 22 1 66
0 1 2 8 10 11 22 56 66 90
时间复杂度
平均时间复杂度:O(nlogn)
最坏时间复杂度:O(nlogn)
测试耗时
// 创建随机数
public static int[] createNum(int size, long seed) {
int[] nums = new int[size];
Random random = new Random(seed);
for (int i = 0; i < nums.length; i++) {
nums[i] = random.nextInt(1000);
}
return nums;
}
// 测试5轮
public static void testTime(int size) {
// 使用不同的种子,使每一轮测试的随机数不一样
// 种子也可以用于其他排序,因为Random是个伪随机数类,
// 只要种子相同,两个Random对象产生的随机数也一样,
// 利用这个特点在测试其他排序时,使用种子一样,他们排序的数据
// 也一样
int[] seeds = new int[]{9999, 8888, 7777, 6666, 5555};
int[] nums = createNum(size, seeds[0]);
long start = System.currentTimeMillis();
mergeSort(nums, 0, nums.length - 1, new int[size]);
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms");
nums = createNum(size, seeds[1]);
start = System.currentTimeMillis();
mergeSort(nums, 0, nums.length - 1, new int[size]);
end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms");
nums = createNum(size, seeds[2]);
start = System.currentTimeMillis();
mergeSort(nums, 0, nums.length - 1, new int[size]);
end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms");
nums = createNum(size, seeds[3]);
start = System.currentTimeMillis();
mergeSort(nums, 0, nums.length - 1, new int[size]);
end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms");
nums = createNum(size, seeds[4]);
start = System.currentTimeMillis();
mergeSort(nums, 0, nums.length - 1, new int[size]);
end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms");
}
结果如下:
>二十万数据
耗时:79ms
耗时:49ms
耗时:55ms
耗时:55ms
耗时:52ms
>一千万数据
耗时:2988ms
耗时:2104ms
耗时:1799ms
耗时:2031ms
耗时:1851ms