Closed qwerty3345 closed 1 month ago
https://www.acmicpc.net/problem/15649
let I = readLine()!.split { $0 == " " }.map { Int($0)! }
let (N,M) = (I[0],I[1])
func task(_ arr: [Int]) {
if arr.count == M {
print(arr.map { String($0) }.joined(separator: " "))
return
}
for i in 1...N where !arr.contains(i) {
task(arr+[i])
}
}
task([])
// 약 100ms
print()
함수가 호출 될 때마다 생각보다 비용이 많이 듭니다. 따라서 print()
문을 한번만 호출 할 수 있게 문자열 변수에 답을 저장해놨습니다.arr
배열을 순회하며 같은 숫자가 있는지 검색하기 때문에 비용이 더 들었습니다. 유튜브 강의와 같게 방문처리 배열을 만들어서 풀면 같은 숫자가 있는지 확인하는 조건을 O(1)
만큼의 시간으로 줄일 수 있습니다.let I = readLine()!.split { $0 == " " }.map { Int($0)! }
let (N,M) = (I[0],I[1])
var visited = Array(repeating: false, count: N+1)
var ans = ""
/// s: 한줄의 숫자 조합 문자열
/// c: s문자열에 들어있는 숫자 개수
func task(_ s: String, _ c: Int) {
if c == M {
ans += s+"\n"
return
}
for i in 1...N {
if !visited[i] {
visited[i] = true
task(s+"\(i) ", c+1)
visited[i] = false
}
}
}
task("", 0)
print(ans)
// 약 40ms
https://www.acmicpc.net/problem/9663
let N = Int(readLine()!)!
// true로 있다면 놓을 수 없음
// visited1: 같은 열 검사
// visited2: 좌하단 우상단 대각선 검사
// visited3: 좌상단 우하단 대각선 검사
var visited1 = Array(repeating: false, count: N)
var visited2 = visited1 + visited1
var visited3 = visited2
var ans = 0
/// y: 행의 위치
func task(_ y: Int) {
if y == N {
ans += 1
return
}
for x in 0..<N {
let c = [x, x+y, x-y+N-1] // x-y+N-1 같은 형태가 나오는 이유는 index를 벗어나게 하지 않기 위함
if !visited1[c[0]] && !visited2[c[1]] && !visited3[c[2]] {
(visited1[c[0]], visited2[c[1]], visited3[c[2]]) = (true, true, true)
task(y+1)
(visited1[c[0]], visited2[c[1]], visited3[c[2]]) = (false, false, false) // 재귀가 풀릴 때 방문처리 해제
}
}
}
task(0)
print(ans)
https://www.acmicpc.net/problem/1182
let I = readLine()!.split { $0 == " " }.map { Int($0)! }
let (N,S) = (I[0],I[1])
let arr = readLine()!.split { $0 == " " }.map { Int($0)! }
var ans = 0
/// c: 현재 자릿수
/// sum: 현재까지의 숫자의 합
func task(_ c: Int, _ sum: Int) {
if c == N {
if sum == S { ans += 1 }
return
}
task(c+1, sum) // 현재 숫자를 포함하지 않는 조건으로 재귀
task(c+1, sum+arr[c]) // 현재 숫자를 포함하는 조건으로 재귀
}
task(0,0)
print(S==0 ? ans-1 : ans) // 공집합 처리
현재 상태에서 가능한 모든 후보군을 따라 들어가며 탐색하는 알고리즘
여러 선택지가 있으며 이에 따라 게임 결과가 달라지는 예시가 있다고 한다면, 하나의 선택지를 골라 게임을 플레이하고 다시 이전으로 돌아와 다른 결과를 확인하기 위해 다른 선택지를 고르는 것! 모든 선택지를 다 해보는 방법이 백트래킹이다.
백트래킹은 이러한 수읽기에 매우 어울린다! 우리도 바둑이나 오목을 둘 때, 머리속으로 상대의 차례때의 행동을 미리 파악하고 돌을 두지 않는지 생각해보면 바로 이해가능하다! -> 우리의 일상속에서 백트래킹이 많이 녹아있다.
조합 탐색 문제
순열 및 조합 생성 문제
N-Queens 문제
미로 탐색 문제
그래프 탐색 문제
Sudoku 문제
백트래킹은 모든 경우의 수를 탐색해야 하거나 결정 문제에서 특정 조건을 만족하는 해를 찾기 위한 상황에서 유용합니다. 순열, 조합, 미로 탐색, N-Queens와 같은 전형적인 문제에서 자주 활용됩니다.
```swift
let input = readLine()!.components(separatedBy: " ").compactMap { Int($0) }
let m = input[0]
let n = input[1]
// print("m: \(m), n: \(n)")
var result: [[Int]] = []
func recr(_ nums: [Int], _ res: [Int]) {
// print("res.count == n: \(res.count == n)")
// print("res.count: \(res.count) n: \(n)")
if res.count == n {
// print(res)
result.append(res)
return
}
// print("nums: \(nums), res: \(res)")
for i in 0..
```swift let input = readLine()!.components(separatedBy: " ").compactMap { Int($0) } let m = input[0] let n = input[1] var visited: [Bool] = Array(repeating: false, count: m + 1) var result: [Int] = [] func recr(_ depth: Int) { if depth == n { print(result.map(String.init).joined(separator: " ")) return } for number in 1...m { if !visited[number] { visited[number] = true result.append(number) recr(depth + 1) visited[number] = false result.removeLast() } } } recr(0) ```
``` 1. 카운트 변수를 만든다. (초기 설정값은 0) 2. n*n의 보드를 만든다. 3. 재귀를 돌릴 함수를 만든다. 이때, 매개변수는 0이다. (제일 첫 가로줄의 인덱스를 뜻함) 4. 만약, n과 매개변수가 같으면 카운트를 세고, 리턴한다. - 그 이유는, 가로의 끝까지 재귀가 다 돌아갔을 경우, 성공했다고 보기 때문이다. 5. board의 row 수만큼 반복문을 둔다. 6. board의 제일 첫 번째 칸에 퀸을 둔다. 7. 만약 퀸을 넣었을 때 충돌하지 않는다면, 해당 함수를 재귀로 돌려 다음 줄로 넘어간다. (조건 검사 실행) 8. 그렇지 않다면 뒀던 퀸을 회수한다. 9. 한 칸의 조건 검사가 끝나면 다음 칸으로 넘어간다. 10. 카운트를 리턴한다. ```
미연시. 내가 선택한 경로에 대해 끝까지 진입하고, 게임이 종료된다면 (조건에 부합하다면) 선택 이전으로 돌아가 다른 선택.
// https://www.acmicpc.net/submit/15649
import Foundation
let input: [Int] = readLine()!.components(separatedBy: " ").map{ Int(String($0))! }
let N = input[0], M = input[1]
var arr = Array(repeating: 0, count: 10) // 결과 값이 담길 배열 [1, 2, 3] <- 첫 번째 조합 예시
var isUsed = Array(repeating: false, count: 10) // index가 곧 키값 (선택된 i에 대한 사용 여부)
func nAndM(k: Int) { // 0<=k<=M // 현재 arr이 뽑힌 상태에 대한 정보
if k == M { // 만약 모든 숫자가 뽑힌 상황이라면
for i in 0..<M {
print(arr[i], separator: " ")
}
return
}
for i in 1...N {
if !isUsed[i] {
arr[k] = i
isUsed[i] = true // 방문처리
nAndM(k: k+1) // 1개 뽑힌 상황 대한 실행
isUsed[i] = false
}
}
}
nAndM(k: 0)
[사용처리 / 가지치기 / 뒤로 돌아가기 위한 isUsed 활용 규칙]
// MARK: - 백트래킹 (2)
// 백트래킹은 미연시다 ! 단계를 밟고, 엔딩을 마주쳤다면 (base Condtion) 돌아가고, 그 전단계에서 다른 선택지를 골라서 다시금 엔딩을 보는 ...
// 재귀에 이어지는 개념으로, base condition이 필요 (반복을 종료할 조건)
// 재귀에 더해진 점은, path를 탐색할 때 사용 중을 나타내는 isUsed 배열을 생성하여 탐색 노드를 고르고, 끝까지 간 이후엔 사용을 해제함으로서 같은 깊이 내 다음 탐색에서 이전에 사용했던 값들을 사용할 수 있도록 체크
import Foundation
let N = Int(readLine()!)!
var isUsedCol: [Bool] = Array(repeating: false, count: N)
var isUsedCross1: [Bool] = Array(repeating: false, count: 2*N)
var isUsedCross2: [Bool] = Array(repeating: false, count: 2*N)
var result = Int()
func nQueen(cur: Int) { // 사용하려는 현재 좌표 (x 좌표)
if cur == N {
result += 1
return
}
for i in 0..<N { // 사용하려는 y 좌표
guard !isUsedCol[i] && !isUsedCross1[cur+i] && !isUsedCross2[cur-i+N-1] else { continue }
isUsedCol[i] = true
isUsedCross1[cur+i] = true
isUsedCross2[cur-i+N-1] = true
nQueen(cur: cur+1)
isUsedCol[i] = false
isUsedCross1[cur+i] = false
isUsedCross2[cur-i+N-1] = false
}
}
nQueen(cur: 0)
print(result)
// MARK: - 백트래킹 (3)
// 목표 수량이 정해지지 않고 (3개를 조합하라) 원하는 합산 값이 있는 문제
// 주어진 값을 사용할 지 말 지에 대한 판단으로 트리 분산
// 즉, 그래프의 뎁스는 곧 몇 번째 값에 대한 어떠한 판단인지를 의미 (false/ true)
let input = readLine()!.components(separatedBy: " ").map { Int($0)! }
let N = input[0], S = input[1]
let arr = readLine()!.components(separatedBy: " ").map { Int($0)! }
var result = S != 0 ? 0 : -1
func arrSum(cur: Int, total: Int) {
if cur == N {
if total == S {
result += 1
}
return
}
arrSum(cur: cur+1, total: total)
arrSum(cur: cur+1, total: total + arr[cur])
}
arrSum(cur: 0, total: 0)
print(result)
if cur == N && total == S { // 이게 문제
result += 1
return
}
[개선]
if cur == N {
if total == S {
result += 1
}
return
}
9/12