2020-1ν•™κΈ°, λŒ€ν•™μ—μ„œ β€˜μ•Œκ³ λ¦¬μ¦˜β€™ μˆ˜μ—…μ„ λ“£κ³  κ³΅λΆ€ν•œ λ°”λ₯Ό μ •λ¦¬ν•œ κΈ€μž…λ‹ˆλ‹€. 지적은 μ–Έμ œλ‚˜ ν™˜μ˜μž…λ‹ˆλ‹€ :)

7 minute read

2020-1ν•™κΈ°, λŒ€ν•™μ—μ„œ β€˜μ•Œκ³ λ¦¬μ¦˜β€™ μˆ˜μ—…μ„ λ“£κ³  κ³΅λΆ€ν•œ λ°”λ₯Ό μ •λ¦¬ν•œ κΈ€μž…λ‹ˆλ‹€. 지적은 μ–Έμ œλ‚˜ ν™˜μ˜μž…λ‹ˆλ‹€ :)

<이뢄 탐색 Binary Search>. 이뢄 탐색은 인λ₯˜ μ§€μ„±μ˜ κ°€μž₯ 직관적이고 νƒμ›”ν•œ 도ꡬ닀! 정말 개인적인 의견으둠, 이 μ•Œκ³ λ¦¬μ¦˜μ΄ λ‚΄κ°€ 되고, λ‚΄κ°€ 이 μ•Œκ³ λ¦¬μ¦˜μ΄ λ˜λŠ” <λͺ°μ•„일체 η‰©ζˆ‘δΈ€ι«”> μˆ˜μ€€μ— 이λ₯΄κΈ° μœ„ν•΄ 정진해야 ν•  μ •λ„λ‘œ β€œμΈμƒβ€μ—μ„œ μ€‘μš”ν•œ μ•Œκ³ λ¦¬μ¦˜μ΄λΌκ³  μƒκ°ν•œλ‹€.

μ•„μ΄λ””μ–΄λŠ” κ°„λ‹¨ν•˜λ‹€. β€œμ ˆλ°˜μœΌλ‘œ 쀄이며 νƒμƒ‰ν•˜κΈ°β€. λ‚˜λŠ” 이걸 <관심 μ˜μ—­ Interest Zone>을 쳐낸닀고 μ •μ˜ν–ˆλ‹€. μΌμ’…μ˜ <가지 치기 Pruning>라고 μƒκ°ν•œλ‹€λŠ” 말이닀. 🌴

<λΆ„ν•  정볡>κ³ΌλŠ” μ•½κ°„ μŠ€νƒ€μΌμ΄ λ‹€λ₯΄λ‹€κ³ λ„ μƒκ°ν•˜λŠ”λ°, λΆ„ν•  정볡은 문제λ₯Ό λΆ„ν• ν•˜κ³  이후에 베이슀 μΌ€μ΄μŠ€λΆ€ν„° λ‹€μ‹œ μŒ“μ•„μ˜¬λ €μ•Ό ν•˜μ§€λ§Œ, <이뢄 탐색>은 μŒ“μ•„μ˜¬λ¦¬λŠ” κ³Όμ • 없이 λΆ„ν• λ§Œ μˆ˜ν–‰ν•œλ‹€.


포슀트의 λͺ©μ μ΄ μˆ˜μ—… λ‚΄μš© μ •λ¦¬λ‹ˆ 일단 정리λ₯Ό ν•΄λ³΄μž!

Goal: Find a key $k$ in sorted order array.

λ¨Όμ € target $k$λ₯Ό λ°°μ—΄μ˜ κ°€μš΄λŽƒ κ°’κ³Ό λΉ„κ΅ν•œλ‹€. 비ꡐ 결과에 따라 쒌츑 μ ˆλ°˜μ„ μ·¨ν•˜κ±°λ‚˜ 우츑 μ ˆλ°˜μ„ μ·¨ν•œλ‹€. 이후 κ·Έ μ ˆλ°˜μ— λŒ€ν•΄μ„œ λ™μΌν•œ 과정을 λ°˜λ³΅ν•œλ‹€!

<λΆ„ν•  정볡>의 κ΄€μ μ—μ„œ λ³Ό λ•Œ, 단 ν•˜λ‚˜μ˜ subproblem이 λ°œμƒν•˜κΈ° λ•Œλ¬Έμ— $a=1$이닀. λ”°λΌμ„œ

\[T(n) = T(n/2) + O(1)\]

<Master Theorem>을 μ μš©ν•˜μž. $a=1$, $b=2$, $c=0$μ΄λ―€λ‘œ $a/b^c = 1$이닀. λ”°λΌμ„œ

\[O(1) \cdot \log n = O(\log n)\]

$\blacksquare$


λ­”κ°€ μ΄λŒ€λ‘œ 포슀트λ₯Ό 끝내기엔 μ•„μ‰¬μ›Œμ„œ PSμ—μ„œ ν”νžˆ ν•˜λŠ” μ‹€μˆ˜ 쀑 ν•˜λ‚˜μ— λŒ€ν•΄ 말해보렀고 ν•œλ‹€.

Off-by-One

β€œThis problem could arise when a programmer makes mistakes such as using β€œis less than or equal to” where β€œis less than” should have been used in a comparison.” - Wikipedia

이뢄 탐색을 ν¬ν•¨ν•œ, λŒ€λΆ€λΆ„μ˜ λΆ„ν•  정볡 λ¬Έμ œμ—μ„œ β€œOff-be-One”은 정말 개발자λ₯Ό ν™”λ‚˜κ²Œ ν•˜λŠ” μ‹€μˆ˜λ‹€!! ν”νžˆ, ν•˜λ‚˜λ₯Ό 덜 ν¬ν•¨ν•˜κ±°λ‚˜ 더 ν¬ν•¨ν•΄μ„œ μƒκΈ°λŠ” μ‹€μˆ˜λ‹€ 😱😱

λΆ„ν•  μ •λ³΅μ˜ 경우, μœ λ… ceil $\lceil x \rceil$을 취할지, floor $\lfloor x \rfloor$λ₯Ό 취할지 ν—·κ°ˆλ¦¬λŠ” κ²½μš°κ°€ λ§Žλ‹€. λ˜λŠ”, iteratoring κ³Όμ •μ—μ„œ arr[0]을 ν¬ν•¨ν• μ§€λ‚˜ arr[last+1]을 포함해야 ν•˜λŠ”μ§€ λ“±λ“± 정말 λ§Žμ€ λΆ€λΆ„μ—μ„œ κ·Έ β€œν•˜λ‚˜β€ λ•Œλ¬Έμ— FAIL을 λ°›κ²Œ λ§Œλ“ λ‹€.

본인이 μƒκ°ν•˜λŠ” 이 μ‹€μˆ˜λ₯Ό μ€„μ΄λŠ” 방법은 κ²°κ΅­, <자기 μ˜μ‹¬ self-doubting>이닀. λΆ„ν•  μ •λ³΅μœΌλ‘œ μ ‘κ·Όν•  λ•Œ, if문의 쑰건을 μ œλŒ€λ‘œ μ“΄ 건지, μžμ‹ μ„ μ˜μ‹¬ν•˜λŠ” κ±°λ‹€. μ‰½κ²Œ λ§ν•˜λ©΄ λŒ€μΆ© λ„˜μ–΄κ°€μ§€ 말고 꼼꼼히 λ΄λΌλŠ” 말이닀.


μ΅œκ·Όμ— Off-by-One λ•Œλ¬Έμ— μ‹œλ‹¬λ¦° λ¬Έμ œκ°€ μžˆμ–΄μ„œ 링크λ₯Ό κ±Έμ–΄λ‘”λ‹€.


Binary Search - Code

Binary Searchλ₯Ό κ΅¬ν˜„ν•˜λŠ” μŠ€νƒ€μΌμ€ μ—¬λŸ¬ 가지가 μžˆλ‹€. 그리고 κ·Έ κ΅¬ν˜„ μ—­μ‹œ Binary Searchμ—μ„œ μš”κ΅¬ν•˜λŠ” λ””ν…ŒμΌμ— λ”°λΌμ„œ if문의 쑰건이 μ•½κ°„μ”© 변경될 μˆ˜λ„ μžˆλ‹€.

Binary Searchλ₯Ό κ΅¬ν˜„ν•˜λŠ” ꢁ극의 ν…œν”Œλ¦Ώμ΄ μžˆλŠ” 건 μ•„λ‹ˆλΌκ³  μƒκ°ν•œλ‹€. 그리고 λͺ¨λ“  μΌ€μ΄μŠ€μ— λŒ€μ‘ν•˜λŠ” Binary Search κ΅¬ν˜„μ„ μ™Έμš°κΈ°λŠ” λΆˆκ°€λŠ₯ν•œλ°, λ¬Έμ œμ—μ„œ μš”κ΅¬ν•˜λŠ” κ²½μš°κ°€ μ›Œλ‚™ λ‹€μ–‘ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

λ‹€λ§Œ, Binary Search의 κΈ°λ³Έ μ›ν˜• μ •λ„λŠ” μ•Œμ•„λ‘λ©΄ μ’‹λ‹€.

Binary Search (Basic)

function binary_search(A, n, T) is
  L := 0
  R := n βˆ’ 1
  while L <= R do
    m := floor((L + R) / 2)
    if A[m] < T then
      L := m + 1
    else if A[m] > T then
      R := m βˆ’ 1
    else:
      return m
  return unsuccessful

μœ„ μ½”λ“œλŠ” β€œμ •λ ¬λœ λ°°μ—΄ $A$μ—μ„œ target $T$의 indexλ₯Ό μ°ΎλŠ”λ‹€β€λŠ” Binary Search의 본래 λͺ©μ μ„ μΆ©μ‹€νžˆ μˆ˜ν–‰ν•˜λŠ” μ½”λ“œλ‹€. ν•˜μ§€λ§Œ, 배열에 μ€‘λ³΅λœ μ›μ†Œκ°€ μžˆμ–΄ Target $T$의 leftmost index, rightmost indexλ₯Ό μ°ΎλŠ” 것은 λΆˆκ°€λŠ₯ν•˜λ‹€. 이λ₯Ό μˆ˜ν–‰ν•˜λ €λ©΄ μ½”λ“œλ₯Ό 많이 μˆ˜μ •ν•΄μ•Ό ν•œλ‹€.

Binary Search (leftmost)

function binary_search_leftmost(A, n, T):
  L := 0
  R := n
  while L < R:
    m := floor((L + R) / 2)
    if A[m] < T:
      L := m + 1
    else:
      R := m
  return L

λ§Žμ€ 뢀뢄이 λ°”λ€Œμ—ˆλ‹€. 첫 μ‹œμž‘μ—μ„œ $R$은 $n-1$이 μ•„λ‹Œ $n$λΆ€ν„° μ‹œμž‘ν•˜κ²Œ λ˜μ—ˆκ³ , return κ°’ μ—­μ‹œ $L$이 λ˜μ—ˆλ‹€.

μ½”λ“œλ₯Ό κ²€μ¦ν•˜κΈ° μœ„ν•΄ 상황을 κ°€μ •ν•΄λ³΄μž.

A = [1, 2, 3, 4, 4, 4, 6, 6, 7, 8] (n = 10)

1. $T=-1$

λ°°μ—΄ $A$에 $-1$의 값은 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€. μœ„ μ½”λ“œλŠ” μ–΄λ–€ 값을 λ±‰κ²Œ 될까? 정닡은 $0$이닀. 항상 A[m] >= T이기 λ•Œλ¬Έμ— μ’…κ΅­μ—λŠ” μ΄ˆκΈ°μ— μ„€μ •ν•œ $L$이 return λœλ‹€. κ·Έ 값은 $0$이닀.

2. $T=10$

λ§ˆμ°¬κ°€μ§€λ‘œ λ°°μ—΄ $A$에 $10$의 값은 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€. 이 경우 μ–΄λ–€ 값을 λ±‰κ²Œ 될까? 정닡은 $10$이닀. 항상 A[m] < T이기 λ•Œλ¬Έμ— μ’…κ΅­μ—λŠ” $L=10$, $R=10$이 되고, $L=10$이 return λœλ‹€.

3. $T=5$

λ°°μ—΄ $A$에 $5$의 값은 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€. 이 경우 μ–΄λ–€ 값을 λ±‰κ²Œ 될까? 정닡은 $5$ (A[5]=4)이닀. μ΄λ•Œ, $5$λΌλŠ” 값은 query $T=5$의 <rank>라고 ν•œλ‹€. μ—¬κΈ°μ„œ rankλŠ” β€œ# of smaller eltsβ€λΌλŠ” 의미둜 μ“°μ˜€λ‹€. 즉, leftmostλ₯Ό μ°ΎλŠ” μœ„μ˜ μ½”λ“œλ₯Ό μ΄μš©ν•΄μ„œ λ°°μ—΄ $A$ λ‚΄μ—μ„œ query의 β€œrank”와 predecessor/successor 등등을 찾을 수 μžˆλ‹€. Wikipediaμ—μ„œλŠ” 이것을 β€œApproximate Matchesβ€œλΌλŠ” ν•­λͺ©μ—μ„œ μ†Œκ°œν•˜κ³  μžˆλ‹€.

Approximate Matches

<Approximate Matches>λŠ” 이진 탐색을 μ‘μš©ν•˜λŠ” κ°€μž₯ λŒ€ν‘œμ μΈ 방법이닀! 그리고 이진 탐색을 λ‹¨μˆœνžˆ <탐색> κΈ°λ²•μœΌλ‘œλ§Œ μ‚¬μš©ν•˜λŠ” 것은 큰 손해닀! 😒

이진 탐색을 잘 μ‚¬μš©ν•˜λ©΄, μ•„λž˜μ™€ 같은 쒋은 정보듀을 찾을 수 μžˆλ‹€.

  • rank: the number of smaller elements
  • predecessor: next-smallest element
  • successor: next-largest element
  • nearest neighbor
  • range queries: seeking the number of elts btw two values

μ•žμ—μ„œλ„ μ‚΄νŽ΄λ΄€λ“―μ΄ <rank>λŠ” β€œleftmost”λ₯Ό μ°ΎλŠ” μ•Œκ³ λ¦¬μ¦˜μ„ 톡해 μ‰½κ²Œ ꡬ할 수 μžˆλ‹€.

λ°˜λŒ€λ‘œ <successor> queryλŠ” β€œrightmost”λ₯Ό μ°ΎλŠ” μ•Œκ³ λ¦¬μ¦˜μ„ 톡해 μ‰½κ²Œ ꡬ할 수 μžˆλ‹€!

<range query>λŠ” 각 query $a \le b$에 λŒ€ν•΄μ„œ $a$의 β€œleftmost”와 $b$의 β€œrightmost”λ₯Ό λ‘˜λ‹€ 찾음으둜써 ꡬ할 수 μžˆλ‹€. μ•žμ—μ„œ μ†Œκ°œν•œ [BAEKJOON] 10816번 - 숫자 μΉ΄λ“œ2 λ¬Έμ œκ°€ λ°”λ‘œ $a=b$ μƒν™©μ—μ„œμ˜ range queryλ₯Ό μˆ˜ν–‰ν•˜λŠ” λ¬Έμ œλ‹€!

Categories:

Updated: