这实际上是一个概念上的(语言不可知的)问题,但为了便于解释,我将使用C ++。我更喜欢可以移植到其他语言的答案(没有指针算术或内存技巧)。
假设我们有:
arr
,我们的矩形某种任意类型的T
void shift(int dx, int dy)
,执行“轮换”的功能numRows
,行数numCols
,列数 shift()
移动数组,使所有行向下移动dx
个位置,并且超出边界的行将环绕到开头。 (同样对于列和dy
。)让我们说这是我们的数组最初的样子:
{{a1, a2, a3, a4},
{b1, b2, b3, b4},
{c1, c2, c3, c4},
{d1, d2, d3, d4}};
在我们调用我们的函数后:shift(2,1)
,arr
应该如下所示:
{{c4, c1, c2, c3},
{d4, d1, d2, d3},
{a4, a1, a2, a3},
{b4, b1, b2, b3}};
在这种情况下,dx
2 ,所以所有内容都移动向下两个位置,dy
1 ,所以一切都转移到正确一个地方。
以下是解决此问题的方法:
void shift(int dx, int dy)
{
T newArr[numRows][numCols];
for(int r = 0; r < numRows; r++)
{
for(int c = 0; c < numCols; c++)
newArr[(r + dx) % numRows][(c + dy) % numCols] = arr[r][c];
}
for(int r = 0; r < numRows; r++)
{
for(int c = 0; c < numCols; c++)
arr[r][c] = newArr[r][c];
}
}
我对这段代码不满意,因为它既不节省时间也不节省空间。我正在寻找一个更优雅的解决方案,通过更少的循环和使用更少的内存来完成更多工作。
答案 0 :(得分:2)
另一种可能性是根本不移动元素。我们的想法是拥有一个函数来转换所使用的索引,使原始数组出现旋转。
通过将原始数组包装在适当的数据类型中,可以获得轻微的性能损失。但无论何时你在旋转(或镜像,或反向,或其他),你都会在记忆和时间方面获得。
答案 1 :(得分:1)
我建议采用以下解决方案:
#include <stdio.h>
#include <memory>
int main() {
const int nrows = 4, ncols = 5;
const int dx = 2, dy = 1;
int a[nrows ][ncols] = { {1, 2, 3, 4, 5},
{ 6, 7, 8, 9, 10 },
{ 11, 12, 13, 14, 15 },
{ 16, 17, 18, 19, 20 }
};
int tmp[nrows][ncols];
for (int i = 0; i < nrows; i++)
for (int j = 0; j < ncols; j++)
tmp[(i + dx) % nrows][(j + dy) % ncols] = a[i][j];
memcpy(a, tmp, sizeof(tmp));
for (int i = 0; i < nrows; i++)
for (int j = 0; j < ncols; j++)
printf(j < ncols - 1 ? "%3d " : "%3d\n", a[i][j]);
}
使用内存复制的替代方法特定于c++
。这是可能的,因为在c++
的内存中存储多维数组的方法是连续的。行的最后一个元素后跟下一个元素。
#include <stdio.h>
#include <memory>
int main() {
const int nrows = 4, ncols = 5;
const int dx = 2, dy = 1;
int a[nrows][ncols] = { {1, 2, 3, 4, 5},
{ 6, 7, 8, 9, 10 },
{ 11, 12, 13, 14, 15 },
{ 16, 17, 18, 19, 20 }
};
int tmp[nrows][ncols];
memcpy(tmp + dx, a, (nrows - dx) * ncols * sizeof(int));
memcpy(tmp, a + (nrows - dx), dx * ncols * sizeof(int));
memcpy(a, tmp, sizeof(tmp));
for (int i = 0; i < nrows; i++) {
memcpy(tmp[i] + dy, a[i], (ncols - dy) * sizeof(int));
memcpy(tmp[i], a[i] + ncols - dy, dy * sizeof(int));
}
memcpy(a, tmp, sizeof(tmp));
for (int i = 0; i < nrows; i++)
for (int j = 0; j < ncols; j++)
printf(j < ncols - 1 ? "%3d " : "%3d\n", a[i][j]);
}
答案 2 :(得分:0)
假设你有一个较低级别的函数可以有效地复制Matrix的子块(处理低级别的内存排列,行主要列主要排序等...),操作可以在概念上分解为4个子块副本。使用matlab数组表示法和基于1的数组索引,就像:
tmp(1:nr , 1:nc) = a(end-nr+1:end , end-nc+1:end );
tmp(1:nr , nc+1:end) = a(end-nr+1:end , 1:end-nc );
tmp(nr+1:end , 1:nc) = a(1:end-nr , end-nc+1:end );
tmp(nr+1:end , nc+1:end) = a(1:end-nr , 1:end-nc );
请注意,让低级别函数执行低级别作业的假设非常常见(例如BLAS和LAPACK)。
答案 3 :(得分:0)
void scroll_left(int Array[8][8])
{
for (u=0; u<8; u++)
{
for(int r=0; r<8; r++)
{
for (int c=0; c<8; c++)
temp[r][c]=A[r][(c+u)%8];
}
delay_1(1000);
}
}
答案 4 :(得分:0)
可以使用O(1)辅助空间以几种方式完成此操作。所有这些都是旋转一维数组的算法的改编。为了使描述/符号保持简单,我假设dx
和dy
是肯定的,并假设如果它们是否定的,您可以弄清楚该怎么做。
有一个非常简单的算法,可以在线性时间内将一维数组旋转1步:将最后一个元素存储在一个临时变量中,将其余元素向右移动一个,然后将该临时变量写入数组中的第一个位置。
可以直接将其水平或垂直旋转2D阵列1步,对角线则需要一点点额外的工作。因此,您可以应用dx
水平旋转和dy
垂直旋转;或者您可以先进行min(dx, dy)
个对角旋转,然后进行max(dx, dy) - min(dx, dy)
个正交旋转。
这需要O(numRows * numCols * (dx + dy))
时间,因此不适用于大班次。但是,如果您只移动一个空格,那不只是很好;还可以。最佳。
让a
为numRows
和dx
的{{3}},而b
为numRows
和dy
的GCD 。然后,所需的旋转将在a * b
个独立的长度为numCols * numRows / (a * b)
的循环中对数组进行置换。可以通过GCD以对数时间计算GCD。
对于i = 0 to a - 1
和j = 0 to b - 1
,使用临时变量方法将从arr[j][i]
开始的循环旋转一步。此循环包含元素arr[j][i]
,然后是arr[j + dy][i + dx]
,然后是arr[j + 2*dy][i + 2*dx]
,依此类推,其中索引分别以numRows
和numCols
为模。 / p>
时间复杂度为O(numRows * numCols)
,因为每个数组组件只能读取一次,并且只能写入一次。但是,由于以下两个原因,该算法的性能可能会降低:进行大量的模运算(当数组维数不是2的幂时,这是错误的);并且连续的读/写操作不在相邻的数组位置(当整个数组位置时,是不好的)数组不适用于缓存)。
长度为n
的一维数组可以通过以下算法向右旋转k
步:
0 .. k-1
,k .. n-1
。我们可以通过将行旋转dx
,然后将列旋转dy
来执行2D旋转。
此算法读取和写入每个数组组件四次,因此其时间复杂度也为O(numRows * numCols)
,但与使用循环分解的算法相比,它在数组上的传递次数更多。但是,它不进行模运算,并且可能有较少的高速缓存未命中。实施起来也更简单。