多維陣列

楊宗儒 @Sprout 2023

改寫自 Sprout 2022 講義

複習

一維陣列

int a[5]; // {?, ?, ?, ?, ?}
int b[5] = {1, 2, 3, 4, 5}; // {1, 2, 3, 4, 5}
int c[5] = {1, 2, 3}; // {1, 2, 3, 0, 0}
int d[5] = {}; // {0, 0, 0, 0, 0}
int e[5] = {0}; // {0, 0, 0, 0, 0}

一維陣列

int a[5];
a[0] = 21;
a[4] = 43;

cout << a[2] << endl; // ???
cout << a[5] << endl; // ???

二維陣列

想像我們想要存一個班級的座位表,

存下每個位置的學號

如果我們的座位長這樣呢?

為什麼一維陣列不太夠用?

一個維度不夠,就用兩個吧!

datatype name[size1][size2];

int arr2d[5][3];
char charr2d[2][10];

int A[4][3]

[0] [1] [2]
A[0] A[0][0] A[0][1] A[0][2]
A[1] A[1][0] A[1][1] A[1][2]
A[2] A[2][0] A[2][1] A[2][2]
A[3] A[3][0] A[3][1] A[3][2]

初始化

int A[4][3] = {1};

int A[4][3]

[0] [1] [2]
A[0] 1 0 0
A[1] 0 0 0
A[2] 0 0 0
A[3] 0 0 0

初始化

int A[4][3] = {1, 2, 3, 4};

int A[4][3]

[0] [1] [2]
A[0] 1 2 3
A[1] 4 0 0
A[2] 0 0 0
A[3] 0 0 0

初始化

int A[4][3] = 
{
  {1, 2, 3}, 
  {4, 5}, 
  {6, }, 
  {7, 8, 9}
};
[0] [1] [2]
A[0] 1 2 3
A[1] 4 5 0
A[2] 6 0 0
A[3] 7 8 9

初始化

cout << A[2][2] << endl;
// 5
cout << A[3][1] << endl;
// 8
cout << A[0][3] << endl;
// ???
[0] [1] [2]
A[0] 1 2 3
A[1] 4 5 0
A[2] 6 0 0
A[3] 7 8 9

遍歷

for (int i = 0; i < 4; i++) {
	for (int j = 0; j < 3; j++) {
    	A[i][j] = ???;
    }
}

for (int k = 0; k < 12; k++) {
	A[k/4][k%3] = ???;
}
[0] [1] [2]
A[0] 1 2 3
A[1] 4 5 0
A[2] 6 0 0
A[3] 7 8 9

(i, j) = (3, 1)

A[3][1] == 8

練習題

#OJ 1419

題解

題目:給定一個矩陣,輸出它的轉置

1 2 3 4
5 6 7 8
9 10 11 12
1 5 9
2 6 10
3 7 11
4 8 12

題解

觀察:第 (i, j) 個元素在轉置後會在哪裡?

0 1 2 3
0 1 2 3 4
1 5 6 7 8
2 9 10 11 12
0 1 2
0 1 5 9
1 2 6 10
2 3 7 11
3 4 8 12

5: (1, 0) -> (0, 1)

12: (2, 3) -> (3, 2)

(i, j) -> (j, i)

題解1

#include<iostream>
using namespace std;

int a[1000][1000];
int b[1000][1000];

int main() {

  int n, m;
  cin >> n >> m;

  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      cin >> a[i][j];
      b[j][i] = a[i][j];
    }
  }
    
  for (int i = 0; i < m; i++) {
    for (int j = 0; j < n; j++) {
      cout << b[i][j];
      if(j != n-1) cout << " ";
    }
    cout << endl;
  }
}

a 是輸入

b 是輸出

在輸入 a 時把翻轉的結果存到 b

最後再輸出

題解2

#include<iostream>
using namespace std;

int a[1000][1000];

int main() {

  int n, m;
  cin >> n >> m;

  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      cin >> a[i][j];
      b[j][i] = a[i][j];
    }
  }
    
  for (int i = 0; i < m; i++) {
    for (int j = 0; j < n; j++) {
      cout << a[j][i];
      if(j != n-1) cout << " ";
    }
    cout << endl;
  }
}

輸入完 a 之後直接輸出

多維陣列

多維陣列

int a[2][3][2];

就跟二維陣列一樣(?

指標!

小複習

  • 在一維陣列的時候,我們提過陣列不加[]時是個指向陣列頭的指標

arr

0xFFF00A40

0xFFF00A36

char arr[0]

'A'

0xFFF00A40

'B'

0xFFF00A41

'C'

0xFFF00A42

'D'

0xFFF00A43

char arr[1]

char arr[2]

char arr[3]

  • 二維也是!

&arr[0][0] + 0

arr[0][0]

arr[0][1]

arr[0][2]

arr[0][3]

arr[1][3]

arr[1][2]

arr[1][1]

arr[1][0]

&arr[0][0] + 1

&arr[0][0] + 2

&arr[0][0] + 3

&arr[0][0] + 4

&arr[0][0] + 5

&arr[0][0] + 6

&arr[0][0] + 7

名詞解釋

Row-major : 第 (i, j) 個元素在 &arr[0][0] + ( i * sizey + j )

Column-major : 第 (i, j) 個元素在 &arr[0][0] + ( j * sizex + i )

0 1 2 3
0 1 2 3 4
1 5 6 7 8
2 9 10 11 12

A[sizey][sizex]

C++ 的二維陣列是 row-major

另一種看法--二維指標

0 1 2 3
0 1 2 3 4
1 5 6 7 8
2 9 10 11 12

A[sizey][sizex]

arr

arr + 1

arr + 2

arr: int **

arr[0]: int*

int arr[3][4];
(arr+1)[0];

小練習

int arr[2][6] = {0};
for(int i = 0 ; i < 2; i++){
  for(int j = 0 ; j < 6 ; j++){
    arr[i][j] = i * 10 + j;
  }
}
cout << **arr << "   " << *arr << "   " << arr << endl;
cout << *arr[0] << "   " << arr[0] << endl;
cout << *(arr[0]+3) << "   " << arr[0]+3 << endl;
cout << *(arr[1]+3) << "   " << arr[1]+3 << endl;
cout << *(arr[0]+9) << "   " << arr[0]+9 << endl;

小練習

int arr[2][6] = {0};
for(int i = 0 ; i < 2; i++){
  for(int j = 0 ; j < 6 ; j++){
    arr[i][j] = i * 10 + j;
  }
}
cout << **arr << "   " << *arr << "   " << arr << endl;
cout << *arr[0] << "   " << arr[0] << endl;
cout << *(arr[0]+3) << "   " << arr[0]+3 << endl;
cout << *(arr[1]+3) << "   " << arr[1]+3 << endl;
cout << *(arr[0]+9) << "   " << arr[0]+9 << endl;
[0] [1] [2] [3] [4] [5]
arr[0] 00 01 02 03 04 05
arr[1] 10 11 12 13 14 15

禁忌/好習慣

禁忌1: 變數陣列大小

int n;
cin >> n;
int arr[n];

好習慣1: 常數陣列大小

const int SIZE = 100005;
int arr[SIZE];
  • 題目通常會給陣列最大的範圍,直接開到那麼大就好
  • 通常會再多開一點點,可以逃避 index out of bound 的 bug

禁忌2: 函數內宣告大陣列

int main() {
    int bigarr[1000][1000];
}

為什麼?

原因有點複雜,與程式如何存資料到記憶體有關

好習慣2: 在外面宣告大陣列

int bigarr[1000][1000];

int main() {
    // TODO
}

存在 global 就不會讓陣列被存在stack,然後出現 stack overflow

最後一題

課內練習

OJ #999

  • 行尾不空格
  • 最後一行要換行

 注意

題解

一個指針從起點開始走

什麼時候要換方向?

Ans. 邊界/填過數字

0 1 2 3
0 1 2 3 4
1 12 13 14 5
2 11 16 15 6
3 10 9 8 7

小技巧:把移動的方向存在一個陣列

int dir[4][2] = {
  {0, 1},
  {1, 0},
  {-1, 0},
  {0, -1}
};

資芽2023-二維陣列

By s0n9yu

資芽2023-二維陣列

  • 128