我想添加更多细节。在此答案中,重复了关键概念,步伐缓慢且有意重复。此处提供的解决方案并不是语法上最紧凑的解决方案,但是,该解决方案供那些希望了解什么是矩阵旋转以及最终实现的人员使用。
首先,什么是矩阵?出于此答案的目的,矩阵只是宽度和高度相同的网格。注意,矩阵的宽度和高度可以不同,但是为简单起见,本教程仅考虑宽度和高度相等的矩阵(正方形矩阵)。是的,矩阵是矩阵的复数。
矩阵示例为:2×2、3×3或5×5。或者,更一般地,N×N。2×2矩阵将具有4个正方形,因为2×2 = 4。5×5矩阵将具有25个正方形,因为5×5 = 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
旋转矩阵的一种方法是一次对其进行一层处理。但是什么是层?想一想洋葱。就像洋葱的各层一样,当每一层都被除去时,我们向中心移动。其他类比是俄罗斯套娃玩偶或包裹传递游戏。
矩阵的宽度和高度决定了该矩阵中的层数。让我们为每一层使用不同的符号:
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 .
. . . . . . .
您可能会注意到,将矩阵的宽度和高度增加1并不一定会增加层数。使用上述矩阵并制表层和尺寸,我们看到层数每增加两个宽度和高度就会增加一次:
+-----+--------+
| 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 | . . . . . |
+-----+-----------+
因为宽度和高度相同,所以总是这样。因此,我们可以仅使用两个值(而不是四个)来定义图层的列和行位置。
向内移动到第二层,列的位置为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
我们的层循环提供了第一列和最后一列以及第一列和最后一行的索引:
+-----+-------+
| 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可以很容易地用于引用矩阵的四个角。这是因为他们自己可使用的各种排列来限定的角部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
and的各种排列定义的角落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