Algorithm

[Algorithm] 8퀸 문제 알고리즘 (재귀)

보라해바라기 2023. 8. 13. 23:17
SMALL

💜 8퀸 문제란?

- 8x8 체스판에 8개의 퀸을 배치한다.

- 조건: 어떤 퀸도 다른 퀸을 위협해서는 안된다

- 추가 설명: 퀸은 상하좌우, 대각선 4방향으로 거리 제한 없이 이동할 수 있는 기물

퀸의 이동 경로


💜 어떤 기능을 하는 함수를 구현해야하는가? (문제 정의)

- 각 열퀸 하나만 배치한다. (규칙1)

- 각 행퀸 하나만 배치한다. (규칙2)

- 하나의 상하좌우, 대각선 4방향으로 거리 제한 없이 이동할 수 있다. (규칙3)

=> 각 퀸은 상대 퀸의 이동 경로를 방해하지 않으면서 위치해야 한다.


💜 문제를 어떻게 해결할 것인가? (아이디어 얻기)

1) 분할정복(Divide and conquer): 큰 문제를 작은 문제로 세분화하여 해결

 

2) 가지 뻗기 (Branch) : 가지를 뻗으며 모든 조합을 나열하는 것

- 목표: "각 열에 퀸이 하나 있다"는 전제를 만족하는 조합 구하기 > 규칙 1 적용

- 퀸의 대각선 이동 경로를 배제하고 생각

 

3) 재귀를 활용!

- 가지뻗기: 하나의 열에 퀸 배치 완료 > 다음 열로 넘어가 퀸 놓는 과정 수행 > "퀸 놓는 과정"은 똑같이 반복 > 재귀 사용!

 

4) 분기 한정법(Branching and bounding method): 가지 뻗기와 한정 조작을 조합하여 문제를 풀어가는 방법

- 한정(bounding) 조작: 필요하지 않은 분기를 없애, 불필요한 조합을 줄이는 방법

- 목표: 규칙 1과 규칙 2를 둘 다 반영하여 문제 풀기

 

5) 분기한정법 심화

- 규칙1, 규칙2 적용 시 > 규칙 3은 고려되지 않음!

- 대각선도 고려하여 최종적으로 문제 풀기!


💜 문제 접근하기  (with 실제 코드와 함께 분석)

1. 가지 뻗기 + 분기 한정법 (1)

- 목표: 각 열에 퀸을 1개만 배치 (규칙 1)

          : 각 행에 퀸을 1개만 배치 (규칙 2)

- 예시 (열은 "세로" 부분을 뜻한다. 행은 "가로" 부분을 뜻한다.)

8퀸 문제 - 가지 뻗기 예시

- 접근 방법

1) 각 열의 인덱스 범위는 0 ~ 7 이다. 각 행의 인덱스 범위도 0 ~ 7 이다.

2) 각 열에 퀸을 하나씩 배치한다.

3) 해당 열의 특정 행의 퀸 유무를 알 수 있는 플래그 배열이 존재한다. (플래그 배열의 값이 1이면 퀸 존재, 0이면 퀸 존재 X)

4) 해당 열의 특정 행에 퀸이 하나 있다면, 플래그 배열 값을 1로 만들어주고 다음 열로 넘어간다.

5) 플래그 배열 값을 다시 0으로 만들어준다. (다음 열에 넘어 갔을 땐, 특정 행에 퀸이 없음을 가정하고 함수 실행 )

6) (2) ~ (5)의 과정은 열이 끝날 때까지 반복 (재귀 함수 사용 가능!)

 

- 코드

// i는 열의 인덱스를 뜻한다.
// j는 행의 인덱스를 뜻한다.

int count = 0;  // 퀸을 배치할 수 있는 총 개수
int pos[8];		// 각 열에서 퀸의 행 위치를 저장하는 배열
int flag[8];	// 각 행의 퀸 유무를 확인하는 배열

void set_eight_queens(int i) {
	for (int j = 0; j < 8; j++) {
		if (!flag[j]) {   // j 행에 퀸을 배치 X
			pos[i] = j;   // 해당 열의 j(행)을 설정
			if (i == 7) { // 모든 열의 배치를 마쳤다. > 끝내라
				count++;
			}
			else {
				flag[j] = 1;             // 해당 행에 퀸이 있음을 확인
				set_eight_queens(i + 1); // 다음 열로 넘어가기
				flag[j] = 0;             // 해당 행을 0으로 초기화 
                                         // (다음 열은 해당 행의 퀸유뮤 모름 > 퀸이 없다고 가정)
			}
		}
		
	}
}

 

 

2. 가지 뻗기 + 분기 한정법(2)

- 목표: 각 열에 퀸을 1개만 배치 (규칙 1)

         : 각 행에 퀸을 1개만 배치 (규칙 2)

         : 어떤 대각선 방향에서도 퀸을 1개만 배치 (규칙 3)

 

- 8x8 체스판에서 대각선의 특징

1) 대각선의 방향 : / , \ (두 방향)

2) 대각선의 총 개수: 15개

3) 대각선의 인덱스 : 0 ~14

 

- 접근 방법 (대각선 방향을 중심으로)

** i: 열의 인덱스, j: 행의 인덱스

1) 대각선의 방향이 / 일 때

: 특정 칸을 지나는 대각선의 인덱스는 해당하는 각 칸의 열과 행의 인덱스 합같다.

: 대각선 인덱스의 값 = i + j 

: 예시

8퀸 문제 대각선 /

 

2) 대각선의 방향이 \ 일 때

: 특정 칸을 지나는 대각선의 인덱스는 해당하는 각 칸의 열의 인덱스에서 행의 인덱스를 빼고 7을 더한 값 같다.

: 대각선 인덱스의 값 = 7 - i + j 

: 예시

8퀸 문제: 대각선 \

- 코드

#define n 8            // n = 8로 설정
int count = 0;         // 퀸을 배치할 수 있는 총 개수
int flag_a[n];         // 각 행에 퀸을 배치했는지 체크하는 배열
int flag_b[n *2 - 1];  // 대각선 "/" 에 퀸을 배치했는지 체크하는 배열
int flag_c[n * 2 - 1]; // 대각선 "\" 에 퀸을 배치했는지 체크하는 배열
int pos[n];            // 각 열에서 퀸의 위치 (해당 열의 행의 위치)

// i열에서 알맞은 위치(행)에 퀸을 배치하는 함수
void set_queens_of_branch(int i) {
	for (int j = 0; j < 8; j++) {
		// 현재 행에 퀸이 없고 대각선에도 퀸이 없다 > 배치 가능
		if (!flag_a[j] && !flag_b[i + j] && !flag_c[7 + i - j]) {
			pos[i] = j;   // i열 j행 위치에 퀸을 배치
			if (i == 7) { // 모든 열에 배치를 마쳤다. > 끝내라
				count++;
			}
			else {
				// 현재 행, 대각선 flag를 1로 설정 > 퀸이 있다!
				flag_a[j] = flag_b[i + j] = flag_c[7 + i - j] = 1; 

				// 다음 열에 배치를 시도
				set_queens_of_branch(i + 1); 

				// 현재 행, 대각선 flag를 0으로 설정. (다음 열에 퀸이 배치되어있지 않다고 가정)
				flag_a[j] = flag_b[i + j] = flag_c[7 + i - j] = 0;
			}
		}
	}
}

💜참고자료

- 여덟 퀸 문제 - 네이버: https://terms.naver.com/entry.naver?docId=5667830&cid=60205&categoryId=60205

- 8퀸 문제 - Jimmy48 : https://velog.io/@jimmy48/8%ED%80%B8-%EB%AC%B8%EC%A0%9C

728x90