就地阵列重新排序?

时间:2011-09-09 18:18:37

标签: arrays algorithm language-agnostic

假设我有一个长度为a的数组n和一个长度为indices的第二个数组nindices包含序列[0, n)的一些任意排列。我想重新排列a,使其按indices指定的顺序排列。例如,使用D语法:

auto a = [8, 6, 7, 5, 3, 0, 9];
auto indices = [3, 6, 2, 4, 0, 1, 5];
reindexInPlace(a, indices);
assert(a == [5, 9, 7, 3, 8, 6, 0]);

这可以在O(1)空间和O(n)时间内完成,最好不要改变indices吗?

8 个答案:

答案 0 :(得分:20)

改变indices :(。看起来不那么难看(参见稳定的就地合并)。

a = [8, 6, 7, 5, 3, 0, 9]
indices = [3, 6, 2, 4, 0, 1, 5]

for i in xrange(len(a)):
    x = a[i]
    j = i
    while True:
        k = indices[j]
        indices[j] = j
        if k == i:
            break
        a[j] = a[k]
        j = k
    a[j] = x

print a

答案 1 :(得分:7)

这就是我称之为“permute from”算法。在类C语言中,它看起来如下

  for (i_dst_first = 0; i_dst_first < n; ++i_dst_first)
  {
    /* Check if this element needs to be permuted */

    i_src = indices[i_dst_first];
    assert(i_src < n);
    if (i_src == i_dst_first)
      /* This element is already in place */
      continue;

    i_dst = i_dst_first;
    pending = a[i_dst];

    /* Follow the permutation cycle */

    do
    {
      a[i_dst] = a[i_src];
      indices[i_dst] = i_dst;

      i_dst = i_src;
      i_src = indices[i_src];
      assert(i_src != i_dst);

    } while (i_src != i_dst_first);

    a[i_dst] = pending;
    indices[i_dst] = i_dst;
  }

请注意,此算法会破坏index数组。我将其称为“permute from”,因为index[i]值指定获取结果序列的第i个元素。

另请注意,序列的就地置换所需的“元素移动”操作数等于number of misplaced elements + number of cycles in the permutation。该算法实现了这个限制,因此就移动次数而言,没有更好的算法是可能的。

该算法的潜在问题在于它基于“杂耍”方法,使其缓存行为远非最佳。因此,虽然这个算法在理论上是最好的算法,但它在现实生活中可能会失去一些更“实用”的算法。

还可以实现“permute to”算法,其中index[i]值指定重新定位原始第i个元素的位置。

答案 2 :(得分:2)

如果a是一个整数数组,则可以使用O(n)-time,O(1)空间算法来保持排列顺序indices。在这种情况下,我们可以将a置换为indexes并使用a作为逆置换的临时存储。在执行置换之后,交换阵列aindices,并使用例如indices原位倒置int [] a = {8, 6, 7, 5, 3, 0, 9}; int [] indices = {3, 6, 2, 4, 0, 1, 5}; int n = indices.length; int i, j, m; // permute a and store in indices // store inverse permutation in a for (j = 0; j < n; ++j) { i = indices[j]; indices[j] = a[i]; a[i] = j; } // swap a and indices for (j = 0; j < n; ++j) { i = indices[j]; indices[j] = a[j]; a[j] = i; } // inverse indices permutation to get the original for (i = 0; i < n; ++i) {indices[i] = -indices[i] - 1;} for (m = n - 1; m >= 0; --m) { // for (i = m, j = indices[m]; j >= 0; i = j, j = indices[j]) ; i = m; j = indices[m]; while (j >= 0) {i = j; j = indices[j];} indices[i] = indices[-j - 1]; indices[-j - 1] = m; } algorithm J from TAoCP。以下是一个可用的Java程序:

{{1}}

答案 3 :(得分:2)

indices数组是可变的时,

This回答了这个问题。

Herenot可变的解决方案。

void mutate(int[] input, int[] indices) {
   int srcInd;

   for (int tarInd = 0; tarInd < input.length; tarInd++) {
      srcInd = indices[tarInd];
      while(srcInd < tarInd) {

         // when src is behind, it will have it's final value already and the original
         // value would have been swapped with src's src pos. Keep searching for the
         // original value until it is somewhere ahead of tarInd.
         srcInd = indices[srcInd];
      }
      swap(input, srcInd, tarInd);
   }
}

答案 4 :(得分:1)

我认为处理这个问题的经典方法是绕过循环,为此,你需要从某个地方获得每个数据项的标记位。在这里,我捏了一下你可以恢复的索引数组的最高位 - 当然这假设你没有-ve数组索引或者使用无符号数的所有位作为索引。对此的一个参考是Knuth第1卷第1.3.3节对问题12的回答,该问题涉及转置矩阵的特殊情况。 Knuth提供了较慢的就地方法。 Fich,Munro和Poblete的论文“Permuting in Place”在最坏的情况下声称nlogn时间和O(1)空间。

import java.util.Arrays;

public class ApplyPerm
{
  public static void reindexInPlace(int[] rearrangeThis, int[] indices) 
  {
    final int TOP_BIT = 0x80000000;
    for (int pos = 0; pos < rearrangeThis.length; pos++)
    {
      if ((indices[pos] & TOP_BIT) != 0)
      { // already dealt with this
        continue;
      }
      if (indices[pos] == pos)
      { // already in place
        continue;
      }
      // Now shift an entire cycle along
      int firstValue = rearrangeThis[pos];
      int currentLocation = pos;
      for (;;)
      {
        // pick up untouched value from here
        int replaceBy = indices[currentLocation];
        // mark as dealt with for the next time we see it
        indices[currentLocation] |= TOP_BIT;
        if (replaceBy == pos)
        { // have worked our way round
          rearrangeThis[currentLocation] = firstValue;
          break;
        }
        if ((replaceBy & TOP_BIT) != 0)
        {
          throw new IllegalArgumentException("Duff permutation");
        }
        // Move value up 
        rearrangeThis[currentLocation] = rearrangeThis[replaceBy];
        // and fill in source of value you have just moved over
        currentLocation = replaceBy;
      }
    }
  }
  public static void main(String[] s)
  {
    int[] a = new int[] {8, 6, 7, 5, 3, 0, 9};
    int[] indices = new int[] {3, 6, 2, 4, 0, 1, 5};
    reindexInPlace(a, indices);
    System.out.println("Result is " + Arrays.toString(a));
  }
}

答案 5 :(得分:0)

您可以通过隐藏真实数组中的值来实现此目的。通过这种方式,您可以在O(1)空间和O(n)时间内执行此操作。

基本上,首先遍历indices数组,将indice数组的值存储在正确的位置。现在,这可以在您选择的算法中完成。对我来说,我只是存储数字的最高位位置的尾随位。在一次遍历中执行此操作。现在基础阵列将搞砸。

在第二个遍历存储期间,所有上半部分位于下半部分。

  

这种技术的明显缺点是存储的整数   值可以保持多达一半的位。意思是你在交易   如果是4字节整数,则值只能是2个字节。但是,如下面的代码所示,不是使用数组的一半,而是使用更好的算法来增强它,您可以在索引数组中隐藏值。在这里,您将要求在最坏的情况下保留的最大位将是数组的长度而不是前一种情况下的常数16。当长度超过2次幂16时,它将比前者表现更差。

import java.util.Arrays;
class MyClass {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        int[] orig_array = {8, 6, 7, 5, 3, 0, 9};
        int[] indices = {3, 6, 2, 4, 0, 1, 5};
        myClass.meth(orig_array, indices);
    }

    public void meth(int[] orig_array, int[] indices){
        for(int i=0;i<orig_array.length;i++)
            orig_array[i] += orig_array[indices[i]] + orig_array[indices[i]] << 15 ;
        for(int i=0;i<orig_array.length;i++)
            orig_array[i] = orig_array[i] >> 16;
        System.out.print(Arrays.toString(orig_array));
    }
}

答案 6 :(得分:0)

这是一个C ++版本(它修改了索引):

#include <algorithm>
#include <iterator>

template<class It, class ItIndices>
void permutate_from(
    It const begin,
    typename std::iterator_traits<It>::difference_type n,
    ItIndices indices)
{
    using std::swap;
    using std::iter_swap;
    for (typename std::iterator_traits<It>::difference_type i = 0; i != n; ++i)
    {
        for (typename std::iterator_traits<ItIndices>::value_type j = i; ; )
        {
            swap(j, indices[j]);
            if (j == i) { break; }
            iter_swap(begin + j, begin + indices[j]);
        }
    }
}

示例:

int main()
{
    int items[]     = { 2, 0, 1, 3 };
    int indices[]   = { 1, 2, 0, 3 };
    permutate_from(items, 4, indices);
    // Now items[] == { 0, 1, 2, 3 }
}

答案 7 :(得分:0)

JavaScript版

var input = [1,2,3,4,5],
    specArr = [0,2,1,4,3];

function mutate(input, specArr) {

    var visited = [0,2]
    for(var i=0; i<specArr.length; i++) {
        var tmp;

        //keep track of array items we've already looped through (wouldn't want to mutate twice :D)
        visited.push(specArr[i]);
        // if index hasn't changed we do nothing to input arr
        if (visited.indexOf(1) < 0) {
          // if it has changed temporarily store the value
          tmp = input[i];
          //swap input array item with spec item
          input[i] = input[specArr[i]];
          //swap specced array item with input item above
          input[specArr[i]] = tmp;
        }
    }
}

mutate(input, specArr);