C++で余分なメモリ領域を使わずに行列を時計回りに90度回転する方法
2次元配列から構成される行列が与えられたとき、その行列を時計回りに90度回転させるのが課題です。回転後は、最後の行が最初の列へ、2番目の行が2番目の列へ、最初の行が3番目の列へと移動します。さらに難しい条件として、余分なメモリ領域(補助配列)を一切使用しないことが求められます。
入出力シナリオの例
例1
入力:
int arr[row_col_size][row_col_size] = { { 5, 1, 4},
{ 9, 16, 12 },
{ 2, 8, 9}}出力:
余分なメモリ領域を使わずに行列を時計回りに90度回転した結果: 2 9 5 8 16 1 9 12 4
解説: 整数型の2次元配列が与えられています。これを時計回りに90度回転させると以下のようになります。
回転前:
{ { 5, 1, 4},
{ 9, 16, 12 },
{ 2, 8, 9}}
回転後:
2 9 5
8 16 1
9 12 4例2
入力:
int arr[row_col_size][row_col_size] = { { 2, 1, 9},
{ 11, 6, 32 },
{ 3, 7, 5}}出力:
余分なメモリ領域を使わずに行列を時計回りに90度回転した結果: 3 11 2 7 6 1 5 32 9
解説: 整数型の2次元配列が与えられています。これを時計回りに90度回転させると以下のようになります。
回転前:
{ { 2, 1, 9},
{ 11, 6, 32 },
{ 3, 7, 5}}
回転後:
3 11 2
7 6 1
5 32 9プログラムのアプローチ
ここでは2つのアプローチを紹介します。どちらもインプレース(追加メモリなし)で回転を実現できます。
1. 素朴なアプローチ(レイヤーごとの4要素サイクル交換)
- 行・列サイズ
row_col_sizeの2次元整数配列を入力として受け取ります。 - データを関数
Rotate_ClockWise(arr)に渡します。 - 関数内部では、外側のループを
i = 0からi < row_col_size / 2まで回します。 - 内側のループを
j = iからj < row_col_size - i - 1まで回します。 - ループ内では、一時変数
ptrを使って、arr[i][j]→arr[row_col_size - 1 - j][i]→arr[row_col_size - 1 - i][row_col_size - 1 - j]→arr[j][row_col_size - 1 - i]の順に4つの要素を循環的に入れ替えます。 - 最後に、全要素を二重ループで走査して
arr[i][j]を出力します。
2. 効率的なアプローチ(転置+行の反転)
- 同じく2次元整数配列を受け取り、関数
Rotate_ClockWise(arr)に渡します。 - まず転置処理を行います。外側のループを
i = 0からi < row_col_sizeまで、内側のループをj = 0からj < row_col_size - iまで回し、arr[i][j]とarr[row_col_size - 1 - j][row_col_size - 1 - i]を入れ替えます(副対角線を軸とした反転)。 - 次に上下反転処理を行います。
i = 0からi < row_col_size / 2まで、各行についてarr[i][j]とarr[row_col_size - 1 - i][j]を入れ替えます。 - 最後に、全要素を出力して完了です。
素朴なアプローチの実装例
#include <bits/stdc++.h>
using namespace std;
#define row_col_size 3
void Rotate_ClockWise(int arr[row_col_size][row_col_size]){
for(int i = 0; i < row_col_size / 2; i++){
for(int j = i; j < row_col_size - i - 1; j++){
int ptr = arr[i][j];
arr[i][j] = arr[row_col_size - 1 - j][i];
arr[row_col_size - 1 - j][i] = arr[row_col_size - 1 - i][row_col_size - 1 - j];
arr[row_col_size - 1 - i][row_col_size - 1 - j] = arr[j][row_col_size - 1 - i];
arr[j][row_col_size - 1 - i] = ptr;
}
}
}
int main(){
int arr[row_col_size][row_col_size] = { { 5, 1, 4},{ 9, 16, 12 },{ 2, 8, 9}};
Rotate_ClockWise(arr);
cout<<"余分なメモリ領域を使わずに行列を時計回りに90度回転した結果:\n";
for(int i = 0; i < row_col_size; i++){
for(int j = 0; j < row_col_size; j++){
cout << arr[i][j] << " ";
}
cout << '\n';
}
return 0;
}実行結果
余分なメモリ領域を使わずに行列を時計回りに90度回転した結果: 2 9 5 8 16 1 9 12 4
効率的なアプローチの実装例
#include <bits/stdc++.h>
using namespace std;
#define row_col_size 3
void Rotate_ClockWise(int arr[row_col_size][row_col_size]){
for(int i = 0; i < row_col_size; i++){
for(int j = 0; j < row_col_size - i; j++){
int ptr = arr[i][j];
arr[i][j] = arr[row_col_size - 1 - j][row_col_size - 1 - i];
arr[row_col_size - 1 - j][row_col_size - 1 - i] = ptr;
}
}
for(int i = 0; i < row_col_size / 2; i++){
for(int j = 0; j < row_col_size; j++){
int ptr = arr[i][j];
arr[i][j] = arr[row_col_size - 1 - i][j];
arr[row_col_size - 1 - i][j] = ptr;
}
}
}
int main(){
int arr[row_col_size][row_col_size] = { { 5, 1, 4},{ 9, 16, 12 },{ 2, 8, 9}};
Rotate_ClockWise(arr);
cout<<"余分なメモリ領域を使わずに行列を時計回りに90度回転した結果:\n";
for(int i = 0; i < row_col_size; i++){
for(int j = 0; j < row_col_size; j++){
cout << arr[i][j] << " ";
}
cout << '\n';
}
return 0;
}実行結果
余分なメモリ領域を使わずに行列を時計回りに90度回転した結果: 2 9 5 8 16 1 9 12 4
まとめ
どちらのアプローチも時間計算量は O(N²)、空間計算量は O(1) となります。素朴なアプローチは4要素を直接循環交換するためインデックスの理解が必要ですが、効率的なアプローチは「転置→上下反転」という直感的な2段階の操作で回転を実現できるため、可読性と実装のしやすさの点で優れています。
-
接続行列を使ってグラフを表現するC++プログラムの解説
接続行列(インシデンス行列)とはグラフの接続行列(インシデンス行列)は、グラフをメモリ上に格納するためのもうひとつの表現方法です。隣接行列と異なり、接続行列は正方行列ではありません。そのサイズは V × E で表されます。ここで V はグラフの頂点数、E は辺の数です。この行列では、各行に頂点が配置され、各列に辺が配置されます。ある辺 e {u, v} に対しては、列 e のうち頂点 u と頂点 v に対応する位置に「1」がマークされます。これにより、「どの頂点がどの辺に接続しているか」という情報を直感的に把握できます。接続行列の計算量とメモリ使用量接続行列による表現では、構築時に O(V ×
-
隣接行列を使ってグラフを表現するC++プログラムの解説
グラフの隣接行列(Adjacency Matrix)とは、サイズが V × V の正方行列のことです。ここでの V はグラフ G の頂点数を表します。行列の行と列にはそれぞれ頂点が対応しており、頂点 i から頂点 j への辺が存在する場合は、i 行 j 列の要素に「1」(重み付きグラフの場合は非ゼロの値)を格納します。辺が存在しない場合は、その位置には「0」が入ります。 隣接行列表現の計算量 隣接行列は計算時に O(V2) の記憶領域を必要とします。グラフが最大数の辺を持つ場合でも最小数の辺しか持たない場合でも、必要なメモリ量は同じです。つまり、辺の数に依存せず常に V × V 分の領域を確