funsolve(initialState:State):List<Solution>{// 탐색을 하며 찾은 정답을 저장할 저장소valsolutions=mutableListOf<Solution>()backtrack(initialState,solutions)returnsolutions}funbacktrack(// 현재 상태currentState:state,// 탐색을 하며 찾은 정답을 저장할 저장소solutions:MutableList<Solution>){// 1. 종료 조건 (Base Case)// 현재 상태가 문제의 해답이라면if(isSolution(currentState)){solutions.add(currentState.toSolution())return}// 2. 재귀 호출 (Recursive Step)// 현재 상태에서 다음으로 진행 가능한 모든 상태들을 탐색for(stateingetNextStates(currentState)){// 상태 확인 (가지치기)if(isPromising(state)){// 상태 변환 (전이)...// 탐색 진행backtrack(state,solutions)// 백트랙// 함수 호출 스택에서 이전 상태로 돌아가는 백트랙은 재귀 호출이 끝난 후 자동으로 이루어진다.// 상태 되돌리기// 하나의 경로 탐색이 끝난 후에 다른 경로를 탐색할 수 있도록 상태를 이전 상태로 명시적으로 되돌려야 하는 경우 이 곳에서 수행한다....}}}// 현재 상태가 문제의 해답인지 확인funisSolution(state:state):Boolean{// 문제의 종료 조건 판별 로직...}// 현재 상태에서 다음으로 진행 가능한 모든 상태를 조회fungetNextStates(state:state):List<state>{// 다음으로 진행할 수 있는 모든 경우의 수를 조회...}// 상태가 적합한지 확인funisPromising(state:state):Boolean{// 제약 조건 만족 검사 로직 구현...}
N-퀸 문제
classSolution{funnQueens(n:Int):Int{// 퀸이 위치한 열 번호를 저장할 배열valboard=IntArray(n){0}valcount=0backtrack(n,0,board,count)returncount}funbacktrack(// 다음 상태 탐색을 위한 변수n:Int,row:Int,// 현재 상태board:IntArray// 탐색을 하며 찾은 정답을 저장할 저장소count:Int){// 1. 종료 조건// 모든 퀸을 성공적으로 배치한 경우if(row==n){count++return}// 2. 재귀 호출// 현재 행(row)에 퀸을 놓을 수 있는 모든 열(col)을 탐색for(colin0untiln){// 상태 확인 (가지치기)// 현재 위치(row, col)에 퀸을 놓을 수 있는지 확인if(isPromising(row,col,board)){// 상태 변환 (전이)// 퀸을 배치board[row]=col// 탐색 진행// 다음 행으로 이동backtrack(n,row+1,board,count)// 백트랙// 상태를 덮어쓰는 방식이므로, 상태 되돌리기 과정은 별도로 필요하지 않다.}}}// 현재 위치(row, col)에 퀸을 놓을 수 있는지 확인funisPromising(row:Int,col:Int,board:IntArray):Boolean{// 같은 열에 퀸이 있는지, 대각선에 퀸이 있는지 확인for(iin0untilrow){// 같은 열에 퀸이 있는지 (board[i] == col)// 또는 대각선에 퀸이 있는지 (abs(row - i) == abs(col - board[i]))if(board[i]==col||(row-i)==Math.abs(col-board[i])){returnfalse}}returntrue}}funmain(){valsolution=Solution()valn=4valresult=solution.nQueens(n)println(result)}
노드: 퀸들의 현재 배치 상태
간선: 한 행에서 다음 행으로 퀸을 놓는 것
가지치기: 퀸을 놓으려는 위치가 이미 다른 퀸의 공격 범위에 있는 경우 퀸을 배치하지 않는 것
루트 노드: 퀸이 놓이지 않은 초기 상태
리프 노드: 더 이상 퀸을 놓을 수 없는 최종 상태
정답인 경우: 모든 퀸이 놓인 상태
정답이 아닌 경우: 더 이상 퀸을 놓을 수 없는 상태
경로 탐색: 루트 노드에서 리프 노드까지 이어지는 일련의 퀸 배치 과정
백트랙: 퀸을 놓을 수 없는 경우 이전 행으로 되돌아가서 다른 경로를 탐색하는 것
순열 문제
classSolution{funpermutations(n:Int,m:Int):List<List<Int>>{valresults=mutableListOf<List<Int>>()valcurrentPermutation=mutableListOf<Int>()// 1부터 n까지의 숫자 사용 여부를 저장할 배열valused=BooleanArray(n+1){false}backtrack(n,m,currentPermutation,used,results)returnresults}funbacktrack(// 다음 상태 탐색을 위한 변수n:Int,m:Int,// 현재 상태currentPermutation:MutableList<Int>,used:BooleanArray,// 탐색을 하며 찾은 정답을 저장할 저장소results:MutableList<List<Int>>){// 1. 종료 조건// 현재 순열의 길이가 m과 같을 경우if(currentPermutation.size==m){results.add(ArrayList(currentPermutation))return}// 2. 재귀 호출// 1부터 n까지의 모든 숫자를 탐색for(iin1..n){// 상태 확인 (가지치기)// 현재 숫자를 사용할 수 있는지 확인if(isPromising(i,used)){// 상태 변환 (전이)currentPermutation.add(i)used[i]=true// 탐색 진행backtrack(n,m,currentPermutation,used,results)// 백트랙// 상태 되돌리기currentPermutation.removeAt(currentPermutation.size-1)used[i]=false}}}// 현재 숫자를 사용할 수 있는지 확인funisPromising(i:Int,used:BooleanArray):Boolean{// 이미 사용한 숫자인지 확인if(used[i]){returnfalse}returntrue}}funmain(){valn=3// 1부터 3까지의 자연수valm=2// 2개를 선택valsolution=Solution()valresult=solution.permutations(n,m)result.forEach{// 각 순열 출력println(it.joinToString(" "))}}
노드: 현재까지 선택된 숫자의 부분 순열
간선: 다음 숫자를 선택하는 것
가지치기: 이미 선택한 숫자를 다시 선택하지 않는 것
루트 노드: 아무 숫자도 선택되지 않은 초기 상태
리프 노드: 더 이상 숫자를 선택할 수 없는 최종 상태
모든 리프 노드가 정답
경로 탐색: 루트 노드에서 리프 노드까지 이어지는 일련의 숫자 선택 과정
백트랙: 선택했던 숫자를 선택하지 않은 상태로 변경하고 다른 숫자를 선택하여 경로를 탐색하는 것
조합 문제
classSolution{funcombinations(n:Int,m:Int):List<List<Int>>{valresults=mutableListOf<List<Int>>()valcurrentCombination=mutableListOf<Int>()backtrack(n,m,1,currentCombination,results)returnresults}privatefunbacktrack(// 다음 상태 탐색을 위한 변수n:Int,m:Int,start:Int,// 현재 상태currentCombination:MutableList<Int>,// 탐색을 하며 찾은 정답을 저장할 저장소results:MutableList<List<Int>>){// 1. 종료 조건 (Base Case)// 현재 조합의 길이가 m과 같을 경우if(currentCombination.size==m){results.add(ArrayList(currentCombination))return}// 2. 재귀 호출 (Recursive Step)// start부터 n까지의 숫자를 탐색for(iinstart..n){// 상태 변환 (전이)currentCombination.add(i)// 탐색 진행// 현재 숫자 다음부터 탐색backtrack(n,m,i+1,currentCombination,results)// 백트랙// 상태 되돌리기currentCombination.removeAt(currentCombination.size-1)}}}funmain(){valn=3// 1부터 3까지의 자연수valm=2// 2개를 선택valsolution=Solution()valresult=solution.combinations(n,m)result.forEach{// 각 조합 출력println(it.joinToString(" "))}}
classSolution{funletterCombinations(digits:String):List<String>{vallettersArray=arrayListOf("","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz")valresult=mutableListOf<String>()valinitialState=StringBuilder()if(digits.isEmpty()){returnemptyList()}funbacktrack(// 다음 상태 탐색을 위한 변수digits:String,index:Int,// 현재 상태currentState:StringBuilder,// 탐색을 하며 찾은 정답을 저장할 저장소result:MutableList<String>){// 1. 종료 조건// 모든 자릿수에 대해 처리한 경우if(index==digits.length){valterminalState=currentState.toString()result.add(terminalState)return}valdigit=Character.getNumericValue(digits[index])valletters=lettersArray[digit]// 2. 재귀 호출// 해당 자릿수의 모든 문자를 탐색for(iinletters.indices){// 상태 확인 (가지치기)valletter=letters[i]currentState.append(letter)// 탐색 진행backtrack(digits,index+1,currentState,result)// 백트랙// 상태 되돌리기currentState.deleteCharAt(currentState.length-1)}}backtrack(digits,0,initialState,result)returnresult}}funmain(){valsolution=Solution()valresult=solution.letterCombinations("23")println(result)}
문자열은 연속된 문자들이 그룹화되어 구성된 자료 구조이다. 따라서 데이터를 그룹화한 추상 자료형인 컬렉션(collection)의 다양한 자료 구조로 문자열을 구조화할 수 있으며 다양한 자료 구조 탐색 알고리즘을 사용하여 부분 문자열들을 탐색 및 비교하는 등의 문제를 해결할 수 있다.
백트래킹(backtracking)(또는 역추적) 알고리즘이란 최적의 해결책을 찾기 위해 모든 가능한 방법을 후보(candidate)로 구성한 후, 점진적으로 후보들을 시도하면서 유효한 후보가 아닐 경우(문제의 정답 조건을 만족하지 않을 경우) 문제 해결 과정에서 제외하고 되돌아가 ...
Comments