알고리즘 02 재귀함수 응용 | 미로찾기, N-Queens, Backtracking, DFS, 멱집합, 순열, 조합 | JS
1. 미로찾기(Decision Problem)
- 미로찾기 문제 유형 중에서도 출구로 빠져나올 수 있는지 없는지 Decision(Yes or No)을 판단하는 문제
- 다른 유형으로는 최단거리나 나오는 방법의 수 등이 있을수 있음
function findPath(x, y) {
if (x < 0 || y < 0 || x >= n || y >= n) {
return false
} else if (maze[x][y] !== PATH) {
return false
} else if (x === n - 1 && y === n - 1) {
maze[x][y] = VISITED
return true
} else {
maze[x][y] = VISITED
if (findPath(x - 1, y) || findPath(x + 1, y) || findPath(x, y - 1) || findPath(x, y + 1)) {
return true
}
maze[x][y] = BLOCKED;
return false
}
}
const maze = [
[0, 0, 0, 0, 0, 0, 0, 1],
[0, 1, 1, 0, 1, 1, 0, 1],
[0, 0, 0, 1, 0, 0, 0, 1],
[0, 1, 0, 0, 1, 1, 0, 0],
[0, 1, 1, 1, 0, 0, 1, 1],
[0, 1, 0, 0, 0, 1, 0, 1],
[0, 0, 0, 1, 0, 0, 0, 1],
[0, 1, 1, 1, 0, 1, 0, 0],
]
const n = 8
const PATH = 0;
const WALL = 1;
const BLOCKED = 2;
const VISITED = 3;
console.log(maze);
console.log(findPath(0, 0));
미로찾기: 결과확인
- 어느 방향으로 먼저 진출하느냐에 따라
result maze
가 달라짐 - 2차원 배열이기 때문에 x축, y축과 다르게
x+1
이면 1차원 배열에서+1
즉, 프린트된 array에서 아래로 한 칸 가는 것과 동일함
y+1
이면 2차원 배열에서+1
즉, 프린트된 array에서 우측으로 한 칸 가는 것과 동일함
if (findPath(x - 1, y) || findPath(x + 1, y)
|| findPath(x, y - 1) || findPath(x, y + 1)) { ... }
[3, 2, 2, 2, 2, 2, 2, 1]
[3, 1, 1, 2, 1, 1, 2, 1]
[3, 2, 2, 1, 2, 2, 2, 1]
[3, 1, 2, 2, 1, 1, 2, 2]
[3, 1, 1, 1, 2, 2, 1, 1]
[3, 1, 3, 3, 3, 1, 2, 1]
[3, 3, 3, 1, 3, 3, 3, 1]
[2, 1, 1, 1, 2, 1, 3, 3]
if (findPath(x, y - 1) || findPath(x, y + 1)
|| findPath(x - 1, y) || findPath(x + 1, y)) { ... }
[3, 0, 0, 0, 0, 0, 0, 1]
[3, 1, 1, 0, 1, 1, 0, 1]
[3, 0, 0, 1, 0, 0, 0, 1]
[3, 1, 0, 0, 1, 1, 0, 0]
[3, 1, 1, 1, 2, 2, 1, 1]
[3, 1, 3, 3, 3, 1, 2, 1]
[3, 3, 3, 1, 3, 3, 3, 1]
[2, 1, 1, 1, 2, 1, 3, 3]
2. Counting Cells in a Blob
- Binary 이미지
- 각 픽셀은 background pixel이거나 image pixel
- 서로 연결된 image pixel들의 집합을 blob이라고 부름
- 상하좌우 및 대각방향으로도 연결된 것으로 간주
I/O
-
입력
: N * N 크기의 2차원 그리드(grid)
: 하나의 좌표(x, y) -
출력
: 픽셀 (x,y)가 포함된 blob의 크기의
: (x,y)가 어떤 blob에도 속하지 않는 경우에는 0
Recursive Thinking
현재 픽셀 이 속한 blob의 크기를 카운트하려면
if 현재 픽셀이 image color가 아니라면
0을 반환한다
else 현재 픽셀이 image color라면
먼저 현재 픽셀을 카운트한다(count = 1)
현재 픽셀이 중복 카운트되는 것을 방지하기 위해 다른 색으로 칠한다.
현재 픽셀이 이우한 모든 픽셀들에 대해서
그 픽셀이 속한 blob의 크기를 카운트하여 카운터에 더해준다.
카운터를 반환한다.
solution
function countcells(arr, x, y) {
if (x < 0 || y < 0 || x >= arr.length || y >= arr.length) {
// 이미지를 벗어난 경우 return
return
} else if (arr[x][y] === 0 || arr[x][y] === 2) {
// background color 이거나 visited인 경우 return
return
} else {
arr[x][y] = 2 // visited
count++;
countcells(arr, x - 1, y - 1)
countcells(arr, x - 1, y)
countcells(arr, x - 1, y + 1)
countcells(arr, x, y - 1)
countcells(arr, x, y)
countcells(arr, x, y + 1)
countcells(arr, x + 1, y - 1)
countcells(arr, x + 1, y)
countcells(arr, x + 1, y + 1)
}
return count
}
solution v2
function countcells(arr, x, y) {
if (x < 0 || y < 0 || x >= arr.length || y >= arr.length) {
return 0
} else if (arr[x][y] !== 1) {
return 0
} else {
arr[x][y] = 2 // visited
return (1
+ countcells(arr, x - 1, y - 1)
+ countcells(arr, x - 1, y)
+ countcells(arr, x - 1, y + 1)
+ countcells(arr, x, y - 1)
+ countcells(arr, x, y)
+ countcells(arr, x, y + 1)
+ countcells(arr, x + 1, y - 1)
+ countcells(arr, x + 1, y)
+ countcells(arr, x + 1, y + 1))
}
return count
}
Test
const arr = [
[1, 0, 0, 0, 0, 0, 0, 1],
[0, 1, 1, 0, 0, 1, 0, 0],
[1, 1, 0, 0, 1, 0, 1, 0],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 1, 0, 1, 0, 1, 0, 0],
[0, 1, 0, 1, 0, 1, 0, 0],
[1, 0, 0, 0, 1, 0, 0, 1],
[0, 1, 1, 0, 0, 1, 1, 1],
];
const x = 5;
const y = 3;
const n = arr.length
let count = 0;
console.log(countcells(arr, x, y)); // 13
3. N-Queens
상태공간트리 | State-Space Tree
- 상태공간트리란 찾는 해를 포함하는 트리.
- 즉 해가 존재한다면 그것은 반드시 이 트리의 어떤 한 노드에 해당함
- 따라서 이 트리를 체계적으로 탐색하면 해를 구할 수 있음
백트래킹 | Backtracking
백트래킹과 깊이우선탐색
깊이우선탐색 | Deep First Search | DFS
가능한 모든 경로(후보)를 탐색합니다. 따라서, 불필요할 것 같은 경로를 사전에 차단하거나 하는 등의 행동이 없으므로 경우의 수를 줄이지 못합니다.
따라서 N! 가지의 경우의 수를 가진 문제는 DFS로 처리가 불가능할 것입니다.
*Recursion 또는 Stack 으로 구현 가능함백트래킹 | Backtracking
해를 찾아가는 도중, 지금의 경로가 해가 될 것 같지 않으면 그 경로를 더이상 가지 않고 되돌아갑니다.
즉, 코딩에서는 반복문의 횟수까지 줄일 수 있으므로 효율적입니다.
이를 가지치기라고 하는데, 불필요한 부분을 쳐내고 최대한 올바른 쪽으로 간다는 의미입니다.정리하자면, 백트래킹은 모든 가능한 경우의 수 중에서 특정한 조건을 만족하는 경우만 살펴보는 것입니다.
주로 문제 풀이에서는 DFS 등으로 모든 경우의 수를 탐색하는 과정에서, 조건문 등을 걸어 답이 절대로 될 수 없는 상황을 정의하고, 그러한 상황일 경우에는 탐색을 중지시킨 뒤 그 이전으로 돌아가서 다시 다른 경우를 탐색하게끔 구현할 수 있습니다.
Design Recursion
if (non-promising) {
report failure and return
} else if (success) {
report answer ans return
} else {
visit children recursively
}
Solution
function solution(n) {
let answer = 0;
const dfs = (board, row) => {
if (row === n) {
answer++;
} else {
for (let i = 1; i <= n; i++) {
board[row + 1] = i;
if (isValid(board, row + 1)) {
dfs(board, row + 1);
}
}
}
}
const isValid = (board, row) => {
for (let i = 1; i < row; i++) {
if (board[i] === board[row]) return false;
if (Math.abs(board[i] - board[row]) === Math.abs(i - row)) return false;
}
return true;
}
for (let i = 1; i <= n; i++) {
const board = new Array(n + 1).fill(0);
board[1] = i;
dfs(board, 1);
}
return answer;
}
*참고2
4. 멱집합 | 모든 부분 집합
- 멱집합 = 주어진 집합의 모든 부분 집합들로 구성된 집합
ex) [1, 2, 3]의 멱집합
= [], [1], [2], [3], [1, 2], [2, 3], [1, 3], [1, 2, 3] - 멱집합의 원소 갯수 = 2^n (n = 주어진 집합의 원소 갯수)
Why? 주어진 집합의 각 원소를 포함하는 경우, 포함하지 않는 경우의 수를 구함
Solution 1
function powerSet(arr) {
const flags = new Array(arr.length).fill(0)
const answer = []
const dfs = (lv) => {
if (lv === arr.length) {
answer.push(arr.filter((v, idx) => flags[idx]))
} else {
flags[lv] = 0
dfs(lv + 1)
flags[lv] = 1
dfs(lv + 1)
}
}
dfs(0)
return answer
}
const arr = [1, 2, 3, 4]
console.log(powerSet(arr));
중간에
flag[lv]
는 초기값이 0인데 왜 0을 넣어주었는지 의아할 수 있으나,
다른 가지를 통해 1로 바뀐 상태에서 flags 배열이 넘어왔을 때를 대비해 초기화를 해야함
Solution 2
const subsets = (nums) => {
const res = [];
const dfs = (start = 0, arr = []) => {
res.push(arr);
if (arr.length === nums) return;
for (let i = start; i < nums.length; i++) {
dfs(i + 1, [...arr, nums[i]]);
}
};
dfs();
return res;
};
*참고3
5. 순열 | Permutation
- 순열(nPr): 서로 다른 n개의 물건 중에서 r개를 택하여 한 줄로 세움.
function getPermutations(arr, num) {
const result = []
if (num === 1) return arr.map(el => [el])
arr.forEach((fixed, index, origin) => {
const rest = [...origin.slice(0, index), ...origin.slice(index + 1)]
const permutations = getPermutations(rest, num - 1)
const attached = permutations.map(el => [fixed, ...el])
result.push(...attached)
})
return result
}
const num = 3
const arr = [1, 2, 3, 4]
console.log(getPermutations(arr, num));
6. 조합 | Combination (추가)
- 조합(nCr): 서로 다른 n개의 물건에서 순서를 생각하지 않고 r개를 선택함.
function getCombinations(arr, num) {
const result = []
if (num === 1) return arr.map(el => [el])
arr.forEach((fixed, index, origin) => {
const rest = origin.slice(index + 1)
const combinations = getCombinations(rest, num - 1)
const attached = combinations.map(el => [fixed, ...el])
result.push(...attached)
})
return result
}
const num = 3
const arr = [1, 2, 3, 4]
console.log(getCombinations(arr, num));
📚 참고
참고1: YOUTUBE | 2015 봄학기 알고리즘 | 권오흠
참고2: [프로그래머스] LV.3 N-Queen (JS) by.longroadhome
참고3: [JS]모든 부분 집합(멱집합) 구하기 by.proshy
Photo by Michael Dziedzic on Unsplash
Author And Source
이 문제에 관하여(알고리즘 02 재귀함수 응용 | 미로찾기, N-Queens, Backtracking, DFS, 멱집합, 순열, 조합 | JS), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@protect-me/알고리즘-02-재귀함수2-JS저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)