《数据结构与算法》7-快排-归并排序

上一节分析了一下冒泡排序,插入排序,选择排序。时间纷杂度都是O(n^2),这一节分析一下速度较快的排序算法,归并排序和快速排序

  • 归并排序

归并排序的思想比较简单,分而治之;

  1. 把排序的数组平均分为左右两部分,递归此操作
  2. 然后对左右两部分进行排序。
4,6,3,7,2,8,1,9

分为两组
{4,6,3,7}-{2,8,1,9}

再分
{4,6}-{3,7}-{2,8}-{1,9}
再分
4-6-3-7-2-8-1-9

分解为单独的数据之后使用合并时排序
4-6合并不用交换位置
3-7合并不用交换位置 
2-8合并不用交换位置
1-9合并不用交换位置
结果
{4,6}--{3,7}--{2,8}--{1,9}
再继续合并

{3,4,6,7}--{1,2,8,9}

再继续合并
1,2,3,4,6,7,8,9

这个过程可以看到是,先分解,再合并。

而且分解之后合并是嵌套执行的,可以使用之前我们学习的递归思想:

java实现

public class TarTest {

    public static void main(String[] args) {
        int[] arr = new int[]{4,2,7,3,9,5,8,1,6};

        split(arr,0,arr.length-1);

        for(int a :arr){
            System.out.print(a);
        }
    }

    private static void split(int[] arr,int p, int r){

        if(p>=r){
            //当分到长度为1的时候跳出循环
            return;
        }
        //找到类中点
        int middle = (p+r)/2;
        //递归左半边
        split(arr,p,middle);
        //递归右半边
        split(arr,middle+1,r);

        //合并且排序两边数据
        mergeSort(arr,p,middle,middle+1,r);
    }

    private static void mergeSort(int[] arr, int p, int lEnd, int rBegin, int r) {
        //左边的和右边的合并,但是要注意排序,
        int i=p,j=rBegin;
        //临时数组
        int[] temp = new int[r-p+1];
        int k = 0;
        //从最左边开始遍历两边的子数组
        while(i <= lEnd && j <= r){
            if(arr[i]<=arr[j]){
                temp[k++] = arr[i++];
            }else {
                temp[k++] = arr[j++];
            }
        }
        //如果左边还没遍历完
        while (i <= lEnd){
            temp[k++] = arr[i++];
        }
        //如果右边还没遍历完
        while (j <= r){
            temp[k++] = arr[j++];
        }
        //p到r的数据保证都是有序的,拷贝到原始数组里面
        for(int s = 0;s<r-p+1;s++){
            arr[p+s] = temp[s];
        }

    }
}

这段代码是比较初级的代码,最主要的逻辑在mergeSort方法里面,这里面需要断点调试,然后确定是否正确。但是代码可以调试,大家还是想一下这个设计思想。

在归并排序的平均复杂度是O(nlog(n));

  • 快速排序

快速排序的思想也是比较简单,

  1. 在最初的数组中选取任意一个位置的数据(一般取数组的最后一位),以此数据为基数。从头和尾向中心遍历数组数据,如果左游标的指向的数小于等于基数,游标+1向右一位,如果右游标的指向的数大于等于基数,游标-1向左一位。
  2. 如果左游标<右游标,交换左右游标的数据
  3. 如果左游标等于大于右游标,跳出遍历
  4. 交换左游标和基数的数据
4,6,3,7,2,8,1,9

选中基数 9,从最左和最右向中间遍历数组
【4】,6,3,7,2,8,1,「9」

左边的游标指向的数据小于等于基数,左游标+1,右边的游标大于等于基数,右游标-1,
4,【6】,3,7,2,8,1,「9」
持续上面的过程
4,6,3,7,2,8,1,【「9」】
直到两个游标相撞,则交换左游标和基数的位置,返回左游标的位置
返回9所在的位置

然后分为左右两边两个数组进行上面的操作
4,6,3,7,2,8,1进行如上操作 





java实现


    public static void main(String[] args) throws ParseException {

        int[] arr = new int[]{4,2,6,1,5,7,9,0,8,3};

        quickSort(arr,0,arr.length-1);
        for(int i :arr){
            System.out.print(i);
        }
    }


    // 快速排序
    private static void quickSort(int[] arr,int p,int r){

        if(p>=r){
            return;
        }
        //根据基数排序左右两部分,然后返回基数的中间位置,以便与下面的划分
        int index = selectNextIndex(arr,p,r);
        quickSort(arr,p,index-1);
        quickSort(arr,index+1,r);
    }

    private static int selectNextIndex(int[] arr, int p, int r) {

        //选取基数
        int key = arr[r];

        //从开头和结尾相向遍历数组
        int i = p,j= r;
        while (i<j){
            while (arr[i]<=key&&i<j){
                i++;
            }
            while (arr[j]>=key&&i<j){
                j--;
            }
            if(i<j){
                int tep = arr[i];
                arr[i] = arr[j];
                arr[j] = tep;
            }
        }
        //把基数换到合适的位置
        arr[r] = arr[i];
        arr[i] = key;
        return i;

    }

上面的算法写的也比较直观明了,但是要注意,遍历的前后断点

快速排序的时间复杂度也是O(nlog(n)),

相对于归并排序,大家一般都喜欢用快速排序,因为归并排序需要生命临时工作内存,temp,空间复杂度不是O(1)。快速排序的内存空间是O(1).

当时看了算法的课程,认为快排也不过如此,只有当我开始写代码的时候,才明白,纸上得来终觉浅,绝知此事需躬行。

多写,多练,不要马什么梅

猜你喜欢

转载自blog.csdn.net/David_lou/article/details/108716396