【问题标题】:Fastest way to sort an array by a separate array of indices/indexes通过单独的索引/索引数组对数组进行排序的最快方法
【发布时间】:2014-03-03 17:41:42
【问题描述】:

假设我有以下设置:

int[] vectorUsedForSorting = new int[] { 1,0,2,6,3,4,5 }
int[] vectorToBeSorted = new int[] {1,2,3,4,5,6,7}

使用vectorUsedForSortingvectorToBeSorted 进行排序的最有效/快速的方法是什么?例如,我希望vectorToBeSorted[0] 变为vectorToBeSorted[1],因为vectorUsedForSorting 的第一个元素是1(即vectorToBeSorted[0] 应该变为`vectorToBeSorted[vectorUsedForSorting[0]],等等)。

我的目标是在排序算法完成后将vectorToBeSorted 变为[2,1,3,5,6,7,4]

我希望能很快实现一些目标。请注意,计算复杂性应该是主要关注点,因为我将排序大小为 1,000,000 及更多的数组。

如果可能的话,我的目标是亚线性时间复杂度。

【问题讨论】:

  • vectorUsedForSorting 是否已经像您的示例中那样列出了未来数组位置?或者那里可以有任意数字,比如{ 0, 3, 4, 7}
  • 我的直觉是,你不能很快做出一些事情,因为你不能依赖比较运算符,因为你没有实现经典的排序......我看到你的方式可以这样做是创建一个对象,该对象包含要分配给该对象的最终索引加上值。这意味着值为 0 的对象将具有索引 1。
  • @Johnride 嗯,你能不能重构一下The way I see you could do this is create an object that contains the final index to be assigned to this object plus the value. 这个句子,无法解析。
  • 哦,我对您的编辑理解得更好。一个简单的for 循环就可以解决问题。
  • @user2763361 好吧,为了获得亚线性,我的第一个想法可能会奏效。我将尝试在这里更清楚地说明:您的 vectorUsedForSorting 和 vectorTBeSorted 将看到它们的值在一个包含index 字段和value 字段的简单对象(比如说 sortableObj)中匹配。这将创建一个如下所示的 sortableObj 数组: [{index : 1, value : 1}, {index : 0, value : 2}, {index : 2, value : 3}...] 其中每对卷曲大括号代表一个对象。之后,您可以对索引值执行快速排序。

标签: java arrays sorting performance


【解决方案1】:

有两种方法可以解决这个问题。第一个是复制快速排序算法并使用可以处理您拥有的间接性的东西更改访问和交换值部分:

int valueAt(int index) { return vectorUsedForSorting[index]; }
int swap(int i1, int i2) {
    int tmp = vectorUsedForSorting[i1];
    vectorUsedForSorting[i1] = vectorUsedForSorting[i2];
    vectorUsedForSorting[i2] = tmp;

    tmp = vectorToBeSorted[i1];
    vectorToBeSorted[i1] = vectorToBeSorted[i2];
    vectorToBeSorted[i2] = tmp;
}

第二种方法是将值复制到一个新对象中:

public class Item {
    int index;
    int value;
}

创建一个数组,并使用两个数组中的值创建的Items 填充它。然后,您可以创建一个Comparator<Item>,将它们与index 进行比较。

当你有了这个,你可以用Arrays.sort(items, comparator)对数组进行排序。

如果这还不够快,那么您可以创建 N 个线程并让每个线程对原始数组的 1/N 进行排序。完成后,您可以使用来自merge sort 的合并步骤来加入结果。

【讨论】:

  • 虽然使用 arrays.sort 不会是线性的。由于 OP 已经知道元素的最终位置,因此只需通过一次并将它们放在它们的最终位置(在新数组中)会更快。这将是线性的。
  • @JoshuaTaylor:根据我的问题的答案,OP 不知道元素的最终位置。我的感觉是到目前为止大多数答案(包括接受的答案)都是错误的。
  • @AaronDigulla 我不确定我是否跟随。 vectorUsedForSorting 的元素(即{ 1,0,2,6,3,4,5 })似乎是从0n-1 的整数(其中有n 元素)。如果将它们用作排序键,则索引为i 的元素将在排序数组中的位置i 结束。如果vectorUsedForSorting 是别的东西,(例如{ 0, 3, 4, 7},正如@Sirko 在评论中所问的那样,情况将不再如此。但是,OP 明确提到“vectorToBeSorted[0] 应该变成vectorToBeSorted[vectorUsedForSorting[0]],等等。”如果...的元素...
  • ...vectorUsedForSorting 不能用作索引,那么我完全同意使用 Arrays.sort 和比较器。但是,如果它们可以用作索引,那么简单的解决方案是线性的,而 Arrays.sort 是不必要的。
  • @JoshuaTaylor:我问了同样的问题,得到了相反的答案。我目前的印象是 OP 并不真正知道问题出在哪里。
【解决方案2】:

当性能是一个问题并且数组很大时,您至少必须考虑一个并行实现(特别是因为这个问题是令人尴尬的并行:这并不需要太多努力,应该会产生一个不错的,随着内核数量的增加接近线性加速):

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ArrayReordering
{
    public static void main(String[] args)
    {
        basicTest();
        performanceTest();
    }

    private static void basicTest()
    {
        int[] vectorUsedForSorting = new int[] { 1,0,2,6,3,4,5 };
        int[] vectorToBeSorted = new int[] {1,2,3,4,5,6,7};      
        int[] sortedVectorLinear = new int[vectorToBeSorted.length];
        int[] sortedVectorParallel = new int[vectorToBeSorted.length];

        sortLinear(vectorUsedForSorting, vectorToBeSorted, sortedVectorLinear);
        sortParallel(vectorUsedForSorting, vectorToBeSorted, sortedVectorParallel);

        System.out.println("Result Linear   "+Arrays.toString(sortedVectorLinear));
        System.out.println("Result Parallel "+Arrays.toString(sortedVectorParallel));
    }

    private static void performanceTest()
    {
        for (int n=1000000; n<=50000000; n*=2)
        {
            System.out.println("Run with "+n+" elements");

            System.out.println("Creating input data");
            int vectorUsedForSorting[] = createVectorUsedForSorting(n);
            int vectorToBeSorted[] = new int[n];
            for (int i=0; i<n; i++)
            {
                vectorToBeSorted[i] = i;
            }
            int[] sortedVectorLinear = new int[vectorToBeSorted.length];
            int[] sortedVectorParallel = new int[vectorToBeSorted.length];

            long before = 0;
            long after = 0;

            System.out.println("Running linear");
            before = System.nanoTime();
            sortLinear(vectorUsedForSorting, vectorToBeSorted, sortedVectorLinear);
            after = System.nanoTime();
            System.out.println("Duration linear   "+(after-before)/1e6+" ms");

            System.out.println("Running parallel");
            before = System.nanoTime();
            sortParallel(vectorUsedForSorting, vectorToBeSorted, sortedVectorParallel);
            after = System.nanoTime();
            System.out.println("Duration parallel "+(after-before)/1e6+" ms");

            //System.out.println("Result Linear   "+Arrays.toString(sortedVectorLinear));
            //System.out.println("Result Parallel "+Arrays.toString(sortedVectorParallel));
            System.out.println("Passed linear?   "+
                Arrays.equals(vectorUsedForSorting, sortedVectorLinear));
            System.out.println("Passed parallel? "+
                Arrays.equals(vectorUsedForSorting, sortedVectorParallel));
        }
    }

    private static int[] createVectorUsedForSorting(int n)
    {
        // Not very elegant, just for a quick test...
        List<Integer> indices = new ArrayList<Integer>();
        for (int i=0; i<n; i++)
        {
            indices.add(i);
        }
        Collections.shuffle(indices);
        int vectorUsedForSorting[] = new int[n];
        for (int i=0; i<n; i++)
        {
            vectorUsedForSorting[i] = indices.get(i);
        }
        return vectorUsedForSorting;
    }

    private static void sortLinear(
        int vectorUsedForSorting[], int vectorToBeSorted[], 
        int sortedVector[])
    {
        sortLinear(vectorUsedForSorting, vectorToBeSorted, 
            sortedVector, 0, vectorToBeSorted.length);
    }

    static void sortParallel(
        final int vectorUsedForSorting[], final int vectorToBeSorted[], 
        final int sortedVector[])
    {
        int numProcessors = Runtime.getRuntime().availableProcessors();
        int chunkSize = (int)Math.ceil((double)vectorToBeSorted.length / numProcessors);
        List<Callable<Object>> tasks = new ArrayList<Callable<Object>>();
        ExecutorService executor = Executors.newFixedThreadPool(numProcessors);
        for (int i=0; i<numProcessors; i++)
        {
            final int min = i * chunkSize;
            final int max = Math.min(vectorToBeSorted.length, min + chunkSize);
            Runnable task = new Runnable()
            {
                @Override
                public void run()
                {
                    sortLinear(vectorUsedForSorting, vectorToBeSorted, 
                        sortedVector, min, max);
                }
            };
            tasks.add(Executors.callable(task));
        }
        try
        {
            executor.invokeAll(tasks);
        }
        catch (InterruptedException e)
        {
            Thread.currentThread().interrupt();
        }
        executor.shutdown();
    }

    private static void sortLinear(
        int vectorUsedForSorting[], int vectorToBeSorted[], 
        int sortedVector[], int min, int max)
    {
        for (int i = min; i < max; i++)
        {
            sortedVector[i] = vectorToBeSorted[vectorUsedForSorting[i]];
        }          
    }

}

【讨论】:

    【解决方案3】:

    您可以创建一个新数组,对该数组执行排序并将vectorToBeSorted 设置为新数组。

    int size = vectorToBeSorted.length;
    int[] array = new int[size];
    for (int i = 0; i < size; ++i)
        array[vectorUsedForSorting[i]] = vectorToBeSorted[i];
    vectorToBeSorted = array;
    

    编辑

    如果您希望能够就地排序,则需要循环,交换适当的值。

    int size = vectorToBeSorted.length;
    for (int i = 0; i < size; ++i) {
        int index = vectorUsedForSorting[i];
        int value = vectorToBeSorted[index];
    
        vectorUsedForSorting[i] = vectorUsedForSorting[index];
        vectorToBeSorted[index] = vectorToBeSorted[i];
    
        vectorUsedForSorting[index] = index;
        vectorToBeSorted[i] = value;
    }
    

    如果您能够创建一个比较索引的对结构。你可以使用排序;但是,排序肯定比线性解决方案慢。

    在这种情况下,这两个语句是等价的。

    array[vectorUsedForSorting[i]] = vectorToBeSorted[i];
    array[i] = vectorToBeSorted[vectorUsedForSorting[i]];
    

    【讨论】:

      【解决方案4】:

      怎么样:

      int size = size(vectorUsedForSorting); 
      int [] sortedVector = new int[size];
      for (int i = 0; i < size; ++i)
      {
          sortedVector[i] = vectorToBeSorted[vectorUsedForSorting[i]];
      }  
      

      还是必须就地排序?

      【讨论】:

      • in place sorting 是什么意思?
      • 我的意思是不创建第三个数组。
      • hsun324的解决方案好像还不错。但如果内存无关紧要,使用第三个数组的解决方案会更快。
      • OP 说计算复杂度是最重要的。当然,内存分配会花费一些时间,但这里的运行时只是通过数组的一次。它是线性的。
      • 那么你会同意我的解决方案吗?
      猜你喜欢
      • 2021-11-15
      • 2018-05-02
      • 2013-06-28
      • 1970-01-01
      • 2016-06-04
      • 1970-01-01
      • 1970-01-01
      • 2022-10-14
      • 1970-01-01
      相关资源
      最近更新 更多