用于执行返回键而不是就地排序的搜索的技术

时间:2014-03-23 18:07:45

标签: java c arrays algorithm sorting

在许多情况下,我需要知道数组的排序顺序,而不是对数组进行排序。例如,假设有五(5)个数组包含不同类型的各种信息(字符串,整数,双等),并且数组是同步的,这意味着每个数组的第n个元素组合在一起。想象一下,现在第一个数组有一个“名称”值,一个字符串,我想删除所有具有重复名称的值。我需要获取名称数组的排序键,并使用它来消除所有五个数组中的重复项。我无法对名称数组进行排序,因为它将不再与其他数组同步。

为了解决这个问题,我一直在编写自己的排序例程。最初,我有几个不同版本的QuickSort(取决于数据类型,数组是基于一个还是从零开始,它是什么类型,升序,降序,区分大小写,不区分大小写等)最近我一直在尝试制作各种基数类型,我发现它们比某些类型的数据快于QuickSort。我的例程返回排序键,而不是对数组本身进行排序。换句话说,它们返回一个包含整数的数组,每个整数指示目标数组的哪个元素属于该位置。因此,例如,如果sort键的第一个值是43,那么它意味着目标数组的第43个元素是排序顺序中的第一个元素。

现在,虽然编写所有这些排序例程令人着迷和教育,但我想知道是否有更好的技术可以让我利用现有的排序库?有没有办法可以使用Java / C中的标准库资源获取排序键?

更新

我尝试了Juan Lopez推荐的间接排序方法,似乎有效。代码:

private final static void test_indirect_sort(){
    final String[] test_array = { "pear", "peach", "doggie", "apple", "dog", "prairie", "a", "tundra", "flamingo", "barn" };
    Integer[] sorted_keys = new Integer[test_array.length];
    for( int i = 0; i < sorted_keys.length; i++ ) sorted_keys[i] = i;
    java.util.Arrays.sort(sorted_keys, new java.util.Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return test_array[o1].compareTo(test_array[o2]);
        }
    });
    for( int xKey = 0; xKey < sorted_keys.length; xKey++ ) System.out.print( test_array[sorted_keys[xKey]] + " " );
    System.out.println();
    for( int xKey = 0; xKey < sorted_keys.length; xKey++ ) System.out.print( test_array[xKey] + " " );
    System.out.println();
}

这个解决方案的主要缺点是需要Integer对象而不是原始int,这意味着密钥数组的空间至少增加4倍,并且在Integer包装器中访问int的速度变慢。

关于效果的说明

从评论看来,有些人似乎认为使用对象数组与使用并行数组一样快。当我运行以下代码时:

private final static void testArrayAccess(){
    int[] a = new int[30000000];
    int[] b = new int[30000000];
    int[] c = new int[30000000];
    MultiArray[] list = new MultiArray[30000000];
    java.util.Random random = new Random();
    for( int x = 0; x < 30000000; x++ ){
        a[x] = random.nextInt(100);
        b[x] = random.nextInt(100);
        c[x] = random.nextInt(100);
        list[x] = new MultiArray();
        list[x].a = a[x];
        list[x].b = b[x];
        list[x].c = c[x];
    }

    long start1 = System.currentTimeMillis();
    int sum = 0;
    for( int x = 0; x < 30000000; x++ ){
        sum += a[x] +  b[x] + c[x];
    }
    long end1 = System.currentTimeMillis();

    long start2 = System.currentTimeMillis();
    sum = 0;
    for( int x = 0; x < 30000000; x++ ){
        sum += list[x].a +  list[x].b + list[x].c;
    }
    long end2 = System.currentTimeMillis();

    System.out.format( "parallel arrays: %d  bundled object: %d\n", (end1 - start1), (end2-start2) );
}

我得到了输出:

parallel arrays: 4  bundled object: 15

显示在我的系统上使用捆绑对象的测试用例几乎比使用并行数组慢4倍(更不用说使用更多内存)。要查看它为什么慢一点,这里是sum +=语句的字节代码反汇编,左边是并行数组,右边是捆绑对象:

   LINENUMBER 32 L20     LINENUMBER 39 L27
    ILOAD 7               ILOAD 7
    ALOAD 0               ALOAD 3
    ILOAD 8               ILOAD 12
    IALOAD                AALOAD
    ALOAD 1               GETFIELD cra/common/MultiArray.a : I
    ILOAD 8               ALOAD 3
    IALOAD                ILOAD 12
    IADD                  AALOAD
    ALOAD 2               GETFIELD cra/common/MultiArray.b : I
    ILOAD 8               IADD
    IALOAD                ALOAD 3
    IADD                  ILOAD 12
    IADD                  AALOAD
    ISTORE 7              GETFIELD cra/common/MultiArray.c : I
                          IADD
                          IADD
                          ISTORE 7

左边的字节代码总是慢于右边的字节代码。

2 个答案:

答案 0 :(得分:5)

您可以创建一个间接数组,并对其进行排序而不是原始数组。示例(在Java中):

final String[] names = {"some", "names", "some"};

Integer[] indirection = new Integer[names.length];
for (int i = 0; i < indirection.length; i++)
    indirection[i] = i;

Arrays.sort(indirection, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return names[o1].compareTo(names[o2]);
    }
});

答案 1 :(得分:1)

一种方法是创建一个包含原始索引信息的“holder”对象,例如:

static class Holder <T> {
    int originalIndex; 
    T data;
}

然后,您可以将它们存储在容器中,相应地设置它们的originalIndex(或其他),然后排序(使用适当的Comparator)。

之后,您可以遍历已排序的数组,originalIndex(或其他)将包含您感兴趣的信息。

更好的方法是根本不使用并行数组,而是像Don Ruby在注释中提到的那样,正确使用类来将所有相关数据保存在一个地方。然后,根本没有必要(加上传递数据更容易,例如,而不是传递firstName[]lastName[]index,你只需传递{{1} })。