一.归并排序

来源:互联网 发布:shadowsock for mac 编辑:程序博客网 时间:2024/05/29 16:58

归并排序


这几天在学算法,打算写成博客记下来加深一下印象,其中有什么理解的不对或者写错的地方请点出。
第一个是归并排序,该算法是分治法的典型应用,完全遵循分治模式,直观的操作如下:
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个随机数进行测试:
测试

原创粉丝点击