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
가 달라짐x+1
이면 1차원 배열에서 +1
y+1
이면 2차원 배열에서 +1
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]
입력
: N * N 크기의 2차원 그리드(grid)
: 하나의 좌표(x, y)
출력
: 픽셀 (x,y)가 포함된 blob의 크기의
: (x,y)가 어떤 blob에도 속하지 않는 경우에는 0
현재 픽셀 이 속한 blob의 크기를 카운트하려면
if 현재 픽셀이 image color가 아니라면
0을 반환한다
else 현재 픽셀이 image color라면
먼저 현재 픽셀을 카운트한다(count = 1)
현재 픽셀이 중복 카운트되는 것을 방지하기 위해 다른 색으로 칠한다.
현재 픽셀이 이우한 모든 픽셀들에 대해서
그 픽셀이 속한 blob의 크기를 카운트하여 카운터에 더해준다.
카운터를 반환한다.
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
}
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
}
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
백트래킹과 깊이우선탐색
깊이우선탐색 | Deep First Search | DFS
가능한 모든 경로(후보)를 탐색합니다. 따라서, 불필요할 것 같은 경로를 사전에 차단하거나 하는 등의 행동이 없으므로 경우의 수를 줄이지 못합니다.
따라서 N! 가지의 경우의 수를 가진 문제는 DFS로 처리가 불가능할 것입니다.
*Recursion 또는 Stack 으로 구현 가능함백트래킹 | Backtracking
해를 찾아가는 도중, 지금의 경로가 해가 될 것 같지 않으면 그 경로를 더이상 가지 않고 되돌아갑니다.
즉, 코딩에서는 반복문의 횟수까지 줄일 수 있으므로 효율적입니다.
이를 가지치기라고 하는데, 불필요한 부분을 쳐내고 최대한 올바른 쪽으로 간다는 의미입니다.정리하자면, 백트래킹은 모든 가능한 경우의 수 중에서 특정한 조건을 만족하는 경우만 살펴보는 것입니다.
주로 문제 풀이에서는 DFS 등으로 모든 경우의 수를 탐색하는 과정에서, 조건문 등을 걸어 답이 절대로 될 수 없는 상황을 정의하고, 그러한 상황일 경우에는 탐색을 중지시킨 뒤 그 이전으로 돌아가서 다시 다른 경우를 탐색하게끔 구현할 수 있습니다.
if (non-promising) {
report failure and return
} else if (success) {
report answer ans return
} else {
visit children recursively
}
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
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 배열이 넘어왔을 때를 대비해 초기화를 해야함
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
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));
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