공부

스파이럴(달팽이) 배열 구현하기 - 방향배열(dx, dy) 방식

CodingNabi 2026. 7. 9. 00:00

시리즈 마지막 방식이다. 코드가 짧고, 방향이 더 늘어나도(대각선 8방향 등) 배열 하나만 늘리면 확장되는 방식이다. 해당 방식은 방향이 필요한 모든 알고리즘에 통용적으로 사용이 가능하며, 게임 알고리즘에서도 종종 사용된다 — 예를 들어 캐릭터의 8방향 이동 처리, 타일 기반 맵에서의 인접 칸 탐색(BFS/DFS), 몬스터의 순찰 경로 계산 같은 곳에서 같은 dx[], dy[] 패턴을 그대로 가져다 쓸 수 있다.

방식 설명

핵심 아이디어는 "방향 자체를 데이터로 만든다"는 것이다.

오른쪽·아래·왼쪽·위, 이 4가지 이동을 if-else나 case로 나누는 대신, dx[], dy[]라는 배열에 미리 저장해두고, dir 값 하나로 "지금 배열의 몇 번째 방향을 쓰는 중인지"만 관리한다.

dx[dir], dy[dir]이 "지금 방향으로 한 칸 이동하면 x, y가 각각 얼마나 바뀌는지"를 나타낸다. dir이 0이면 (dx,dy)=(1,0)이라 오른쪽으로, dir이 1이면 (0,1)이라 아래로 이동한다. 막히면(dir+1) % 4로 배열의 다음 칸을 가리키게 해서 방향을 90도 꺾는다.

switch 방식과 결정적으로 다른 점은, switch는 한 방향으로 갈 수 있는 데까지 for문으로 한 번에 쭉 이동하지만, 이 방식은 딱 한 칸씩만 이동하고 매번 "다음 칸이 유효한가"를 검사한다는 것이다.

void fillSpiralByDirectionVector(int arr[SIZE][SIZE]) {
	int x = 0;
	int y = 0;
	int value = 0;
	int dx[] = { 1, 0, -1, 0 };
	int dy[] = { 0, 1, 0, -1 };
	int dir = 0;
	while (value != SIZE * SIZE) {
		arr[y][x] = ++value;

		int nextX = x + dx[dir];
		int nextY = y + dy[dir];

		if (nextX < 0 || nextX >= SIZE || nextY < 0 || nextY >= SIZE || arr[nextY][nextX] != 0) {
			dir = (dir + 1) % 4;
			nextX = x + dx[dir];
			nextY = y + dy[dir];
		}
		x = nextX;
		y = nextY;
	}
}

그림

과정

dir 값이 어떤 이동량을 뜻하는지부터 정리하면 이렇다.

방향이 막힐 때마다 dir이 0→1→2→3→0 순서로 순환하며, 각 값이 dx[], dy[]의 대응 인덱스로 실제 이동량을 결정한다. 이 순환 구조가 아래 그림에 나와 있다.

실제 값이 채워지는 순서(1~25)는 앞선 두 방식과 완전히 동일하다 — 어떤 방식을 쓰든 스파이럴이라는 문제 자체의 답은 하나뿐이기 때문이다. 차이는 오직 "어떻게 다음 칸을 계산하는가"에 있다.

(0,0)에서 시작해 value=25가 될 때까지 매 스텝 다음 칸을 검사하는 흐름을 요약하면:

(0,0)=1 → 다음칸(1,0) 유효 → 이동
(1,0)=2 → 다음칸(2,0) 유효 → 이동
...
(4,0)=5 → 다음칸(5,0) 범위 밖! → dir=0→1(DOWN)로 전환 → 다음칸(4,1) 유효 → 이동
(4,1)=6 → 다음칸(4,2) 유효 → 이동
...
(4,4)=9 → 다음칸(4,5) 범위 밖! → dir=1→2(LEFT)로 전환 → 다음칸(3,4) 유효 → 이동
...
(0,4)=13 → 다음칸(-1,4) 범위 밖! → dir=2→3(UP)로 전환 → 다음칸(0,3) 유효 → 이동
...
(0,1)=16 → 다음칸(0,0) 이미 채워짐(1)! → dir=3→0(RIGHT)로 전환 → 다음칸(1,1) 유효 → 이동

switch 방식이 "이 방향으로 갈 수 있는 만큼 for문으로 몰아서 채우고 case를 넘긴다"였다면, 이 방식은 "한 걸음씩 걷다가 막히면 그 자리에서 바로 꺾는다"는 감각에 가깝다.

특징 분석

장점

  • 코드가 짧다 : 방향 로직 전체가 dx[], dy[], dir 세 줄로 압축된다.
  • 방향이 늘어나도 확장이 쉽다 : 대각선을 포함한 8방향으로 바꾸고 싶으면 dx[], dy[]에 값 4개만 더 추가하면 된다. switch 방식이라면 case를 4개 더 만들어야 한다.
  • while 하나로 전체 로직이 끝난다 : 별도의 for문 중첩이나 case 분기 없이, 매 스텝 "이동 가능한지 검사 → 이동"만 반복한다.

단점

  • 매 칸마다 조건 검사가 실행된다 : switch나 경계값 방식은 방향이 안 바뀌는 구간에서는 검사 없이 쭉 진행하지만, 이 방식은 25칸 전부에서 "다음 칸이 유효한가"를 매번 확인한다. (다만 이 정도 크기에서는 성능 차이가 체감될 수준은 아니다.)
  • 배열 값에 의존한다 : switch 방식과 마찬가지로 arr[nextY][nextX] != 0으로 판단하기 때문에, 경계값 방식이 가진 "배열 값과 무관하게 안전하다"는 장점은 없다.
  • 처음 보는 사람은 이해하는 데 시간이 걸린다 : dx[], dy[], dir이 무엇을 의미하는지 알아야 코드가 읽힌다. 코드량은 가장 적지만 압축도가 높아서, 가독성만 놓고 보면 switch 방식보다 낮다.

네 가지 방식 최종 비교

 

항목 switch 대칭 축소(nLength) 경계값(Boundary) 방향배열(dx,dy)
코드 길이 가장 짧음 중간 짧음
배열 값 의존 의존 의존 안 함(인덱스만) 의존 안 함 의존
for문 인덱스 보정 필요 필요 없음 필요 없음 필요 없음
짝수 크기 지원 지원 미지원 지원 지원
직사각형(N×M) 확장 가능 불가능 가능 가능
방향 개수 확장(8방향 등) 어려움(case 추가) 불가능 어려움 쉬움(배열에 값 추가)
가독성 가장 좋음 낮음 좋음 중간
코딩테스트 표준성 낮음 낮음 높음 중간~높음

시리즈 정리

같은 문제라도 "무엇을 기준으로 다음 칸을 판단하는가"에 따라 코드의 모양이 완전히 달라진다는 걸 이 네 방식을 통해 확인할 수 있었다.

  • 처음 배우는 단계라면 switch 방식으로 시작하는 게 맞다. 방향 전환이라는 개념 자체를 코드 구조로 직접 눈에 보이게 익힐 수 있다.
  • 코딩테스트를 준비한다면 경계값 방식을 표준으로 외워두는 게 가장 범용적이다. 크기·모양에 안 흔들리고, 실제로 이 유형 문제의 정석 풀이로 통한다.
  • 방향이 복잡해지거나(8방향, 불규칙 패턴) 코드량을 최소화하고 싶다면 방향배열 방식이 유리하다.
  • 문제가 홀수 정사각형으로 명확히 고정되어 있고 짧은 코드가 최우선이라면 대칭 축소 방식도 나쁘지 않지만, 범용성은 가장 떨어진다.

결국 "더 좋은 코드"는 상황에 따라 다르다. 지금 풀고 있는 문제의 제약(크기가 고정인지, 확장 가능성이 있는지, 누가 이 코드를 다시 읽을지)에 맞춰 방식을 고르는 게 핵심이다.