【问题标题】:Java Array sort: Quick way to get a sorted list of indices of an arrayJava 数组排序:获取数组索引的排序列表的快速方法
【发布时间】:2010-10-31 09:53:53
【问题描述】:

问题:考虑以下浮点数[]:

d[i] =     1.7 -0.3  2.1  0.5

我想要的是一个 int[] 数组,它表示带有索引的原始数组的顺序。

s[i] =       1    3    0    2
d[s[i]] = -0.3  0.5  1.7  2.1

当然,它可以通过自定义比较器、一组排序的自定义对象来完成,或者通过简单地对数组进行排序然后搜索原始数组中的索引来完成(颤抖)。

我实际上在寻找的是 Matlab's sort function 的第二个返回参数的等价物。

有没有简单的方法来做到这一点(


更新:

感谢您的回复。不幸的是,到目前为止,没有一个提议与我希望的简单有效的解决方案相似。因此,我在 JDK 反馈论坛中打开了一个帖子,建议添加一个新的类库函数来解决这个问题。让我们看看 Sun/Oracle 对此问题的看法。

http://forums.java.net/jive/thread.jspa?threadID=62657&tstart=0

【问题讨论】:

  • 即使这被放入 JDK,我真的怀疑会发生什么,它最终会成为 Arrays 类(或类似的东西)上的静态实用方法,并且最终会被非常类似于下面的东西。那为什么不能直接写函数呢?
  • 自定义比较器作为解决方案有什么问题?我可能误解了您心中所暗示的方法。
  • 也许我还不够努力,但据我所知,如果不将每次调用 Comparator 的每个元素都装箱,就无法使用 Comparator。对于一个包含 n 个浮点数的数组,这意味着垃圾收集器有 2*n*log(n) 个浮点数。 n=10000 表示 80000 块垃圾。我更喜欢 Arrays 类中的静态实用程序方法。如果我不关心垃圾,我本来可以使用 TreeMap 或其他东西。
  • 这很有用,我希望你能用 Java 来做。在 R 中,您可以只使用 rank() 函数。
  • 如果您的号码少于 5000 个,请使用冒泡排序。它易于阅读、简短且足够快。

标签: java arrays sorting class-library


【解决方案1】:

创建索引器数组的简单解决方案:比较数据值对索引器进行排序:

final Integer[] idx = { 0, 1, 2, 3 };
final float[] data = { 1.7f, -0.3f,  2.1f,  0.5f };

Arrays.sort(idx, new Comparator<Integer>() {
    @Override public int compare(final Integer o1, final Integer o2) {
        return Float.compare(data[o1], data[o2]);
    }
});

【讨论】:

  • 相当优雅,但不幸的是使用了自动装箱。因此,我怀疑它不如 kd304 的答案有效。不过它更简单。
  • 尝试加速测试它。如果整数很小,则不会使用太多自动装箱。而且java的排序比kd304的更优化。
  • 绝对是我想要的。 final 是一个小缺点
  • @keyser 为什么 final 是一个缺点?
  • @NeoMHacker 这是一个要求,因为我们在匿名内部类中使用它,在这种情况下并不麻烦,因为我们并没有修改数据数组。见stackoverflow.com/q/4162531/209882
【解决方案2】:

为索引创建一个TreeMap

    float[] array = new float[]{};
    Map<Float, Integer> map = new TreeMap<Float, Integer>();
    for (int i = 0; i < array.length; ++i) {
        map.put(array[i], i);
    }
    Collection<Integer> indices = map.values();

索引将按它们指向的浮点数排序,原始数组保持不变。如果确实有必要,将Collection&lt;Integer&gt; 转换为int[] 留作练习。

编辑: 如 cmets 中所述,如果浮点数组中有重复值,则此方法不起作用。这可以通过将Map&lt;Float, Integer&gt; 变成Map&lt;Float, List&lt;Integer&gt;&gt; 来解决,尽管这会使for 循环的内部和最终集合的生成稍微复杂化。

【讨论】:

  • 正是我整理的映射。
  • 使用了不幸的自动装箱量,但可以解决问题。 +1
  • 这种方法的缺陷是不能处理重复的浮点值。
【解决方案3】:

我会定制快速排序算法以同时对多个数组执行交换操作:索引数组和值数组。例如(基于此quicksort):

public static void quicksort(float[] main, int[] index) {
    quicksort(main, index, 0, index.length - 1);
}

// quicksort a[left] to a[right]
public static void quicksort(float[] a, int[] index, int left, int right) {
    if (right <= left) return;
    int i = partition(a, index, left, right);
    quicksort(a, index, left, i-1);
    quicksort(a, index, i+1, right);
}

// partition a[left] to a[right], assumes left < right
private static int partition(float[] a, int[] index, 
int left, int right) {
    int i = left - 1;
    int j = right;
    while (true) {
        while (less(a[++i], a[right]))      // find item on left to swap
            ;                               // a[right] acts as sentinel
        while (less(a[right], a[--j]))      // find item on right to swap
            if (j == left) break;           // don't go out-of-bounds
        if (i >= j) break;                  // check if pointers cross
        exch(a, index, i, j);               // swap two elements into place
    }
    exch(a, index, i, right);               // swap with partition element
    return i;
}

// is x < y ?
private static boolean less(float x, float y) {
    return (x < y);
}

// exchange a[i] and a[j]
private static void exch(float[] a, int[] index, int i, int j) {
    float swap = a[i];
    a[i] = a[j];
    a[j] = swap;
    int b = index[i];
    index[i] = index[j];
    index[j] = b;
}

【讨论】:

  • 尽管与期望的
  • 这很可悲。我有同样的问题,这是大数据集唯一可接受的解决方案。
  • 但是这个解决方案修改了数据数组。通常,对索引进行排序的目的是避免修改数据顺序。要将其设为只读,请不要交换 exch() 中的数据并将每个 a[x] 替换为 a[index[x]]。
  • 终于有人明白了!谢谢!
【解决方案4】:

使用 Java 8 特性(无需额外库),简洁的实现方式。

int[] a = {1,6,2,7,8}
int[] sortedIndices = IntStream.range(0, a.length)
                .boxed().sorted((i, j) -> Integer.compareTo(a[i], b[i]))
                .mapToInt(ele -> ele).toArray();

【讨论】:

  • 太棒了。如果我有一个不允许使用 a[i] - a[j] 的元素列表或数组怎么办?
  • 将 Integer.compareTo 的答案编辑为 a[i] - b[i] 会导致整数溢出。
【解决方案5】:

Functional Java:

import static fj.data.Array.array;
import static fj.pre.Ord.*;
import fj.P2;

array(d).toStream().zipIndex().sort(p2Ord(doubleOrd, intOrd))
  .map(P2.<Double, Integer>__2()).toArray();

【讨论】:

  • 不要误会...但是 - 哇!完全不可读的代码!抱歉 - 无法抗拒 :-) 它确实符合
  • 不可读怎么办?让我们读一读。将数组放入流中,将每个元素与其索引配对(压缩),按配对顺序快速排序(首先按双精度,然后按整数),获取每对的后半部分,然后将所有内容放入数组。简单!
  • 如果你是函数式编程的新手,它只是不可读的。这种事情在函数式语言中是完全正常的。这可以在没有第三方库的 Java 8 中完成吗?
【解决方案6】:

Jherico's answer 允许重复值的更一般情况是:

// Assuming you've got: float[] array; defined already

TreeMap<Float, List<Integer>> map = new TreeMap<Float, List<Integer>>();
for(int i = 0; i < array.length; i++) {
    List<Integer> ind = map.get(array[i]);
    if(ind == null){
        ind = new ArrayList<Integer>();
        map.put(array[i], ind);
    }
    ind.add(i);
}

// Now flatten the list
List<Integer> indices = new ArrayList<Integer>();
for(List<Integer> arr : map.values()) {
    indices.addAll(arr);
}

【讨论】:

  • 我认为 map.put(array[i], ind);应该放在 ind.add(i) 之后;
  • @lizzie 它按原样工作,因为 ind 保存列表的位置,并且 ind.add 正在更新的事物将与地图中的事物相同。
【解决方案7】:

最好的解决方案是 C 的 qsort,它允许您指定用于比较和交换的函数,因此 qsort 不需要知道被排序数据的类型或组织。这是一个你可以尝试的。由于 Java 没有函数,所以使用 Array 内部类来包装要排序的数组或集合。然后将其包装在 IndexArray 中并排序。 IndexArray 上的 getIndex() 的结果将是一个索引数组,如 JavaDoc 中所述。

public class QuickSortArray {

public interface Array {
    int cmp(int aindex, int bindex);
    void swap(int aindex, int bindex);
    int length();
}

public static void quicksort(Array a) {
    quicksort(a, 0, a.length() - 1);
}

public static void quicksort(Array a, int left, int right) {
    if (right <= left) return;
    int i = partition(a, left, right);
    quicksort(a, left, i-1);
    quicksort(a, i+1, right);
}

public static boolean isSorted(Array a) {
    for (int i = 1, n = a.length(); i < n; i++) {
        if (a.cmp(i-1, i) > 0)
            return false;
    }
    return true;
}

private static int mid(Array a, int left, int right) {
    // "sort" three elements and take the middle one
    int i = left;
    int j = (left + right) / 2;
    int k = right;
    // order the first two
    int cmp = a.cmp(i, j);
    if (cmp > 0) {
        int tmp = j;
        j = i;
        i = tmp;
    }
    // bubble the third down
    cmp = a.cmp(j, k);
    if (cmp > 0) {
        cmp = a.cmp(i, k);
        if (cmp > 0)
            return i;
        return k;
    }
    return j;
}

private static int partition(Array a, int left, int right) {
    int mid = mid(a, left, right);
    a.swap(right, mid);
    int i = left - 1;
    int j = right;

    while (true) {
        while (a.cmp(++i, right) < 0)
            ;
        while (a.cmp(right, --j) < 0)
            if (j == left) break;
        if (i >= j) break;
        a.swap(i, j);
    }
    a.swap(i, right);
    return i;
}

public static class IndexArray implements Array {
    int[] index;
    Array a;

    public IndexArray(Array a) {
        this.a = a;
        index = new int[a.length()];
        for (int i = 0; i < a.length(); i++)
            index[i] = i;
    }

    /**
     * Return the index after the IndexArray is sorted.
     * The nested Array is unsorted. Assume the name of
     * its underlying array is a. The returned index array
     * is such that a[index[i-1]] <= a[index[i]] for all i
     * in 1..a.length-1.
     */
    public int[] index() {
        int i = 0;
        int j = index.length - 1;
        while (i < j) {
            int tmp = index[i];
            index[i++] = index[j];
            index[j--] = tmp;
        }
        int[] tmp = index;
        index = null;
        return tmp;
    }

    @Override
    public int cmp(int aindex, int bindex) {
        return a.cmp(index[aindex], index[bindex]);
    }

    @Override
    public void swap(int aindex, int bindex) {
        int tmp = index[aindex];
        index[aindex] = index[bindex];
        index[bindex] = tmp;
    }

    @Override
    public int length() {
        return a.length();
    }

}

【讨论】:

    【解决方案8】:
    public static int[] indexSort(final double[] v, boolean keepUnsorted) {
        final Integer[] II = new Integer[v.length];
        for (int i = 0; i < v.length; i++) II[i] = i;
        Arrays.sort(II, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Double.compare(v[o1],v[o2]);
            }
        });
        int[] ii = new int[v.length];
        for (int i = 0; i < v.length; i++) ii[i] = II[i];
        if (!keepUnsorted) {
            double[] clon = v.clone();
            for (int i = 0; i < v.length; i++) v[i] = clon[II[i]];
        }
        return ii;
    }
    

    【讨论】:

    • 对这段代码加个小解释会更好。
    • 不错!我喜欢这个!
    【解决方案9】:

    将输入转换为如下所示的对类,然后使用 Arrays.sort() 对其进行排序。 Arrays.sort() 确保为等值保留原始顺序,就像 Matlab 所做的那样。然后,您需要将排序结果转换回单独的数组。

    class SortPair implements Comparable<SortPair>
    {
      private int originalIndex;
      private double value;
    
      public SortPair(double value, int originalIndex)
      {
        this.value = value;
        this.originalIndex = originalIndex;
      }
    
      @Override public int compareTo(SortPair o)
      {
        return Double.compare(value, o.getValue());
      }
    
      public int getOriginalIndex()
      {
        return originalIndex;
      }
    
      public double getValue()
      {
        return value;
      }
    

    }

    【讨论】:

      【解决方案10】:

      另一个不简单的解决方案。这是一个合并排序版本,它是 stable,不会修改源数组,尽管合并需要额外的内存。

      public static int[] sortedIndices(double[] x) {
          int[] ix = new int[x.length];
          int[] scratch = new int[x.length];
          for (int i = 0; i < ix.length; i++) {
              ix[i] = i;
          }
          mergeSortIndexed(x, ix, scratch, 0, x.length - 1);
          return ix;
      }
      
      private static void mergeSortIndexed(double[] x, int[] ix, int[] scratch, int lo, int hi) {
          if (lo == hi)
              return;
          int mid = (lo + hi + 1) / 2;
          mergeSortIndexed(x, ix, scratch, lo, mid - 1);
          mergeSortIndexed(x, ix, scratch, mid, hi);
          mergeIndexed(x, ix, scratch, lo, mid - 1, mid, hi);
      }
      
      private static void mergeIndexed(double[] x, int[] ix, int[] scratch, int lo1, int hi1, int lo2, int hi2) {
          int i = 0;
          int i1 = lo1;
          int i2 = lo2;
          int n1 = hi1 - lo1 + 1;
          while (i1 <= hi1 && i2 <= hi2) {
              if (x[ix[i1]] <= x[ix[i2]])
                  scratch[i++] = ix[i1++];
              else
                  scratch[i++] = ix[i2++];
          }
          while (i1 <= hi1)
              scratch[i++] = ix[i1++];
          while (i2 <= hi2)
              scratch[i++] = ix[i2++];
          for (int j = lo1; j <= hi1; j++)
              ix[j] = scratch[j - lo1];
          for (int j = lo2; j <= hi2; j++)
              ix[j] = scratch[(j - lo2 + n1)];
      }
      

      【讨论】:

        【解决方案11】:
        //Here index array(of length equal to length of d array) contains the numbers from 0 to length of d array   
              public static Integer [] SortWithIndex(float[] data, Integer [] index)
            {
            int len = data.length;
            float temp1[] = new float[len];
            int temp2[] = new int[len];
        
        
        
                 for (int i = 0; i <len; i++) {
        
        
                        for (int j = i + 1; j < len; j++) {
        
        
                          if(data[i]>data[j])
                          {
                            temp1[i] = data[i];
                            data[i] = data[j];
                            data[j] = temp1[i];
        
        
        
                            temp2[i] = index[i];
                            index[i] = index[j];
                            index[j] = temp2[i];
        
                            }
                          }
        
                }
        
                return index;
        
            }
        

        【讨论】:

          【解决方案12】:

          我会这样做:

          public class SortedArray<T extends Comparable<T>> {
              private final T[] tArray;
              private final ArrayList<Entry> entries;
          
              public class Entry implements Comparable<Entry> {
                  public int index;
          
                  public Entry(int index) {
                      super();
                      this.index = index;
                  }
          
                  @Override
                  public int compareTo(Entry o) {
                      return tArray[index].compareTo(tArray[o.index]);
                  }
              }
          
              public SortedArray(T[] array) {
                  tArray = array;
                  entries = new ArrayList<Entry>(array.length);
                  for (int i = 0; i < array.length; i++) {
                      entries.add(new Entry(i));
                  }
                  Collections.sort(entries);
              }
          
              public T getSorted(int i) {
                  return tArray[entries.get(i).index];
          
              }
          
              public T get(int i) {
                  return tArray[i];
              }
          }
          

          【讨论】:

            【解决方案13】:

            下面是一种基于插入排序的方法

            public static int[] insertionSort(float[] arr){
                int[] indices = new int[arr.length];
                    indices[0] = 0;
                    for(int i=1;i<arr.length;i++){
                        int j=i;
                        for(;j>=1 && arr[j]<arr[j-1];j--){
                                float temp = arr[j];
                                arr[j] = arr[j-1];
                                indices[j]=indices[j-1];
                                arr[j-1] = temp;
                        }
                        indices[j]=i;
                    }
                    return indices;//indices of sorted elements
             }
            

            【讨论】:

              【解决方案14】:

              我想用这个,因为它很快。但是我用它来做int,你可以把它改成float。

              private static void mergeSort(int[]array,int[] indexes,int start,int end){
                  if(start>=end)return;
                  int middle = (end-start)/2+start;
                  mergeSort(array,indexes,start,middle);
                  mergeSort(array,indexes,middle+1,end);
                  merge(array,indexes,start,middle,end);
              }
              private static void merge(int[]array,int[] indexes,int start,int middle,int end){
                  int len1 = middle-start+1;
                  int len2 = end - middle;
                  int leftArray[] = new int[len1];
                  int leftIndex[] = new int[len1];
                  int rightArray[] = new int[len2];
                  int rightIndex[] = new int[len2];
                  for(int i=0;i<len1;++i)leftArray[i] = array[i+start];
                  for(int i=0;i<len1;++i)leftIndex[i] = indexes[i+start];
                  for(int i=0;i<len2;++i)rightArray[i] = array[i+middle+1];
                  for(int i=0;i<len2;++i)rightIndex[i] = indexes[i+middle+1];
                  //merge
                  int i=0,j=0,k=start;
                  while(i<len1&&j<len2){
                      if(leftArray[i]<rightArray[j]){
                          array[k] = leftArray[i];
                          indexes[k] = leftIndex[i];
                          ++i;
                      }
                      else{
                          array[k] = rightArray[j];
                          indexes[k] = rightIndex[j];
                          ++j;
                      }
                      ++k;
                  }
                  while(i<len1){
                      array[k] = leftArray[i];
                      indexes[k] = leftIndex[i];
                      ++i;++k;
                  }
                  while(j<len2){
                      array[k] = rightArray[j];
                      indexes[k] = rightIndex[j];
                      ++j;++k;
                  }
              }
              

              【讨论】:

                【解决方案15】:

                我想最简单的方法是在创建数组时对其进行索引。您将需要键值对。如果索引是一个单独的结构,那么我看不出没有其他对象如何做到这一点(虽然有兴趣看到它)

                【讨论】:

                  猜你喜欢
                  • 2013-07-24
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-08-30
                  • 1970-01-01
                  • 2013-08-25
                  • 2020-12-20
                  • 1970-01-01
                  • 2012-12-20
                  相关资源
                  最近更新 更多