八大排序算法之归并排序(结合算法领域圣经《算法4》)

写在前面

如果说各种编程语言是程序员的招式,那么数据结构和算法就相当于程序员的内功。

想写出精炼、优秀的代码,不通过不断的锤炼,是很难做到的。

八大排序算法

排序算法作为数据结构的重要部分,系统地学习一下是很有必要的。

1、排序的概念

排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。

排序分为内部排序和外部排序。

若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。

反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。

2、排序分类

八大排序算法均属于内部排序。如果按照策略来分类,大致可分为:交换排序、插入排序、选择排序、归并排序和基数排序。如下图所示:
排序分类
以上内容摘抄自:https://cuijiahua.com/blog/2018/01/alogrithm_9.html
还有一些性能分析之类,就不贴图,这些都不是今天的重点,今天的重点是 归 并 排 序 \color{red}{归并排序}

归并排序

之前也在b站上看过韩顺平老师讲的视频《图解数据结构与算法》,可能由于本人愚笨,其中的归并排序,看了几遍都没看懂(其它的很多也没看懂),无奈放弃了。但是再次《算法》第4版中,看到归并排序的时候,豁然贯通了。

归并排序是算法中分治思想的一种体现,将一个问题分解一个个小的问题,将这些小的问题解决,将小问题的答案整理合并,得到最后这个问题的答案。当然,这些小的问题可能还可以再分。

其实我觉得归并排序的理解重点,不是排序,而是归并。如果只看代码,连排序的逻辑都看不到,那归并排序到底是怎么排序的呢?让我们先来看归并。

两数组归并

什么是归并?归并是将两个有序的数组,合并成一个新的有序数组,注意注意:前提是两个数组已经是有序的,才可以进行归并,否则即使归并了也没什么意义,因为归并之后的数组可能是无序的。
思考一下:如果是自己实现,如何将两个有序的数组合并成一个有序的数组呢?

思路

先创建一个数组,用于存储合并后的元素。
遍历这个新数组,遍历的时候,用两个指针,分别指向两个需要合并的小数组的头,每次都比较这个两个数,将小的放进目标数组,放了之后就将相应的指针向前移动一位。
当然,在比较之前,需要先判断指针是否越界,如果某个数组的指针已经越界了,那么直接将另一个数组里的所有数都添加进目标数组里。这样,遍历完成之后,新得到的数组一定是两个数组合并而成,并且有序的。

代码
    public static int[] merge(int[] a, int[] b)
    {
        int len1 = a.length,len2 = b.length;
        int [] target = new int[len1+len2];

        int i = 0, j = 0;
        for (int k = 0; k < len1+len2; k++)
        {
        	//如果a中的元素已经取完了,那么就直接将b中的所有元素加到目标集合中
            if (i >= len1) target[k] = b[j++];
            //如果b中的元素已经取完了,那么就直接将a中的元素加到目标集合中,
            else if (j >= len2) target[k] = a[i++];
            //否则就比较两个数字,将小的那个添加进目标集合
            else if(a[i] < b[j]) target[k] = a[i++];
            else target[k] = b[j++];
        }
        return target;
    }

没什么难点,主要是理解遍历那中所做的事情。

原地归并

理解了上面的两个数组归并之后,我们就可以继续思考了,可否对同一个数组进行归并呢?因为我们最终是要对一个数组排序的。
其实是可以的,我们可以将一个数组从中间“分开”,把左右两边各当成一个数组,对左右两边进行归并。

代码
    public static void merge(int[] a)
    {
        int len = a.length,mid = len/2;
        int[] temp = new int[len];
        //将原数组复制到目标数组里
        for (int m = 0; m < len; m++)
        { temp[m] = a[m]; }
        
        int i = 0,j = mid;
        //进行原地归并
        for (int k = 0; k < len; k++)
        {
            if (i >= mid) a[k] = temp[j++];
            else if(j >= len) a[k] = temp[i++];
            else if (temp[i] < temp[j]) a[k] = temp[i++];
            else a[k] = temp[j++];
        }
    }

这样做了,就可以对一个数组本身进行归并,但是有个问题:需要左右两边都是有序的才可以,那岂不是很鸡肋?

实现排序

仔细思考一下:如果一个数组只有两个,那么不就可以排序了?我们可以将一个数组看个两个数组,再将两个小数组再看成两个小的数组,直到这个小数组只有两个数的时候,就可以排序了,然后在将小数组归并,再一步一步的归并,最后就是有序的了。

所以,我们可以使用递归,直到一个数组只有一个元素时,再往回归并,最后就实现了排序。

代码
public class IntArrayMerge
{
    private static int[] temp;

    public static void sort(int[] a)
    {
        temp = new int[a.length];
        sort(a,0,a.length-1);
    }

    //用于递归调用的排序方法
    private static void sort(int[] a, int lo, int hi)
    {
        if (lo >= hi) return;
        int mid = lo+(hi-lo)/2;
        sort(a,lo,mid);
        sort(a,mid+1,hi);
        merge(a,lo,mid,hi);
    }

    //原地归并的方法
    private static void merge(int[] a, int lo, int mid, int hi)
    {
        for (int k = lo; k <= hi; k++)
        { temp[k] = a[k]; }

        int i = lo,j = mid+1;
        for (int k = lo; k<= hi; k++)
        {
            if (i > mid) a[k] = temp[j++];
            else if (j > hi) a[k] = temp[i++];
            else if (temp[i] > temp[j]) a[k] = temp[j++];
            else a[k] = temp[i++];
        }
    }
}

这是相当于是一个工具类,整合成一个方法,应该也能实现,只是略显臃肿,并且不好理解。

自我提升

上面的工具类,只能实现对数组进行排序,因为参数只能传入int类型的数组,但其实字符也是能排序的,那应该怎么实现了?

可以自己先思考一下,提示:可以往Comparable接口方向思考。

代码
public class MyMerge
{
    //临时容器
    private static Comparable[] temp;


    //对外暴露的排序方法
    public static void sort(Comparable[] a)
    {
        temp = new Comparable[a.length];
        sort(a,0,a.length-1);
    }

    //内部使用,用于递归的排序方法
    private static void sort(Comparable[] a, int lo, int hi)
    {
        if (lo >= hi) return;
        int mid = lo+(hi-lo)/2;
        sort(a,lo,mid);
        sort(a,mid+1,hi);
        merge(a,lo,mid,hi);
    }


    //原地归并的方法
    private static void merge(Comparable[] a, int lo, int mid, int hi)
    {
        //将原数组复制到临时数组中
        int i = lo,j = mid+1;
        for (int k = lo; k <= hi ; k++)
        { temp[k] = a[k]; }

        //原地归并
        for (int k = lo; k <= hi; k++)
        {
            if (i > mid) a[k] = temp[j++];
            else if (j > hi) a[k] = temp[i++];
            else if (less(temp[i],temp[j])) a[k] = temp[i++];
            else a[k] = temp[j++];
        }
    }

    
    //比较v是否小于w
    private static boolean less(Comparable v, Comparable w)
    { return v.compareTo(w) < 0; }
}

实现Comparable 这个接口,就必须实现其内的compareTo方法,所以就可以根据这个元素的比较逻辑进行排序。

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值