리더십 서밋이나 대규모 네트워킹 이벤트를 주최할 때는 특유의 운영상 골칫거리가 따릅니다. 바로 **참석자 간의 교류를 어떻게 극대화할 것인가?**라는 문제입니다.
여러 라운드에 걸쳐 테이블 토론을 진행한다면, 사람들이 끊임없이 새로운 얼굴을 만나도록 해야 합니다. 이상적으로는 그 어떤 참석자도 이전 라운드에서 이미 만났던 사람과 다시 같은 테이블에 앉지 않아야 합니다.
이 과제는 고전적인 조합론 퍼즐(*소셜 골퍼 문제(Social Golfer Problem)*와 밀접한 관련이 있음)입니다. 이 글에서는 각각 명이 앉는 개의 테이블에 대해 중복 조합 없이 최대 라운드까지 좌석을 배정할 수 있는 Go 언어로 작성된 백트래킹(backtracking) 솔루션을 살펴보겠습니다.
코드로 들어가기 전에, 중복 조합이 수학적으로 불가피해지기 전까지 예약할 수 있는 절대 최대 라운드 수를 먼저 확인해 봅시다.
최대 라운드 수는 입니다.
증명: 개의 테이블이 있고 각 테이블에 명이 앉는다고 가정해 봅시다. 그러면 총 (즉, )명의 참석자가 있습니다.
이제 단일 참석자인 참석자 A의 관점에서 이벤트를 살펴보겠습니다.
라운드가 지나면 모든 사람이 다른 모든 사람과 완벽하게 네트워킹을 마치게 됩니다.
이를 효율적으로 해결하기 위해 백트래킹 알고리즘을 사용할 수 있습니다. 하지만 탐색 공간을 획기적으로 줄이고 실행 속도를 높이기 위해 깔끔한 트릭을 사용하여 처음 두 라운드를 하드코딩할 수 있습니다:
백트래킹 진행 상황을 추적하기 위해 현재 테이블, 라운드, 그리고 누가 누구와 교류했는지를 추적하는 맵을 보유하는 State 구조체를 사용합니다.
핵심 로직은 탐색 루프, 충돌 검증, 상태 관리의 세 부분으로 나뉩니다.
solveImpl 함수는 재귀적으로 다음 빈 좌석에 사람을 배정하려고 시도합니다. 만약 특정 경로에서 충돌이 발생하면 백트래킹(unset)하고 다음 사람을 시도합니다.
func solveImpl(state *State) bool {
if state.solved {
return true
}
// 첫 번째 라운드에서는 모든 사람을 고려함
start := 0
end := state.people
// 두 번째 라운드부터는 탐색 공간을 제한함
if len(state.res) > 0 {
start = len(state.table) * state.tableSize
end = start + state.tableSize
}
for person := start; person < end; person++ {
if !valid(state, person) {
continue // 이 배정이 충돌을 일으키면 건너뜀
}
set(state, person) // 좌석 배정
if solveImpl(state) {
return true // 성공!
}
unset(state, person) // 백트래킹
}
return false
}
다음과 같은 경우 배정이 유효하지 않습니다(충돌 발생):
func valid(state *State, person int) bool {
// 이 사람이 이번 라운드에 이미 좌석을 배정받았는가?
if state.usedPerson[person] {
return false
}
// 이 사람이 현재 이 테이블에 있는 누군가와 이전에 함께 앉은 적이 있는가?
for _, there := range state.table {
first := min(there, person)
second := max(there, person)
if state.usedPair[first][second] {
return false
}
}
return true
}
set 및 unset)
🔗사람을 좌석에 배정할 때 상태 추적기를 업데이트해야 합니다. 테이블이 다 차면 다음 테이블로 이동하고, 라운드가 다 차면 다음 라운드로 이동합니다.
func set(state *State, person int) {
state.usedPerson[person] = true
// 이 사람이 현재 테이블에 있는 모든 사람과 짝을 이뤘음을 표시
for _, there := range state.table {
first := min(there, person)
second := max(there, person)
if _, ok := state.usedPair[first]; !ok {
state.usedPair[first] = map[int]bool{}
}
state.usedPair[first][second] = true
}
state.table = append(state.table, person)
// 테이블이 꽉 찼으면 다음 테이블로 이동
if len(state.table) != state.tableSize {
return
}
state.round = append(state.round, state.table)
state.table = []int{}
// 모든 테이블이 꽉 찼으면 다음 라운드로 이동
if len(state.round) != state.numTables {
return
}
state.res = append(state.res, state.round)
state.round = [][]int{}
state.usedPeople = append(state.usedPeople, state.usedPerson)
state.usedPerson = map[int]bool{}
// 완전히 끝났는지 확인
if len(state.res) != state.numRounds {
return
}
state.solved = true
}
unset 함수는 정확히 그 역순으로 동작하며, 재귀 트리가 막다른 길에 도달했을 때 상태를 조심스럽게 되돌립니다.
func unset(state *State, person int) {
state.solved = false
// 현재 테이블이 비어 있으면 이전 테이블로 되돌아감
if len(state.table) == 0 {
if len(state.round) == 0 {
last := len(state.res) - 1
state.round = state.res[last]
state.res = state.res[:last]
state.usedPerson = state.usedPeople[last]
state.usedPeople = state.usedPeople[:last]
}
last := len(state.round) - 1
state.table = state.round[last]
state.round = state.round[:last]
}
// 테이블에서 해당 사람을 제거
state.table = state.table[:len(state.table)-1]
// 짝 표시 해제
for _, there := range state.table {
first := min(there, person)
second := max(there, person)
state.usedPair[first][second] = false
}
state.usedPerson[person] = false
}
다음은 규모가 작은 서밋에 대한 출력 결과입니다. 라운드 0에서는 숫자가 테이블을 가로질러 순차적으로 증가하고, 라운드 1에서는 이전 테이블들을 세로로 가로질러 순차적으로 배치되는 것을 확인할 수 있습니다.
round 0:
table 0: 0 1 2 3
table 1: 4 5 6 7
table 2: 8 9 10 11
table 3: 12 13 14 15
round 1:
table 0: 0 4 8 12
table 1: 1 5 9 13
table 2: 2 6 10 14
table 3: 3 7 11 15
round 2:
table 0: 0 5 10 15
table 1: 1 4 11 14
table 2: 2 7 8 13
table 3: 3 6 9 12
round 3:
table 0: 0 6 11 13
table 1: 1 7 10 12
table 2: 2 4 9 15
table 3: 3 5 8 14
round 4:
table 0: 0 7 9 14
table 1: 1 6 8 15
table 2: 2 5 11 12
table 3: 3 4 10 13
처음 두 라운드를 미리 채워 탐색 트리를 지능적으로 가지치기(pruning)했기 때문에, 알고리즘이 더 큰 그룹으로도 훌륭하게 확장됩니다. 아래는 8개의 테이블에 걸쳐 64명의 참석자를 배치한 솔루션입니다.
round 0:
table 0: 0 1 2 3 4 5 6 7
table 1: 8 9 10 11 12 13 14 15
table 2: 16 17 18 19 20 21 22 23
table 3: 24 25 26 27 28 29 30 31
table 4: 32 33 34 35 36 37 38 39
table 5: 40 41 42 43 44 45 46 47
table 6: 48 49 50 51 52 53 54 55
table 7: 56 57 58 59 60 61 62 63
round 1:
table 0: 0 8 16 24 32 40 48 56
table 1: 1 9 17 25 33 41 49 57
table 2: 2 10 18 26 34 42 50 58
table 3: 3 11 19 27 35 43 51 59
table 4: 4 12 20 28 36 44 52 60
table 5: 5 13 21 29 37 45 53 61
table 6: 6 14 22 30 38 46 54 62
table 7: 7 15 23 31 39 47 55 63
round 2:
table 0: 0 9 18 27 36 45 54 63
table 1: 1 8 19 26 37 44 55 62
table 2: 2 11 16 25 38 47 52 61
table 3: 3 10 17 24 39 46 53 60
table 4: 4 13 22 31 32 41 50 59
table 5: 5 12 23 30 33 40 51 58
table 6: 6 15 20 29 34 43 48 57
table 7: 7 14 21 28 35 42 49 56
round 3:
table 0: 0 10 20 30 35 41 55 61
table 1: 1 11 21 31 34 40 54 60
table 2: 2 8 22 28 33 43 53 63
table 3: 3 9 23 29 32 42 52 62
table 4: 4 14 16 26 39 45 51 57
table 5: 5 15 17 27 38 44 50 56
table 6: 6 12 18 24 37 47 49 59
table 7: 7 13 19 25 36 46 48 58
round 4:
table 0: 0 11 22 29 39 44 49 58
table 1: 1 10 23 28 38 45 48 59
table 2: 2 9 20 31 37 46 51 56
table 3: 3 8 21 30 36 47 50 57
table 4: 4 15 18 25 35 40 53 62
table 5: 5 14 19 24 34 41 52 63
table 6: 6 13 16 27 33 42 55 60
table 7: 7 12 17 26 32 43 54 61
round 5:
table 0: 0 12 19 31 38 42 53 57
table 1: 1 13 18 30 39 43 52 56
table 2: 2 14 17 29 36 40 55 59
table 3: 3 15 16 28 37 41 54 58
table 4: 4 8 23 27 34 46 49 61
table 5: 5 9 22 26 35 47 48 60
table 6: 6 10 21 25 32 44 51 63
table 7: 7 11 20 24 33 45 50 62
round 6:
table 0: 0 13 17 28 34 47 51 62
table 1: 1 12 16 29 35 46 50 63
table 2: 2 15 19 30 32 45 49 60
table 3: 3 14 18 31 33 44 48 61
table 4: 4 9 21 24 38 43 55 58
table 5: 5 8 20 25 39 42 54 59
table 6: 6 11 23 26 36 41 53 56
table 7: 7 10 22 27 37 40 52 57
round 7:
table 0: 0 14 23 25 37 43 50 60
table 1: 1 15 22 24 36 42 51 61
table 2: 2 12 21 27 39 41 48 62
table 3: 3 13 20 26 38 40 49 63
table 4: 4 10 19 29 33 47 54 56
table 5: 5 11 18 28 32 46 55 57
table 6: 6 8 17 31 35 45 52 58
table 7: 7 9 16 30 34 44 53 59
round 8:
table 0: 0 15 21 26 33 46 52 59
table 1: 1 14 20 27 32 47 53 58
table 2: 2 13 23 24 35 44 54 57
table 3: 3 12 22 25 34 45 55 56
table 4: 4 11 17 30 37 42 48 63
table 5: 5 10 16 31 36 43 49 62
table 6: 6 9 19 28 39 40 50 61
table 7: 7 8 18 29 38 41 51 60
다음에 네트워킹 이벤트를 주최해야 할 때는 수동 스프레드시트 작업을 건너뛰고 백트래킹 알고리즘에게 무거운 작업을 맡겨보세요!
카우에의 삶의 방식 오프라인 에디션을 받아보세요—🍏 건강, 💰 부, ❤️ 사랑, 🧘 의미를 최적화하기 위한 실전 가이드입니다.
더 나은 삶을 위한 새로운 에세이, 도서 요약, 실전 가이드를 발행할 때 알림을 받아보세요. 스팸은 없으며 언제든 구독을 취소할 수 있습니다.
질문, 의견 또는 제안이 있으신가요? 아래에 생각을 남겨주세요. 댓글은 GitHub Discussions(광고 및 스팸 없음)를 통해 제공됩니다—댓글이나 반응을 남기려면 아래의 GitHub으로 로그인을 클릭하세요(무료 GitHub 계정은 1분 이내에 만들 수 있습니다).
최종 업데이트: 2026. 10. 11..
면책 조항: 본 사이트의 콘텐츠는 정보 제공 및 교육 목적으로만 제공되며 의학, 심리, 재무, 투자, 세무 또는 법률 자문을 구성하지 않습니다. 여기에 제시된 생각, 큐레이션 및 의견은 전적으로 저 개인의 것이며 고용주의 입장을 대변하지 않습니다. 도서, 강연 및 아티클 요약은 독립적인 교육적 해설이며 각 저자의 지적 재산입니다. 편집, 서식 지정 및 요약을 돕기 위해 AI 도구가 사용될 수 있으며, 정확성을 기하기 위해 최선을 다하고 있으나 오류가 포함될 수 있습니다. 개인정보 처리방침: 본 사이트는 선택적 뉴스레터 구독(follow.it), 댓글(giscus / GitHub Discussions) 및 임베디드 미디어(YouTube)를 위해 제3자 서비스를 사용하며, 해당 서비스들은 각자의 개인정보 처리방침에 따라 방문자의 IP 주소와 쿠키를 처리할 수 있습니다.