考虑一个数组。根据另一个给出元素新位置的数组来置换元素的好方法是什么(不先制作数组的副本)?
例如
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>
答案 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];
由于a
和b
都是int数组,因此您不会耗尽任何多余的内存。您在a
中以正确的值结束,而不使用比给您更多的内存。这不是最大效率(尽管它是O(n)),但它最容易理解。
答案 1 :(得分:1)
通过一次处理一个置换数组的周期,可以在O(n)时间内用O(1)额外存储器完成。
注意:此方法比此特定设置所需的更复杂(a
和b
都是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个交换。