根据另一个元素置换数组的元素,而不复制

时间:2017-12-18 19:19:32

标签: c++ arrays algorithm permutation

考虑一个数组。根据另一个给出元素新位置的数组来置换元素的好方法是什么(不先制作数组的副本)?

例如

int a[]={37,43,5,10}; //array to permute
int b[]={3,1,4,2};  //new position of each element

所以应该成为

{43,10,37,5}

我自然而然地考虑制作a的副本,然后在新位置重新分配其元素。但有没有办法在不制作数组副本的情况下(即更简单的方法)?

注意:如果可能,执行此操作的方法不应使用特定的C ++标头,而只应使用<iostream>

3 个答案:

答案 0 :(得分:4)

最简单的答案是将a复制到b,然后随时销毁b:

for (i = 0; i < B_SIZE; ++i):
    b[i] = a[b[i] - 1];

然后,如果必须,请将b复制回a

for (i = 0; i < B_SIZE; ++i):
    a[i] = b[i];

由于ab都是int数组,因此您不会耗尽任何多余的内存。您在a中以正确的值结束,而不使用比给您更多的内存。这不是最大效率(尽管它是O(n)),但它最容易理解。

答案 1 :(得分:1)

通过一次处理一个置换数组的周期,可以在O(n)时间内用O(1)额外存储器完成。

注意:此方法比此特定设置所需的更复杂(ab都是int数组),但它有一些好处:

  • 它可以处理任意数据类型(例如置换数组a可以是字符串数组)。

  • 它可以保留b排列数组中的原始值。

考虑最初的例子:

int a[] = {37, 43, 5, 10};  // array to permute
int b[] = { 3,  1, 4,  2};  // new position of each element

数组b表示我们要进行以下分配链:

a[1] <-- a[3] <-- a[4] <-- a[2] <-- a[1]

问题在于,在上一次分配时,我们无法再访问a[1](它已被替换为a[3])。

但是,起始元素的原始值可以保存在辅助变量中,以便我们在关闭循环时使用它(保证当我们关闭循环时,我们将精确到达我们已经开始的元素从 - 否则一些元素可以多种方式到达,我们会有b [i] = b [j]),对于某些i!= j)。

通常,置换可能包含多个周期。处理完一个循环后,我们需要从一个尚未更新的元素开始(它不是目前处理的循环的一部分。)

因此,我们需要知道到目前为止哪些元素未被处理。

一种可能的方法是临时修改置换向量b,以便跟踪哪些元素已更新,例如否定b中相应位置的值更新a中的元素时。这样做的好处是,最后,我们可以遍历b的所有元素并恢复初始值(通过再次否定所有元素)。

以下是之前想法的实现。

int main() {
    int a[] = {11, 22, 33, 44};
    int b[] = { 2,  1,  4,  3};
    int aux, crtIdx, nxtIdx;

    int n = sizeof(a) / sizeof(a[0]);

    for (int i = 0; i < n; i++) {
        // check whether the i'th element
        // was already processed
        if (b[i] < 0) {
            continue;
        }

        // start processing of a new cycle;
        // backup the first value to aux
        aux = a[i];
        crtIdx = i;
        nxtIdx = b[i] - 1;

        // advance along the cycle until we reach 
        // again the first element 
        while (nxtIdx != i) {
            a[crtIdx] = a[nxtIdx];

            // use the b array to mark that the 
            // element at crtIdx was updated
            b[crtIdx] = -b[crtIdx];

            crtIdx = nxtIdx;
            nxtIdx = b[nxtIdx] - 1;
        }

        // finalize the cycle using the aux variable
        a[crtIdx] = aux;
        b[crtIdx] = -b[crtIdx];
    }

    // restore the original values of b[i]
    for (int i = 0; i < n; i++) {
        b[i] = -b[i];
    }
}

注意:虽然代码包含两个嵌套循环,但时间复杂度为O(n)。这可以通过考虑每个元素只更新一次的事实来看出(如果我们到达已经处理过的元素,则外部循环会立即继续)。

我将在此处显示算法执行的主要步骤,使用此示例:

a = {11, 22, 33, 44}
b = { 2,  1,  4,  3}

第1步。 我们查看第一个元素(请参阅代码中的for外部i循环)。第一个元素不是已处理循环的一部分,因此我们开始处理新循环。我们通过在aux中存储此元素的初始值来实现此目的。

a = {11, 22, 33, 44}
b = { 2,  1,  4,  3}
aux = 11

第2步。 我们沿着这个循环,更新元素,将它们标记为更新(通过否定b数组中的相应元素),直到我们再次到达第一个元素。

a = {22, 22, 33, 44}
b = {-2,  1,  4,  3}
aux = 11

第3步。 我们再次到达循环的第一个元素,并且需要其初始值以更新循环的最后一个元素。这是我们使用辅助变量的地方。这样,第一个循环就完全处理了。

a = {22, 11, 33, 44}
b = {-2, -1,  4,  3}
aux = 11

第4步。 我们继续外循环(for超过i)。我们看到第二个元素已经处理过了(因为b[1]是负数),因此我们不会在这里开始一个新的循环。我们继续,并在第三个元素(尚未处理)开始一个新的循环。

现在我们可以重用相同的aux变量来备份此循环的第一个元素(我们不再需要保留第一个循环中的值,因为该循环已完全解析)。

a = {22, 11, 33, 44}
b = {-2, -1,  4,  3}
aux = 33

第5步。 第二个循环的处理以前面步骤中描述的类似方式进行,结果如下:

a = {22, 11, 44, 33}
b = {-2, -1, -4, -3}
aux = 33

第6步。 i上的循环继续,并且未找到未处理的元素。 现在,我们知道所有元素都已处理完毕,我们可以否定b中的每个元素以恢复原始值。

a = {22, 11, 44, 33}
b = { 2,  1,  4,  3}

答案 2 :(得分:1)

如果您想避免复制数组,通常意味着限制自己进行交换。

如果我们使用互换来对b[]进行排序,并对a[]使用相同的互换,那么a[]将根据b[]的值进行置换。

我将逐步完成下面的算法。为简单起见,我开始在1处进行数组计数,尽管C数组计数从0开始计算。您必须在代码中进行调整。

a[]={37, 43, 5, 10} //array to permute
b[]={3, 1, 4, 2}  //new position of each element

i = 1; b[i] = 3
swap(a[1], a[3]); a[] = {5, 43, 37, 10}
swap(b[1], b[3]); b[] = {4, 1, 3, 2}

i = 1; b[i] = 4
swap(a[1], a[4]); a[] = {10, 43, 37, 5}
swap(b[1], b[4]); b[] = {2, 1, 3, 4}

i = 1; b[i] = 2
swap(a[1], a[2]); a[] = {43, 10, 37, 5}
swap(b[1], b[2]); b[] = {1, 2, 3, 4}

i = 1; b[i] = 1
++i

i = 2; b[i] = 2
++i

i = 3; b[i] = 3
++i

i = 4; b[i] = 4
++i

i = 5; i > 4
DONE

请注意我们如何在那里结束b[]。考虑b[]={2, 1, 4, 3}

的情况
a[] = {37, 43, 5, 10}
b[] = {2, 1, 4, 3}

i = 1; b[i] = 2
swap(a[1], a[2]); a[] = {43, 37, 5, 10}
swap(b[1], b[2]); b[] = {1, 2, 4, 3}

i = 1; b[i] = 1
++i

i = 2; b[i] = 2
++i

i = 3; b[i] = 4
swap(a[3], a[4]); a[] = {43, 37, 10, 5}
swap(b[3], b[4]); b[] = {1, 2, 3, 4}

i = 3; b[i] = 3
++i

i = 4; b[i] = 4
++i

i = 5; i > 4
DONE

每次交换时,数组中的一个元素最终位于正确的位置,这意味着我们最多执行N个交换。