Arrays.sort()自定义比较函数

        在刷题时,有时候需要对数组元素进行一个排序,甚至有时候我们还需要自定义一个比较器,来让元素按照我们想要的来排序。下面通过写几个demo来增加对它的理解。

本文包括的知识点:数组排序、冒泡排序算法、可比较性、自定义比较器

源码分析

        先来看看Arrays.sort()和比较器Comparator的源码

        1、可以看到Arrays.sort()使用了泛型,所以不能直接使用java基础类型。假如需要对基础类型数组进行排序,则要把数组转换成元素的封装类型数组,比如int [] 转换成Integer []。

        2、如果不提供比较器对象,向使用Arrays.sort()对数组进行排序,则需要保证数组元素是可比较的,即必须实现Comparable接口

        3、如果提供比较器对象,则在比较器的compare()方法中实现元素的比较逻辑即可。

public static <T> void sort(T[] a, Comparator<? super T> c) {
    if (c == null) {
        sort(a);
    } else {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a, c);
        else
            TimSort.sort(a, 0, a.length, c, null, 0, 0);
    }
}

//比较器
public interface Comparator<T> {
    ...
    int compare(T o1, T o2);
    ...
}

//可比较接口
public interface Comparable<T> {
    public int compareTo(T o);
}

 Comparator接口中compare()返回结果

返回值>0,  代表 o1>02;

返回值<0,  代表 o1<02;

返回值=0,  代表 o1=02;

如果想知道Arrays.sort()内部是如何利用Comparator对数组进行排序的,可以看下面代码。

private static <T> void legacyMergeSort(T[] a, Comparator<? super T> c) {
    T[] aux = a.clone();
    if (c==null)
        mergeSort(aux, a, 0, a.length, 0);
    else
        mergeSort(aux, a, 0, a.length, 0, c);
}
    
private static void mergeSort(Object[] src,
                              Object[] dest,
                              int low, int high, int off,
                              Comparator c) {
    int length = high - low;

    // Insertion sort on smallest arrays
    if (length < INSERTIONSORT_THRESHOLD) {
        for (int i=low; i<high; i++)
            for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
                swap(dest, j, j-1);
        return;
    }

    // Recursively sort halves of dest into src
    int destLow  = low;
    int destHigh = high;
    low  += off;
    high += off;
    int mid = (low + high) >>> 1;
    mergeSort(dest, src, low, mid, -off, c);
    mergeSort(dest, src, mid, high, -off, c);

    // If list is already sorted, just copy from src to dest.  This is an
    // optimization that results in faster sorts for nearly ordered lists.
    if (c.compare(src[mid-1], src[mid]) <= 0) {
       System.arraycopy(src, low, dest, destLow, length);
       return;
    }

    // Merge sorted halves (now in src) into dest
    for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
        if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
            dest[i] = src[p++];
        else
            dest[i] = src[q++];
    }
}

        可以看到其克隆了一份原始数组,在mergeSort()内部把长数组进行了不断地平均拆成两等份,然后递归调用mergeSort(),直到数组长度小于7时才对该数组进行排序。之所以要这么做应该是出于效率的考量。

来看排序的这段代码,使用了两层for循环,

        外层循环用来遍历数组中每个位置的元素;

        内层循环用来保证外层循环遍历到的当前位置index之前所有元素都是排好序的。

注意:这个算法中,当外循环进行到第index + 1次时,0~index的元素都是排好序的。所以只需要把index + 1位置处的数据依次和前面的元素比较即可,如果发现value(index -1) < value(jidex)就终止内循环这好像就是冒泡排序!!!

来解释下这个比较过程:

        1、假设外层循环目前遍历到了下标为止index的位置;

        2、内层循环中就用index - 1位置处元素和index处位置进行比较,如果value(index -1) > value(jidex),则交换jndex -1 和index两个位置的元素值。接着又比较index -2和index-1位置元素大小,同样前一个元素如果大于后一个元素,则交换两个位置的元素值。直到j=0,或value(index -1) < value(jidex)。

        3、当外循环遍历完最后一个元素后,整个数据就是有序排列的了。

// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) {
    for (int i=low; i<high; i++)
        for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--)
            swap(dest, j, j-1);
    return;
}

注意只要满足c.compare(dest[j-1], dest[j])>0就交换两个元素假如我要倒序排该怎么办呢?Arrays.sort()内部代码已经写好了,我不可能动了,只能从外部想办法。

  x代表数组中前一个位置元素,y代表后一个元素。 

       ( x,  y) -> x - y : x-y>0则代表前一个元素大于后一个元素,Arrays.sort()中的c.compare(dest[j-1], dest[j])>0条件满足,则Arrays.sort()会交换两个元素,变成小的元素在前,大的元素在后,符合正向排序

      (x,  y) -> y - x :  y-x>0则代表后一个元素大于前一个元素,Arrays.sort()中的c.compare(dest[j-1], dest[j])>0条件也满足,Arrays.sort()会交换两个元素,变成大的元素在前,小的元素在后,这不就是倒序排序嘛。

        总结:所以我们只需要调整一下compare()函数内部的逻辑,把它设成相反,就能从正向排序变成倒向排序了。

        最开始还讲到一点,即如果不提供比较器对象,向使用Arrays.sort()对数组进行排序,则需要保证数组元素是可比较的,即必须实现Comparable接口。

        来看看不提供比较器数组是如何排序的代码就能证明了。可以发现内循环中和之前不一样的。上面调用的是比较器对象的compare()方法来比较对象。而现在则是需要把元素类型先转换成Comparable,再调用compareTo()来比较两个元素。 所以不提供比较器的话,元素必须实现Comparable接口才行。

// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) {
    for (int i=low; i<high; i++)
        for (int j=i; j>low &&
                 ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
            swap(dest, j, j-1);
    return;
}

1、整型数组的排序

        先来个正序及其输出结果

public static void main(String[] args) {
    Integer [] array = {12, 34, 9, 2, 11, 123, 13};
    Arrays.sort(array, ( x,  y) -> x - y);
    for (Integer i:array) {
        System.out.print(i + " ");
    }
}

输出结果: 2 9 11 12 13 34 123

        再来个倒序及其结果,其实倒序只是对正序的比较器做了简单处理(y-x)= -(x-y)。

public static void main(String[] args) {
    Integer [] array = {12, 34, 9, 2, 11, 123, 13};
    Arrays.sort(array, ( x,  y) -> y - x);
    for (Integer i:array) {
        System.out.print(i + " ");
    }
}

输出结果: 123 34 13 12 11 9 2

2、字符串数组排序

        字符串实现了Comparable接口,所以字符串是可比较的。它的实现是

1、先比较两个字符串相同长度部分,如果能比较出结果,则返回比较结果;

2、相同长度部分一模一样,则比较两个字符串的长度len1 - len2

所以如果Arrays.sort()中不提供比较器,则使用的是字符串自己的比较函数compareTo()来进行比较。即先按字母排序,后再按字符串长度排序。

String [] array = {"l", "hen", "bb", "bbc", "li", "liuq", "bbcb"};
Arrays.sort(array);

输出结果为:bb bbc bbcb hen l li liuq

如果只想按字符串长度比较呢?

String [] array = {"l", "hen", "bb", "bbc", "li", "liuq", "bbcb"};
Arrays.sort(array, (x, y) -> x.length() - y.length());

输出结果:l bb li hen bbc liuq bbcb 

更复杂一点,把字符串数组所有字符串拼接起来成一个字符串,要求这个字符串是所有能拼接出来结果中按字母排序的最小一个。

String [] array = {"l", "hen", "bb", "bbc", "li", "liuq", "bbcb"};
Arrays.sort(array, (x, y) -> (x+y).compareTo(y+x));

输出结果为:bb bbcb bbc hen li liuq l

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值