归并排序
这几天在学算法,打算写成博客记下来加深一下印象,其中有什么理解的不对或者写错的地方请点出。
第一个是归并排序,该算法是分治法的典型应用,完全遵循分治模式,直观的操作如下:
1.分解:分解待排序的n个元素的序列成两个n/2的子序列。
2.解决:使用归并排序递归地排序两个子序列。
3.合并:合并两个已排序的子序列以产生已排序的最终序列。
当分解到序列的长度为1时,递归开始回升,此时长度为1的每个序列都是已经排好序的。
关键操作是合并两个已经排好序的子数组,该步骤通过一个合并方法来完成,以玩扑克牌的例子,
假设桌上有两堆已排序的扑克牌,最小的牌在上面,我们在这两堆牌的最上面那一张中选取较小的牌,
将该牌取出,放到输出堆。重复这个步骤,直到其中一个堆空了,此时把另一堆剩下的牌也放在输出堆,
由此,我们就把这两堆牌合并成有序的一堆牌。该方法有4个参数,分别是一个数组和3个数组的下标,
假设子数组arr[p…q]和arr[q+1…r]已经排好序,其中p<=q
public static void merge(int[] arr, int p, int q, int r)
{
int n1 = q - p + 1;
int n2 = r - q;
int[] left = new int[n1];
int[] right = new int[n2];
for (int i = 0; i < n1; i++){
left[i] = arr[p + i];
}
for (int j = 0; j < n2; j++){
right[j] = arr[q + j +1];
}
int a = 0, b = 0;
for (int k = p; k <= r; k++){
if (a < n1 && b < n2){
if (left[a] <= right[b]){
arr[k] = left[a];
a++;
continue;
}else{
arr[k] = right[b];
b++;
continue;
}
}
if (a >= n1 && b < n2){
arr[k] = right[b];
b++;
continue;
}
if (a < n1 && b >= n2){
arr[k] = left[a];
a++;
continue;
}
}
}
接下来就是第一步分解和第二步解决了,递归排序的代码实现如下:
public static void megerSort(int[] arr, int p, int r)
{
if (p < r){
int q = (p + r) / 2;
megerSort(arr, p, q);
megerSort(arr, q + 1, r);
merge(arr, p, q, r);
}
}
假设排序数组[87,80,8,24,31,12,78,57,83,7]中的元素,9/2后两个子数组分为[87,80,8,24,31],[12,78,57,83,7],
针对左数组[87,80,8,24,31],4/2后两个子数组分为[87,80,8],[24,31],2/2后两个子数组分为[87,80],[8],最后可以得到[87],[80]两个
子数组,此时两个子数组已经有序,而且不满足p < r,进行回升的过程,调用merge方法,得到有序数组[80,87],接着合并[87,80],[8],
得到[8,80,87],合并[24],[31]得到,[24,31],然后合并[8,80,87],[24,31],得到[8,24,31,80,87],右数组的过程也是如此,在这整个归并排序
中,首先排序左数组,当左数组有序,接着排序右数组,然后合并,重复这个过程直到排序完成。
算法分析:
1. 时间复杂度
在归并排序中可以看到,利用分治法的思想,每次都把问题分解为两个原先问题的一半规模的子问题,所以原问题的时间为两个子问题的和,由于它的形式就是一颗二叉树,根据二叉树可以得知它的时间复杂度为O(nlog2n)(以2为底)。
2. 空间复杂度
归并排序不具有空间原址性(任何时候都只需要常数个额外的元素空间存储临时的数据),在合并的时候需要待合并元素数量大小的存储空间。
测试:
public static void main(String[] args) {
int[] nums = new int[50];
Random random = new Random();
for (int i = 0; i < nums.length; i++) {
nums[i] = random.nextInt(100);
}
for (int i = 0; i < 10; i++) {
System.out.print(nums[i] + " ");
}
System.out.println();
long start = System.currentTimeMillis();
megerSort(nums, 0, nums.length - 1);
long end = System.currentTimeMillis();
System.out.println("运行时间:" + (end - start)+"毫秒");
for (int i = 0; i < 50; i++) {
System.out.print(nums[i] + " ");
if(i % 10 == 0){
System.out.println();
}
}
}
接下来分别使用10000,100000,500000个随机数进行测试: