我已经阅读了有关旋转矩阵的所有堆栈溢出问题 除了在单层或整个矩阵的任一方向上转置或旋转90度或180度外,它们都没有解决任何其他问题。
我已经回答了有关完成该问题的问题,但是我正在寻找快速和/或“就地”方法,最好是任何方法 除了我给的以外,如果只是为了 教育目的。我的示例在Perl 5中,但是只要您的意图很明确,大多数任何语言都应该可以接受。
我的示例中使用的矩阵如下
# 2D array @arr has 3 layers.
# @arr is
# 0 1 2 3 4 5
# a b c d e f
# 5 4 3 2 1 0
# 9 8 7 6 5 4
# f e d c b a
# 4 5 6 7 8 9
Goal->顺时针旋转中间2层以获得以下内容...
请注意中间层'b c'的左上角如何向右移动2个位置,而从中间左侧开始的'8 4'现在是'b c'的位置。
# Rotated @arr is
# 0 1 2 3 4 5
# a 8 4 b c f
# 5 e 3 2 d 0
# 9 d 7 6 e 4
# f c b 5 1 a
# 4 5 6 7 8 9
目前,我一直在做一些类似的事情,这确实很慢。尤其是在许多大型阵列上。
#!/usr/bin/env perl
use strict; use warnings;
# Coordinates are $arr[$row][$col]
my @arr;
$arr[0][0]='0'; $arr[0][1]='1'; $arr[0][2]='2'; $arr[0][3]='3'; $arr[0][4]='4'; $arr[0][5]='5';
$arr[1][0]='a'; $arr[1][1]='b'; $arr[1][2]='c'; $arr[1][3]='d'; $arr[1][4]='e'; $arr[1][5]='f';
$arr[2][0]='5'; $arr[2][1]='4'; $arr[2][2]='3'; $arr[2][3]='2'; $arr[2][4]='1'; $arr[2][5]='0';
$arr[3][0]='9'; $arr[3][1]='8'; $arr[3][2]='7'; $arr[3][3]='6'; $arr[3][4]='5'; $arr[3][5]='4';
$arr[4][0]='f'; $arr[4][1]='e'; $arr[4][2]='d'; $arr[4][3]='c'; $arr[4][4]='b'; $arr[4][5]='a';
$arr[5][0]='4'; $arr[5][1]='5'; $arr[5][2]='6'; $arr[5][3]='7'; $arr[5][4]='8'; $arr[5][5]='9';
# Print matrix
print_matrix(@arr);
# Extract layer 2
my $target_layer=2;
my $layer_two=extract_layer($target_layer,@arr);
# From the top left corner of the layer, it is as follows
# bcde15bcde84
print "\n$layer_two\n";
# Rotate layer 2 clockwise 2 places
$layer_two=rotate_layer_cl($layer_two,2);
# 84bcde15bcde
print "$layer_two\n\n";
# Replace layer 2 in the same way
@arr=replace_layer($target_layer,$layer_two,@arr);
# Print again
print_matrix(@arr);
### Sub functions ###
# Extract layer by walking around it's coordinates like so
# [1,1]-[1,4] Top(left->right)
# [2,4]-[4,4] Right(top->bottom)
# [4,3]-[4,1] Bottom(right->left)
# [3,1]-[2,1] Left(bottom->top)
sub extract_layer {
my ($layer_cnt,@matrix)=@_;
my $width=scalar(@matrix);
my $layer_width=$width-$layer_cnt;
# layer_cnt=2
# width=6
# layer_width=4
my $layer;
for my $col ( $layer_cnt-1..$layer_width ) {
$layer.=$matrix[$layer_cnt-1][$col];
}
for my $row ( $layer_cnt..$layer_width ) {
$layer.=$matrix[$row][$layer_width];
}
my $cnt=$layer_width-1;
while ( $cnt >= $layer_cnt-1 ) {
$layer.=$matrix[$layer_width][$cnt];
$cnt--;
}
$cnt=$layer_width-1;
while ( $cnt >= $layer_cnt ) {
$layer.=$matrix[$cnt][$layer_cnt-1];
$cnt--;
}
return $layer;
}
# Shift input to the right by $n places, wrapping around.
sub rotate_layer_cl {
my $n=$_[1];
my $buf=substr($_[0],length($_[0])-$n,$n);
return $buf.substr($_[0],0,length($_[0])-$n);
}
# Replace each char from the rotated layer.
sub replace_layer {
my ($layer_cnt,$layer,@matrix)=@_;
my $width=scalar(@matrix);
my $layer_width=$width-$layer_cnt;
# layer_cnt=2
# width=6
# layer_width=4
my $slot=0;
for my $col ( $layer_cnt-1..$layer_width ) {
$matrix[$layer_cnt-1][$col]=substr($layer,$slot,1);
$slot++;
}
for my $row ( $layer_cnt..$layer_width ) {
$matrix[$row][$layer_width]=substr($layer,$slot,1);
$slot++;
}
my $cnt=$layer_width-1;
while ( $cnt >= $layer_cnt-1 ) {
$matrix[$layer_width][$cnt]=substr($layer,$slot,1);
$slot++;
$cnt--;
}
$cnt=$layer_width-1;
while ( $cnt >= $layer_cnt ) {
$matrix[$cnt][$layer_cnt-1]=substr($layer,$slot,1);
$slot++;
$cnt--;
}
return @matrix;
}
# Prints given matrix
sub print_matrix {
foreach my $row (@_) {
my $cnt=0;
foreach my $char (@$row) {
print $char; $cnt++;
if ( $cnt == scalar(@_) ) {
print "\n";
} else {
print " ";
}
}
}
}
上面的代码输出如下,
0 1 2 3 4 5
a b c d e f
5 4 3 2 1 0
9 8 7 6 5 4
f e d c b a
4 5 6 7 8 9
bcde15bcde84
84bcde15bcde
0 1 2 3 4 5
a 8 4 b c f
5 e 3 2 d 0
9 d 7 6 e 4
f c b 5 1 a
4 5 6 7 8 9
其中显示了旋转中间层之前的数组,中间层作为字符串,中间层作为字符串移位,以及中间层向右旋转2个位置后的最终结果数组。
-----编辑-----
到目前为止,我发现最快的方法是不实际使用完整的2D数组,而是将块放置在常规数组中,例如...
my @hex_ary=('1234', '5678', '90ab', 'cdef');
然后创建图层字符串,并按照我的原始示例所示的方式进行移动。
但是,因为它们是琴弦,所以我只需要上下左右行走即可。顶部和底部简称为...
my $top=substr($hex_ary[0],0,3);
和
my $bot=reverse(substr($hex_ary[3],0,3));
在这个小型4x4阵列中的外层,而侧面则通过进行遍历
my $right_side_char=substr($hex_ary[$top_row-$i],-1);
my $left_side_char=substr($hex_ary[$bot_row+$i],0,1);
这样做可以提高性能。之所以采用2D方法,是因为100%是因为从数组中获取的切片数是一半+2。
也许对C有更好了解的人可以创建更有效的东西。我觉得Perl有时候只能做到这么多。
答案 0 :(得分:1)
简而言之:
遍历列表索引
右移N个位置:shift == N
#!/usr/bin/perl
use strict;
use warnings;
sub dump_array($$) {
my($label, $array) = @_;
print "${label}:\n";
foreach my $row (@{ $array }) {
print join(" ", @{ $row }), "\n";
}
print "\n";
}
sub copy_array($) {
my($array) = @_;
my @copy;
foreach my $row (@{ $array }) {
push(@copy, [ @{ $row } ]);
}
return(\@copy);
}
sub ring_index_to_row_col($$$$$) {
my($N, $ring, $total, $length, $index) = @_;
my($row, $col);
if ($index < $length) {
# top row, left to right
$row = 0;
$col = $index;
} elsif ($index < 2 * $length - 2) {
# top to bottom, right row
$row = $index - $length + 1;
$col = $N - 1 - 2 * $ring;
} elsif ($index < 3 * $length - 2) {
# bottom row, right to left
$row = $N - 1 - 2 * $ring;
#$col = $length - 1 - ($index - 2 * $length + 2);
$col = -$index + 3 * $length - 3;
} else {
# bottom to top, left row
#$row = $total - 1 - $index + 1;
$row = $total - $index;
$col = 0;
}
#print "${index}\t of ${total}\t-> ${row}, ${col}\n";
# shift $length x length array to offset ($ring, $ring)
return([$row + $ring, $col + $ring]);
}
sub ring_members($$) {
my($N, $ring) = @_;
my @list;
# @TODO: odd N?
#
# Examples for N == 6
# 0 -> 2*6 + 2*4 = 20
# 1 -> 2*4 + 2*2 = 12
# 2 -> 2*2 + 2*0 = 4
#
# Examples for N == 5
# 0 -> 2*5 + 2*3
# 1 -> 2*3 + 2*1
# 2 -> 1
#
# Examples for N == 4
# 0 -> 2*4 + 2*2 = 12
# 1 -> 2*2 + 2*0 = 4
my $length = $N - 2 * $ring;
my $members = 4 * $N - 8 * $ring - 4;
foreach my $index (0..$members-1) {
push(@list, ring_index_to_row_col($N, $ring, $members, $length, $index));
}
return(\@list);
}
sub rotate_array_ring(\@$$) {
my($source, $ring, $shift) = @_;
# Sanity check. @TODO is the check correct for odd N?
my $N = @{ $source };
die "ERROR: invalid ring '${ring}' for 2D array of size $N!\n"
unless $ring < ($N / 2);
my $copy = copy_array($source);
my $list = ring_members($N, $ring);
my $length = @{ $list };
foreach my $index (0..$length-1) {
my($row, $col) = @{ $list->[ $index ] };
my($s_row, $s_col) = @{ $list->[($index + $shift) % $length ] };
$copy->[$s_row]->[$s_col] = $source->[$row]->[$col];
}
return($copy);
}
my @source;
while (<DATA>) {
chomp;
push(@source, [ split(/\s+/, $_) ]);
}
dump_array('SOURCE', \@source);
dump_array('SHIFT 1 RING 0', rotate_array_ring(@source, 0, 1));
dump_array('SHIFT 1 RING 1', rotate_array_ring(@source, 1, 1));
dump_array('SHIFT 1 RING 2', rotate_array_ring(@source, 2, 1));
dump_array('SHIFT 2 RING 0', rotate_array_ring(@source, 0, 2));
dump_array('SHIFT 2 RING 1', rotate_array_ring(@source, 1, 2));
dump_array('SHIFT 2 RING 2', rotate_array_ring(@source, 2, 2));
exit 0;
__DATA__
0 1 2 3 4 5
a b c d e f
5 4 3 2 1 0
9 8 7 6 5 4
f e d c b a
4 5 6 7 8 9
示例输出:
$ perl dummy.pl
SOURCE:
0 1 2 3 4 5
a b c d e f
5 4 3 2 1 0
9 8 7 6 5 4
f e d c b a
4 5 6 7 8 9
SHIFT 1 RING 0:
a 0 1 2 3 4
5 b c d e 5
9 4 3 2 1 f
f 8 7 6 5 0
4 e d c b 4
5 6 7 8 9 a
SHIFT 1 RING 1:
0 1 2 3 4 5
a 4 b c d f
5 8 3 2 e 0
9 e 7 6 1 4
f d c b 5 a
4 5 6 7 8 9
SHIFT 1 RING 2:
0 1 2 3 4 5
a b c d e f
5 4 7 3 1 0
9 8 6 2 5 4
f e d c b a
4 5 6 7 8 9
SHIFT 2 RING 0:
5 a 0 1 2 3
9 b c d e 4
f 4 3 2 1 5
4 8 7 6 5 f
5 e d c b 0
6 7 8 9 a 4
SHIFT 2 RING 1:
0 1 2 3 4 5
a 8 4 b c f
5 e 3 2 d 0
9 d 7 6 e 4
f c b 5 1 a
4 5 6 7 8 9
SHIFT 2 RING 2:
0 1 2 3 4 5
a b c d e f
5 4 6 7 1 0
9 8 2 3 5 4
f e d c b a
4 5 6 7 8 9
优化:不复制整个数组,但是
$list
将环成员复制到列表中,然后即两个循环而不是一个循环:
my $list = ring_members($N, $ring);
my $length = @{ $list };
# in-place approach
my @members;
foreach my $index (0..$length-1) {
my($row, $col) = @{ $list->[ $index ] };
push(@members, $source->[$row]->[$col]);
}
foreach my $index (0..$length-1) {
my($row, $col) = @{ $list->[ ($index + $shift) % $length ] };
$source->[$row]->[$col] = $members[$index];
}
return($source);
优化2:如果您需要将一系列操作应用于一组相同大小的2D数组,则可以预先生成它们并一遍又一遍地处理它们使用功能:
my $N = 6; # all arrays are of same size
my @rings = (
ring_members($N, 0),
ring_members($N, 1),
ring_members($N, 2),
);
my @operations = (
{ ring => $rings[0], shift => 1 },
{ ring => $rings[1], shift => -1 },
{ ring => $rings[2], shift => 2 },
# ...
)
# apply one operation in-place on array
sub apply_operation($$) {
my($array, $operation) = @_;
my $list = $operation->{ring};
my $shift = $operation->{shift};
my $length = @{ $list };
my @members;
foreach my $index (0..$length-1) {
my($row, $col) = @{ $list->[ $index ] };
push(@members, $array->[$row]->[$col]);
}
foreach my $index (0..$length-1) {
my($row, $col) = @{ $list->[ ($index + $shift) % $length ] };
$array->[$row]->[$col] = $members[$index];
}
}
# apply operation sequence in-place on array
sub apply_operations($$) {
my($array, $operations) = @_;
foreach my $operation (@{ $operations }) {
apply_operation($array, $operation);
}
}
apply_operations(\@array1, \@operations);
apply_operations(\@array2, \@operations);
apply_operations(\@array3, \@operations);
...
优化3:不会使用就地方法复制完整的组,而只会复制那些可能被轮换覆盖的成员。即使用3个循环:
$shift
成员(取决于旋转方向)复制到@cache
数组中。$length - abs($shift)
个成员。@cache
个成员复制到其轮换的位置。 优化4:不使用2D数组(AoA),而是使用包含$N * $N
个条目的1D数组。即
ring_index_to_row_col()
返回[$row, $col]
,而不是ring_index_to_array_index()
返回$row * $N + $col
。->[$row]->[$col]
将替换为->[$array_index]
。答案 1 :(得分:1)
我的方法的想法是使用包含n
的{{3}}(好吧,我在这里使用Perl数组是因为我找不到合适的模块,但它远非理想)。元素(其中n
是要旋转图层的位置的数量),并遍历该图层的每个元素,并且每次将push
的值放入队列中并替换与队列的第一个元素。
这是子rotate_matrix
的工作。
-$i
和$j
代表图层当前元素的索引。它们的值介于$target_layer
和$max_i
/ $max_j
之间;子next_ij
负责计算层的下一个元素的索引。
-有趣的事情发生在while(1)
循环内(实际上是do...while
;但是do while
循环在Perl中是queue):当前元素的值被压入队列,如果它们在队列中超过$n
个元素(这意味着我们已经推送了$n
个元素,表示我们想旋转$n
个事实),我们将其值替换为队列的第一个元素。 continue
块负责递增$i
和$j
,并在返回到图层的第一个元素后停止循环。
-完成循环后,该图层的前n
个元素尚未更新(因为此next unless @queue > $n
),因此我们需要注意:这就是{{1 }}循环)。
复杂度方面,无需复制数组。内存使用量为while (@queue)
(队列的大小)。在时间上,您遍历该图层的每个元素一次,并且每次您从队列中O(n)
和push
(使用更好的数据结构,如链接列表,这将是{{1 }},但使用Perl数组,它可能位于shift
的行中;但可能摊销到O(1)
),并计算下一个索引(O(n)
)。因此,您最终会浪费时间,最终会遇到O(1)
。
一些警告:如果O(1)
大于图层中的元素数量,则将无法正常工作(但可以使用简单的模数对其进行修复)。如果您要的是矩阵中不存在的一层(例如2x2矩阵的第4层),则会发生怪异的事情(但是再一次,很容易修复)。
O(size of the layer to rotate)