맨위로가기

피셔-예이츠 셔플

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

피셔-예이츠 셔플은 주어진 숫자 집합을 무작위로 섞는 알고리즘으로, 1938년 로널드 피셔와 프랭크 예이츠에 의해 처음 소개되었다. 이 알고리즘은 종이에 숫자를 적고 난수표를 활용하는 방식으로 시작되었으며, 1964년 리처드 더스텐펠드에 의해 컴퓨터 사용에 최적화된 현대적인 형태로 개선되었다. 더스텐펠드의 알고리즘은 배열의 요소를 교환하는 방식으로 시간 복잡도를 O(n)으로 줄였으며, 사톨로 알고리즘과 같은 변형도 존재한다. 피셔-예이츠 셔플은 구현 오류, 난수 생성기의 문제, 모듈로 편향 등의 문제로 인해 편향된 결과를 생성할 수 있으므로 주의가 필요하다.

더 읽어볼만한 페이지

  • 조합 알고리즘 - 탐욕 알고리즘
    탐욕 알고리즘은 각 단계에서 가장 좋아 보이는 선택을 하는 알고리즘 설계 패러다임으로, 최적 부분 구조와 탐욕 선택 속성을 가진 문제에 최적의 해를 보장하며, 다익스트라, 프림, 크루스칼 알고리즘 등에 사용된다.
  • 조합 알고리즘 - 게일-섀플리 알고리즘
    게일-섀플리 알고리즘은 두 그룹 간의 안정적인 매칭을 찾아내는 알고리즘으로, 각 참가자의 선호도를 기반으로 안정적인 매칭을 생성하며 제안하는 측에 최적의 결과를 제공하고, 2012년 노벨 경제학상을 수상했다.
  • 확률적 알고리즘 - 알고리즘 정보 이론
    알고리즘 정보 이론은 콜모고로프 복잡성을 기반으로 객체의 정보량을 측정하는 척도를 제공하며, 최소 설명 길이 원리 등에 응용될 수 있다.
  • 확률적 알고리즘 - 몬테카를로 알고리즘
    몬테카를로 알고리즘은 정확한 답을 보장하지 않는 확률적 알고리즘으로, 단측 또는 양측 오류를 가지며 반복 실행으로 오류를 줄일 수 있고, 라스베가스 알고리즘과 함께 랜덤 알고리즘의 주요 분류를 이루며 다양한 분야에 응용된다.
  • 몬테카를로 방법 - 메트로폴리스-헤이스팅스 알고리즘
    메트로폴리스-헤이스팅스 알고리즘은 마르코프 연쇄 몬테카를로 방법으로, 확률 밀도 함수에 비례하는 함수를 알 때 원하는 확률 분포에서 난수열을 생성하며 통계 모델링, 데이터 분석 등에 응용된다.
  • 몬테카를로 방법 - 파티클 필터
    파티클 필터는 칼만 필터와 유사하게 상태 공간 모델에서 관측 불가능한 상태 열의 확률 분포를 추정하는 알고리즘으로, 비선형 및 비가우시안 시스템에도 적용 가능한 장점이 있어 로봇 공학, 신호 처리, 금융 공학 등 다양한 분야에서 활용된다.
피셔-예이츠 셔플
알고리즘 개요
종류순열 생성 알고리즘
특징제자리 섞기 (in-place shuffle) 알고리즘
역사
최초 고안자로널드 피셔와 프랭크 예이츠
발표 시기1938년
목적수동으로 확률을 계산하기 위한 방법
컴퓨터 적용1964년 리처드 더스텐필드에 의해 컴퓨터에 적합한 형태로 변형됨
대중화도널드 커누스의 "컴퓨터 프로그래밍의 예술"에 소개되면서 널리 알려짐
작동 방식
기본 원리배열의 각 요소를 무작위로 선택하여 남은 요소와 교환하는 과정을 반복
각 요소가 선택될 확률이 동일하도록 보장
시간 복잡도O(n)
공간 복잡도O(1) (제자리 알고리즘)
장점
효율성선형 시간 복잡도로 매우 빠름
공정성모든 순열이 생성될 확률이 동일
제자리 연산추가적인 메모리 공간을 거의 필요로 하지 않음
구현 방법
기본 구현배열의 뒤에서부터 시작하여 아직 선택되지 않은 요소 중에서 무작위로 하나를 선택하여 현재 위치의 요소와 교환
주의사항올바른 난수 생성기 사용이 중요하며, 그렇지 않으면 편향된 결과가 발생할 수 있음
활용 분야
응용 예시카드 게임의 카드 섞기
무작위 표본 추출
실험 설계
암호학
변형
안쪽에서 바깥쪽으로 섞기 (inside-out algorithm)새로운 배열을 생성하지 않고 기존 배열을 수정하는 대신, 새로운 배열을 만들 때 각 요소를 무작위 위치에 삽입
기타 명칭
다른 이름Knuth shuffle (커누스 셔플)
Durstenfeld shuffle (더스텐필드 셔플)

2. 역사

2. 1. 피셔와 예이츠의 최초 방법 (1938)

로널드 피셔와 프랭크 예이츠가 1938년 저서 ''Statistical table for bio, Agriculture, Medical Research''에서 처음으로 기술했다.[38][3][26] 이 방법은 연필과 종이를 사용하여 1부터 N까지의 숫자를 무작위 순열로 생성하는 방법을 설명하며, 난수표를 사용하여 무작위성을 확보했다.

기본적인 방법은 다음과 같다.

# 1부터 ''N''까지의 숫자를 쓴다.

# 1과 지워지지 않고 남아 있는 수의 개수(포함) 사이의 임의의 수 ''k''를 선택한다.

# 작은 쪽부터 세어 아직 지워지지 않은 ''k'' 번째 수를 지워서 별도의 목록 끝에 적어둔다.

# 모든 수가 지워질 때까지 2단계부터 반복한다.

# 3단계에서 기록한 일련의 수가 임의 순열이 된다.

이때 2단계에서 선택한 임의의 수가 실제 난수이고 편향되지 않은 경우 결과 순열도 무작위성을 보장받는다. 피셔와 예이츠는 난수표에서 원하는 범위의 편향되지 않은 난수를 얻는 방법을 중요하게 설명하였다. 또한 1에서 ''N'' 까지의 난수를 선택하고 중복을 버리는 간단한 방법을 제안하고 있는데, 이 방법을 앞쪽 절반에만 사용하고 나머지 절반에는 더 복잡한 알고리즘을 적용하도록 설명하였다. 이는 뒤쪽 절반에서 중복이 많아지는 문제를 해결하기 위함이다.

2. 2. 현대적인 방법 (1964)

1964년 리처드 더스텐펠드(Richard Durstenfeld)는 피셔-예이츠 셔플을 컴퓨터 사용에 최적화한 현대적인 알고리즘을 제안했다.[4] 이 알고리즘은 도널드 E. 커누스(Donald E. Knuth)가 자신의 저서 ''컴퓨터 프로그래밍의 예술''에서 "알고리즘 P"로 소개하면서 널리 알려지게 되었다.[5] 더스텐펠드와 커누스는 초판에서 피셔와 예이츠의 연구를 인지하지 못해 언급하지 않았지만, 이후 판에서는 피셔와 예이츠의 기여를 언급했다.[6][29]

더스텐펠드의 알고리즘은 원래 피셔와 예이츠가 제시한 방법과 비교했을 때, 숫자를 '지우는' 대신 '선택된' 숫자를 아직 선택되지 않은 마지막 숫자와 교환하는 방식을 사용한다. 이러한 방식은 각 단계에서 남은 숫자를 세는 불필요한 시간을 없애, 알고리즘의 시간 복잡도를 O(''n'')으로 단축시켜 효율성을 크게 향상시켰다.[7][30]

다음은 더스텐펠드 알고리즘의 예시이다. 0부터 시작하는 배열을 사용한다.

  • - ''n''개의 요소(인덱스 0..''n''-1)를 가진 배열 ''a''를 섞으려면:

'''for''' ''i'' '''from''' ''n''−1 '''down to''' 1 '''do'''

''j'' ← 0 ≤ ''j'' ≤ ''i''인 임의의 정수

''a''[''j'']와 ''a''[''i'']를 교환

배열을 반대 방향(최소 인덱스에서 최대 인덱스까지)으로 섞는 버전은 다음과 같다.

  • - ''n''개의 요소(인덱스 0..''n''-1)를 가진 배열 ''a''를 섞으려면:

'''for''' ''i'' '''from''' 0 '''to''' ''n''−2 '''do'''

''j'' ← ''i'' ≤ ''j'' ≤ ''n-1''인 임의의 정수

''a''[''i'']와 ''a''[''j'']를 교환

처음에는 A에서 H까지의 문자를 쓴다.

범위난수종이결과
A B C D E F G H



첫 번째는 1에서 8까지 중 임의의 숫자를 선택한다. 6을 선택하고 목록에서 6번째와 8번째 문자를 바꾼다.

범위난수종이결과
1–86A B C D E H GF



다음 난수는 1에서 7까지 중에 2가 선택되었다. 따라서 두 번째와 일곱 번째 문자를 바꾸고 계속 진행한다.

범위난수종이결과
1–72A G C D E HB F



다음 난수는 1에서 6이며, 6이 나왔다. 목록의 6번째 문자 H가 현재 선택되지 않은 마지막 글자이므로 교환 후에도 그대로이다. 그리고, 순열이 완료될 때까지 같은 방식으로 계속 진행한다.

범위난수종이결과
1–66A G C D EH B F
1–51E G C DA H B F
1–43E G DC A H B F
1–33E GD C A H B F
1–21GE D C A H B F



이 시점에서 더 이상 수행할 수 있는 것은 없으므로 결과 순열은 G E D C A H B F이다.

3. 알고리즘

3. 1. 피셔와 예이츠의 방법

피셔와 예이츠의 최초 방법은 1부터 N까지의 숫자를 종이에 쓰는 것으로 시작한다. 그런 다음, 1과 아직 지워지지 않은 숫자의 개수(N) 사이에서 무작위 숫자 k를 선택한다. 지워지지 않은 k번째 숫자를 지우고 별도의 목록에 기록하는 과정을, 모든 숫자가 지워질 때까지 반복한다.

예를 들어, A부터 H까지의 문자를 섞는 경우, 다음과 같이 진행된다.

범위난수종이결과
A B C D E F G H
1–83A B C D E F G HC
1–74A B C D E F G HC E
1–65A B C C E F G HC E G
1–53A B C D E F G HC E G D
1–44A B C D E F G HC E G D H
1–31A B C D E F G HC E G D H A
1–22A B C D E F G HC E G D H A F
A B C D E F G HC E G D H A F B



위 표는 1–8 의 숫자를 섞는 예시를 보여준다.

3. 2. 현대적인 방법 (더스텐펠드 알고리즘)

피셔-예이츠 셔플의 현대적인 알고리즘은 1964년 리처드 더스텐펠드가 발표했으며[27], 도널드 커누스가 ''The Art of Computer Programming''에서 "Algorithm P"로 소개하면서 널리 알려졌다[28]. 더스텐펠드와 커누스는 처음에는 피셔와 예이츠의 연구를 인지하지 못했으나, 이후 판본에서 이를 언급하였다[29].

더스텐펠드의 알고리즘은 피셔-예이츠 셔플과 유사하지만, 선택된 요소를 제거하고 배열을 재정렬하는 대신, 선택된 요소를 배열의 끝으로 이동시키면서 아직 선택되지 않은 마지막 요소와 교환하는 방식으로 효율성을 높였다. 이 방식은 계산량을 ''O''(''n''2)에서 ''O''(''n'')으로 감소시킨다[30].

알고리즘의 핵심은 다음과 같다.

  • 배열 a[0..n-1]을 섞는다.

for i from n-1 down to 1 do

  • 0 ≤ j ≤ i 인 무작위 정수 j를 선택한다.
  • a[j]와 a[i]를 교환한다.


예를 들어, A부터 H까지의 문자를 섞는 경우를 생각해 보자.

범위난수종이결과
A B C D E F G H
1–86A B C D E H GF
1–72A G C D E HB F
1–66A G C D EH B F
1–51E G C DA H B F
1–43E G DC A H B F
1–33E GD C A H B F
1–21GE D C A H B F



위 표와 같이, 각 단계에서 선택된 문자와 아직 선택되지 않은 마지막 문자를 교환한다. 최종적으로 G E D C A H B F 순열을 얻는다.

다른 구현 방식으로는 다음과 같이 배열의 앞쪽에서부터 셔플을 진행할 수도 있다.


  • 배열 a[0..n-1]을 섞는다.

for i from 0 to n - 2 do

  • i ≤ j < n 인 무작위 정수 j를 선택한다.
  • a[j]와 a[i]를 교환한다.

3. 3. "인사이드-아웃" 알고리즘

더스텐펠트가 구현한 피셔-예이츠 셔플은 ''제자리 셔플''이다. 즉, 미리 초기화된 배열이 주어지면 배열의 셔플된 사본을 생성하는 대신 배열의 요소를 제자리에서 셔플한다. 셔플할 배열이 큰 경우 이는 장점이 될 수 있다.

배열을 동시에 초기화하고 셔플하기 위해 셔플의 "안에서 밖으로" 버전을 사용하면 조금 더 효율성을 얻을 수 있다. 이 버전에서는 배열의 처음 ''i'' 위치 중 임의의 위치에 차례로 요소 번호 ''i''를 배치한 후 해당 위치를 이전에 차지했던 요소를 위치 ''i''로 이동시킨다. 별도의 초기화는 필요하지 않다. ''source''가 실행 중에 변경되지 않으므로 값을 얻는 방법에 상당한 유연성이 있다. ''source''가 0에서 ''n'' − 1까지의 정수와 같은 간단한 함수로 정의되는 일반적인 경우 ''source''를 단순히 함수로 바꿀 수 있다.

''n''개의 요소 배열 ''a''[]를 ''source''[]의 무작위로 셔플된 복사본으로 초기화하려면, 0부터 시작한다.

'''for''' ''i'' '''from''' 0 '''to''' ''n'' − 1 '''do'''

''j'' ← 0 ≤ ''j'' ≤ ''i''인 임의의 정수

''a''[''i''] ← ''source''[''i'']

''a''[''i''] ← ''a''[''j'']

''a''[''j''] ← ''source''[''i'']

''a''[''i'']에 대한 겉보기에는 중복된 초기 할당은 ''i'' = ''j''인 경우 다음 ''a''[''j'']에 대한 액세스가 초기화되지 않은 변수가 되지 않도록 하기 위해 있다. 초기화 값은 중요하지 않다. 0도 마찬가지로 효과가 있다. 그런 경우 두 번째 할당에서 복사된 값은 ''a''[''i'']''a''[''j'']에 대한 할당으로 즉시 덮어쓰여지기 때문에 중요하지 않지만, 많은 인기 있는 언어(특히 CC++를 포함하며, 여기서 적용되지 않는 제한된 예외가 있습니다[8])는 단순히 초기화되지 않은 값을 읽는 것은 정의되지 않은 동작이며 프로그래밍 오류라고 명시한다.

안에서 밖으로 셔플은 귀납법에 의해 정확하다고 볼 수 있다. 루프 반복 ''i'' 후에 배열의 처음 ''i''개 요소는 임의의 순열을 포함한다. 각 루프 반복은 ''i''를 증가시키면서 이 속성을 유지한다. 또는 임의의 숫자 ''j''의 ''n''!개의 서로 다른 시퀀스가 있으며 각각 다른 순열에 해당한다는 것을 보여줄 수 있다. 따라서 각 순열은 정확히 한 번 얻습니다. 완벽한 난수 생성기를 가정하면 모두 동일한 확률로 발생한다.

이 변형의 또 다른 장점은 ''source''의 요소 수인 ''n''을 미리 알 필요가 없다는 것이다. 소스 데이터의 끝에 도달했을 때 이를 감지할 수만 있으면 된다.

"꺼내기" 버전의 셔플이 올바른지는 귀납적으로 확인할 수 있다. 완전한 임의의 수치를 생성할 수 있다고 가정한다면, ''n''!의 서로 다른 난수를 모두 하나씩 얻을 수 있다. 그렇다면 모두 다른 순열을 생성할 수 있으며, 그것들은 완전히 한 번씩의 순열이 된다.

3. 4. 사톨로 알고리즘 (Sattolo's algorithm)

사톨로 알고리즘은 더스텐펠드 알고리즘과 유사하지만, 결과 순열이 항상 단일 사이클로 구성된다는 차이점이 있다.[44][9][10][31] 이는 무작위 숫자 j의 범위를 1과 i-1 사이로 제한하여 구현된다. 이렇게 하면 순열이 항상 길이 ''n''의 원순열이 된다.

사톨로 알고리즘으로 생성된 ABCD의 6개 (3!) 순환 순열
순환
순열
예시
BCDAABCD→DABC→CDAB→BCDA→ABCD...
DABCABCD→BCDA→CDAB→DABC→ABCD...
BDACABCD→CADB→DCBA→BDAC→ABCD...
CADBABCD→BDAC→DCBA→CADB→ABCD...
CDBAABCD→DCAB→BADC→CDBA→ABCD...
DCABABCD→CDBA→BADC→DCAB→ABCD...



사톨로 알고리즘은 수학적 귀납법을 통해 길이 ''n''의 사이클을 생성함을 증명할 수 있다. 초기 반복 후, 나머지 반복이 처음 ''n'' − 1개 요소를 길이 ''n'' − 1의 사이클에 따라 순열한다고 가정한다. 초기 요소를 새로운 위치 ''p''로 추적하고, 원래 위치 ''p''의 요소를 새로운 위치로 추적하는 방식으로, 다른 모든 위치를 방문한 후에 초기 위치로 돌아온다.

사톨로 알고리즘은 (''n''−1)!개의 고유한 가능한 난수 시퀀스를 가지며, 각 시퀀스는 서로 다른 순열을 생성한다. 난수 소스가 편향되지 않았다고 가정하면 각 순열은 동일한 확률로 발생한다. 이렇게 생성된 (''n''−1)!개의 서로 다른 순열은 길이 ''n''의 사이클 집합을 모두 포함한다.

파이썬으로 구현된 사톨로 알고리즘의 예는 다음과 같다:



from random import randrange

def sattolo_cycle(items) -> None:

"""Sattolo's algorithm."""

i = len(items)

while i > 1:

i = i - 1

j = randrange(i) # 0 <= j <= i-1

items[j], items[i] = items[i], items[j]



일반적인 피셔-예이츠 셔플을 구현할 때, 의도치 않게 사톨로 알고리즘을 구현하는 경우가 발생할 수 있다. 이는 ''n''! 개의 전체 순열 집합이 아닌 (''n'' −1)! 개의 더 작은 집합에서 선택되도록 하여 결과를 편향시킨다.

4. 예시

4. 1. 연필과 종이 방법 (피셔-예이츠)

피셔-예이츠 셔플의 연필과 종이 방법은 간단하다. 예를 들어 A부터 H까지의 문자를 무작위로 섞는다고 가정하자. 먼저, 종이에 A부터 H까지의 문자를 순서대로 쓴다.

범위난수종이결과
A B C D E F G H



그런 다음 1부터 8까지의 숫자 중 임의의 숫자 k를 선택한다. 예를 들어 k가 3이라고 하자. 종이에 k번째(즉, 세 번째) 문자를 지우고 결과에 기록한다.

범위난수종이결과
1–83A B C D E F G HC



이제 1부터 7까지의 범위에서 두 번째 난수를 선택한다. 예를 들어 4가 나왔다면, 아직 지워지지 않은 네 번째 문자(E)를 지우고 결과에 추가한다.

범위난수종이결과
1–74A B C D E F G HC E



이후 1부터 6, 1부터 5와 같이 범위를 줄여가며 위 과정을 반복한다. 매번 선택된 문자를 지우고 결과에 추가하는 방식으로 진행하여, 모든 문자가 지워질 때까지 반복하면 무작위 순열이 완성된다.

범위난수종이결과
1–65A B C D E F G HC E G
1–53A B C D E F G HC E G D
1–44A B C D E F G HC E G D H
1–31A B C D E F G HC E G D H A
1–22A B C D E F G HC E G D H A F
A B C D E F G HC E G D H A F B



이와 같은 방식으로 숫자, 문자 등 다양한 대상을 섞을 수 있다.

4. 2. 현대적인 방법 (더스텐펠드)

리처드 더스텐펠드가 1964년에 발표한 피셔-예이츠 셔플의 현대적인 알고리즘은 컴퓨터에서 사용하기 위해 설계되었으며, 이전 버전과 달리 셔플 시간을 O(''n'')으로 단축시켰다. 이 알고리즘은 선택된 항목을 제거하고 다른 위치로 이동하는 대신, 아직 선택되지 않은 마지막 항목과 위치를 교환하는 방식으로 작동한다.

A부터 H까지의 문자를 예시로 들어 셔플 과정을 단계별로 살펴보면 다음과 같다.

범위난수종이결과
1–86A B C D E H GF
1–72A G C D E HB F
1–66A G C D EH B F
1–51E G C DA H B F
1–43E G DC A H B F
1–33E GD C A H B F
1–21GE D C A H B F



처음에는 1-8 범위에서 난수를 생성하여, 해당 위치의 문자와 마지막 문자를 교환한다. 예를 들어 난수가 6이면 6번째 문자인 F와 8번째 문자인 H를 교환한다. 다음 단계에서는 1-7 범위에서 난수를 생성하여, 해당 위치의 문자와 7번째 문자를 교환한다. 위 표에서는 난수 2가 생성되어, 2번째 문자인 B와 7번째 문자인 G가 교환되었다. 이러한 방식으로 마지막 단계까지 반복하여 최종 순열을 얻는다.

위의 예시와 동일한 과정을 숫자에도 적용할 수 있다. 1부터 8까지의 숫자를 사용하여 동일한 방식으로 셔플을 진행한다.

범위난수메모결과
1–861 2 3 4 5 8 76
1–721 7 3 4 5 82 6
1–661 7 3 4 58 2 6
1–515 7 3 41 8 2 6
1–435 7 43 1 8 2 6
1–335 74 3 1 8 2 6
1–2175 4 3 1 8 2 6



최종적으로 "7 5 4 3 1 8 2 6" 순열을 얻게 된다. 이처럼 더스텐펠드의 알고리즘은 각 단계에서 선택되지 않은 요소 중 마지막 요소와 현재 선택된 요소를 교환하는 간단한 과정을 통해 효율적인 셔플을 구현한다.

5. 구현 예시

피셔-예이츠 셔플은 파이썬자바스크립트 등 다양한 프로그래밍 언어로 구현할 수 있다.

다음은 파이썬을 사용한 피셔-예이츠 셔플의 구현 예시이다.

```python

import random

def shuffle(n: int) -> list[int]:

numbers = list(range(n))

shuffled = []

while numbers:

k = random.randint(0, len(numbers) - 1)

shuffled.append(numbers[k])

numbers.pop(k)

return shuffled

```

다음은 자바스크립트를 사용한 피셔-예이츠 셔플의 구현 예시이다.

```javascript

function shuffleArray(array) {

for (let i = array.length - 1; i >= 0; i--) {

const j = Math.floor(Math.random() * (i + 1));

[array[i], array[j]] = [array[j], array[i]];

}

}

6. 변형 알고리즘

6. 1. 병렬 알고리즘

피셔-예이츠 셔플을 기반으로 한 여러 병렬 셔플 알고리즘이 개발되었다.[11]

1990년, 앤더슨은 공유 메모리에 접근하는 소수의 프로세서를 가진 기계를 위한 병렬 버전을 개발했다.[11] 이 알고리즘은 하드웨어가 공정하게 작동하는 한 균일하게 임의의 순열을 생성한다.

2015년, 바처 외 연구진은 배열을 대략 같은 크기의 블록으로 나누고, 각 블록을 셔플하기 위해 피셔-예이츠 셔플을 사용한 다음, 셔플된 배열을 만들기 위해 무작위 병합을 재귀적으로 사용하는 MERGESHUFFLE 알고리즘을 개발했다.[12]

6. 2. k개의 요소 선택

''n''개의 원소 중 ''k'' ≤ ''n''개를 선택할 때 일반 셔플과 역 셔플을 비교할 수 있다. 일반 알고리즘은 입력 값으로 초기화된 ''n''개 항목의 배열이 필요하지만, 무작위로 ''k''개의 원소를 선택하기 위해 단지 ''k''번의 반복만 필요하다. 따라서 시간 복잡도는 ''O''(''k'')이고 공간 복잡도는 ''n''이다.

안에서 밖으로(inside-out) 알고리즘은 단지 ''k''개의 원소로 이루어진 배열 ''a''를 사용하여 구현할 수 있다. 이 알고리즘은 소스가 절차적으로 생성될 수 있고 공간을 차지하지 않는다고 가정하면, 출력을 최종적으로 만들기 위해 모든 ''n''번의 반복을 수행해야 하지만, 단지 ''k''개의 저장 공간만 필요하다. 일반 알고리즘과 비교하면 공간 및 시간 요구 사항이 반전된다.

일반 알고리즘은 미리 ''n''을 알아야 하지만 ''k''는 몰라도 된다. 반면 역 알고리즘은 미리 (상한선인) ''k''를 알아야 하지만 ''n''은 몰라도 된다.

7. 다른 셔플 알고리즘과의 비교

피셔-예이츠 셔플은 시간 및 공간 복잡도 면에서 최적이며, 양질의 편향되지 않은 난수 소스와 결합하면 편향되지 않은 결과를 생성하는 것이 보장된다.[13] 또한, 결과 순열의 일부만 필요한 경우 중간에 중단하거나, 반복적으로 중단 및 재시작하여 필요에 따라 순열을 점진적으로 생성할 수 있다는 장점이 있다.

다른 셔플 알고리즘과 비교했을 때, 각 요소를 무작위로 선택된 다른 요소와 바꾸는 단순한 방법(Naive method)은 편향된 결과를 생성한다.[13] 이는 서로 다른 순열의 수(n!)가 알고리즘의 무작위 결과의 수(n^n)를 균등하게 나누지 않기 때문이다. 예를 들어, 베르트랑의 가설에 따르면 n/2n 사이에 적어도 하나의 소수가 존재하며, 이 수는 n!을 나누지만 n^n을 나누지 않는다.

또 다른 방법은 셔플할 집합의 각 요소에 임의의 숫자를 할당한 다음, 할당된 숫자에 따라 집합을 정렬하는 것이다. 이 방법은 피셔-예이츠 셔플과 동일한 점근적 시간 복잡도를 가지며, 기수 정렬을 사용하면 ''O''(''n'') 시간에 효율적으로 정렬할 수 있다. 또한, 이 방법은 편향되지 않은 결과를 생성한다.[14] 그러나 할당된 임의 숫자가 중복되지 않도록 주의해야 하며,[32] 추가 저장 공간 ''O''(''n'')이 필요하다는 단점이 있다. 반면 피셔-예이츠 셔플은 ''O''(1)의 공간만을 사용한다. 정렬 방법은 병렬 알고리즘 구현이 가능하다는 장점이 있다.

사용자 지정 비교 함수로 정렬을 지원하는 언어에서, 임의의 값을 반환하는 비교 함수로 정렬하여 셔플하는 방법도 사용될 수 있다. 그러나 이 방법은 매우 비균일한 분포를 생성할 가능성이 높으며,[15][16][33][34][35] 사용된 정렬 알고리즘에 크게 의존한다.[36] 예를 들어, 퀵 정렬에서 첫 번째 피벗 요소를 고정된 요소로 사용하는 경우, 피벗 요소의 최종 위치는 이항 분포를 따르게 되어 시퀀스 중앙에 위치할 확률이 높아진다. 병합 정렬과 같은 다른 정렬 방법을 사용하더라도, 두 시퀀스를 선택할 확률이 균일하지 않으면 완전한 균일 분포를 얻기 어렵다.

8. 잠재적인 편향 발생 원인

피셔-예이츠 셔플을 구현할 때, 알고리즘 자체의 구현과 이를 기반으로 하는 난수 생성 모두에 주의를 기울여야 한다. 그렇지 않으면 결과에 감지 가능한 편향이 나타날 수 있다. 다음은 편향이 발생하는 대표적인 요인이다.

== 구현 오류 ==

피셔-예이츠 셔플을 구현할 때 흔히 저지르는 오류는 가져올 난수의 범위를 잘못 설정하는 것이다.[37] 잘못된 알고리즘이 제대로 작동하는 것처럼 보일 수 있지만, 모든 가능한 순열을 동일한 확률로 생성하지 않으며, 특정 순열을 전혀 생성하지 못할 수도 있다. 예를 들어, 교체할 항목의 인덱스 ''j''를 교체될 항목의 인덱스 ''i''보다 항상 엄격하게 작게 선택하는 오프 바이 원 오류가 발생하면 피셔-예이츠 셔플이 사톨로 알고리즘으로 바뀐다. 이 경우 얻을 수 있는 순열은 원순열뿐이며, 모든 요소는 원래 위치에 배치되지 않는다.[37]

마찬가지로, ''모든'' 반복에서 유효한 배열 인덱스의 전체 범위에서 항상 ''j''를 선택하는 것도 편향된 결과를 생성한다. 이는 ''n''''n''개의 서로 다른 가능한 교체 시퀀스가 생성되지만, ''n''개 요소 배열의 가능한 순열은 ''n''!개뿐이라는 사실에서 알 수 있다. ''n''''n''은 ''n''!로 균등하게 나눌 수 없으므로, 일부 순열은 다른 순열보다 더 많이 생성되어야 한다.

예를 들어, 3개 요소 배열 [1, 2, 3]을 섞었을 때 가능한 순열은 6개(3! = 6)이지만, 알고리즘은 27개(33 = 27)의 가능한 셔플을 생성한다. 이 경우, [1, 2, 3], [3, 1, 2], [3, 2, 1]은 각각 27개의 셔플 중 4개에서 생성되는 반면, 나머지 3개의 순열은 각각 27개의 셔플 중 5번 나타난다.[37]

길이가 7인 목록에서 각 요소가 다른 위치에 놓일 확률을 보여주는 행렬을 보면, 대부분의 요소에서 원래 위치(행렬의 주 대각선)에 있는 확률이 가장 낮고, 한 칸 뒤로 이동하는 확률이 가장 높다는 것을 알 수 있다.[37]

== 모듈로 편향 (Modulo bias) ==

대부분의 난수 생성기는 — 참이든 의사 난수이든 — 0에서 RAND_MAX까지의 고정된 범위의 숫자만 직접 제공하며, 일부 라이브러리에서는 RAND_MAX가 32767까지 낮을 수 있다.[18] 이러한 숫자를 원하는 범위로 강제하는 간단하고 일반적으로 사용되는 방법은 나머지 연산자를 적용하는 것이다.[19] 즉, 난수를 범위의 크기로 나누고 나머지를 구하는 것이다. 그러나 피셔-예이츠 셔플에서 0–1에서 0–''n''까지의 모든 범위에서 난수를 생성해야 하므로 이러한 범위 중 일부가 난수 생성기의 자연 범위를 균등하게 나누지 못할 가능성이 거의 확실하다. 따라서 나머지가 항상 균등하게 분포되지 않으며, 더 나쁜 것은 바이어스가 작은 나머지를 선호하는 방향으로 체계적으로 나타난다는 것이다.[20]

예를 들어, 난수 소스가 0에서 99까지의 숫자를 제공한다고 가정해 보자.(이는 100개의 값이다.) 그리고 0에서 15(16개 값)까지의 바이어스 없는 난수를 얻고 싶다고 가정해 보자. 단순히 숫자를 16으로 나누고 나머지를 구하면 0–3의 숫자가 다른 숫자보다 약 17% 더 자주 발생한다는 것을 알 수 있다. 이는 16이 100을 균등하게 나누지 않기 때문이다. 100보다 작거나 같은 16의 가장 큰 배수는 6×16 = 96이며, 바이어스를 유발하는 것은 불완전한 범위 96–99의 숫자이다. 이 문제를 해결하는 가장 간단한 방법은 나머지를 구하기 전에 해당 숫자를 버리고 적절한 범위의 숫자가 나올 때까지 다시 시도하는 것이다.[21] 원칙적으로 최악의 경우 영원히 걸릴 수 있지만, 재시도 기대값은 항상 1보다 작다.

관련된 문제는 먼저 임의의 부동 소수점 숫자를 생성한 다음 — 일반적으로 [0,1] 범위에서 — 원하는 범위의 크기를 곱하고 내림하는 구현에서도 발생한다. 여기에서 문제는 아무리 신중하게 생성하더라도 임의의 부동 소수점 숫자는 항상 유한한 정밀도만을 갖는다는 것이다. 즉, 주어진 범위 내에서 가능한 부동 소수점 값의 수가 유한하며, 범위를 이 숫자로 균등하게 나누지 않는 여러 세그먼트로 나누면 일부 세그먼트에 다른 세그먼트보다 더 많은 가능한 값이 생깁니다. 결과적인 바이어스는 체계적인 하향 추세를 보이지 않지만 여전히 존재한다.

== 의사 난수 생성기 (Pseudorandom generators) ==

피셔-예이츠 셔플을 의사 난수 생성기(PRNG)와 함께 사용할 때 몇 가지 문제가 발생할 수 있다. 생성기에 의해 출력되는 숫자 시퀀스는 시작 시점의 내부 상태에 의해 결정되므로, 생성기가 가질 수 있는 고유한 상태보다 더 많은 고유 순열을 생성할 수 없다.[23] PRNG의 상태 수는 순열 수보다 적어도 여러 자릿수 더 커야 편향을 최소화할 수 있다.

예를 들어, 많은 프로그래밍 언어 및 라이브러리의 내장 의사 난수 생성기는 32비트 내부 상태를 가지며, 이는 232개의 서로 다른 숫자 시퀀스만 생성할 수 있음을 의미한다. 이러한 생성기로 52장의 트럼프 카드 덱을 셔플하면, 가능한 52! ≈ 2225.6개의 순열 중 극히 일부만 생성할 수 있다.[24] 226비트 미만의 내부 상태를 가진 생성기는 52장의 카드 덱의 가능한 모든 순열을 생성할 수 없다.

어떤 의사 난수 생성기도 초기화할 수 있는 고유한 시드 값보다 더 많은 고유 시퀀스를 생성할 수 없다.[25] 1024비트 내부 상태를 가진 생성기라도 32비트 시드로 초기화되면 초기화 직후 232개의 서로 다른 순열만 생성할 수 있다. 초기화와 순열 생성 사이에 생성기를 여러 번 사용하면 더 많은 순열을 생성할 수 있지만, 이는 무작위성을 증가시키는 매우 비효율적인 방법이다.

선형 합동 PRNG를 사용하고 범위 축소에 나누기-나머지 방법을 사용할 때 추가적인 문제가 발생한다. 모듈로 2''e''를 갖는 선형 합동 PRNG의 하위 비트는 상위 비트보다 무작위성이 떨어진다.[6] 제수가 2의 거듭제곱일 때 나머지를 구하는 것은 상위 비트를 버리는 것을 의미하여 무작위성이 낮은 값을 얻게 된다. 이는 품질이 낮은 RNG 또는 PRNG가 품질이 낮은 셔플을 생성한다는 일반적인 규칙의 한 예이다.

8. 1. 구현 오류

피셔-예이츠 셔플을 구현할 때 흔히 저지르는 오류는 가져올 난수의 범위를 잘못 설정하는 것이다.[37] 잘못된 알고리즘이 제대로 작동하는 것처럼 보일 수 있지만, 모든 가능한 순열을 동일한 확률로 생성하지 않으며, 특정 순열을 전혀 생성하지 못할 수도 있다. 예를 들어, 교체할 항목의 인덱스 ''j''를 교체될 항목의 인덱스 ''i''보다 항상 엄격하게 작게 선택하는 오프 바이 원 오류가 발생하면 피셔-예이츠 셔플이 사톨로 알고리즘으로 바뀐다. 이 경우 얻을 수 있는 순열은 원순열뿐이며, 모든 요소는 원래 위치에 배치되지 않는다.[37]

thumb

thumb

마찬가지로, ''모든'' 반복에서 유효한 배열 인덱스의 전체 범위에서 항상 ''j''를 선택하는 것도 편향된 결과를 생성한다. 이는 ''n''''n''개의 서로 다른 가능한 교체 시퀀스가 생성되지만, ''n''개 요소 배열의 가능한 순열은 ''n''!개뿐이라는 사실에서 알 수 있다. ''n''''n''은 ''n''!로 균등하게 나눌 수 없으므로, 일부 순열은 다른 순열보다 더 많이 생성되어야 한다.

예를 들어, 3개 요소 배열 [1, 2, 3]을 섞었을 때 가능한 순열은 6개(3! = 6)이지만, 알고리즘은 27개(33 = 27)의 가능한 셔플을 생성한다. 이 경우, [1, 2, 3], [3, 1, 2], [3, 2, 1]은 각각 27개의 셔플 중 4개에서 생성되는 반면, 나머지 3개의 순열은 각각 27개의 셔플 중 5번 나타난다.[37]

길이가 7인 목록에서 각 요소가 다른 위치에 놓일 확률을 보여주는 행렬을 보면, 대부분의 요소에서 원래 위치(행렬의 주 대각선)에 있는 확률이 가장 낮고, 한 칸 뒤로 이동하는 확률이 가장 높다는 것을 알 수 있다.[37]

8. 2. 모듈로 편향 (Modulo bias)

대부분의 난수 생성기는 — 참이든 의사 난수이든 — 0에서 RAND_MAX까지의 고정된 범위의 숫자만 직접 제공하며, 일부 라이브러리에서는 RAND_MAX가 32767까지 낮을 수 있다.[18] 이러한 숫자를 원하는 범위로 강제하는 간단하고 일반적으로 사용되는 방법은 나머지 연산자를 적용하는 것이다.[19] 즉, 난수를 범위의 크기로 나누고 나머지를 구하는 것이다. 그러나 피셔-예이츠 셔플에서 0–1에서 0–''n''까지의 모든 범위에서 난수를 생성해야 하므로 이러한 범위 중 일부가 난수 생성기의 자연 범위를 균등하게 나누지 못할 가능성이 거의 확실하다. 따라서 나머지가 항상 균등하게 분포되지 않으며, 더 나쁜 것은 바이어스가 작은 나머지를 선호하는 방향으로 체계적으로 나타난다는 것이다.[20]

예를 들어, 난수 소스가 0에서 99까지의 숫자를 제공한다고 가정해 보자.(이는 100개의 값이다.) 그리고 0에서 15(16개 값)까지의 바이어스 없는 난수를 얻고 싶다고 가정해 보자. 단순히 숫자를 16으로 나누고 나머지를 구하면 0–3의 숫자가 다른 숫자보다 약 17% 더 자주 발생한다는 것을 알 수 있다. 이는 16이 100을 균등하게 나누지 않기 때문이다. 100보다 작거나 같은 16의 가장 큰 배수는 6×16 = 96이며, 바이어스를 유발하는 것은 불완전한 범위 96–99의 숫자이다. 이 문제를 해결하는 가장 간단한 방법은 나머지를 구하기 전에 해당 숫자를 버리고 적절한 범위의 숫자가 나올 때까지 다시 시도하는 것이다.[21] 원칙적으로 최악의 경우 영원히 걸릴 수 있지만, 재시도 기대값은 항상 1보다 작다.

관련된 문제는 먼저 임의의 부동 소수점 숫자를 생성한 다음 — 일반적으로 [0,1] 범위에서 — 원하는 범위의 크기를 곱하고 내림하는 구현에서도 발생한다. 여기에서 문제는 아무리 신중하게 생성하더라도 임의의 부동 소수점 숫자는 항상 유한한 정밀도만을 갖는다는 것이다. 즉, 주어진 범위 내에서 가능한 부동 소수점 값의 수가 유한하며, 범위를 이 숫자로 균등하게 나누지 않는 여러 세그먼트로 나누면 일부 세그먼트에 다른 세그먼트보다 더 많은 가능한 값이 생깁니다. 결과적인 바이어스는 체계적인 하향 추세를 보이지 않지만 여전히 존재한다.

8. 3. 의사 난수 생성기 (Pseudorandom generators)

피셔-예이츠 셔플을 의사 난수 생성기(PRNG)와 함께 사용할 때 몇 가지 문제가 발생할 수 있다. 생성기에 의해 출력되는 숫자 시퀀스는 시작 시점의 내부 상태에 의해 결정되므로, 생성기가 가질 수 있는 고유한 상태보다 더 많은 고유 순열을 생성할 수 없다.[23] PRNG의 상태 수는 순열 수보다 적어도 여러 자릿수 더 커야 편향을 최소화할 수 있다.

예를 들어, 많은 프로그래밍 언어 및 라이브러리의 내장 의사 난수 생성기는 32비트 내부 상태를 가지며, 이는 232개의 서로 다른 숫자 시퀀스만 생성할 수 있음을 의미한다. 이러한 생성기로 52장의 트럼프 카드 덱을 셔플하면, 가능한 52! ≈ 2225.6개의 순열 중 극히 일부만 생성할 수 있다.[24] 226비트 미만의 내부 상태를 가진 생성기는 52장의 카드 덱의 가능한 모든 순열을 생성할 수 없다.

어떤 의사 난수 생성기도 초기화할 수 있는 고유한 시드 값보다 더 많은 고유 시퀀스를 생성할 수 없다.[25] 1024비트 내부 상태를 가진 생성기라도 32비트 시드로 초기화되면 초기화 직후 232개의 서로 다른 순열만 생성할 수 있다. 초기화와 순열 생성 사이에 생성기를 여러 번 사용하면 더 많은 순열을 생성할 수 있지만, 이는 무작위성을 증가시키는 매우 비효율적인 방법이다.

선형 합동 PRNG를 사용하고 범위 축소에 나누기-나머지 방법을 사용할 때 추가적인 문제가 발생한다. 모듈로 2''e''를 갖는 선형 합동 PRNG의 하위 비트는 상위 비트보다 무작위성이 떨어진다.[6] 제수가 2의 거듭제곱일 때 나머지를 구하는 것은 상위 비트를 버리는 것을 의미하여 무작위성이 낮은 값을 얻게 된다. 이는 품질이 낮은 RNG 또는 PRNG가 품질이 낮은 셔플을 생성한다는 일반적인 규칙의 한 예이다.

참조

[1] 간행물 Fisher–Yates shuffle https://www.isa-afp.[...] 2016
[2] 웹사이트 Let's Do the Knuth Shuffle https://golangprojec[...] 2023-04-02
[3] 서적 Statistical tables for biological, agricultural and medical research Oliver & Boyd
[4] 간행물 Algorithm 235: Random permutation https://dl.acm.org/d[...] 1964-07
[5] 서적 Seminumerical algorithms Addison–Wesley
[6] 서적 Seminumerical algorithms Addison–Wesley
[7] 웹사이트 Fisher–Yates shuffle https://xlinux.nist.[...] National Institute of Standards and Technology 2005-12-19
[8] 간행물 Uninitialized Reads: Understanding the proposed revisions to the C language https://cacm.acm.org[...] 2017-04-01
[9] 간행물 An algorithm to generate a random cyclic permutation 1986-05-30
[10] 학회자료 Overview of Sattolo's Algorithm http://algo.inria.fr[...] 2004-06-21
[11] 서적 Proceedings of the second annual ACM symposium on Parallel algorithms and architectures - SPAA '90 1990
[12] 논문 MERGESHUFFLE: A Very Fast, Parallel Random Permutation Algorithm 2015
[13] 웹사이트 The Danger of Naïveté https://blog.codingh[...] 2007-12-07
[14] 웹사이트 Provably perfect shuffle algorithms http://okmij.org/ftp[...] 2001-09-03
[15] 웹사이트 A simple shuffle that proved not so simple after all http://szeryf.wordpr[...] 2007-06-19
[16] 웹사이트 Doing the Microsoft Shuffle: Algorithm Fail in Browser Ballot http://www.robweir.c[...] 2010-02-27
[17] 웹사이트 Writing a sort comparison function, redux https://devblogs.mic[...] 2009-05-08
[18] 웹사이트 The GNU C Library: ISO Random https://www.gnu.org/[...]
[19] 웹사이트 Uniformity of random numbers taken modulo N https://stackoverflo[...]
[20] 웹사이트 Efficiently Generating a Number in a Range https://www.pcg-rand[...] 2018-07-22
[21] 서적 C Programming FAQs: Frequently Asked Questions https://c-faq.com/li[...] Addison-Wesley 1995
[22] 간행물 Fast Random Integer Generation in an Interval 2019-02-23
[23] 서적 Generating Random Permutations (PhD Thesis) https://maths-people[...] Australian National University 2009
[24] 웹사이트 How can I shuffle the contents of an array? https://benpfaff.org[...]
[25] 웹사이트 Random Number Generator Recommendations for Applications - Shuffling https://peteroupc.gi[...]
[26] 서적 Statistical tables for biological, agricultural and medical research Oliver & Boyd
[27] 간행물 Algorithm 235: Random permutation 1964-07
[28] 서적 Seminumerical algorithms Addison-Wesley
[29] 서적 Seminumerical algorithms Addison-Wesley
[30] 웹사이트 Fisher-Yates shuffle http://www.nist.gov/[...] National Institute of Standards and Technology 2005-12-19
[31] 학회자료 Overview of Sattolo's Algorithm http://algo.inria.fr[...] 2004-06-21
[32] 웹사이트 Provably perfect shuffle algorithms http://okmij.org/ftp[...] 2001-09-03
[33] 웹사이트 A simple shuffle that proved not so simple after all https://szeryf.wordp[...] 2007-06-19
[34] 웹사이트 Doing the Microsoft Shuffle: Algorithm Fail in Browser Ballot http://www.robweir.c[...] 2010-02-27
[35] 웹사이트 MS、Webブラウザ選択画面にアルゴリズム上のバグか https://atmarkit.itm[...] 2010-03-01
[36] 웹사이트 Writing a sort comparison function, redux http://blogs.msdn.co[...] 2009-05-08
[37] 웹사이트 The Danger of Naïveté http://www.codinghor[...] 2007-12-07
[38] 서적 Statistical tables for biological, agricultural and medical research https://archive.org/[...]
[39] 문서 최신 버전의 알고리즘은 효율적이어서 섞는 항목 수에 비례하여 시간이 걸리고 [[제자리 알고리즘|제자리에서]] 섞는다.
[40] 저널 Algorithm 235: Random permutation 1964-07
[41] 서적 Seminumerical algorithms
[42] 서적 Seminumerical algorithms
[43] 웹인용 Fisher–Yates shuffle https://xlinux.nist.[...] National Institute of Standards and Technology 2005-12-19
[44] 저널 An algorithm to generate a random cyclic permutation 1986-05-30



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com