본문 바로가기

공부/C언어

(C 예제) 다차원 배열과 예제

//다차원 배열
int arrOneDim[10];
int arrTwoDim[5][5];
int arrThreeDim[3][3][3];

위에서부터 순서대로

길이가 10인 1차원 int형 배열

가로, 세로의 길이가 각각 5인 2차원 int형 배열

가로, 세로, 높이의 길이가 각각 3인 3차원 int형 배열

이 이상도 선언은 가능하지만, 의미가 없다.


2차원 배열의 선언

2차원 배열의 선언은 TYPE arr[세로길이][가로길이]; 이다.

3 x 4 x 4 = 48

7 x 9 x 4 = 252


2차원 배열요소의 접근

이를 일반화하면

// 일반화
arr[N - 1][M - 1] = 20;
printf("%d", arr[N - 1][M - 1]);

세로 N, 가로 M의 위치에 값을 저장 및 참조한다.

 

관련 예제를 보자.

가구별 거주인원 입력부터 보자.

바깥에 있는 for문은 전체 층수이다. 1층부터 4층이므로,

0부터 3까지 반복하면서 1씩 증가한다,

 

안쪽에 있는 for문은 층별 호 수 이다.

printf로 출력시에는 0이 아니라 1로 나와야하므로 i와 j에게 1씩 더해준다.

scanf에서 for문으로 바뀌는 숫자마다 인구수가 입력되고 저장된다.

 

다음은 빌라의 층별 인구수 출력을 보자.

당연히 for문은 0부터 3까지 반복되며 1씩 증가한다.

우선 인구수를 0으로 초기화해주고 호 수는 2개 밖에 없으므로 0과 1로

고정이다. i가 0부터 3까지 증가하면서 1층부터 4층까지의 거주인원을

더해서 popu에 저장한다.

마지막으로, printf로 출력한다.


2차원 배열의 메모리상 할당의 형태

실제 메모리는 1차원의 형태로 주소 값이 지정된다.

따라서 위와 같은 형태로 2차원 배열의 주소 값이 지정된다.


2차원 배열 선언과 동시에 초기화 하기

// 배열 초기화
int arr[3][3] = {
	{1, 2, 3},
	{4, 5, 6},
	{7, 8, 9}
};

초기화 리스트 안에는 행 단위로 초기화할 값들을 별도의 중괄호로 명시한다.

int arr[3][3] = {
	{1,
	{4, 5},
	{7, 8, 9}
};

채워지지 않은 빈 공간은 아래와 같이 0으로 채워진다.

 

만약 별도의 중괄호를 사용하지 않으면 좌 상단부터

시작해서 우 하단으로 순서대로 초기화된다.

// 중괄호 사용x 배열 초기화1
int arr[3][3] = {
	1, 2, 3,
	4, 5, 6,
	7
};

// 중괄호 사용x 배열 초기화2
int arr[3][3] = { 1,2,3,4,5,6,7 };

이렇게해도 알아서 빈자리에 0으로 채워진다.

 

예를 보자.

// 2차원 배열 선언과 동시에 초기화 하기 예제
#include <stdio.h>

void main(void)
{
	int i, j;

	//2차원 배열 초기화 예1
	int arr1[3][3] = {
		{1, 2, 3},
		{4, 5, 6,},
		{7, 8, 9}
	};

	//2차원 배열 초기화 예2
	int arr2[3][3] = {
		{1},
		{4, 5},
		{7, 8, 9}
	};

	//2차원 배열 초기화 예3
	int arr3[3][3] = { 1, 2, 3, 4, 5,6,7 };

	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
			printf("%d ", arr1[i][j]);
		printf("\n");
	}
	printf("\n");

	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
			printf("%d ", arr2[i][j]);
		printf("\n");
	}
	printf("\n");

	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 3; j++)
			printf("%d ", arr3[i][j]);
		printf("\n");
	}
	printf("\n");
}

빈 곳은 알아서 0으로 채워진 것을 볼 수 있다.


배열의 크기를 알려주지 않고 초기화

배열의 크기를 나타내는 두 곳이 모두 비어있으면 컴파일러가

채워 넣을 숫자를 결정하지 못한다.

그래서 세로 길이만 생략할 수 있도록 약속되어 있다.

// 세로 길이만 생략 가능
int arr1[][4] = {1, 2, 3, 4, 5, 6, 7, 8};
int arr2[][2] = {1, 2, 3, 4, 5, 6, 7, 8};

이렇게 적으면 컴파일러가 알아서 아래처럼 세로 길이를 계산해서 처리한다.

// 세로 길이만 생략 가능
int arr1[2][4] = {1, 2, 3, 4, 5, 6, 7, 8};
int arr2[4][2] = {1, 2, 3, 4, 5, 6, 7, 8};

3차원 배열의 구조

2 x 3 x 4 x 4 = 96

세로 3, 가로 4인 배열이 2개 겹친 형태

 

5 x 5 x 5 x 8 = 1000

세로 5, 가로 5인 배열이 5개 겹친 형태


3차원 배열의 선언과 접근

// 학급 평균 내리기
#include <stdio.h>

void main(void)
{
	int mean = 0, i, j;
	int record[3][3][2] = {
	{
		{70, 80}, // A 학급 학생1의 성적
		{94, 90}, // A 학급 학생2의 성적
		{70, 85}
	},
	{
		{83, 90}, // B 학급 학생1의 성적
		{95, 60},
		{90, 82}
	},
	{
		{98, 89},
		{99, 94},
		{91, 87}
	}
	};

	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 2; j++)
			mean += record[0][i][j];
	} printf("A 학급 전체 평균: %g \n", (double)mean / 6);

	mean = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 2; j++)
			mean += record[1][i][j];
	} printf("B 학급 전체 평균: %g \n", (double)mean / 6);

	mean = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 2; j++)
			mean += record[2][i][j];
	} printf("C 학급 전체 평균: %g \n", (double)mean / 6);
}

세로 3 가로 2인 3차원 배열을 3개 선언한다.

세로 3 가로 2인 각각의 2차원 배열은 한 반이 된다.

세로에는 학생의 성적

가로에는 과목이 된다.

 

안쪽 for문부터보자.

j가 0부터 1까지 반복하며 1씩 증가한다. == 2번 반복.

[0][i][j]로 했으므로, A학급으로 고정되며,

가로에 있는 수부터 mean에 더해진다.

 

바깥쪽 for문은 i가 0부터 2까지 1씩 증가하므로, 

학급의 모든 학생(3명)의 성적에 접근할 수 있다.

 

이렇게 하면 학급의 학생 1명씩 모든 과목(2개)를

더할 수 있다.


행렬 경로

  • 4 x 4 행렬이 있음.
  • 행렬의 좌상단에서 시작하여 우하단까지 이동

이동방법

  • 오른쪽이나 아래쪽으로만 이동가능
  • 왼쪽, 위쪽, 대각선 이동은 허용하지 않음

목표

  • 행렬의 좌상단에서 시작하여 우하단까지 이동하되,
    방문한 칸에 있는 수들을 더한 값이 최대화되도록 한다.
6 7 12 5
5 3 11 18
7 17 3 3
8 10 14 9
// 행렬 경로
#include <stdio.h>

int max2(int a, int b)
{
	if (a > b)
		return a;
	else
		return b;
}

void main(void)
{
	int i, j;
	int mat[4][4] =
	{ {6,7,12,5},{5,3,11,18},{7,17,3,3}, {8,10,14,9} };

	for (i = 1; i < 4; i++)
	{
		mat[i][0] += mat[i - 1][0]; // 2.
		mat[0][i] += mat[0][i - 1]; // 1.
	}

	for (i = 1; i < 4; i++)
		for (j = 1; j < 4; j++)
			mat[i][j] += max2(mat[i - 1][j], mat[i][j - 1]); // 3.
	printf("최대 경로 합은 %d\n", mat[3][3]);
}

주석에 번호를 달았다.

 

첫 for문부터 보자.

i가 1부터 3까지 1씩 증가한다.

 

1. 0번째 층의 1번째 숫자 7은 0번째 층의 0번째 숫자 6을 더하고 저장된다.

 

다음에 i가 2가 되면 0번째 층의 2번째 숫자 12는 0번째 층의 1번째 숫자에 저장된

13과 더해져서 25가 된다.

 

그 다음에 i가 3이 되면 0번째 층의 3번째 숫자 5가 0번째 층의 2번째 숫자에 저장된

25와 더해져서 30이 된다.

 

2. 0번째 숫자의 1번째 층의 숫자 5는 0번째 숫자의 0번째 층의 숫자 6과 더하고 저장된다.

 

다음에 i가 2가 되어 0번째 숫자의 2번째 층의 숫자 7은 0번째 숫자의 1번째 층의 숫자에 저장된

11과 더해져서 18이 된다.

 

그 다음에 i가 3이 되어 0번째 숫자의 3번째 층의 숫자 8은 0번째 숫자의 2번째 층의 숫자에 저장된

18과 더해져서 26이 된다.

 

이제 남은 곳은

3 11 18
17 3 3
10 14 9

여기다.

 

여기는 왼쪽에서 오거나 위에서만 올 수 있다. 

위에서 오면 한 층 밑에있는 숫자가 온거고,

왼쪽에서 오면 층은 똑같지만 왼쪽에 있는 숫자가 온거다.

이걸 코드로 바꾸면 아래와 같다.

 

3.의 코드에서

mat[i - 1][j]는 위에서 온거고,

mat[i][j - 1]은 왼쪽에서 온거다. 

여기서 위에 있는 max2 함수로 가면

두 개의 값 중에서 큰 것만 왼쪽에 있는 mat[i][j]에 더해지고 저장된다.

이것은 i와 j가 각각 1부터 3까지 1씩 더해지면서 for문으로 반복된다.

 

이렇게하면 최적의 경로를 찾아 최대 합을 출력시켜준다.


영한 사전

// 영한 사전
#include <stdio.h>

void main(void)
{
	int i;
	char Dic[2][3][10] = { {"book", "boy", "rain"}, {"책", "소년", "비"} };

	for (i = 0; i < 3; i++)
		printf("%s의 뜻은 %s입니다.\n\n", Dic[0][i], Dic[1][i]);

	printf("끝\n");
}

세로 3, 가로 10인 배열이 2개 있다.

10에는 20이든 100이든 상관없다.

세로에 들어갈 단어의 길이를 넉넉하게 잡아서 10을 준거다.

 

for문을 보자.

i는 0부터 2까지 1씩 증가하면서 반복한다.

%s는 문자열을 표현할 때 사용하고, %s에 그 단어의 첫 주소를 넣으면

그 첫주소부터 뒤까지 쭉 찍어준다.

 

i가 0일때 첫 번째 %s는 book, 두 번째 %s는 책이 나온다.

i가 1일때는 첫 번째 %s에서 boy, 두 번째 %s에서 소년이 나온다.

i가 2일때는 첫 번째 %s에서 rain, 두 번째 %s에서 비가 나온다.

 

그렇게 되면

Dic[ ][ ][ ]

에서 첫 번째 대괄호는 0일때 영어, 1일때 한국어

두 번째 대괄호는 0일때 책, 1일때 소년, 2일때 비가 나온다.

마지막 세 번째 대괄호에는

첫 번째 대괄호와 두 번째 대괄호가 0일때

b가 나오면서 각각의 글자 하나하나가 나온다는 것을 알 수 있다.

 

하지만 Dic은 3차원 배열이고, 우리는 출력할때 2차원만 사용했다.

 

Dic 자체는 그 배열의 첫주소이다.

Dic[0]은 영어들이 모여있는 배열의 첫주소이다.

Dic[0][0]은 영어들이 모여있는 배열의 book의 b 주소이다.

Dic[0][1]은 영어들이 모여있는 배열의 boy의 b 주소이다.

 

그러므로

book과 책을 출력하기 위해서

Dic[0][0][0]과 Dic[1][0][0]의 각 글자의 첫 주소를 부르고 싶으면

&Dic[0][0][0]과 &Dic[1][0][0]처럼 &를 적어서 그 글자의

주소를 부르면 된다.

아니면 &를 없애고, Dic[0][0]과 Dic[1][0]을 해서

book의 첫주소와 책의 첫주소를 부르면 된다.


영한 사전 응용

// 영한 사전 응용
#include <stdio.h>
#include <string.h>

void main(void)
{
	int i;
	int idx = 1;
	int idx_found;

	char word[30]; // 넉넉하게 30으로 잡음
	char Dic[2][3][30] = { {"book", "boy", "rain"}, {"책", "소년", "비"} };

	while (idx)
	{
		printf("영어 단어 입력(q 입력시 종료): ");
		scanf("%s", word);

		if (strcmp(word, "q") == 0)
			idx = 0;
		else
		{
			idx_found = 0;
			for (i = 0; i < 3; i++)
			{
				if (strcmp(word, Dic[0][i]) == 0)
				{
				printf("%s의 뜻은 %s 입니다.\n\n", word, Dic[1][i]);
				idx_found = 1;
				}
			}
			if (idx_found == 0)
				printf("%s단어는 없습니다.\n\n", word);
		}
	}
	printf("끝\n");
}

strcmp를 사용하기 위해 헤더파일 string.h를 선언하였다.

 

strcmp는 두개의 문자열을 비교하고 완전히 같으면 0,

하나라도 틀리면 -1또는 1을 반환한다.

 

-1과 1은 매개변수로 들어온 문자열을 비교하다가 다른 문자가

나왔을 때 그 문자의 아스키코드 값을 비교해서 결정한다.

만약에 str1 < str2인 경우에는 -1, str1 > str2인 경우에는 1을 반환한다.

 

while문을 보자.

 

idx변수를 1로 선언과 동시에 초기화했다.

while문은 1이면 참값이므로 계속 반복하게 된다.

 

q에 ""큰 따옴표를 붙여준 이유는 ''는 그냥 q이고 ""는 q에 \0 널이 합쳐진거라서

큰 따옴표를 붙여줬다. %s로 입력을 받기때문에 널문자가 없으면 안된다.

 

만약 word에 입력한게 q와 널이 붙은거라면 word와 "q"를 비교해서

strcmp가 0을 내놓고 if문에서 0이랑 같으면 idx의 값이 0이 되면서

반복문에서 빠져나온다.

 

만약에 word에 입력한게 q와 널이 붙지않은거라면 else로 이동한다.

여기에서도 입력받은 단어를 for문으로 살펴보면서 일치한지 strcmp가

보고 일치하면 0값을 주고 해당되는 단어의 뜻을 출력해주고, idx_found를

1로 해준다.

만약에 반복문에서 3번 반복해도 일치하는 단어가 없으면 밑에 있는

if문으로 와서 해당 단어가 없다고 출력해준다.