数组的就地排列遵循此规则

时间:2010-06-17 15:16:28

标签: algorithm

假设有一个数组,我们想要找到奇数索引中的所有内容(索引从0开始),然后将其移到最后。 偶数索引中的所有内容都将其移至开头。 保留所有奇数索引项和所有偶数索引项的相对顺序。

即。如果数组是

a1 b1 a2 b2 ...  an bn    
操作后

变为

a1 a2 a3 ... an b1 b2 ... bn

这可以在原地和O(n)时间内完成吗?

4 个答案:

答案 0 :(得分:7)

这是可能的,但它非常复杂!一个更简单的O(nlogn)和O(1)空间解决方案可能更适合编码和缓存。

我们将解决与您不同的问题,但一旦我们解决了这个问题,您的问题就无法解决。

将数组视为

b1, a1, b2, a2, ..., bn, an

,你必须将其转换为

a1, a2, ..., an, b1, b2, ..., bn

使用索引1到2n,

我们看到这是由

给出的
i -> (n+1)*i (mod 2n+1).

O(nlogn)时间O(1)空间解

我们可以如下使用分而治之。

首先是接近n / 2转换的一些m

b1, a1, ..., bn , an

a1,a2,...am, b1,b2, ..bm, a(m+1), ..., an, b(m+1), ... , bn

通过递归地应用于前2m个元素,然后是剩余的。

现在我们需要通过m个点对中间数组进行循环移位(这可以在O(n)时间和O(1)空间中完成)

给予

a1, a2, .., am , a(m+1), ..., an, b1, b2, ..., bm, b(m+1), ..., bn.

当然,正如IVlad指出的那样,这需要O(logn)堆栈空间。我们可以通过以下方式解决这个问题:

我们有:

b1 a1, b2 a2, .. bm am, b(m+1) a(m+1), ..., bn an

现在在数组的后半部分交换对以提供

b1 a1, b2 a2, .. bm am, a(m+1) b(m+1), ..., an bn

现在循环移位奇数位置的元素:b1, b2, .., bm, a(m+1), a(m+2) ..., a(n).

这就像

a(m+1) a1, a(m+2) a2, ..., a(2m) am, a(2m+1) b(m+1),...,an b(n-m), b1 b(n-m+1),...,bm bn

现在再次交换数组的后半部分以提供

a(m+1) a1, a(m+2) a2, ..., a(2m) am, b(m+1) a(2m+1),...,b(n-m) an,b(n-m+1) b1,..., bn bm

现在递归地解决第一部分和第二部分给出

[a1 a2 ... am][a(m+1) ... a(2m)]   [a(2m+1) ...an b1 b2 .. bm][b(m+1) ... bn]

这适用于2m> = n或不是。

所以,这是O(nlogn)时间和O(1)空间算法。


O(n)时间O(1)空间解决方案。

使用的想法类似于以下论文中使用的想法: A simple in-place algorithm for Inshuffle

您需要阅读该论文以了解以下内容。我建议你也阅读:How to master in-place array modification algorithms?

这基本上是上述论文所解决的逆变换。

当2n + 1是3 =(3 ^ m)的幂时,这足以解决这个问题,因为我们可以使用除法和征服(如O(nlogn)解决方案)。

现在2n + 1和n + 1是相对素数,所以工作模3 ^ m,我们看到n + 1 必须是2的幂。(再看那篇论文,看看为什么:基本上任何数模3 ^ m,相对于3 ^ m的素数是2的幂,再次模3 ^ m)。

说n + 1 = 2 ^ k(我们还不知道k,注意这是模3 ^ m)。

找出k的方法,计算n + 1模3 ^ m的幂,直到它变为1.这给了我们k(并且最多是O(n)时间。)

现在我们可以看到排列的周期(见上文/ stackoverflow链接的内容)从

开始

2 ^一个* 3 ^ B

其中0 <= a&lt; k,0&lt; = b&lt;米。

所以你从每个可能的对(a,b)开始并按照排列的循环,这给出了一个O(n)时间,就地算法,因为你触摸每个元素不超过一个常数次!

这有点简短(!)如果您需要更多信息,请告诉我。

答案 1 :(得分:3)

答案 2 :(得分:0)

好吧,让我们来看看这个例子:

0 1 2 3 4 5 6 7 8 9 10

0 2 4 6 8 10 1 3 5 7 9

结果的前半部分包含索引为原始索引i的i / 2的元素,而另一半为i - n / 2 + 1,其中n是数组中的项目数。

这基本上是排序算法的特例,只有设置了特殊的顺序。

因此,这是一个使用bubblesort算法对整数数组进行排序的想法。我们需要的是将偶数值拉到开头,留下奇数值:

0 1 2 3 4 5 6 7 8 9 10
  * *
0 2 1 3 4 5 6 7 8 9 10
      * *
0 2 1 4 3 5 6 7 8 9 10
    * *
0 2 4 1 3 5 6 7 8 9 10
          * *
0 2 4 1 3 6 5 7 8 9 10
        * *
...
0 2 4 6 1 3 5 7 8 9 10
              * *
...
0 2 4 6 8 1 3 5 7 9 10
                  * *

这只有在你可以保留索引时才会起作用(例如,在这种情况下,索引与数组值相关)。而bubblesort的性能为O(n ^ 2),内存使用率为1。

无法在O(n)时间和1个内存中完成[编辑:仅使用通用排序算法!],最佳通用排序算法在O(nlog n)中执行:

http://en.wikipedia.org/wiki/Sorting_algorithm

要解决您的问题,最好的方法是生成与您的标准匹配的索引数组:

size_t* indices = new size_t[n]; 

// n is the number of items in the original array
for (int i = 0; i < n; i++)
{
    if (i < n / 2)
    {
       indices[i] = i * 2;
    }
    else
    {
       indices[i] = i - n / 2 + 1;
    }
}

然后只需使用它将原始数组中的项目映射到新位置:

// i is the new index here, which makes it appear as if ValuesArray has been sorted
Type* getOddEvenValue(size_t newIndex)
{
     // ValuesArray and indices should be available in this scope, of course
     return ValuesArray[indices[newIndex]];
}

这个东西会在O(n)中运行,但也需要O(n)内存。但是如果sizeof大小比sizeof大得多,那么它仍然是内存使用量的增加(与复制Type对象相反)。

[EDIT2:]

而不是那些漂亮的OOP代码,而且还需要你将原始矢量复制到你的类实例中,你可以简单地编写一个函数:

 size_t permuted_index(size_t i, size_t vecSize)
 {
    return ( i < vecSize/2 ? i * 2 : 2 * (i - vecSize/2) + 1);
 }

并在需要获取permutted值时使用它。实际上,这只需要O(n)时间,因为对于给定的向量,permutted_index将被评估不超过n次(至少在每个“循环”中)。

答案 3 :(得分:0)

O(1)时间,O(1)额外空间

#include <vector>
#include <iostream>
#include <ctime>
#include <boost/random.hpp>

template <typename T>
class pvec
{
private:
    // Stores values to permute
    std::vector<T> v;

    // Flag; true when permuted; false when not
    bool permuted;

public:
    pvec(size_t size) : v(size), permuted(false) {}

    // Set the permutation flag
    void set_p(bool p) { permuted = p; }

    // When the permuted flag is set, return the permuted element, otherwise
    // return the non-permuted element
    T& operator[](size_t i) { return (permuted ? v[p_ind(i)] : v[i]); }

    // Pass through the size of the vector used
    size_t size() const { return v.size(); }

private:
    // Used to determine the permuted index of a given index
    size_t p_ind(size_t i) const
    {
        return ( i < v.size()/2 ? i * 2 : 2 * (i - v.size()/2) + 1);
    }
};

// Used to display a pvec instance
template <typename T>
std::ostream& operator<<(std::ostream& os, pvec<T>& v)
{
    for (size_t i = 0; i < v.size(); ++i)
        os << (i == 0 ? "<" : ", ") << i << ":" << v[i];
    os << ">";
    return os;
}

int main(int argc, char** argv)
{
    const size_t size = 8;
    pvec<uint32_t> v(size);  // Array containing test values

    // Boost Random Number Library used to generate random values in the test
    // array
    boost::mt19937 rng(std::time(0));
    boost::uniform_int<> dist(0,65536);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> >
        var(rng, dist);

    // Generate random test values
    for (size_t i = 0; i < size; ++i)
        v[i] = var();

    // Observer original vector
    std::cout << v << std::endl;

    // Set the permutation flag O(1) time
    v.set_p(true);

    // Observe the permuted vector
    std::cout << v << std::endl;

    return 0;
}

输出:

<0:44961, 1:246, 2:54618, 3:41468, 4:12646, 5:21691, 6:26697, 7:36409>
<0:44961, 1:54618, 2:12646, 3:26697, 4:246, 5:41468, 6:21691, 7:36409>