我想多加一点细节。在这个答案中,关键概念被重复,节奏缓慢,故意重复。这里提供的解决方案在语法上并不是最紧凑的,但是,它是为那些希望了解什么是矩阵轮换以及由此产生的实现而设计的。
首先,什么是矩阵?就这个答案而言,矩阵只是一个宽度和高度相同的网格。注意,矩阵的宽度和高度可能不同,但为了简单起见,本教程只考虑宽度和高度相等的矩阵(方阵)。是的,矩阵是矩阵的复数。
例子矩阵为:2×2,3×3或5×5,或者更一般的N×N,A2×2矩阵因2×2=4而有4个平方,5×5矩阵因5×5=25而有25个方阵。每个正方形被称为元素或条目。我们将用句点表示每个元素(.)在下图中:
2×2矩阵. .
. .
3×3矩阵. . .
. . .
. . .
4×4矩阵. . . .
. . . .
. . . .
. . . .
那么,旋转矩阵是什么意思呢?让我们取一个2×2矩阵,在每个元素中加入一些数字,这样就可以观察到旋转:0 1
2 3
把这个旋转90度给我们:2 0
3 1
我们真的把整个矩阵向右转了一次,就像转动汽车的方向盘一样。它可能有助于考虑将矩阵“倾斜”到它的右侧。我们希望用Python编写一个函数,它接受一个矩阵并向右旋转一次。函数签名将是:def rotate(matrix):
# Algorithm goes here.
矩阵将使用二维数组定义:matrix = [
[0,1],
[2,3]
]
因此,第一个索引位置访问该行。第二个索引位置访问该列:matrix[row][column]
我们将定义一个实用函数来打印一个矩阵。def print_matrix(matrix):
for row in matrix:
print row
旋转矩阵的一种方法是一次做一层。但是什么是图层呢?想想洋葱吧。就像洋葱层一样,当每一层被移除时,我们向中心移动。其他类比是Matryoshka娃娃或者是一场传球游戏。
矩阵的宽度和高度决定了该矩阵中的层数。让我们为每个层使用不同的符号:
2×2矩阵有1层. .
. .
3×3矩阵有2层. . .
. x .
. . .
4×4矩阵有2层. . . .
. x x .
. x x .
. . . .
5×5矩阵有3层. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
6×6矩阵有3层. . . . . .
. x x x x .
. x O O x .
. x O O x .
. x x x x .
. . . . . .
7×7矩阵有4层. . . . . . .
. x x x x x .
. x O O O x .
. x O - O x .
. x O O O x .
. x x x x x .
. . . . . . .
您可能会注意到,将矩阵的宽度和高度增加一个,并不总是增加层数。以上述矩阵为例,对层数和维数进行制表,我们看到层数每增加一次,宽度和高度就会增加一次:+-----+--------+
| N×N | Layers |
+-----+--------+
| 1×1 | 1 |
| 2×2 | 1 |
| 3×3 | 2 |
| 4×4 | 2 |
| 5×5 | 3 |
| 6×6 | 3 |
| 7×7 | 4 |
+-----+--------+
然而,并不是所有的层都需要旋转。1×1矩阵在旋转前后是相同的。无论整体矩阵有多大,中心1×1层在旋转前后总是相同的:+-----+--------+------------------+
| N×N | Layers | Rotatable Layers |
+-----+--------+------------------+
| 1×1 | 1 | 0 |
| 2×2 | 1 | 1 |
| 3×3 | 2 | 1 |
| 4×4 | 2 | 2 |
| 5×5 | 3 | 2 |
| 6×6 | 3 | 3 |
| 7×7 | 4 | 3 |
+-----+--------+------------------+
给定N×N矩阵,我们如何编程确定我们需要旋转的层数?如果我们将宽度或高度除以2,忽略其余部分,则得到以下结果。+-----+--------+------------------+---------+
| N×N | Layers | Rotatable Layers | N/2 |
+-----+--------+------------------+---------+
| 1×1 | 1 | 0 | 1/2 = 0 |
| 2×2 | 1 | 1 | 2/2 = 1 |
| 3×3 | 2 | 1 | 3/2 = 1 |
| 4×4 | 2 | 2 | 4/2 = 2 |
| 5×5 | 3 | 2 | 5/2 = 2 |
| 6×6 | 3 | 3 | 6/2 = 3 |
| 7×7 | 4 | 3 | 7/2 = 3 |
+-----+--------+------------------+---------+
注意N/2匹配需要旋转的层数?有时,可旋转层数比矩阵中的总层数少一个。当最里面的层仅由一个元素(即1×1矩阵)组成,因此不需要旋转时,就会发生这种情况。它只是被忽略了。
毫无疑问,我们需要函数中的这些信息来旋转矩阵,所以现在让我们添加它:def rotate(matrix):
size = len(matrix)
# Rotatable layers only.
layer_count = size / 2
现在我们知道了哪些层是什么,以及如何确定实际需要旋转的层数,我们如何隔离单个层以使其旋转?首先,我们检查一个矩阵从最外面的层,向内,到最里面的层。5×5矩阵共有三层,两层需要旋转:. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
让我们先看看列。假设我们从0计数,定义最外层的列的位置是0和4:+--------+-----------+
| Column | 0 1 2 3 4 |
+--------+-----------+
| | . . . . . |
| | . x x x . |
| | . x O x . |
| | . x x x . |
| | . . . . . |
+--------+-----------+
0和4也是最外层的行的位置。+-----+-----------+
| Row | |
+-----+-----------+
| 0 | . . . . . |
| 1 | . x x x . |
| 2 | . x O x . |
| 3 | . x x x . |
| 4 | . . . . . |
+-----+-----------+
由于宽度和高度是相同的,所以情况总是如此。因此,我们可以定义一个层的列和行位置,只需两个值(而不是4个)。
向内移动到第二层,列的位置是1和3。是的,你猜到了,行的位置是一样的。在向内移动到下一层时,我们必须同时增加和减少行和列的位置,这一点很重要。+-----------+---------+---------+---------+
| Layer | Rows | Columns | Rotate? |
+-----------+---------+---------+---------+
| Outermost | 0 and 4 | 0 and 4 | Yes |
| Inner | 1 and 3 | 1 and 3 | Yes |
| Innermost | 2 | 2 | No |
+-----------+---------+---------+---------+
因此,要检查每个层,我们需要一个循环,它包含一个递增计数器和递减计数器,表示从最外层开始向内移动。我们称之为“层循环”。def rotate(matrix):
size = len(matrix)
layer_count = size / 2
for layer in range(0, layer_count):
first = layer
last = size - first - 1
print 'Layer %d: first: %d, last: %d' % (layer, first, last)
# 5x5 matrix
matrix = [
[ 0, 1, 2, 3, 4],
[ 5, 6, 6, 8, 9],
[10,11,12,13,14],
[15,16,17,18,19],
[20,21,22,23,24]
]
rotate(matrix)
上面的代码循环通过任何需要旋转的层的(行和列)位置。Layer 0: first: 0, last: 4
Layer 1: first: 1, last: 3
我们现在有一个循环,提供每个层的行和列的位置。变量first和last标识第一行和最后一行和列的索引位置。返回到我们的行表和列表:+--------+-----------+
| Column | 0 1 2 3 4 |
+--------+-----------+
| | . . . . . |
| | . x x x . |
| | . x O x . |
| | . x x x . |
| | . . . . . |
+--------+-----------+
+-----+-----------+
| Row | |
+-----+-----------+
| 0 | . . . . . |
| 1 | . x x x . |
| 2 | . x O x . |
| 3 | . x x x . |
| 4 | . . . . . |
+-----+-----------+
这样我们就可以穿越矩阵的各个层。现在我们需要一种在层中导航的方法,这样我们就可以在该层周围移动元素。注意,元素永远不会从一个层跳到另一个层,但是它们确实在各自的层中移动。
旋转一个层中的每个元素会旋转整个层。旋转矩阵中的所有层会旋转整个矩阵。这句话很重要,所以在继续之前请尽量理解它。
现在,我们需要一种实际移动元素的方法,即旋转每个元素,然后是层,最后是矩阵。为了简单起见,我们将恢复到一个3x3矩阵,它有一个可旋转的层。0 1 2
3 4 5
6 7 8
我们的Layer循环提供了第一列和最后一列以及第一行和最后一行的索引:+-----+-------+
| Col | 0 1 2 |
+-----+-------+
| | 0 1 2 |
| | 3 4 5 |
| | 6 7 8 |
+-----+-------+
+-----+-------+
| Row | |
+-----+-------+
| 0 | 0 1 2 |
| 1 | 3 4 5 |
| 2 | 6 7 8 |
+-----+-------+
因为我们的矩阵总是平方的,我们只需要两个变量,first和last,因为行和列的索引位置是相同的。def rotate(matrix):
size = len(matrix)
layer_count = size / 2
# Our layer loop i=0, i=1, i=2
for layer in range(0, layer_count):
first = layer
last = size - first - 1
# We want to move within a layer here.
首先和最后的变量可以很容易地用于引用矩阵的四个角。这是因为角本身可以使用first和last(这些变量没有减法、加法或抵消):+---------------+-------------------+-------------+
| Corner | Position | 3x3 Values |
+---------------+-------------------+-------------+
| top left | (first, first) | (0,0) |
| top right | (first, last) | (0,2) |
| bottom right | (last, last) | (2,2) |
| bottom left | (last, first) | (2,0) |
+---------------+-------------------+-------------+
由于这个原因,我们从外部的四个角开始旋转-我们先旋转这些角。让我们用*.* 1 *
3 4 5
* 7 *
我们想交换每一个*带着*在右边。因此,让我们继续打印我们的角定义仅使用各种排列first和last:def rotate(matrix):
size = len(matrix)
layer_count = size / 2
for layer in range(0, layer_count):
first = layer
last = size - first - 1
top_left = (first, first)
top_right = (first, last)
bottom_right = (last, last)
bottom_left = (last, first)
print 'top_left: %s' % (top_left)
print 'top_right: %s' % (top_right)
print 'bottom_right: %s' % (bottom_right)
print 'bottom_left: %s' % (bottom_left)
matrix = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
]
rotate(matrix)
产出应是:top_left: (0, 0)
top_right: (0, 2)
bottom_right: (2, 2)
bottom_left: (2, 0)
现在,我们可以很容易地从层循环中交换每个角:def rotate(matrix):
size = len(matrix)
layer_count = size / 2
for layer in range(0, layer_count):
first = layer
last = size - first - 1
top_left = matrix[first][first]
top_right = matrix[first][last]
bottom_right = matrix[last][last]
bottom_left = matrix[last][first]
# bottom_left -> top_left
matrix[first][first] = bottom_left
# top_left -> top_right
matrix[first][last] = top_left
# top_right -> bottom_right
matrix[last][last] = top_right
# bottom_right -> bottom_left
matrix[last][first] = bottom_right
print_matrix(matrix)
print '---------'
rotate(matrix)
print_matrix(matrix)
旋转角之前的矩阵:[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
旋转角后的矩阵:[6, 1, 0]
[3, 4, 5]
[8, 7, 2]
太棒了!我们成功地旋转了矩阵的每个角落。但是,我们还没有在每一层的中间旋转元素。显然,我们需要一种在层中迭代的方法。
问题是,到目前为止,我们函数中唯一的循环(我们的层循环)在每次迭代中移动到下一个层。由于我们的矩阵只有一个可旋转的层,所以层环只在旋转角后退出。让我们来看看一个更大的,5×5矩阵会发生什么(其中两层需要旋转)。函数代码已被省略,但与上述相同:matrix = [
[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24]
]
print_matrix(matrix)
print '--------------------'
rotate(matrix)
print_matrix(matrix)
产出如下:[20, 1, 2, 3, 0]
[ 5, 16, 7, 6, 9]
[10, 11, 12, 13, 14]
[15, 18, 17, 8, 19]
[24, 21, 22, 23, 4]
最外层的角已经旋转,这并不奇怪,但是,您可能也注意到下一层的角(向内)也被旋转了。这是有道理的。我们编写了代码来浏览各层,并旋转每一层的角。这似乎是一种进步,但不幸的是,我们必须后退一步。在前一个(外层)层被完全旋转之前,移动到下一个层是没有好处的。也就是说,直到层中的每个元素都被旋转。只旋转角是不行的!
先做一下深呼吸。我们需要另一个回路。一个嵌套的循环。新的嵌套循环将使用first和last变量,加上在层中导航的偏移量。我们将把这个新循环称为“元素循环”。元素循环将沿着顶部行访问每个元素,每个元素沿着右侧,每个元素沿着底部行,每个元素沿着左侧。沿着上一行向前移动需要增加列索引。
向右移动需要增加行索引。
沿着底部向后移动需要减少列索引。
向左移动需要减少行索引。
这听起来很复杂,但这很容易,因为我们为实现上述目标而增加和减少的次数在矩阵的所有四个方面都保持不变。例如:将1元素移动到顶部行。
向右移动1个元素。
沿底部行向后移动1元素。
向左侧移动1个元素。
这意味着我们可以将单个变量与first和last变量在层中移动。需要注意的是,跨越顶部行和从右侧向下移动都需要递增。当沿着底部和左侧向后移动时,两者都需要递减。def rotate(matrix):
size = len(matrix)
layer_count = size / 2
# Move through layers (i.e. layer loop).
for layer in range(0, layer_count):
first = layer
last = size - first - 1
# Move within a single layer (i.e. element loop).
for element in range(first, last):
offset = element - first
# 'element' increments column (across right)
top_element = (first, element)
# 'element' increments row (move down)
right_side = (element, last)
# 'last-offset' decrements column (across left)
bottom = (last, last-offset)
# 'last-offset' decrements row (move up)
left_side = (last-offset, first)
print 'top: %s' % (top)
print 'right_side: %s' % (right_side)
print 'bottom: %s' % (bottom)
print 'left_side: %s' % (left_side)
现在,我们只需将顶部分配给右侧,右侧分配给底部,底部分配给左侧,左侧分配给顶部。把这一切结合在一起,我们得到:def rotate(matrix):
size = len(matrix)
layer_count = size / 2
for layer in range(0, layer_count):
first = layer
last = size - first - 1
for element in range(first, last):
offset = element - first
top = matrix[first][element]
right_side = matrix[element][last]
bottom = matrix[last][last-offset]
left_side = matrix[last-offset][first]
matrix[first][element] = left_side
matrix[element][last] = top
matrix[last][last-offset] = right_side
matrix[last-offset][first] = bottom
根据矩阵表:0, 1, 2
3, 4, 5
6, 7, 8
我们的rotate职能的结果是:6, 3, 0
7, 4, 1
8, 5, 2