基于两个标准对列表进行排序的最佳算法是什么?

时间:2015-10-29 11:44:27

标签: java algorithm list sorting

我有一个列表,我需要根据两个标准进行排序。 第一个标准是public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and // keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } ,让我们说Boolean。第二个是isBig,表示时间戳。

我需要以这种方式排序列表的元素:在Long之前,然后是isBig = true。在这些组中,单个元素应根据其时间戳降序排列。

基本上,我希望结果是这样的:

isBig = false

让我们说对象是这样的:

isBig - 2015/10/29
isBig - 2015/10/28
isBig - 2015/10/27
!isBig - 2015/10/30
!isBig - 2015/10/27
!isBig - 2015/10/26

,列表只是public class Item { Boolean isBig; Long timestamp; // ... }

我发现一种方法是制作三个for-cycles:第一个组成两个组:List<Item> listisBig。第二个和第三个用于对其中的元素进行排序。最后,我合并了两个列表。

是否有更有效的算法可以根据两个标准对列表进行排序?

5 个答案:

答案 0 :(得分:6)

您可以使用自定义比较方法直接对列表进行排序,该方法会检查这两个条件。

使用Collections.sort方法并将方法compare覆盖的自定义比较器传递给:

 int compare(Item o1, Item o2) {
   if (o1.isBig && !o2.isBig)
     return -1;
   if (!o1.isBig && o2.isBig)
     return 1;
   if (o1.timestamp < o2.timestamp)
     return -1;
   if (o1.timestamp > o2.timestamp)
     return 1;
   return 0;
 }

如果你沉迷于表现,你可能会用更复杂的方法加速几个百分点,但是对于几百个元素的列表,收益可以忽略不计。

优化的比较方法:

int compare(Item o1, Item o2) {
   int bigness = (o2.isBig ? 2 : 0) - (o1.isBig ? 2 : 0);
   long diff = o1.timestamp - o2.timestamp;
   return bigness + (int) Long.signum(diff);
}

它没有条件分支,这意味着它可能比上面的天真版本更快。

这可能是性能所能完成的一切。如果我们对您的数据有更多了解(例如,总是有大对象而不是小对象,或者所有时间戳都是唯一的,或者所有时间戳都来自某个窄范围等),我们可能会提出一些更好的解决方案。但是,当我们假设您的数据是任意的并且没有特定的模式时,最好的解决方案是使用我上面所示的标准排序实用程序。

将列表拆分为两个子列表并单独排序肯定会慢一些。实际上,排序算法很可能将数据分成两组,然后递归地分成四组,依此类推。但是,该部门不会遵循isBig标准。如果您想了解更多信息,请阅读quick sortmerge sort的工作方式。

答案 1 :(得分:2)

理论上,使用两个单独列表的方法应该比使用两步Comparator的方法更快,因为基于一个字段的比较明显快于基于比较的方法两个。通过使用两个列表,您正在加速具有O(n log n)时间复杂度(排序)的算法部分,代价是额外的初始阶段(分成两部分),其具有时间复杂度O(n) 。自n log n > n起,对于非常非常大的n值,两个列表方法应该更快。

然而,在实践中,我们正在讨论在两个列表方法胜出之前你必须拥有极长列表的时间上的微小差异,因此在开始遇到诸如此类问题之前使用列表来证明差异是非常困难的。作为OutOfMemoryError

但是,如果您使用数组而不是列表,并使用巧妙的技巧来实现它而不是使用单独的数据结构,则有可能超越两步Comparator方法,如下面的代码所示。在任何人抱怨之前:是的,我知道这不是一个合适的基准!

即使sort2sort1快,我也可能不会在生产代码中使用它。最好使用熟悉的成语和明显有效的代码,而不是代码更难理解和维护,即使它稍微快一些。

public class Main {

    static Random rand = new Random();

    static Compound rand() {
        return new Compound(rand.nextBoolean(), rand.nextLong());
    }

    static Compound[] randArray() {
        int length = 100_000;
        Compound[] temp = new Compound[length];
        for (int i = 0; i < length; i++)
            temp[i] = rand();
        return temp;
    }

    static class Compound {
        boolean bool;
        long time;

        Compound(boolean bool, long time) {
            this.bool = bool;
            this.time = time;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) 
                return true;
            if (o == null || getClass() != o.getClass()) 
                return false;
            Compound compound = (Compound) o;
            return bool == compound.bool && time == compound.time;
        }   

        @Override
        public int hashCode() {
            int result = (bool ? 1 : 0);
            result = 31 * result + (int) (time ^ (time >>> 32));
            return result;
        }
    }

    static final Comparator<Compound> COMPARATOR = new Comparator<Compound>() {
        @Override
        public int compare(Compound o1, Compound o2) {
            int result = (o1.bool ? 0 : 1) - (o2.bool ? 0 : 1);
            return result != 0 ? result : Long.compare(o1.time, o2.time);
        }
    };

    static final Comparator<Compound> LONG_ONLY_COMPARATOR = new Comparator<Compound>() {
        @Override
        public int compare(Compound o1, Compound o2) {
            return Long.compare(o1.time, o2.time);
        }
    };

    static void sort1(Compound[] array) {
        Arrays.sort(array, COMPARATOR);
    }

    static void sort2(Compound[] array) {
        int secondIndex = array.length;
        if (secondIndex == 0)
            return;
        int firstIndex = 0;
        for (Compound c = array[0];;) {
            if (c.bool) {
                array[firstIndex++] = c;
                if (firstIndex == secondIndex)
                    break;
                c = array[firstIndex];
            } else {
                Compound c2 = array[--secondIndex];
                array[secondIndex] = c;
                if (firstIndex == secondIndex)
                    break;
                c = c2;
            }
        }
        Arrays.sort(array, 0, firstIndex, LONG_ONLY_COMPARATOR);
        Arrays.sort(array, secondIndex, array.length, LONG_ONLY_COMPARATOR);
    }

    public static void main(String... args) {

        // Warm up the JVM and check the algorithm actually works.
        for (int i = 0; i < 20; i++) {
            Compound[] arr1 = randArray();
            Compound[] arr2 = arr1.clone();
            sort1(arr1);
            sort2(arr2);
            if (!Arrays.equals(arr1, arr2))
                throw new IllegalStateException();
            System.out.println(i);
        }

        // Begin the test proper.
        long normal = 0;
        long split = 0;
        for (int i = 0; i < 100; i++) {
            Compound[] array1 = randArray();
            Compound[] array2 = array1.clone();

            long time = System.nanoTime();
            sort1(array1);
            normal += System.nanoTime() - time;

            time = System.nanoTime();
            sort2(array2);
            split += System.nanoTime() - time;

            System.out.println(i);
            System.out.println("COMPARATOR:           " + normal);
            System.out.println("LONG_ONLY_COMPARATOR: " + split);
        }
    }
}

答案 2 :(得分:2)

要使两个可比较的对象在两个参数上进行排序,您需要做以下事情。

  1. 您需要为两个可比较的对象实现Comparator,即一个布尔值和一个时间戳。
  2. 您需要将这些比较器传递给Collections.sort(),因为它们是比较两个键并且数据结构不是基元的对象,它们需要Collections.sort()。

    /**
     * Comparator to sort employees list or array in order of Salary
     */
    public static Comparator<BooleanComaprator> booleanComparator= new Comparator<BooleanComaprator>() {
    
        @Override
        public int compare(BooleanComaprator e1, BooleanComaprator e2) {
            if (e1.isBig && !e2.isBig)
                return -1;
            if (!e1.isBig && e2.isBig)
                return 1;
            else 
                return 0;
        }
    }
    

    Collections.sort(booleanComparator);

  3. 中使用此对象

答案 3 :(得分:1)

这称为按多个键排序,这很容易。如果您正在使用一个排序库函数,该函数使用比较器回调函数来决定两个元素的相对排序,请定义比较器函数,以便它首先检查两个输入值a和b是否相等{{1} }值,如果没有,立即返回isBig(我假设在这里为布尔值定义a.isBig > b.isBig;如果没有,则替换明显的测试)。但如果>值相等,则应返回isBig

答案 4 :(得分:1)

您可以定义自定义比较器并使用它来对List进行排序。 E.g。

class ItemComparator implements Comparator {
    @Override
    public int compare (Item a, Item b) {
        int bc = Boolean.compare(a.isBig, b.isBig);
        if (bc != 0)
            return bc;
        return Long.compare(a.timestamp, b.timestamp);
    }
}

并像这样使用

Collections.sort(list, ItemComparator);