어떤 array에서 random한 i번째 element를 뽑고 싶다고 하자. 일반적으로 전체 배열의 size를 알아내고 rand () % size를 한 후 해당 index를 가져오는 방식을 택할 것이다. 그러나 모종의 이유로 전체 배열 size를 알 수 없는 상황에서도 무작위 추출을 할 수 있다. 예를 들어 array가 아니라 singly linked list여서 끝을 모르고, 단 1번 순회해야 한다는 조건이라면? 1번의 순회로 size를 알아내도 index에 접근하는 데 O(n)이 걸리기 때문에 해결할 수 없다. 이런 상황에서 Reservoir sampling을 사용한다.
Reservoir Sampling
정의
Reservoir sampling은 어떤 배열을 1번에 1개의 element만 보며 순회할 때 무작위 추출할 수 있는 알고리즘이다.
오직 1개의 값만 저장할 수 있는 공간을 만든다. 이 공간에 i번째 element는 $\frac{1}{i}$의 확률로 가져오고, $\frac{i-1}{i}$의 확률로 이전 element를 유지한다. 이 간단한 방법으로 단 1번 순회하면서 무작위 추출을 할 수 있다.
예를 들어...
1번째 순회에는 1번째 element를 1의 확률로 가져온다. 1번째 element를 가져오지 않을 확률은 없다.
2번째 순회에는 2번째 element를 1/2의 확률로 가져오고, 1/2의 확률로 원래 있던 element를 유지한다.(1번째 element)
3번째 순회에는 3번째 element를 1/3의 확률로 가져오고, 2/3의 확률로 원래 있던 element를 유지한다.(2번째 순회에서 살아남은 element)
...
n번째 순회에는 n번째 element를 1/n의 확률로 가져오고, $\frac{n-1}{n}$의 확률로 원래 있던 element를 유지한다.(n-1번째 순회에서 살아남은 element)
pseudo code : 1번의 순회, O(n)
pseudo code는 아래와 같다. singly linked list를 탐색한다는 조건이다.
// input : head vertex of singly linked list
// node has value element and next element
// node->value : value of each node
// node->next : next pointer of each node
procedure Reservoir(head)
count = 1
curNode = head // 순회에 사용할 vertex
curValue // 리턴값
while curNode != NULL do
if rand() % count == 0 then // k번째 vertex는 1/k의 확률로 남김
curValue = curNode->value
end if
curNode = curNode->next
count++
end while
return curValue
처음부터 끝까지 단 1번 순회하기 때문에 time complexity는 O(n)이며, 별다른 추가 공간을 사용하지 않기 때문에 space complex는 O(1)이다.
증명
n번째 element가 선택될 확률은 n번째 순회에서 n번째 element가 선택될 확률과 같으므로 1/n이다.
n-1번째 element가 선택될 확률은
- n-1번째 순회에서 n-1번째 element가 선택될 확률 $\frac{1}{n-1}$
- n번째 순회에서 n번째 element가 선택되지 않을 확률 $\frac{n-1}{n}$
- 이 둘의 곱이므로 $\frac{1}{n}$이다.
n-2번째가 선택될 확률은
- n-2번째 순회에서 n-2번째 element가 선택될 확률 $\frac{1}{n-2}$
- n-1번째 순회에서 n-1번째 element가 선택되지 않을 확률 $\frac{n-2}{n-1}$
- n번째 순회에서 n번째 element가 선택되지 않을 확률 $\frac{n-1}{n}$
- 이 셋의 곱이므로 $\frac{1}{n}$이다.
...
1번째가 선택될 확률은
- 1번째 순회에서 1번째 element가 선택될 확률 1
- 2번째 순회에서 2번째 element가 선택되지 않을 확률 1/2
- 3번째 순회에서 3번째 element가 선택되지 않을 확률 2/3
- ...
- n-1번째 순회에서 n-1번째 element가 선택될 확률 $\frac{n-2}{n-1}$
- n번째 순회에서 n번째 element가 선택되지 않을 확률 $\frac{n-1}{n}$
- 이들의 곱이므로 $\frac{1}{n}$이다.
수식으로 증명한다면
어떤 수열 $A = A_1, A_2, ... , A_n$이 있다고 하자. i번째 sampling 결과를 $S_i$라고 하자.
objective : Reservoir sampling을 따랐을 때 $A_1, A_2, ... , A_n$이 뽑힐 확률이 모두 $\frac{1}{n}$이다.
Reservoir sampling을 따르면 i번째 sampling 결과는
$S_i =
\begin{cases}
& A_i \text{ with p = 1/i } \\
& S_{i-1} \text{ otherwise }
\end{cases}$
이다. 이 때 $1 <= j <= n$인 어떤 j에 대해 $A_{j} = S_{n}$일 확률을 구하면 된다.
$P[A_j = S_n] \\
= P[S_n = S_{n-1} = S_{n-2} = ... = S_{j+1} = S_{j} = A_{j}] \\
= P[(S_{n} = S_{n-1}) \ \cap \ (S_{n-1} = S_{n-2}) \ \cap \ ... \ \cap \ (S_{j+1} = S_{j}) \ \cap \ (S_{j} = A_{j})] \\
= P[(S_{n} = S_{n-1})] \ \times \ P[(S_{n-1} = S_{n-2})] \ \times \ ... \ \times \ P[(S_{j+1} = S_{j})] \ \times \ P[(S_{j} = A_{j})] \\
= \frac{n-1}{n} \ \times \ \frac{n-2}{n} \ \times \ ... \ \times \ \frac{j}{j+1} \ \times \ \frac{1}{j} \\
= \frac{1}{n}$
1->2 : $A_j$가 $S_n$이 $S_n = S_{n-1}$이어야 하고, $S_{n-1} = S_{n-2}$, ... , $S_{j+1} = S{j}$여야 하며 $S_j = A_j$여야 한다.
2->3 : 각 확률의 intersect로 표현되기 때문에 바뀐다.
3->4 : 각 확률을 나타낸다.
정리하면 $\frac{1}{n}$이 된다.
즉, $1 <= j <= n$인 어떤 j에 대해 $A_{j} = S_{n}$가 성립한다. 따라서 array의 모든 index가 뽑힐 확률이 $\frac{1}{n}$이다.
Q.E.D.
'PS > Algorithm' 카테고리의 다른 글
Two Pointer, Sliding Window, Meet in the Middle ***TODO (0) | 2022.08.22 |
---|---|
누적 합 Prefix Sum (0) | 2022.07.10 |
동적 계획법 Dynamic Programming (+ LIS) (0) | 2022.07.09 |
좌표 압축 (0) | 2022.06.28 |
정렬 ***TODO (0) | 2022.06.27 |