<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>blog.mskim.org</title>
    <subtitle><![CDATA[Minseo Kim's Blog]]></subtitle>
    <link href="https://blog.mskim.org/atom.xml" rel="self" />
    <link href="https://blog.mskim.org" />
    <id>https://blog.mskim.org/atom.xml</id>
    <author>
        <name>Minseo Kim</name>
        
        <email>minseo@mskim.org</email>
        
    </author>
    <updated>2026-01-20T14:30:00+09:00</updated>
    <entry>
    <title>Haskell 토막글: Fibonacci 함수 구현 및 성능 개선</title>
    <link href="https://blog.mskim.org/posts/11/haskell-fibonacci" />
    <id>https://blog.mskim.org/posts/11/haskell-fibonacci</id>
    <published>2024-10-17T17:00:00+09:00</published>
    <updated>2026-01-20T14:30:00+09:00</updated>
    <summary type="html"><![CDATA[<article>
  <section class="header">
    <span class="date">작성: <time datetime="2024-10-17T17:00:00+09:00">2024-10-17</time></span>
    
    /
    <span class="lastmod">수정: <time datetime="2026-01-20T14:30:00+09:00">2026-01-20</time></span>
    
  </section>
  <section class="article-body">
    <h2 id="무한-리스트로-작성한-피보나치-수열">무한 리스트로 작성한 피보나치 수열</h2>
<p>다음은 Haskell로 정의한 피보나치 수열인데, Haskell의 lazy evaluation을 이해하기
딱 좋다. 코드를 살펴보자.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ot">fibs ::</span> [<span class="dt">Int</span>]</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>fibs <span class="ot">=</span> <span class="dv">0</span> <span class="op">:</span> <span class="dv">1</span> <span class="op">:</span> <span class="fu">zipWith</span> (<span class="op">+</span>) fibs (<span class="fu">drop</span> <span class="dv">1</span> fibs)</span></code></pre></div>
<p><code>fibs</code>는 무한 리스트<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>로 정의되는 피보나치 수열이다.<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a> <code>fibs</code>의 0, 1 번째
원소는 이미 평가가 완료된 상태(fully evaluated)이며, 2 번째 이후의 원소는 아직
평가되지 않았다. 이 평가는 해당 값이 필요해지는 시점에 시작된다. 이처럼 값이
필요할 때까지 평가를 미루는 전략을 <strong>지연 평가(lazy evaluation)</strong>라고 한다.</p>
<p>지연 평가를 채택하면 필요한 시점에만 계산이 수행되며, 필요하지 않은 값은
평가되지 않는다. 이처럼 Haskell은 지연 평가 전략을 채택한 함수형 언어다. 반면,
OCaml과 같이 즉시 평가(eager evaluation)을 기본으로 채택한 함수형 언어도
존재한다.</p>
<h2 id="지연-평가-vs.-즉시-평가">지연 평가 vs. 즉시 평가</h2>
<p>지연 평가, 즉시 평가 각각 장단점이 있다. 지연 평가를 사용하면 프로그래머가 계산
순서를 명시적으로 관리할 필요가 없으며, 필요하지 않은 부분은 계산하지 않기
때문에 불필요한 연산을 줄이고 무한한 크기의 자료구조를 쉽게 추상화하여 다룰 수
있다.</p>
<p>다만, 지연 평가는 프로그래머 입장에서 성능을 예상하는게 조금은 난해할 수 있다는
단점이 있다. 메모리 사용 패턴을 제대로 이해해서 설계하지 못한다면 계산이 지연된
표현식들이 폭발적으로 쌓여서 메모리 점유 및 성능상 문제를 일으킨다.</p>
<p>이와 비교해서 즉시 평가는 프로그래머가 계산 순서를 명확히 이해하고 제어할 수
있으므로 최적화가 용이하고 성능에 깊이 개입할 수 있다는 장점이 있다.</p>
<h2 id="비효율적인-메모리-사용으로-인한-성능-저하">비효율적인 메모리 사용으로 인한 성능 저하</h2>
<p>n 번째 피보나치 수를 계산하는 함수를 작성해보자. 아까 작성한 피보나치 수열을
활용하면 다음과 같이 naive하게 구현할 수 있다.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ot">fib ::</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">Int</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>fib n <span class="ot">=</span> fibs <span class="op">!!</span> n</span></code></pre></div>
<p>우리는 n 번째 값만 구하면 되지만, <code>fibs</code>의 정의상 1 부터 n-1 까지의 모든 원소를
적어도 한 번씩은 계산해야 한다. 다행히 우리가 정의한 피보나치 함수는 <code>fibs</code>
리스트를 활용하여 일종의 메모이제이션을 달성하기 때문에 <span class="math inline">\(\text{O}(n)\)</span>의 시간
복잡도를 갖는다.</p>
<p>그러나, 구하고자 하는 표현식인 n 번째 피보나치 수가 최종적으로 평가 완료될
때까지 이들 중간값이 모두 살아있어야 하는 것은 아니다. 중간값들은 리스트의
원소이기도 하기 때문에 garbage collect되지 않고, 결국 <span class="math inline">\(\text{O}(n)\)</span>의 공간
복잡도를 가진다. 따라서, n이 커지면 cache miss<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>의 영향으로 성능이 저하된다.</p>
<h2 id="지연-평가로-인한-성능-저하">지연 평가로 인한 성능 저하</h2>
<p>다음과 같은 코드를 생각해볼 수 있다.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ot">fib ::</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">Int</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>fib n <span class="ot">=</span> fib&#39; n <span class="dv">0</span> <span class="dv">1</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>  <span class="kw">where</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>    fib&#39; <span class="dv">0</span> a _ <span class="ot">=</span> a</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>    fib&#39; k a b <span class="ot">=</span> fib&#39; (k <span class="op">-</span> <span class="dv">1</span>) b (a <span class="op">+</span> b)</span></code></pre></div>
<p>앞선 예시와 다르게 필요한 중간값들이 모두 함수 인자로 제공되므로 수명이 다한
중간값들이 garbage collect되지 않는 문제는 피할 수 있을 것처럼 보인다.</p>
<p>그러나, 이번에도 n이 커지면 느려진다. 프로파일링 해보면 앞선 예시와 램 사용량,
실행시간 모두 비슷하게 나온다. 이번 예시 또한 <span class="math inline">\(\text{O}(n)\)</span>의 공간 복잡도를
갖는 것은 아닐까?</p>
<p>원인은 함수의 인자인 a, b<a href="#fn4" class="footnote-ref" id="fnref4" role="doc-noteref"><sup>4</sup></a>가 지연 평가되기 때문이다. 아직 평가되지 않은
표현식이 매우 깊은 트리<a href="#fn5" class="footnote-ref" id="fnref5" role="doc-noteref"><sup>5</sup></a>의 형태로 나타난다. 예를 들어, 다음과 같다.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>fib&#39; <span class="dv">1000000</span> <span class="dv">0</span> <span class="dv">1</span> <span class="op">--&gt;</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>fib&#39; <span class="dv">999999</span> <span class="dv">1</span> (<span class="dv">0</span> <span class="op">+</span> <span class="dv">1</span>) <span class="op">--&gt;</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>fib&#39; <span class="dv">999998</span> (<span class="dv">0</span> <span class="op">+</span> <span class="dv">1</span>) (<span class="dv">1</span> <span class="op">+</span> (<span class="dv">0</span> <span class="op">+</span> <span class="dv">1</span>)) <span class="op">--&gt;</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>fib&#39; <span class="dv">999997</span> (<span class="dv">1</span> <span class="op">+</span> (<span class="dv">0</span> <span class="op">+</span> <span class="dv">1</span>)) ((<span class="dv">0</span> <span class="op">+</span> <span class="dv">1</span>) <span class="op">+</span> (<span class="dv">1</span> <span class="op">+</span> (<span class="dv">0</span> <span class="op">+</span> <span class="dv">1</span>))) <span class="op">--&gt;</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>fib&#39; <span class="dv">999996</span> ((<span class="dv">0</span> <span class="op">+</span> <span class="dv">1</span>) <span class="op">+</span> (<span class="dv">1</span> <span class="op">+</span> (<span class="dv">0</span> <span class="op">+</span> <span class="dv">1</span>))) ((<span class="dv">1</span> <span class="op">+</span> (<span class="dv">0</span> <span class="op">+</span> <span class="dv">1</span>)) <span class="op">+</span> ((<span class="dv">0</span> <span class="op">+</span> <span class="dv">1</span>) <span class="op">+</span> (<span class="dv">1</span> <span class="op">+</span> (<span class="dv">0</span> <span class="op">+</span> <span class="dv">1</span>)))) <span class="op">--&gt;</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="op">...</span></span></code></pre></div>
<p>결국 <span class="math inline">\(\text{O}(n)\)</span>의 공간 복잡도를 갖는다. 언뜻 보면 <span class="math inline">\(\text{O}(2^n)\)</span>처럼 보일
수 있는데, 각 중간 표현식들은 일종의 그래프와 같은 형태를 취하고 있어서 공통된
하위 표현식이 하나의 thunk와 같다. 예를 들어, <code>fib' b (a + b)</code>에서 첫 번째
인수의 b, 두 번째 인수의 b가 모두 같은 thunk다. 따라서 공통된 하위 표현식들은
계산이 완료된 값을 서로 공유한다.</p>
<h2 id="엄격strict한-인수-평가">엄격(Strict)한 인수 평가</h2>
<p>다음과 같이 인수가 strict하게 평가되도록 강제할 수 있다. Strict한 인수는
지연 평가되지 않고 즉시 평가된다.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ot">fib ::</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">Int</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>fib n <span class="ot">=</span> fib&#39; n <span class="dv">0</span> <span class="dv">1</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>  <span class="kw">where</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    fib&#39; <span class="dv">0</span> <span class="op">!</span>a <span class="op">!</span>_ <span class="ot">=</span> a</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>    fib&#39; k <span class="op">!</span>a <span class="op">!</span>b <span class="ot">=</span> fib&#39; (k <span class="op">-</span> <span class="dv">1</span>) b (a <span class="op">+</span> b)</span></code></pre></div>
<p>더이상 인수가 지연 평가되지 않고, 다음과 같이 즉시 평가된다.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>fib&#39; <span class="dv">1000000</span> <span class="dv">0</span> <span class="dv">1</span> <span class="op">--&gt;</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>fib&#39; <span class="dv">999999</span> <span class="dv">1</span> <span class="dv">1</span> <span class="op">--&gt;</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>fib&#39; <span class="dv">999998</span> <span class="dv">1</span> <span class="dv">2</span> <span class="op">--&gt;</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>fib&#39; <span class="dv">999997</span> <span class="dv">2</span> <span class="dv">3</span> <span class="op">--&gt;</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>fib&#39; <span class="dv">999996</span> <span class="dv">3</span> <span class="dv">5</span> <span class="op">--&gt;</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="op">...</span></span></code></pre></div>
<p>이번 예시는 메모리 사용량이 상수에 해당하며, C++로 작성한 동등한 알고리즘의
코드와 비교하여 메모리는 더 많이 쓰지만 계산 속도는 더 빨랐다. 벤치마크 결과를
다음 링크에서 확인할 수 있다.
<a href="https://github.com/kimminss0/haskell-playground/tree/main/fibonacci/benchmark">벤치마크 결과</a></p>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>무한 리스트임을 보이자. (one-based index로) 첫 번째, 두 번째 원소의 값이
각각 0, 1임은 자명하다. n (n&gt;1)번째 원소는 <code>zipWith</code> 함수로부터 귀납적으로
정의된다. <code>fibs</code>가 0, 1, …이고, <code>drop 1 fibs</code>가 1, …인데, <code>zipWith (+)</code>
함수는 이 둘을 0+1, 1+?, …와 같은 형태로 합친다. 따라서 최소한 세 번째
원소는 존재하며, 그 값은 0+1로 정의됨을 알 수 있다. 이처럼 모든 자연수 n에
대하여 n 번째 원소가 존재하면 n+1 번째 원소가 존재함을 보일 수 있다.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p><code>fibs</code>를 수열 <span class="math inline">\(a_n\)</span>으로 두고 생각해보자. <span class="math inline">\(a_1 = 0, a_2 = 1\)</span>임은 정의
<code>0 : 1 : (...)</code>로부터 알 수 있다. <span class="math inline">\(a_n\:(n&gt;2)\)</span>부터는 귀납적으로 정의되는데,
수열 <span class="math inline">\(a&#39;_n = a_{n+2}\)</span>로 정의해보자. <span class="math inline">\(a&#39;_n = a_n + a_{n+1}\)</span>은 위 코드에서
<code>zipWith (+) fibs (drop 1 fibs)</code>에 대응된다. <span class="math inline">\(a&#39;_n\)</span>의 정의로부터
<span class="math inline">\(a_{n+2} = a_n + a_{n+1}\)</span>임이 도출되고, 이는 피보나치 수열의 점화식이다.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p>연속된 메모리 공간에 순차적으로 쓰기를 한다고 가정하면 캐시 용량 초과로 인해
write miss와 eviction이 반복되며 병목이 발생한다. 다만, GHC가 컴파일한
코드가 런타임에 실제로 메모리를 연속적으로 할당한다는 보장은 없다. GHC가
내부적으로 연속된 쓰기를 어떻게 최적화하는지는 잘 모르겠다.<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn4"><p>k는 지연 평가되지 않는다. 패턴 매칭 과정에서 값이 즉시 평가된다.<a href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn5"><p>모든 표현식은 일종의 트리로 표현 가능하다.<a href="#fnref5" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
  </section>
</article>
]]></summary>
</entry>
<entry>
    <title>Haskell 토막글: 연산 우선순위와 결합 방향</title>
    <link href="https://blog.mskim.org/posts/10/haskell-precedence-and-associativity" />
    <id>https://blog.mskim.org/posts/10/haskell-precedence-and-associativity</id>
    <published>2024-10-12T11:48:00+09:00</published>
    <updated>2024-10-12T02:48:00Z</updated>
    <summary type="html"><![CDATA[<article>
  <section class="header">
    <span class="date">작성: <time datetime="2024-10-12T11:48:00+09:00">2024-10-12</time></span>
    
  </section>
  <section class="article-body">
    <blockquote>
<p><strong>참고</strong>: 영어로 연산 우선순위는 precedence, 결합 방향은 associativity다.</p>
</blockquote>
<p>Haskell에서는 <strong>function application</strong>과 <strong>operation application</strong>을 구분한다.
Function application의 예시로는 <code>show 123</code>이 있고, operation application의
예시로는 <code>1 + 2</code>, <code>3 `div` 2</code> 등이 있다.</p>
<p>결합 우선순위는 function application이 operation application보다 항상 높다.
다음은 간단한 예시다.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="fu">show</span> <span class="dv">1</span> <span class="op">+</span> <span class="dv">3</span>    <span class="co">-- Error; (show 1) + 3</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="fu">show</span> (<span class="dv">1</span> <span class="op">+</span> <span class="dv">3</span>)  <span class="co">-- Ok</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="fu">show</span> <span class="op">$</span> <span class="dv">1</span> <span class="op">+</span> <span class="dv">3</span>  <span class="co">-- Ok, ($) is an operator; show $ (1 + 3)</span></span></code></pre></div>
<p>같은 우선순위 내에서는 <strong>결합 방향(associativity)</strong>에 따라 괄호를 묶는다.
Haskell에서는 결합 방향을 왼쪽(left associative), 오른쪽(right associative),
정의되지 않음(non-associative)의 3가지 경우로 나눌 수 있다.</p>
<p>Function application은 모두 left associative다. 예를 들어, <code>f1 f2 f3</code>은
<code>((f1 f2) f3)</code>와 같이 왼쪽부터 괄호로 묶인다.</p>
<p>Operation application은 결합 방향을 사용자 정의할 수 있다. 연산자의 결합 방향은
연산이 두 개 이상이어서 <code class="sourceCode haskell">a <span class="ot">`op1`</span> b <span class="ot">`op2`</span> c</code>와 같은 형태일 때 괄호를
치는 방법을 생각하면 결합 방향을 이해할 수 있다. 결합 방향은 <code>infixl</code>,
<code>infixr</code>, <code>infix</code> 세 가지로 정의할 수 있다.</p>
<p><code>infixl</code>의 예시로 <code>+</code>, <code>-</code>, <code>/</code>, <code>*</code>와 같은 산술 연산자를 생각해볼 수 있다.
다음 예시를 살펴보자.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="dv">1</span> <span class="op">+</span> <span class="dv">2</span> <span class="op">-</span> <span class="dv">3</span> <span class="op">-</span> <span class="dv">4</span> <span class="op">==</span> (((<span class="dv">1</span> <span class="op">+</span> <span class="dv">2</span>) <span class="op">-</span> <span class="dv">3</span>) <span class="op">-</span> <span class="dv">4</span>)</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="dv">1</span> <span class="op">*</span> <span class="dv">2</span> <span class="op">*</span> <span class="dv">3</span> <span class="op">*</span> <span class="dv">4</span> <span class="op">==</span> (((<span class="dv">1</span> <span class="op">*</span> <span class="dv">2</span>) <span class="op">*</span> <span class="dv">3</span>) <span class="op">*</span> <span class="dv">4</span>)</span></code></pre></div>
<p>모두 왼쪽부터 괄호를 치는 것을 알 수 있다. 위 예시는 결합 우선순위가 모두 같은
경우였다. 결합 우선순위가 다른 경우를 살펴보자.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="dv">1</span> <span class="op">+</span> <span class="dv">2</span> <span class="op">+</span> <span class="dv">3</span> <span class="op">*</span> <span class="dv">4</span> <span class="op">==</span> ((<span class="dv">1</span> <span class="op">+</span> <span class="dv">2</span>) <span class="op">+</span> (<span class="dv">3</span> <span class="op">*</span> <span class="dv">4</span>))</span></code></pre></div>
<p><code>+</code>는 <code>infixl 6</code>, <code>*</code>는 <code>infixl 7</code>이므로 <code>*</code>가 우선순위가 높다는 점을
알아두자. 이 경우는 우선순위가 높은 연산이 먼저 결합되고, 그 이후 <code>1</code>, <code>2</code>,
<code>(3 * 4)</code>가 왼쪽부터 괄호로 결합됨을 알 수 있다.</p>
<p><code>infixr</code>과 <code>infix</code>의 예시도 살펴보자. <code>infixr</code>은 right associative이므로
오른쪽부터 괄호를 묶는다. 예시로 <code>$</code>, <code>.</code>, <code>-&gt;</code>가 있다.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- Note the precedence and associativity for the following operators:</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="co">-- infixr 0 $</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co">-- infixr 9 .</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- TFAE:</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="fu">show</span> <span class="op">.</span> <span class="fu">sum</span> <span class="op">.</span> <span class="fu">map</span> <span class="fu">read</span> <span class="op">$</span> [<span class="st">&quot;1&quot;</span>, <span class="st">&quot;2&quot;</span>]</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="fu">show</span> <span class="op">.</span> <span class="fu">sum</span> <span class="op">.</span> (<span class="fu">map</span> <span class="fu">read</span>) <span class="op">$</span> [<span class="st">&quot;1&quot;</span>, <span class="st">&quot;2&quot;</span>]      <span class="co">-- Function application has higher precedence.</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>(<span class="fu">show</span> <span class="op">.</span> <span class="fu">sum</span> <span class="op">.</span> (<span class="fu">map</span> <span class="fu">read</span>)) <span class="op">$</span> [<span class="st">&quot;1&quot;</span>, <span class="st">&quot;2&quot;</span>]    <span class="co">-- (.) has higher precedence than ($).</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>(<span class="fu">show</span> <span class="op">.</span> (<span class="fu">sum</span> <span class="op">.</span> (<span class="fu">map</span> <span class="fu">read</span>))) <span class="op">$</span> [<span class="st">&quot;1&quot;</span>, <span class="st">&quot;2&quot;</span>]  <span class="co">-- (.) is right-associative.</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="co">-- Result: &quot;3&quot; :: String</span></span></code></pre></div>
<p><code>infix</code>의 예시로는 <code>==</code>이 있다. 결합 방향이 정의되지 않으므로 명시적으로 괄호를
묶어야 한다. 예를 들어, <code>a == b == c</code>는 오류가 발생한다. <code>(a == b) == c</code> 또는
<code>a == (b == c)</code>로 명시적으로 작성해야 한다.</p>
  </section>
</article>
]]></summary>
</entry>
<entry>
    <title>Haskell 토막글: η-reduction과 pointfree programming</title>
    <link href="https://blog.mskim.org/posts/9/haskell-eta-reduction" />
    <id>https://blog.mskim.org/posts/9/haskell-eta-reduction</id>
    <published>2024-10-12T11:22:00+09:00</published>
    <updated>2025-11-22T01:00:00+09:00</updated>
    <summary type="html"><![CDATA[<article>
  <section class="header">
    <span class="date">작성: <time datetime="2024-10-12T11:22:00+09:00">2024-10-12</time></span>
    
    /
    <span class="lastmod">수정: <time datetime="2025-11-22T01:00:00+09:00">2025-11-22</time></span>
    
  </section>
  <section class="article-body">
    <blockquote>
<p><strong>요점</strong>: η-reduction을 남발하면 가독성 떨어지는데, 적절하게 쓰면 깔끔하다.</p>
</blockquote>
<p><strong>η-reduction</strong>은 함수의 인자를 명시하지 않고 함수를 표현하는 방법이다. 예를 들어, <code>f x = g x</code>는 간단하게 <code>f = g</code>로 표현할 수 있다. 주로 함수형 프로그래밍에서 함수의 간결한 작성과 가독성을 위해 자주 사용한다.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- Ex) makeGreeting &quot;Hello&quot; &quot;Minseo&quot; =&gt; &quot;Hello Minseo&quot;</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ot">makeGreeting ::</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">String</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>makeGreeting salutation person <span class="ot">=</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>  salutation <span class="op">&lt;&gt;</span> <span class="st">&quot; &quot;</span> <span class="op">&lt;&gt;</span> person</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- Apply eta reduction once</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="ot">makeGreeting1 ::</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">String</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>makeGreeting1 salutation <span class="ot">=</span> ((salutation <span class="op">&lt;&gt;</span> <span class="st">&quot; &quot;</span>) <span class="op">&lt;&gt;</span>)</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="co">-- Apply eta reduction twice</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="ot">makeGreeting2 ::</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">String</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>makeGreeting2 <span class="ot">=</span> (<span class="op">&lt;&gt;</span>) <span class="op">.</span> (<span class="op">&lt;&gt;</span> <span class="st">&quot; &quot;</span>)</span></code></pre></div>
<p>인자를 명시적으로 사용해 함수를 정의하는 방식을 <strong>pointful</strong> 스타일이라고 하며, 인자를 생략하고 기존 함수들의 조합만으로 새로운 함수를 정의하는 방식을 <strong>pointfree</strong> 스타일이라고 한다.</p>
<p>위의 예시에서는 원본 함수가 pointful, eta-reduction 적용한 1, 2번이 pointfree 스타일이다. 그런데 pointful한 원본이 가장 읽기 쉽지 않나? 이 예시에서는 pointfree 스타일이 <em>pointless</em><a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>하다. 그냥 아래처럼 명확한 경우만 η-reduction 써야겠다.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ot">addOne ::</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">Int</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>addOne x <span class="ot">=</span> x <span class="op">+</span> <span class="dv">1</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="co">-- Apply eta reduction; much clearer</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="ot">addOne&#39; ::</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">Int</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>addOne&#39; <span class="ot">=</span> (<span class="op">+</span> <span class="dv">1</span>)</span></code></pre></div>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>pointless: 무의미한, 할 가치가 없는<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
  </section>
</article>
]]></summary>
</entry>
<entry>
    <title>Haskell 토막글: flip 함수</title>
    <link href="https://blog.mskim.org/posts/8/haskell-flip" />
    <id>https://blog.mskim.org/posts/8/haskell-flip</id>
    <published>2024-10-12T10:30:00+09:00</published>
    <updated>2025-05-07T18:30:00+09:00</updated>
    <summary type="html"><![CDATA[<article>
  <section class="header">
    <span class="date">작성: <time datetime="2024-10-12T10:30:00+09:00">2024-10-12</time></span>
    
    /
    <span class="lastmod">수정: <time datetime="2025-05-07T18:30:00+09:00">2025-05-07</time></span>
    
  </section>
  <section class="article-body">
    <blockquote>
<p><strong>요점</strong>: <code>flip</code> 안 쓰고 infix operation 활용하는 방법이 있는데, 그게 더 흔한
것 같긴 하다.</p>
</blockquote>
<p>타입 시그니처를 살펴보자.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="fu">flip</span><span class="ot"> ::</span> (a <span class="ot">-&gt;</span> b <span class="ot">-&gt;</span> c) <span class="ot">-&gt;</span> b <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> c</span></code></pre></div>
<p>두 개의 인자를 받는 함수에서 arg1, arg2의 적용 순서를 arg2, arg1로 바꾼다. type
signature를 같이 살펴보면 아주 명확하다. 다음은 적용 예시다.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ot">fn ::</span> <span class="dt">T1</span> <span class="ot">-&gt;</span> <span class="dt">T2</span> <span class="ot">-&gt;</span> <span class="dt">U</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="ot">val2 ::</span> <span class="dt">T2</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="ot">fn&#39; ::</span> <span class="dt">T1</span> <span class="ot">-&gt;</span> <span class="dt">U</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="co">-- TFAE:</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>fn&#39; <span class="ot">=</span> <span class="fu">flip</span> fn val2 <span class="co">-- flip 사용</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>fn&#39; <span class="ot">=</span> (<span class="ot">`fn`</span> val2)  <span class="co">-- infix로 표현 후 함수로 변형</span></span></code></pre></div>
<p><strong>Tricky case</strong>. 인수를 꼭 2개만 받는 함수여야만 할까? 사실, <code>flip</code>의 arg1인 <code>a -&gt; b -&gt; c</code>에서 <code>c :: * -&gt; *</code>일 수 있다! <code>T1 -&gt; T2 -&gt; T3 -&gt; U</code>인 함수도 <code>T1 -&gt; T2 -&gt; (T3 -&gt; U)</code>로 볼 수 있다.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">-- T1 -&gt; T2 -&gt; (T3 -&gt; U)</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ot">tn ::</span> <span class="dt">T1</span> <span class="ot">-&gt;</span> <span class="dt">T2</span> <span class="ot">-&gt;</span> <span class="dt">T3</span> <span class="ot">-&gt;</span> <span class="dt">U</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="ot">val2 ::</span> <span class="dt">T2</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co">-- T1 -&gt; (T3 -&gt; U)</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="ot">tn&#39; ::</span> <span class="dt">T1</span> <span class="ot">-&gt;</span> <span class="dt">T3</span> <span class="ot">-&gt;</span> <span class="dt">U</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>tn&#39; <span class="ot">=</span> <span class="fu">flip</span> tn val2</span></code></pre></div>
<p><strong>질문</strong>. <code>flip</code>를 실제로 많이 쓰나? 다음 예시처럼 flip을 굳이 쓰지 않고도
infix operation을 활용할 수 있다.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ot">fn ::</span> <span class="dt">T</span> <span class="ot">-&gt;</span> <span class="dt">String</span> <span class="ot">-&gt;</span> <span class="dt">U</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="ot">fn&#39; ::</span> <span class="dt">T</span> <span class="ot">-&gt;</span> <span class="dt">U</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">-- TFAE:</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>fn&#39; <span class="ot">=</span> <span class="fu">flip</span> fn <span class="st">&quot;foobar&quot;</span>  <span class="co">-- flip 사용</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>fn&#39; <span class="ot">=</span> (<span class="ot">`fn`</span> <span class="st">&quot;foobar&quot;</span>)   <span class="co">-- infix로 표현 후 함수로 변형</span></span></code></pre></div>
<p>infix operation 활용하는게 더 흔할까? flip도 괜찮은 선택일까? 개인적으로는
함수를 infix 이항 연산자로 바라봤을 때 부자연스럽지 않다면, flip보다 연산자로
접근하는 방법이 나은 것 같다.</p>
  </section>
</article>
]]></summary>
</entry>
<entry>
    <title>FreeBSD VNET Jail 구성 방법</title>
    <link href="https://blog.mskim.org/posts/7/freebsd-vnet-jail-config" />
    <id>https://blog.mskim.org/posts/7/freebsd-vnet-jail-config</id>
    <published>2024-09-25T15:27:41+09:00</published>
    <updated>2025-05-07T18:30:00+09:00</updated>
    <summary type="html"><![CDATA[<article>
  <section class="header">
    <span class="date">작성: <time datetime="2024-09-25T15:27:41+09:00">2024-09-25</time></span>
    
    /
    <span class="lastmod">수정: <time datetime="2025-05-07T18:30:00+09:00">2025-05-07</time></span>
    
  </section>
  <section class="article-body">
    <p>FreeBSD의 VNET Jail이 무엇인지, 어디에 쓰는지 소개한다. 구성 과정에서 마주칠 수
있는 문제와 해결 방안을 다룬다.</p>
<h2 id="freebsd-jail">FreeBSD Jail</h2>
<p>FreeBSD의 <strong>Jail</strong> 기능은 프로세스, 파일 시스템, 네트워크, 사용자 및 권한을
격리하는 환경을 제공한다. Linux의 <strong>Docker</strong>와 비교하자면, Jail은 Docker와 달리
운영체제의 커널 레벨에서 지원하는 기능이다. 따라서 운영체제와 밀접히 통합되어
있으며, 보안 및 안정성, 자원 관리, 네트워크 분리, ZFS 파일 시스템의 활용 등
강력한 장점이 있다.</p>
<p>사실 FreeBSD Jail과 직접적으로 비교할 수 있는 대상은 Docker보다는 LXC가 더
적합해보인다. LXC는 리눅스 커널 차원에서 지원하는 기능이고, 한때 Docker도 LXC를
기반으로 개발되었던 것으로 알고 있다. 지금은 runc라는 별도의 컨테이너 런타임을
사용하고 있다.</p>
<p>Docker는 개발 편의를 위한 도구로써 유용하다. Docker 이미지의 생성 및
레포지토리를 통한 배포 등 개발 환경의 구축을 간소화하고 편의를 제공해주는
도구라는 측면에서 장점이 분명하다. 하지만 모든 컨테이너가 하나의 docker
daemon에 의해 관리되고, 이 프로세스가 root 권한으로 실행된다는 점에서 근본적인
보안 문제가 있다.</p>
<p>이를 해결한 Docker의 대안으로는 <strong>Podman</strong>이 있는데, 각 컨테이너들이 독립적으로
실행되고 이들을 관리하기 위한 daemon 또한 존재하지 않으며, 루트 권한 없이
실행<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>되므로 보안상 안전하다. FreeBSD에서도 ocijail
런타임을 활용하여 Podman을 사용할 수 있다. ocijail은 Jail을 활용하여 구현한
컨테이너 런타임이다. 따라서 FreeBSD에서 Docker와 같은 개발 편의 도구가
필요하다면 Podman을 활용할 수 있다.</p>
<p>본론으로 돌아와서, FreeBSD에서는 Podman 등을 사용하지 않아도 기본적으로
제공하는 Jail 기능만 활용하더라도 강력한 격리 환경을 구성할 수 있다.</p>
<h2 id="vnet-jail">VNET Jail</h2>
<blockquote>
<p>A FreeBSD VNET jail is a virtualized environment that allows for the
isolation and control of network resources for processes running within it.
It provides a high level of network segmentation and security by creating a
separate network stack for processes within the jail, ensuring that network
traffic within the jail is isolated from the host system and other jails.<br />
– <a href="https://docs.freebsd.org/en/books/handbook/jails/#vnet-jails">FreeBSD Handbook</a>, <strong>17.2.4. VNET Jails.</strong></p>
</blockquote>
<p><strong>VNET Jail</strong>은 호스트와 완전히 분리된 네트워크 스택(L2~L7 계층)을 가진다.
즉, 별도의 가상 이더넷 인터페이스(epair, vnet)로 고유한 MAC·L2 링크를 할당받고,
브리지나 VLAN 등 L2 수준에서 자유롭게 구성할 수 있다는 뜻이다. 이로 인해 VNET
Jail은 다음과 같은 특징을 갖는다.</p>
<ol type="1">
<li><p><strong>VM과 마찬가지로 호스트와 별개의 IP를 할당받을 수 있다.</strong> 독립된 L2
인터페이스를 통해 호스트와 다른 서브넷에 IP를 붙일 수 있으며, IP aliasing
없이도 완전히 분리된 라우팅·방화벽 정책을 적용 가능하다.</p></li>
<li><p><strong>고유의 MAC 주소를 가진 가상 NIC를 사용한다.</strong> epair 인터페이스를 통해 L2
프레임을 주고받으며, 브리지(bridge)나 스위치에 직접 연결할 수 있다.</p></li>
<li><p><strong>브리지·VLAN·터널링 같은 L2 기능을 그대로 활용할 수 있다.</strong> 호스트의
bridge0에 VNET Jail의 인터페이스를 addm 하거나, VLAN 태그를 트렁킹해서
동일한 L2 도메인에 참여시킬 수 있다.</p></li>
<li><p><strong>방화벽·라우팅·네트워크 네임스페이스가 호스트와 완전히 분리된다.</strong> Jail
내부 전용 방화벽 규칙을 운영하고, routing table을 별도로 관리할 수 있다.
Non-VNET Jail에서는 policy-based routing으로 라우팅을 분리할 수 있다.</p></li>
</ol>
<p>반면 Non-VNET Jail은 호스트의 물리 NIC가 속한 동일 L2 도메인(같은 MAC 링크)을
공유하고, IP aliasing을 통해 같은 서브넷 내에서만 IP를 할당받는다. 따라서 L2
계층부터 완전 분리가 필요한 경우에는 반드시 VNET Jail을 사용해야 한다.</p>
<h3 id="vnet-jail의-활용">VNET Jail의 활용</h3>
<p>운용 예시를 살펴보면 VNET Jail의 매력을 이해하는 데 도움이 된다. VNET Jail은
호스트와 다른 네트워크 인터페이스를 가지므로 각 Jail을 서로 다른 VLAN에 둘 수
있다. 또한 라우팅 테이블이 호스트와 다르므로 일부 Jail을 특정한 VPN에
연결하도록 구성할 수 있다.</p>
<p>개인적으로 VNET Jail을 활용하여 호스트와 각 Jail에 고유한 IP를 부여하여 다양한
웹 서비스를 운영하고 있다. 또한, <strong>dnsmasq</strong>로 로컬 DNS 서버를 구축해두고 각
서비스에 서로 다른 로컬 도메인 이름을 매핑해 두었다. 하나의 reverse proxy
아래에 모든 서비스를 두는 방법도 있었지만, 이렇게 네트워크를 구성한 이유는 각
Jail을 물리적으로 구분되는 별도의 서버로 가정해 운영해보고 싶었기 때문이다.</p>
<p>또한 VLAN을 나누어, 각 Jail을 서로 다른 VLAN에 배치하는 구성도 실험하고자 했다.
VNET Jail은 호스트 및 다른 VNET Jail들과 각각 독립된 네트워크 인터페이스를
가지기 때문에, 각 Jail을 서로 다른 VLAN에 두면 이들 간의 통신은 반드시 라우터를
거쳐야만 이루어질 수 있다. 이러한 구성을 통해 각 VLAN에 각각 다른 방화벽 정책을
설정하는 등의 실험을 진행할 수 있었다.</p>
<p>홈 네트워크 구성 또한 나중에 기회가 되면 포스팅하겠다.</p>
<h3 id="vnet-jail-구성-방법">VNET Jail 구성 방법</h3>
<p>VNET Jail은 epair 인터페이스를 생성하여 한 쪽은 브릿지에, 다른 쪽은 Jail에
연결하도록 구성한다.</p>
<p>다음 내용을 <code>/etc/rc.conf</code>에 추가한다.</p>
<pre class="unix"><code>cloned_interface=&quot;bridge0&quot;

ifconfig_bridge0=&quot;addm em0 up&quot;</code></pre>
<blockquote>
<p><strong>Note.</strong> <code>em0</code>와 같은 인터페이스 이름은 기기마다 다를 수 있으니 그대로
사용할 수는 없다.</p>
</blockquote>
<p>다음 내용을 <code>/etc/jail.conf</code>에 추가한다.</p>
<pre class="unix"><code>my-vnet-jail {
# ...

# VNET/VIMAGE
  vnet;
  vnet.interface = &quot;${epair}b&quot;;

# NETWORKS/INTERFACES
  $id = &quot;154&quot;;
  $ip = &quot;192.168.1.${id}/24&quot;;
  $gateway = &quot;192.168.1.1&quot;;
  $bridge = &quot;bridge0&quot;;
  $epair = &quot;epair${id}&quot;;

# ADD TO bridge INTERFACE
  exec.prestart  = &quot;/sbin/ifconfig ${epair} create up&quot;;
  exec.prestart += &quot;/sbin/ifconfig ${epair}a up descr jail:${name}&quot;;
  exec.prestart += &quot;/sbin/ifconfig ${bridge} addm ${epair}a up&quot;;
  exec.start    += &quot;/sbin/ifconfig ${epair}b ${ip} up&quot;;
  exec.start    += &quot;/sbin/route add default ${gateway}&quot;;
  exec.poststop = &quot;/sbin/ifconfig ${bridge} deletem ${epair}a&quot;;
  exec.poststop += &quot;/sbin/ifconfig ${epair}a destroy&quot;;
}</code></pre>
<p>자세한 내용은 <a href="https://docs.freebsd.org/en/books/handbook/jails/#creating-vnet-jail">FreeBSD handbook</a>를 참조한다.</p>
<h4 id="문제점">문제점</h4>
<p>간혹 Jail을 호스트에서 제거<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>한 이후에도 <code>epair###b</code>
인터페이스가 Jail에서 release되지 않아서 호스트에서 보이지 않는
문제<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>가 발생한다. 원래 <code>jail.conf</code><a href="#fn4" class="footnote-ref" id="fnref4" role="doc-noteref"><sup>4</sup></a>에서
<code>vnet.interface</code>의 인수로 설정한 인터페이스는 자동으로 release되어야
한다.<a href="#fn5" class="footnote-ref" id="fnref5" role="doc-noteref"><sup>5</sup></a></p>
<h4 id="해결-방법">해결 방법</h4>
<p>Jail이 제거되는 시점에 호스트에서 <code>ifconfig -vnet</code> 명령어로 수동으로
인터페이스를 release해줄 수 있다. <code>jail.conf</code>의 <code>exec.prestop</code> 인수에 다음과
같이 명령어를 추가하면 된다.</p>
<pre class="unix"><code>my-vnet-jail {
# ...

# ADD TO bridge INTERFACE
  exec.prestart  = &quot;/sbin/ifconfig ${epair} create up&quot;;
  exec.prestart += &quot;/sbin/ifconfig ${epair}a up descr jail:${name}&quot;;
  exec.prestart += &quot;/sbin/ifconfig ${bridge} addm ${epair}a up&quot;;
  exec.start    += &quot;/sbin/ifconfig ${epair}b ${ip} up&quot;;
  exec.start    += &quot;/sbin/route add default ${gateway}&quot;;

  # Add this line
  exec.prestop  += &quot;/sbin/ifconfig ${epair}b || /sbin/ifconfig ${epair}b -vnet $name&quot;;

  exec.poststop = &quot;/sbin/ifconfig ${bridge} deletem ${epair}a&quot;;
  exec.poststop += &quot;/sbin/ifconfig ${epair}a destroy&quot;;
}</code></pre>
<ul>
<li><code>exec.stop</code>은 Jail 제거 시점에 Jail 내부에서 실행된다.</li>
<li><code>exec.prestop</code>과 <code>exec.poststop</code>은 각각 Jail 제거 직전과 직후에 호스트에서
실행된다.</li>
</ul>
<h2 id="jail-구축-관련-참고-사항">Jail 구축 관련 참고 사항</h2>
<h3 id="dhcp를-이용한-ip-할당">DHCP를 이용한 IP 할당</h3>
<p>위에서 소개한 방법은 각 Jail의 IP를 수동으로 할당한다. DHCP를 이용하여 자동
할당받고자 하는 경우, 각 Jail에서 DHCP 클라이언트를 따로 구성해줘야 한다. 또한
<code>jail.conf</code>에서 각 Jail에 대해 일부 restriction을 추가로 해제할 필요가 있던
것으로 기억하는데, 정확한 방법은 기억나지 않는다.</p>
<h3 id="postgresql-등-db-구축">PostgreSQL 등 DB 구축</h3>
<p>VNET Jail과 관련은 없으나, PostgreSQL과 같은 일부 데이터베이스<a href="#fn6" class="footnote-ref" id="fnref6" role="doc-noteref"><sup>6</sup></a>를
Jail에서 정상적으로 구동하기 위해서는 해당 Jail에 별도의 restriction을 해제해야
한다. Jail을 소개할 때 언급했듯이, Jail은 각각 자원의 할당 및 권한 관리를
세세히 설정할 수 있다.</p>
<p>PostgreSQL의 경우, <code>sysvipc</code><a href="#fn7" class="footnote-ref" id="fnref7" role="doc-noteref"><sup>7</sup></a> restriction을 해제해야 한다.
<code>jail.conf</code> 파일에 다음과 같이 추가한다.</p>
<pre><code>my-postgres-jail {
# ...

# PERMISSIONS
  allow.sysvipc;
}</code></pre>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>리눅스에서 지원. 아쉽게도 아직까지 FreeBSD에서의
Podman은 루트 권한 없이 컨테이너를 생성하지 못한다.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>Jail의 <strong>생성(create)/제거(remove)</strong>라는 표현은 Jail을
구성하는 <strong>userland</strong>의 생성/제거와 독립적이므로 주의해야 한다. Docker에
익숙한 경우, Jail의 생성/제거는 Docker 컨테이너의
생성(create)/제거(rm)보다는 <strong>시작(start)/정지(stop)</strong>와 더 비슷하다고
이해할 수 있다.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p>해당 문제 보고는 <a href="https://forums.FreeBSD.org/threads/interface-does-not-return-to-host-after-kill-jail.92730/post-648334">FreeBSD 포럼</a> 참조.<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn4"><p><code>man 5 jail.conf</code> 참조.<a href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn5"><p><code>man 8 jail</code> 참조:</p>
<pre><code>vnet.interface
        A network interface to give to a vnet-enabled jail after is it
        created.  The interface will automatically be released when the
        jail is removed.</code></pre>
<a href="#fnref5" class="footnote-back" role="doc-backlink">↩︎</a></li>
<li id="fn6"><p>SQLite는 따로 restriction의 해제를 요구하지 않았다.<a href="#fnref6" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn7"><p><code>man 8 jail</code> 참조:</p>
<pre><code>allow.sysvipc
        A process within the jail has access to System V IPC
        primitives.  This is deprecated in favor of the per-
        module parameters (see below).  When this parameter is
        set, it is equivalent to setting sysvmsg, sysvsem, and
        sysvshm all to “inherit”.</code></pre>
<a href="#fnref7" class="footnote-back" role="doc-backlink">↩︎</a></li>
</ol>
</section>
  </section>
</article>
]]></summary>
</entry>
<entry>
    <title>[BOJ] #2887 행성 터널</title>
    <link href="https://blog.mskim.org/posts/6/boj-2887" />
    <id>https://blog.mskim.org/posts/6/boj-2887</id>
    <published>2024-09-22T17:00:00+09:00</published>
    <updated>2024-09-22T08:00:00Z</updated>
    <summary type="html"><![CDATA[<article>
  <section class="header">
    <span class="date">작성: <time datetime="2024-09-22T17:00:00+09:00">2024-09-22</time></span>
    
  </section>
  <section class="article-body">
    <p>백준 <a href="https://www.acmicpc.net/problem/2887">2887번 - 행성 터널</a>의 풀이다.</p>
<h2 id="tldr">TL;DR</h2>
<ul>
<li>밀집 그래프로 보고 Prim으로 풀면 시간 및 메모리 제한에 걸린다.</li>
<li>대부분의 간선들은 MST 알고리즘 실행 전에 미리 제거될 수 있다.
<ul>
<li><span class="math inline">\(\left| E \right| = \left| V \right|^2\)</span>에서 <span class="math inline">\(\left| E \right| = 3\left| V \right|\)</span>까지 줄일 수 있다</li>
</ul></li>
<li>간선을 대부분 제거하여 희소 그래프가 되므로 Kruskal 알고리즘으로 구현한다.</li>
</ul>
<h2 id="풀이-과정">풀이 과정</h2>
<blockquote>
<p>문제 전문은 BOJ 웹사이트에서 확인할 수 있습니다. <a href="https://www.acmicpc.net/problem/2887">(링크)</a></p>
</blockquote>
<h3 id="문제점">문제점</h3>
<p>처음에는 그래프가 dense하다고 판단하여 Prim 알고리즘으로 풀 생각이었다. 정점의
좌표가 주어지고, 간선의 가중치를 정점 간 거리로 설정하므로
<span class="math inline">\(\left| E \right|=\left| V \right|^2\)</span>가 되기 때문이다.</p>
<p>하지만 이렇게 풀었더니 메모리와 시간을 초과했다. 간선 가중치를 미리 계산해
저장하려다가 메모리 초과가 발생했고, 가중치를 정점 좌표로부터 실시간으로
계산해서 얻어오도록 변경했으나 시간 초과가 발생했다.</p>
<p>Prim 알고리즘이 밀집 그래프에 적합하다는 사실을 고려해보면, 문제에서 주어진
그래프를 그대로 MST 알고리즘에 적용할 수는 없었다.</p>
<h3 id="간선-제거---1차원-공간">간선 제거 - 1차원 공간</h3>
<p>간선의 수를 줄여서 sparse graph로 만들 수 있는지 살펴봤다. 이 문제는 각
정점마다 좌표가 주어지고, 가중치가 정점 간 거리로 정의된다는 점에서 정점을
정렬해볼 수 있었다.</p>
<p>문제를 쉽게 생각하기 위해서 3차원 대신 1차원 공간에서의 정점을 생각해봤다.</p>
<figure>
<img src="/assets/images/006/img1.png" style="max-height: 60px;" alt="일직선상으로 정렬된 1차원 공간의 정점들" />
<figcaption aria-hidden="true">일직선상으로 정렬된 1차원 공간의 정점들</figcaption>
</figure>
<p>정점들을 좌표 순서대로 정렬하면, 직관적으로 MST는 이들을 순차적으로 연결한
리스트의 형태임을 알 수 있다.<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a> 즉, MST를 구성하는데 필요한 간선은 좌표로
정렬된 상태에서 인접한 정점들 간의 간선만 포함하면 된다. 이와 같이 1차원
공간에서는 MST를 구성하는 간선 <span class="math inline">\(\left| V \right|-1\)</span>개를 정확히 찾을 수 있다.</p>
<h3 id="간선-제거---3차원-공간">간선 제거 - 3차원 공간</h3>
<p>3차원 공간에서는 간선들을 일직선상으로 정렬할 수 없기 때문에, 1차원
공간에서처럼 MST를 구성하는 간선을 정확히 특정하기 어렵다. 그러나, 정점이 3차원
좌표를 갖기 때문에 이를 x, y, z 방향 3개의 성분으로 분리하여 각각 정렬해볼 수는
있다.</p>
<figure>
<img src="/assets/images/006/img2.png" style="max-height: 300px; max-width: 70%;" alt="각 성분에 대해 정렬된 3차원 공간의 정점들" />
<figcaption aria-hidden="true">각 성분에 대해 정렬된 3차원 공간의 정점들</figcaption>
</figure>
<p>위 그림에서 정점 b, c는 x축 성분으로 정렬했을 때 서로 인접한다. 정점 a, c는 y,
z축 성분으로 각각 정렬했을 때 서로 인접한다. 정점 a, b는 x, y, z축 성분으로
각각 정렬한 3가지 경우 모두에서 서로 인접한다.</p>
<p>이 모형으로부터 아래와 같은 그래프를 그려보겠다.</p>
<figure>
<img src="/assets/images/006/img3.png" style="max-height: 400px; max-width: 90%;" alt="subgraph" />
<figcaption aria-hidden="true">subgraph</figcaption>
</figure>
<p>이 그래프는 두 정점 사이에 간선을 여러 개 가질 수 있다. 각 간선의 개수는 두
정점을 특정 성분으로 정렬했을 때 서로 인접하도록 하는 성분의 수와 같고,
가중치는 해당 성분에 대한 두 정점의 좌표 차에 해당한다.</p>
<p>문제에서 주어진 그래프를 살펴보면, 간선의 가중치는 두 정점 간의
거리로 정의된다. 좌표가 각각 <span class="math inline">\((x, y, z)\)</span>와 <span class="math inline">\((x&#39;, y&#39;, z&#39;)\)</span>인 두 정점
간의 거리는 <span class="math inline">\(\min \left( \left| x-x&#39; \right|, \left| y-y&#39; \right|, \left| z-z&#39; \right| \right)\)</span>로
정의한다. 이제 이 간선을 가중치가 각각 <span class="math inline">\(\left| x-x&#39; \right|\)</span>, <span class="math inline">\(\left| y-y&#39; \right|\)</span>,
<span class="math inline">\(\left| z-z&#39; \right|\)</span>인 3개의 간선으로 치환해보자. 이렇게 치환하더라도 MST를 구하는 데는
차이가 없다. 두 정점 사이에 여러 개의 간선이 존재할 경우, MST 알고리즘은 항상
최소 가중치의 간선을 선택하기 때문에 결과적으로 동일한 MST가
만들어진다.</p>
<p>따라서, 우리가 그린 그래프는 문제에서 주어진 그래프의 부분 그래프(subgraph)임을
알 수 있다. 이제, 이 부분 그래프에 포함되지 않은 간선은 MST를 구성할 수 없음을
보이겠다. 어떤 간선 <span class="math inline">\(vw_x\)</span>가 이 부분 그래프에 추가되지 않았고, 그 양 끝 정점
<span class="math inline">\(v\)</span>, <span class="math inline">\(w\)</span>의 좌표가 각각 <span class="math inline">\((x, y, z)\)</span>와 <span class="math inline">\((x&#39;, y&#39;, z&#39;)\)</span>이며, 가중치는 <span class="math inline">\(\left| x-x&#39; \right|
\gt 0\)</span>이라고 가정하자. 이 간선이 부분 그래프에 추가되지 않은 이유는 어떤 정점
<span class="math inline">\(u\)</span>가 있어서 그 좌표가 <span class="math inline">\((x&#39;&#39;, y&#39;&#39;, z&#39;&#39;)\)</span>이고 다음을 만족하기 때문이다.</p>
<p><span class="math display">\[
\begin{align*}
\left| x\:-x&#39;&#39; \right| &amp;\geq 0 \\
\left| x&#39;-x&#39;&#39; \right| &amp;\geq 0 \\
\left| x\:-x&#39;\: \right| &amp;= \left| x-x&#39;&#39; \right| + \left| x&#39;-x&#39;&#39; \right|
\end{align*}
\]</span></p>
<p><span class="math inline">\(\left| x-x&#39;&#39; \right|\)</span>와 <span class="math inline">\(\left| x&#39;-x&#39;&#39; \right|\)</span>이 각각 <span class="math inline">\(\left| x-x&#39; \right|\)</span>보다 작기 때문에, Kruskal
알고리즘으로 MST를 찾을 때 가중치가 <span class="math inline">\(\left| x-x&#39; \right|\)</span>인 간선 <span class="math inline">\(vw_x\)</span>를 선택하는
시점에는 이미 <span class="math inline">\(v-u-w\)</span> 간의 경로가 존재하게 되어, 이 간선 <span class="math inline">\(vw_x\)</span>는 선택될 수
없다.  <span class="math inline">\(\square\)</span></p>
<p>앞서 구한 부분 그래프는 간선 개수가 <span class="math inline">\(\left| E \right|=3\left| V \right|\)</span>로, 기존의
<span class="math inline">\(\left| E \right|=\left| V \right|^2\)</span>에 비해 간선의 수가 훨씬 적은 희소 그래프(sparse graph)다.
Kruskal 알고리즘을 사용하여 제한된 메모리와 시간 내에 MST를 구할 수 있었다.</p>
<h2 id="답안">답안</h2>
<div class="sourceCode" id="cb1"><pre class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">/**</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span><span class="an">@brief</span><span class="co"> BOJ No. 2887 &quot;행성 터널&quot;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co"> *</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span><span class="an">@author</span><span class="co"> Minseo Kim </span><span class="kw">&lt;kimminss0@outlook.kr&gt;</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span><span class="an">@details</span><span class="co"> 밀집 그래프로 보고 Prim으로 풀면 시간 및 메모리 제한에 걸린다.</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="co"> * 이 문제에서 대부분의 간선들은 MST 알고리즘 실행 전에 미리 제거될 수 있는데,</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="co"> * |E| = |V|^2에서 |E| = 3|V|까지 줄일 수 있어 사실상 희소 그래프로 풀 수 있기에</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="co"> * Kruskal로 구현한다.</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="co"> */</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;algorithm&gt;</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;iostream&gt;</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;tuple&gt;</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;utility&gt;</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;vector&gt;</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="kw">using</span> <span class="kw">namespace</span> std<span class="op">;</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> UnionFind <span class="op">{</span></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a><span class="kw">private</span><span class="op">:</span></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a>  vector<span class="op">&lt;</span><span class="dt">int</span><span class="op">&gt;</span> parent<span class="op">;</span></span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span><span class="op">:</span></span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a>  UnionFind<span class="op">(</span><span class="dt">int</span> N<span class="op">)</span> <span class="op">:</span> parent<span class="op">(</span>N<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> N<span class="op">;</span> i<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a>      parent<span class="op">[</span>i<span class="op">]</span> <span class="op">=</span> i<span class="op">;</span></span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-29"><a href="#cb1-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-30"><a href="#cb1-30" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> find<span class="op">(</span><span class="dt">int</span> x<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-31"><a href="#cb1-31" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>parent<span class="op">[</span>x<span class="op">]</span> <span class="op">==</span> x<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-32"><a href="#cb1-32" aria-hidden="true" tabindex="-1"></a>      <span class="cf">return</span> x<span class="op">;</span></span>
<span id="cb1-33"><a href="#cb1-33" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-34"><a href="#cb1-34" aria-hidden="true" tabindex="-1"></a>    <span class="co">// path compression</span></span>
<span id="cb1-35"><a href="#cb1-35" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> parent<span class="op">[</span>x<span class="op">]</span> <span class="op">=</span> find<span class="op">(</span>parent<span class="op">[</span>x<span class="op">]);</span></span>
<span id="cb1-36"><a href="#cb1-36" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-37"><a href="#cb1-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-38"><a href="#cb1-38" aria-hidden="true" tabindex="-1"></a>  <span class="dt">bool</span> unite<span class="op">(</span><span class="dt">int</span> x<span class="op">,</span> <span class="dt">int</span> y<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-39"><a href="#cb1-39" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> rootX <span class="op">=</span> find<span class="op">(</span>x<span class="op">);</span></span>
<span id="cb1-40"><a href="#cb1-40" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> rootY <span class="op">=</span> find<span class="op">(</span>y<span class="op">);</span></span>
<span id="cb1-41"><a href="#cb1-41" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>rootX <span class="op">==</span> rootY<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-42"><a href="#cb1-42" aria-hidden="true" tabindex="-1"></a>      <span class="cf">return</span> <span class="kw">false</span><span class="op">;</span></span>
<span id="cb1-43"><a href="#cb1-43" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-44"><a href="#cb1-44" aria-hidden="true" tabindex="-1"></a>    parent<span class="op">[</span>rootX<span class="op">]</span> <span class="op">=</span> rootY<span class="op">;</span></span>
<span id="cb1-45"><a href="#cb1-45" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="kw">true</span><span class="op">;</span></span>
<span id="cb1-46"><a href="#cb1-46" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-47"><a href="#cb1-47" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span>
<span id="cb1-48"><a href="#cb1-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-49"><a href="#cb1-49" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-50"><a href="#cb1-50" aria-hidden="true" tabindex="-1"></a>  cin<span class="op">.</span>tie<span class="op">(</span><span class="kw">nullptr</span><span class="op">)-&gt;</span>sync_with_stdio<span class="op">(</span><span class="kw">false</span><span class="op">);</span></span>
<span id="cb1-51"><a href="#cb1-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-52"><a href="#cb1-52" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> N<span class="op">;</span></span>
<span id="cb1-53"><a href="#cb1-53" aria-hidden="true" tabindex="-1"></a>  cin <span class="op">&gt;&gt;</span> N<span class="op">;</span></span>
<span id="cb1-54"><a href="#cb1-54" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-55"><a href="#cb1-55" aria-hidden="true" tabindex="-1"></a>  <span class="co">// idx, coordinate (along the axis)</span></span>
<span id="cb1-56"><a href="#cb1-56" aria-hidden="true" tabindex="-1"></a>  vector<span class="op">&lt;</span>pair<span class="op">&lt;</span><span class="dt">int</span><span class="op">,</span> <span class="dt">int</span><span class="op">&gt;&gt;</span> x<span class="op">(</span>N<span class="op">),</span> y<span class="op">(</span>N<span class="op">),</span> z<span class="op">(</span>N<span class="op">);</span></span>
<span id="cb1-57"><a href="#cb1-57" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-58"><a href="#cb1-58" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> N<span class="op">;</span> i<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb1-59"><a href="#cb1-59" aria-hidden="true" tabindex="-1"></a>    x<span class="op">[</span>i<span class="op">].</span>first <span class="op">=</span> i<span class="op">;</span></span>
<span id="cb1-60"><a href="#cb1-60" aria-hidden="true" tabindex="-1"></a>    y<span class="op">[</span>i<span class="op">].</span>first <span class="op">=</span> i<span class="op">;</span></span>
<span id="cb1-61"><a href="#cb1-61" aria-hidden="true" tabindex="-1"></a>    z<span class="op">[</span>i<span class="op">].</span>first <span class="op">=</span> i<span class="op">;</span></span>
<span id="cb1-62"><a href="#cb1-62" aria-hidden="true" tabindex="-1"></a>    cin <span class="op">&gt;&gt;</span> x<span class="op">[</span>i<span class="op">].</span>second <span class="op">&gt;&gt;</span> y<span class="op">[</span>i<span class="op">].</span>second <span class="op">&gt;&gt;</span> z<span class="op">[</span>i<span class="op">].</span>second<span class="op">;</span></span>
<span id="cb1-63"><a href="#cb1-63" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-64"><a href="#cb1-64" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-65"><a href="#cb1-65" aria-hidden="true" tabindex="-1"></a>  <span class="cf">if</span> <span class="op">(</span>N <span class="op">==</span> <span class="dv">1</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-66"><a href="#cb1-66" aria-hidden="true" tabindex="-1"></a>    cout <span class="op">&lt;&lt;</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb1-67"><a href="#cb1-67" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb1-68"><a href="#cb1-68" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-69"><a href="#cb1-69" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-70"><a href="#cb1-70" aria-hidden="true" tabindex="-1"></a>  <span class="co">// sort by coordinate</span></span>
<span id="cb1-71"><a href="#cb1-71" aria-hidden="true" tabindex="-1"></a>  <span class="kw">auto</span> cmp <span class="op">=</span> <span class="op">[](</span>pair<span class="op">&lt;</span><span class="dt">int</span><span class="op">,</span> <span class="dt">int</span><span class="op">&gt;</span> a<span class="op">,</span> pair<span class="op">&lt;</span><span class="dt">int</span><span class="op">,</span> <span class="dt">int</span><span class="op">&gt;</span> b<span class="op">)</span> <span class="op">-&gt;</span> <span class="dt">bool</span> <span class="op">{</span></span>
<span id="cb1-72"><a href="#cb1-72" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> a<span class="op">.</span>second <span class="op">&lt;</span> b<span class="op">.</span>second<span class="op">;</span></span>
<span id="cb1-73"><a href="#cb1-73" aria-hidden="true" tabindex="-1"></a>  <span class="op">};</span></span>
<span id="cb1-74"><a href="#cb1-74" aria-hidden="true" tabindex="-1"></a>  sort<span class="op">(</span>x<span class="op">.</span>begin<span class="op">(),</span> x<span class="op">.</span>end<span class="op">(),</span> cmp<span class="op">);</span></span>
<span id="cb1-75"><a href="#cb1-75" aria-hidden="true" tabindex="-1"></a>  sort<span class="op">(</span>y<span class="op">.</span>begin<span class="op">(),</span> y<span class="op">.</span>end<span class="op">(),</span> cmp<span class="op">);</span></span>
<span id="cb1-76"><a href="#cb1-76" aria-hidden="true" tabindex="-1"></a>  sort<span class="op">(</span>z<span class="op">.</span>begin<span class="op">(),</span> z<span class="op">.</span>end<span class="op">(),</span> cmp<span class="op">);</span></span>
<span id="cb1-77"><a href="#cb1-77" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-78"><a href="#cb1-78" aria-hidden="true" tabindex="-1"></a>  <span class="co">// w, v, u</span></span>
<span id="cb1-79"><a href="#cb1-79" aria-hidden="true" tabindex="-1"></a>  vector<span class="op">&lt;</span>tuple<span class="op">&lt;</span><span class="dt">int</span><span class="op">,</span> <span class="dt">int</span><span class="op">,</span> <span class="dt">int</span><span class="op">&gt;&gt;</span> edges<span class="op">;</span></span>
<span id="cb1-80"><a href="#cb1-80" aria-hidden="true" tabindex="-1"></a>  edges<span class="op">.</span>reserve<span class="op">(</span>N <span class="op">*</span> <span class="dv">3</span><span class="op">);</span></span>
<span id="cb1-81"><a href="#cb1-81" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span><span class="at">const</span> <span class="kw">auto</span> <span class="op">&amp;</span>ax <span class="op">:</span> <span class="op">{</span>x<span class="op">,</span> y<span class="op">,</span> z<span class="op">})</span> <span class="op">{</span></span>
<span id="cb1-82"><a href="#cb1-82" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> it1 <span class="op">=</span> <span class="dv">0</span><span class="op">,</span> it2 <span class="op">=</span> <span class="dv">1</span><span class="op">;</span> it2 <span class="op">&lt;</span> N<span class="op">;</span> it1<span class="op">++,</span> it2<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb1-83"><a href="#cb1-83" aria-hidden="true" tabindex="-1"></a>      <span class="dt">int</span> v <span class="op">=</span> ax<span class="op">[</span>it1<span class="op">].</span>first<span class="op">;</span></span>
<span id="cb1-84"><a href="#cb1-84" aria-hidden="true" tabindex="-1"></a>      <span class="dt">int</span> u <span class="op">=</span> ax<span class="op">[</span>it2<span class="op">].</span>first<span class="op">;</span></span>
<span id="cb1-85"><a href="#cb1-85" aria-hidden="true" tabindex="-1"></a>      <span class="dt">int</span> w <span class="op">=</span> abs<span class="op">(</span>ax<span class="op">[</span>it1<span class="op">].</span>second <span class="op">-</span> ax<span class="op">[</span>it2<span class="op">].</span>second<span class="op">);</span></span>
<span id="cb1-86"><a href="#cb1-86" aria-hidden="true" tabindex="-1"></a>      edges<span class="op">.</span>emplace_back<span class="op">(</span>make_tuple<span class="op">(</span>w<span class="op">,</span> v<span class="op">,</span> u<span class="op">));</span></span>
<span id="cb1-87"><a href="#cb1-87" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-88"><a href="#cb1-88" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-89"><a href="#cb1-89" aria-hidden="true" tabindex="-1"></a>  <span class="co">// sort by weight, ascending</span></span>
<span id="cb1-90"><a href="#cb1-90" aria-hidden="true" tabindex="-1"></a>  <span class="bu">std::</span>sort<span class="op">(</span>edges<span class="op">.</span>begin<span class="op">(),</span> edges<span class="op">.</span>end<span class="op">(),</span></span>
<span id="cb1-91"><a href="#cb1-91" aria-hidden="true" tabindex="-1"></a>            <span class="op">[](</span><span class="kw">auto</span> a<span class="op">,</span> <span class="kw">auto</span> b<span class="op">)</span> <span class="op">-&gt;</span> <span class="dt">bool</span> <span class="op">{</span> <span class="cf">return</span> get<span class="op">&lt;</span><span class="dv">0</span><span class="op">&gt;(</span>a<span class="op">)</span> <span class="op">&lt;</span> get<span class="op">&lt;</span><span class="dv">0</span><span class="op">&gt;(</span>b<span class="op">);</span> <span class="op">});</span></span>
<span id="cb1-92"><a href="#cb1-92" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-93"><a href="#cb1-93" aria-hidden="true" tabindex="-1"></a>  <span class="co">// KruskalMST</span></span>
<span id="cb1-94"><a href="#cb1-94" aria-hidden="true" tabindex="-1"></a>  UnionFind uf<span class="op">(</span>N<span class="op">);</span></span>
<span id="cb1-95"><a href="#cb1-95" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> mstWeight <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb1-96"><a href="#cb1-96" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span><span class="kw">auto</span> <span class="op">[</span>w<span class="op">,</span> v<span class="op">,</span> u<span class="op">]</span> <span class="op">:</span> edges<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-97"><a href="#cb1-97" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>uf<span class="op">.</span>unite<span class="op">(</span>v<span class="op">,</span> u<span class="op">))</span> <span class="op">{</span></span>
<span id="cb1-98"><a href="#cb1-98" aria-hidden="true" tabindex="-1"></a>      mstWeight <span class="op">+=</span> w<span class="op">;</span></span>
<span id="cb1-99"><a href="#cb1-99" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-100"><a href="#cb1-100" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-101"><a href="#cb1-101" aria-hidden="true" tabindex="-1"></a>  cout <span class="op">&lt;&lt;</span> mstWeight<span class="op">;</span></span>
<span id="cb1-102"><a href="#cb1-102" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-103"><a href="#cb1-103" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb1-104"><a href="#cb1-104" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<table>
<thead>
<tr>
<th>메모리</th>
<th>시간</th>
</tr>
</thead>
<tbody>
<tr>
<td>10248 KB</td>
<td>88 ms</td>
</tr>
</tbody>
</table>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>증명은 생략한다<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
  </section>
</article>
]]></summary>
</entry>
<entry>
    <title>[BOJ] #1197 최소 스패닝 트리</title>
    <link href="https://blog.mskim.org/posts/5/boj-1197" />
    <id>https://blog.mskim.org/posts/5/boj-1197</id>
    <published>2024-09-21T20:00:00+09:00</published>
    <updated>2025-05-07T18:00:00+09:00</updated>
    <summary type="html"><![CDATA[<article>
  <section class="header">
    <span class="date">작성: <time datetime="2024-09-21T20:00:00+09:00">2024-09-21</time></span>
    
    /
    <span class="lastmod">수정: <time datetime="2025-05-07T18:00:00+09:00">2025-05-07</time></span>
    
  </section>
  <section class="article-body">
    <p>백준 <a href="https://www.acmicpc.net/problem/1197">1197번 - 최소 스패닝 트리</a>의
풀이다.</p>
<h2 id="tldr">TL;DR</h2>
<ul>
<li>희소 그래프에서는 Kruskal 알고리즘을, 밀집 그래프에서는 Prim 알고리즘을
사용하자.</li>
<li>Kruskal 알고리즘 구현 시 path compression 하나만으로 효율이 충분히 높아진다.
<ul>
<li>Union-by-rank, path halving은 유의미한 성능 개선이 없었다.</li>
</ul></li>
</ul>
<h2 id="답안">답안</h2>
<h3 id="kruskal">Kruskal</h3>
<p>그래프가 sparse할 것 같아서 우선 <a href="/posts/3/kruskal-algorithm">Kruskal</a>로
구현해봤다. 답안은 다음과 같다.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">/**</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span><span class="an">@brief</span><span class="co"> BOJ No. 1197 &quot;최소 스패닝 트리&quot;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co"> *</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span><span class="an">@author</span><span class="co"> Minseo Kim </span><span class="kw">&lt;kimminss0@outlook.kr&gt;</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="co"> * </span><span class="an">@details</span><span class="co"> Solve minimum spanning tree using Kruskal&#39;s algorithm with</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="co"> * union-find</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="co"> */</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;algorithm&gt;</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;iostream&gt;</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;vector&gt;</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="kw">using</span> <span class="kw">namespace</span> std<span class="op">;</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="kw">class</span> UnionFind <span class="op">{</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a><span class="kw">private</span><span class="op">:</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>  vector<span class="op">&lt;</span><span class="dt">int</span><span class="op">&gt;</span> parents<span class="op">;</span> <span class="co">// parent&#39;s id if non-root; negated rank if root.</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span><span class="op">:</span></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a>  UnionFind<span class="op">(</span><span class="dt">int</span> num_vertices<span class="op">)</span> <span class="op">:</span> parents<span class="op">(</span>num_vertices <span class="op">+</span> <span class="dv">1</span><span class="op">,</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{}</span></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a>  <span class="co">// path halving</span></span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> find<span class="op">(</span><span class="dt">int</span> vertex<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a>    <span class="co">// assert vertex &gt; 0</span></span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a>    <span class="cf">while</span> <span class="op">(</span>parents<span class="op">[</span>vertex<span class="op">]</span> <span class="op">&gt;</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a>      <span class="cf">if</span> <span class="op">(</span>parents<span class="op">[</span>parents<span class="op">[</span>vertex<span class="op">]]</span> <span class="op">&gt;</span> <span class="dv">0</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a>        parents<span class="op">[</span>vertex<span class="op">]</span> <span class="op">=</span> parents<span class="op">[</span>parents<span class="op">[</span>vertex<span class="op">]];</span></span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a>        vertex <span class="op">=</span> parents<span class="op">[</span>vertex<span class="op">];</span></span>
<span id="cb1-29"><a href="#cb1-29" aria-hidden="true" tabindex="-1"></a>      <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb1-30"><a href="#cb1-30" aria-hidden="true" tabindex="-1"></a>        <span class="cf">return</span> parents<span class="op">[</span>vertex<span class="op">];</span></span>
<span id="cb1-31"><a href="#cb1-31" aria-hidden="true" tabindex="-1"></a>      <span class="op">}</span></span>
<span id="cb1-32"><a href="#cb1-32" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-33"><a href="#cb1-33" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> vertex<span class="op">;</span></span>
<span id="cb1-34"><a href="#cb1-34" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-35"><a href="#cb1-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-36"><a href="#cb1-36" aria-hidden="true" tabindex="-1"></a>  <span class="co">// union</span></span>
<span id="cb1-37"><a href="#cb1-37" aria-hidden="true" tabindex="-1"></a>  <span class="dt">bool</span> unite<span class="op">(</span><span class="dt">int</span> vertex1<span class="op">,</span> <span class="dt">int</span> vertex2<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-38"><a href="#cb1-38" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> root1 <span class="op">=</span> find<span class="op">(</span>vertex1<span class="op">);</span></span>
<span id="cb1-39"><a href="#cb1-39" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> root2 <span class="op">=</span> find<span class="op">(</span>vertex2<span class="op">);</span></span>
<span id="cb1-40"><a href="#cb1-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-41"><a href="#cb1-41" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>root1 <span class="op">==</span> root2<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-42"><a href="#cb1-42" aria-hidden="true" tabindex="-1"></a>      <span class="cf">return</span> <span class="kw">false</span><span class="op">;</span></span>
<span id="cb1-43"><a href="#cb1-43" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-44"><a href="#cb1-44" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-45"><a href="#cb1-45" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> rank1 <span class="op">=</span> <span class="op">-</span>parents<span class="op">[</span>root1<span class="op">];</span></span>
<span id="cb1-46"><a href="#cb1-46" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> rank2 <span class="op">=</span> <span class="op">-</span>parents<span class="op">[</span>root2<span class="op">];</span></span>
<span id="cb1-47"><a href="#cb1-47" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-48"><a href="#cb1-48" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>rank1 <span class="op">&gt;</span> rank2<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-49"><a href="#cb1-49" aria-hidden="true" tabindex="-1"></a>      parents<span class="op">[</span>root2<span class="op">]</span> <span class="op">=</span> root1<span class="op">;</span></span>
<span id="cb1-50"><a href="#cb1-50" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb1-51"><a href="#cb1-51" aria-hidden="true" tabindex="-1"></a>      parents<span class="op">[</span>root1<span class="op">]</span> <span class="op">=</span> root2<span class="op">;</span></span>
<span id="cb1-52"><a href="#cb1-52" aria-hidden="true" tabindex="-1"></a>      <span class="cf">if</span> <span class="op">(</span>rank1 <span class="op">==</span> rank2<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-53"><a href="#cb1-53" aria-hidden="true" tabindex="-1"></a>        parents<span class="op">[</span>root2<span class="op">]--;</span></span>
<span id="cb1-54"><a href="#cb1-54" aria-hidden="true" tabindex="-1"></a>      <span class="op">}</span></span>
<span id="cb1-55"><a href="#cb1-55" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-56"><a href="#cb1-56" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="kw">true</span><span class="op">;</span></span>
<span id="cb1-57"><a href="#cb1-57" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-58"><a href="#cb1-58" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span>
<span id="cb1-59"><a href="#cb1-59" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-60"><a href="#cb1-60" aria-hidden="true" tabindex="-1"></a><span class="kw">struct</span> Edge <span class="op">{</span></span>
<span id="cb1-61"><a href="#cb1-61" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> vertex1<span class="op">;</span></span>
<span id="cb1-62"><a href="#cb1-62" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> vertex2<span class="op">;</span></span>
<span id="cb1-63"><a href="#cb1-63" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> weight<span class="op">;</span></span>
<span id="cb1-64"><a href="#cb1-64" aria-hidden="true" tabindex="-1"></a>  <span class="dt">bool</span> <span class="kw">operator</span><span class="op">&lt;(</span><span class="at">const</span> Edge <span class="op">&amp;</span>that<span class="op">)</span> <span class="at">const</span> <span class="op">{</span> <span class="cf">return</span> <span class="kw">this</span><span class="op">-&gt;</span>weight <span class="op">&lt;</span> that<span class="op">.</span>weight<span class="op">;</span> <span class="op">}</span></span>
<span id="cb1-65"><a href="#cb1-65" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span>
<span id="cb1-66"><a href="#cb1-66" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-67"><a href="#cb1-67" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-68"><a href="#cb1-68" aria-hidden="true" tabindex="-1"></a>  ios_base<span class="op">::</span>sync_with_stdio<span class="op">(</span><span class="kw">false</span><span class="op">);</span></span>
<span id="cb1-69"><a href="#cb1-69" aria-hidden="true" tabindex="-1"></a>  cin<span class="op">.</span>tie<span class="op">(</span><span class="kw">nullptr</span><span class="op">);</span></span>
<span id="cb1-70"><a href="#cb1-70" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-71"><a href="#cb1-71" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> num_vertices<span class="op">,</span> num_edges<span class="op">;</span></span>
<span id="cb1-72"><a href="#cb1-72" aria-hidden="true" tabindex="-1"></a>  cin <span class="op">&gt;&gt;</span> num_vertices <span class="op">&gt;&gt;</span> num_edges<span class="op">;</span></span>
<span id="cb1-73"><a href="#cb1-73" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-74"><a href="#cb1-74" aria-hidden="true" tabindex="-1"></a>  UnionFind uf<span class="op">{</span>num_vertices<span class="op">};</span></span>
<span id="cb1-75"><a href="#cb1-75" aria-hidden="true" tabindex="-1"></a>  vector<span class="op">&lt;</span>Edge<span class="op">&gt;</span> edges<span class="op">;</span></span>
<span id="cb1-76"><a href="#cb1-76" aria-hidden="true" tabindex="-1"></a>  edges<span class="op">.</span>reserve<span class="op">(</span>num_edges<span class="op">);</span></span>
<span id="cb1-77"><a href="#cb1-77" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-78"><a href="#cb1-78" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> num_edges<span class="op">;</span> i<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb1-79"><a href="#cb1-79" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> v<span class="op">,</span> u<span class="op">,</span> w<span class="op">;</span></span>
<span id="cb1-80"><a href="#cb1-80" aria-hidden="true" tabindex="-1"></a>    cin <span class="op">&gt;&gt;</span> v <span class="op">&gt;&gt;</span> u <span class="op">&gt;&gt;</span> w<span class="op">;</span></span>
<span id="cb1-81"><a href="#cb1-81" aria-hidden="true" tabindex="-1"></a>    edges<span class="op">.</span>emplace_back<span class="op">(</span>Edge<span class="op">{</span>v<span class="op">,</span> u<span class="op">,</span> w<span class="op">});</span></span>
<span id="cb1-82"><a href="#cb1-82" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-83"><a href="#cb1-83" aria-hidden="true" tabindex="-1"></a>  sort<span class="op">(</span>edges<span class="op">.</span>begin<span class="op">(),</span> edges<span class="op">.</span>end<span class="op">());</span></span>
<span id="cb1-84"><a href="#cb1-84" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-85"><a href="#cb1-85" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> mst_weight <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb1-86"><a href="#cb1-86" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span><span class="kw">auto</span> <span class="op">[</span>u<span class="op">,</span> v<span class="op">,</span> weight<span class="op">]</span> <span class="op">:</span> edges<span class="op">)</span> <span class="op">{</span></span>
<span id="cb1-87"><a href="#cb1-87" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>uf<span class="op">.</span>unite<span class="op">(</span>u<span class="op">,</span> v<span class="op">))</span> <span class="op">{</span></span>
<span id="cb1-88"><a href="#cb1-88" aria-hidden="true" tabindex="-1"></a>      mst_weight <span class="op">+=</span> weight<span class="op">;</span></span>
<span id="cb1-89"><a href="#cb1-89" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb1-90"><a href="#cb1-90" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb1-91"><a href="#cb1-91" aria-hidden="true" tabindex="-1"></a>  cout <span class="op">&lt;&lt;</span> mst_weight<span class="op">;</span></span>
<span id="cb1-92"><a href="#cb1-92" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb1-93"><a href="#cb1-93" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<table>
<thead>
<tr>
<th>메모리</th>
<th>시간</th>
</tr>
</thead>
<tbody>
<tr>
<td>3356 KB</td>
<td>32 ms</td>
</tr>
</tbody>
</table>
<p>Kruskal 알고리즘을 구현하기 위해 union-find 자료구조를 먼저 구현했다. 원래
union-find는 <code>makeset</code> 연산을 지원하지만 위 구현에서는 생성자가 그 역할을
대신하고, 생성자 외부에서 makeset 연산이 필요하지 않기에 public 메서드로 노출할
필요가 없다.</p>
<p>Union-by-rank와 <a href="/posts/2/union-find#pseudocode">path halving</a>으로 최적화했다. 일반적인 path
compression은 재귀 호출 시 call stack이 쌓이는 문제를 피하고 싶었다.</p>
<p>그러나, 사실 최적화는 path compression 하나로도 충분했다. 위 코드에서 path
halving 대신 path compression으로 구현을 변경해봤으나 실행시간 및 메모리 사용량
차이가 없었다. union-by-rank 또한 적용하지 않은 것과 차이가 없었다.</p>
<p>Union-by-rank와 path halving이 효과적이지 못했던 이유를 추측하자면, find 연산이
매우 자주 일어나므로 path compression 과정에서 재귀가 깊어지기 어렵고, path
compression에 의해 트리의 높이가 매우 낮게 유지되므로 union-by-rank을 하지
않아도 union 이후에 트리의 높이가 유의미하게 증가하지 않기 때문인 듯하다.</p>
<p>Path compression 외 다른 최적화를 적용하지 않은 답안은
<a href="http://boj.kr/356c42ab3a2c4f62afc4d3d1f7cbbcc9">이 링크</a>에서 확인할 수 있다.</p>
<h3 id="prim">Prim</h3>
<p>희소 그래프에서 Prim과 Kruskal의 성능 차이가 궁금하여
<a href="/posts/4/prim-algorithm">Prim</a>을 추가 구현하였다. Adjacency list와 priority
queue로 답안을 작성했다.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode cpp"><code class="sourceCode cpp"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;algorithm&gt;</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;iostream&gt;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;queue&gt;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;utility&gt;</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="pp">#include </span><span class="im">&lt;vector&gt;</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="kw">using</span> <span class="kw">namespace</span> std<span class="op">;</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="dt">int</span> main<span class="op">(</span><span class="dt">void</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>  ios_base<span class="op">::</span>sync_with_stdio<span class="op">(</span><span class="kw">false</span><span class="op">);</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>  cin<span class="op">.</span>tie<span class="op">(</span><span class="kw">nullptr</span><span class="op">);</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> V<span class="op">,</span> E<span class="op">;</span></span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a>  cin <span class="op">&gt;&gt;</span> V <span class="op">&gt;&gt;</span> E<span class="op">;</span></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a>  vector<span class="op">&lt;</span>vector<span class="op">&lt;</span>pair<span class="op">&lt;</span><span class="dt">int</span><span class="op">,</span> <span class="dt">int</span><span class="op">&gt;&gt;&gt;</span> adj<span class="op">(</span>V <span class="op">+</span> <span class="dv">1</span><span class="op">);</span></span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> E<span class="op">;</span> i<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a>    <span class="dt">int</span> v<span class="op">,</span> u<span class="op">,</span> w<span class="op">;</span></span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a>    cin <span class="op">&gt;&gt;</span> v <span class="op">&gt;&gt;</span> u <span class="op">&gt;&gt;</span> w<span class="op">;</span></span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a>    adj<span class="op">[</span>v<span class="op">].</span>emplace_back<span class="op">(</span>make_pair<span class="op">(</span>u<span class="op">,</span> w<span class="op">));</span></span>
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true" tabindex="-1"></a>    adj<span class="op">[</span>u<span class="op">].</span>emplace_back<span class="op">(</span>make_pair<span class="op">(</span>v<span class="op">,</span> w<span class="op">));</span></span>
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb2-23"><a href="#cb2-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-24"><a href="#cb2-24" aria-hidden="true" tabindex="-1"></a>  <span class="at">const</span> <span class="dt">int</span> INF <span class="op">=</span> <span class="dv">1000001</span><span class="op">;</span></span>
<span id="cb2-25"><a href="#cb2-25" aria-hidden="true" tabindex="-1"></a>  vector<span class="op">&lt;</span><span class="dt">bool</span><span class="op">&gt;</span> vertex_in_mst<span class="op">(</span>V <span class="op">+</span> <span class="dv">1</span><span class="op">,</span> <span class="kw">false</span><span class="op">);</span></span>
<span id="cb2-26"><a href="#cb2-26" aria-hidden="true" tabindex="-1"></a>  vector<span class="op">&lt;</span><span class="dt">int</span><span class="op">&gt;</span> vertex_cost<span class="op">(</span>V <span class="op">+</span> <span class="dv">1</span><span class="op">,</span> INF<span class="op">);</span></span>
<span id="cb2-27"><a href="#cb2-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-28"><a href="#cb2-28" aria-hidden="true" tabindex="-1"></a>  vector<span class="op">&lt;</span>pair<span class="op">&lt;</span><span class="dt">int</span><span class="op">,</span> <span class="dt">int</span><span class="op">&gt;&gt;</span> _data<span class="op">;</span></span>
<span id="cb2-29"><a href="#cb2-29" aria-hidden="true" tabindex="-1"></a>  _data<span class="op">.</span>reserve<span class="op">(</span>V <span class="op">+</span> <span class="dv">1</span><span class="op">);</span></span>
<span id="cb2-30"><a href="#cb2-30" aria-hidden="true" tabindex="-1"></a>  <span class="cf">for</span> <span class="op">(</span><span class="dt">int</span> i <span class="op">=</span> <span class="dv">1</span><span class="op">;</span> i <span class="op">&lt;=</span> V<span class="op">;</span> i<span class="op">++)</span> <span class="op">{</span></span>
<span id="cb2-31"><a href="#cb2-31" aria-hidden="true" tabindex="-1"></a>    _data<span class="op">.</span>emplace_back<span class="op">(</span>make_pair<span class="op">(</span>i<span class="op">,</span> INF<span class="op">));</span></span>
<span id="cb2-32"><a href="#cb2-32" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb2-33"><a href="#cb2-33" aria-hidden="true" tabindex="-1"></a>  priority_queue pq<span class="op">(</span></span>
<span id="cb2-34"><a href="#cb2-34" aria-hidden="true" tabindex="-1"></a>      <span class="op">[](</span><span class="at">const</span> <span class="kw">auto</span> <span class="op">&amp;</span>a<span class="op">,</span> <span class="at">const</span> <span class="kw">auto</span> <span class="op">&amp;</span>b<span class="op">)</span> <span class="op">-&gt;</span> <span class="dt">bool</span> <span class="op">{</span> <span class="cf">return</span> a<span class="op">.</span>second <span class="op">&gt;</span> b<span class="op">.</span>second<span class="op">;</span> <span class="op">},</span></span>
<span id="cb2-35"><a href="#cb2-35" aria-hidden="true" tabindex="-1"></a>      <span class="bu">std::</span>move<span class="op">(</span>_data<span class="op">));</span></span>
<span id="cb2-36"><a href="#cb2-36" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-37"><a href="#cb2-37" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> mst_weight <span class="op">=</span> <span class="op">-</span>INF<span class="op">;</span></span>
<span id="cb2-38"><a href="#cb2-38" aria-hidden="true" tabindex="-1"></a>  <span class="dt">int</span> i <span class="op">=</span> V<span class="op">;</span></span>
<span id="cb2-39"><a href="#cb2-39" aria-hidden="true" tabindex="-1"></a>  <span class="cf">while</span> <span class="op">(</span>i<span class="op">)</span> <span class="op">{</span></span>
<span id="cb2-40"><a href="#cb2-40" aria-hidden="true" tabindex="-1"></a>    <span class="at">const</span> <span class="kw">auto</span> <span class="op">[</span>v<span class="op">,</span> w<span class="op">]</span> <span class="op">=</span> pq<span class="op">.</span>top<span class="op">();</span></span>
<span id="cb2-41"><a href="#cb2-41" aria-hidden="true" tabindex="-1"></a>    pq<span class="op">.</span>pop<span class="op">();</span></span>
<span id="cb2-42"><a href="#cb2-42" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> <span class="op">(</span>vertex_in_mst<span class="op">[</span>v<span class="op">])</span></span>
<span id="cb2-43"><a href="#cb2-43" aria-hidden="true" tabindex="-1"></a>      <span class="cf">continue</span><span class="op">;</span></span>
<span id="cb2-44"><a href="#cb2-44" aria-hidden="true" tabindex="-1"></a>    vertex_in_mst<span class="op">[</span>v<span class="op">]</span> <span class="op">=</span> <span class="kw">true</span><span class="op">;</span></span>
<span id="cb2-45"><a href="#cb2-45" aria-hidden="true" tabindex="-1"></a>    mst_weight <span class="op">+=</span> w<span class="op">;</span></span>
<span id="cb2-46"><a href="#cb2-46" aria-hidden="true" tabindex="-1"></a>    i<span class="op">--;</span></span>
<span id="cb2-47"><a href="#cb2-47" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> <span class="op">(</span><span class="kw">auto</span> <span class="op">[</span>u<span class="op">,</span> w<span class="op">]</span> <span class="op">:</span> adj<span class="op">[</span>v<span class="op">])</span> <span class="op">{</span></span>
<span id="cb2-48"><a href="#cb2-48" aria-hidden="true" tabindex="-1"></a>      <span class="cf">if</span> <span class="op">(</span>w <span class="op">&lt;</span> vertex_cost<span class="op">[</span>u<span class="op">])</span> <span class="op">{</span></span>
<span id="cb2-49"><a href="#cb2-49" aria-hidden="true" tabindex="-1"></a>        vertex_cost<span class="op">[</span>u<span class="op">]</span> <span class="op">=</span> w<span class="op">;</span></span>
<span id="cb2-50"><a href="#cb2-50" aria-hidden="true" tabindex="-1"></a>        pq<span class="op">.</span>emplace<span class="op">(</span>make_pair<span class="op">(</span>u<span class="op">,</span> w<span class="op">));</span></span>
<span id="cb2-51"><a href="#cb2-51" aria-hidden="true" tabindex="-1"></a>      <span class="op">}</span></span>
<span id="cb2-52"><a href="#cb2-52" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span></span>
<span id="cb2-53"><a href="#cb2-53" aria-hidden="true" tabindex="-1"></a>  <span class="op">}</span></span>
<span id="cb2-54"><a href="#cb2-54" aria-hidden="true" tabindex="-1"></a>  cout <span class="op">&lt;&lt;</span> mst_weight<span class="op">;</span></span>
<span id="cb2-55"><a href="#cb2-55" aria-hidden="true" tabindex="-1"></a>  <span class="cf">return</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb2-56"><a href="#cb2-56" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<table>
<thead>
<tr>
<th>메모리</th>
<th>시간</th>
</tr>
</thead>
<tbody>
<tr>
<td>5684 KB</td>
<td>36 ms</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>Edit(2024-09-24):</strong> 위 구현에서 priority queue에 모든 vertices에 대해
<code>{i, INF}</code>를 삽입하고 시작하는 것을 볼 수 있다. 그러나 <strong>시작 정점 하나만
priority queue에 넣고 시작하는 것이 효율적</strong>이다. 구체적으로는 시작 정점
v<sub>0</sub>에 대해 cost를 0으로 설정하여 <code>{0, 0}</code>을 삽입하고, <code>mst_weight</code> 또한
<code>-INF</code> 대신 <code>0</code>으로 초기화할 수 있다.</p>
</blockquote>
<p>일반적으로 희소 그래프에서는 Kruskal 알고리즘이 Prim 알고리즘보다 효율적이라고
알려져 있다. 실제로 이 문제에서도 메모리 사용량과 실행 시간 모두 Kruskal
알고리즘에 비해 좋지 못했다. 확실히 희소 그래프에서는 힙의 오버헤드와 같은
요인으로 인해 Prim 알고리즘보다 Kruskal 알고리즘이 더 효율적인 것으로 보인다.</p>
  </section>
</article>
]]></summary>
</entry>
<entry>
    <title>Prim 알고리즘</title>
    <link href="https://blog.mskim.org/posts/4/prim-algorithm" />
    <id>https://blog.mskim.org/posts/4/prim-algorithm</id>
    <published>2024-09-18T18:00:00+09:00</published>
    <updated>2024-09-18T09:00:00Z</updated>
    <summary type="html"><![CDATA[<article>
  <section class="header">
    <span class="date">작성: <time datetime="2024-09-18T18:00:00+09:00">2024-09-18</time></span>
    
  </section>
  <section class="article-body">
    <blockquote>
<p>PS(Problem Solving) 문제 풀이를 위해 작성한 글입니다. 이론적인 설명은
배제했습니다.</p>
</blockquote>
<h2 id="개요">개요</h2>
<p>Prim 알고리즘은 <a href="/posts/1/minimum-spanning-tree">minimum spanning tree</a>를 찾는
그리디 알고리즘이다. 임의의 시작 정점에서부터 트리를 성장시키며 MST를 찾으며,
priority queue의 이점을 활용한다.</p>
<h2 id="알고리즘">알고리즘</h2>
<p>프림 알고리즘은 임의의 시작 정점에서 시작하여 트리를 확장해나가는데, 트리에
인접한 간선 중 최소 비용인 것을 선택한다. 트리에 인접한 최소 가중치 간선을 찾기
위해 <strong>연결 비용</strong>이라는 개념을 도입한다.</p>
<p>연결 비용은 각 정점에 대해 정의되며, 해당 정점에서 트리와 연결될 수 있는 간선들
중 가장 작은 가중치로 결정된다. 만약 트리와 연결되는 간선이 존재하지 않으면,
연결 비용은 ∞로 설정된다. 트리에 인접한 간선들 중 연결 비용이 가장 작은 정점과
간선을 선택함으로써 최소 가중치의 간선을 찾을 수 있다.</p>
<p>연결 비용을 매번 계산하면 비효율적이므로, 미리 계산해두고 필요시 갱신한다.
구체적으로는 새로운 정점이 트리에 추가되었을 때, 그 정점과 연결된 모든 인접
정점들에 대해 연결 비용을 다시 계산한다.</p>
<p>연결 비용을 다시 계산할 때도 모든 간선의 가중치를 비교할 필요는 없다. 트리에
추가된 정점 <span class="math inline">\(v\)</span>와 그 인접 정점 <span class="math inline">\(w\)</span>에 대해, <span class="math inline">\(w\)</span>의 새로운 연결 비용은 기존 연결
비용과 간선 <span class="math inline">\(vw\)</span>의 가중치 중 작은 값이다.</p>
<h2 id="자료구조">자료구조</h2>
<p>연결 비용은 priority queue로 관리된다. priority queue는 데이터가 항상 정렬된
상태로 유지되며, 데이터의 삽입과 추출이 빈번한 상황에서 효율적이다. 연결 비용은
자주 갱신되며, 최소값을 반복적으로 검색해야 하므로 priority queue를 사용하는
것이 적합하다.</p>
<p>구체적으로, priority queue는 <strong>(정점, 연결 비용, 간선)</strong><a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>으로 구성된 튜플을
관리한다. 이때 연결 비용이 가장 낮은 튜플이 우선적으로 선택된다. 또한, 연결
비용이나 간선이 갱신될 때마다 새로운 튜플을 priority queue에 삽입한다.</p>
<p>연결 비용과 간선을 직접 수정하지 않고 새로운 튜플을 추가하는 이유는 priority
queue에서 이미 삽입된 데이터를 수정하기가 어렵기 때문이다. 또한, 연결 비용은
항상 감소하는 방향으로만 갱신되므로, 새로 삽입된 튜플이 기존의 튜플보다 높은
우선순위를 갖는 것이 보장된다. 추가로, 이전에 삽입된 outdated된 튜플을 무시하기
위해 별도의 lookup list를 사용하여 보완한다.</p>
<h2 id="구현">구현</h2>
<blockquote>
<p>구현은 BOJ 문제 답안을 참고한다.</p>
</blockquote>
<ul>
<li><a href="/posts/5/boj-1197#prim">[BOJ] #1197 최소 스패닝 트리</a></li>
</ul>
<h2 id="기타">기타</h2>
<h3 id="kruskal-vs-prim">Kruskal vs Prim</h3>
<p>Kruskal 알고리즘 또한 MST를 찾는 알고리즘이다. Kruskal은 희소 그래프에서,
Prim은 밀집 그래프에서 효율적이라고 알려져 있다. 이는 시간복잡도와도 관련이
있지만 그보다도 Kruskal 알고리즘에 쓰이는 union-find 자료구조가 Prim
알고리즘에서 쓰이는 priority queue보다 overhead가 적기 때문인 것으로 보인다.</p>
<p>시간복잡도만으로는 Kruskal 알고리즘이 희소 그래프에서 효율적인 이유를 설명하기
어려울 수 있다. 두 알고리즘의 시간복잡도는 Kruskal 알고리즘이
<span class="math inline">\(\text{O}(\left| E \right|\log{\left| E \right|})\)</span>, Prim 알고리즘을 이진 힙으로 구현 시
<span class="math inline">\(\text{O}(\left| E \right|\log{\left| V \right|})\)</span>이다. 정점 개수가 <span class="math inline">\(\left| V \right|\)</span>인 트리의 간선 수는
<span class="math inline">\(\left| V-1 \right|\)</span>이므로 임의의 연결 그래프는 <span class="math inline">\(\left| E \right| \geq \left| V \right|-1\)</span>이기 때문에,
희소 그래프라 하더라도 대부분의 연결 그래프는 <span class="math inline">\(\left| E \right| \gt \left| V \right|\)</span>이다.
<span class="math inline">\(\left| E \right|\log{\left| E \right|} &gt; \left| E \right|\log{\left| V \right|}\)</span>이므로 Prim 알고리즘이 Kruskal
알고리즘보다 빨라야 한다는 결론이 도출된다.</p>
<p>이는 시간 복잡도만으로 알고리즘의 속도를 평가할 수 없음을 시사한다. Big-O
표기법에서 상수 계수 등은 생략하기 때문이다.</p>
<h2 id="참조">참조</h2>
<ul>
<li><a href="https://algs4.cs.princeton.edu/43mst/">프린스턴 대학 알고리즘 강의 - Minimum Spanning Trees</a></li>
<li><a href="https://en.wikipedia.org/wiki/Prim%27s_algorithm">Wikipedia - Prim’s algorithm</a></li>
</ul>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>실제로는 <strong>(정점, 연결 비용, 다른 정점)</strong>으로 충분하다. 간선이 이미
정점과 연결 비용(간선 가중치)를 포함하고 있기 때문이다. 이 튜플은 간선과
1:1 대응된다.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
  </section>
</article>
]]></summary>
</entry>
<entry>
    <title>Kruskal 알고리즘</title>
    <link href="https://blog.mskim.org/posts/3/kruskal-algorithm" />
    <id>https://blog.mskim.org/posts/3/kruskal-algorithm</id>
    <published>2024-09-18T17:00:00+09:00</published>
    <updated>2025-05-07T18:00:00+09:00</updated>
    <summary type="html"><![CDATA[<article>
  <section class="header">
    <span class="date">작성: <time datetime="2024-09-18T17:00:00+09:00">2024-09-18</time></span>
    
    /
    <span class="lastmod">수정: <time datetime="2025-05-07T18:00:00+09:00">2025-05-07</time></span>
    
  </section>
  <section class="article-body">
    <blockquote>
<p>PS(Problem Solving) 문제 풀이를 위해 작성한 글입니다. 이론적인 설명은
배제했습니다.</p>
</blockquote>
<h2 id="개요">개요</h2>
<p>Kruskal 알고리즘은 <a href="/posts/1/minimum-spanning-tree">minimum spanning tree</a>를
찾는 그리디 알고리즘이다. 간선을 정렬하여 최소 비용 간선부터 선택하며, 이
과정에서 사이클이 형성되지 않도록 한다. 사이클의 형성 여부를 판별하기 위해
Union-find 자료구조를 사용한다.</p>
<h2 id="알고리즘">알고리즘</h2>
<p>알고리즘의 핵심은 매 순간 <strong>최소 비용 간선을 선택</strong>하면서, <strong>사이클이 형성되지
않도록</strong> 주의하는 것이다.</p>
<p>매번 최소 비용 간선을 고른다는 점에서 알고리즘이 탐욕적(greedy)임을 알 수 있다.
이런 접근이 가능한 이유는 그래프의 cut property에서 기인한다. 증명 과정은
생략하겠다.</p>
<p>사이클을 피하려면 같은 컴포넌트에 속하는 정점 사이에는 간선을 추가하면 안 된다.
다른 표현으로는, 두 정점 간의 연결성(connectivity)<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>를 검사하여
disconnected인 경우에만 간선을 추가할 수 있다. 이와 같은 연결성 문제를
효율적으로 다루기 위해 <strong>union-find</strong> 자료구조를 사용한다.</p>
<p>위 내용을 정리하면 다음과 같다.</p>
<ul>
<li>간선을 정렬하여 가중치가 작은 간선부터 선택한다.</li>
<li>해당 간선을 추가할 때 사이클이 형성되는지 확인한다. 사이클 형성 여부를
확인하기 위해 union-find 자료구조를 사용한다.</li>
</ul>
<h2 id="자료구조">자료구조</h2>
<h3 id="union-find">Union-find</h3>
<p><a href="/posts/2/union-find">Union-find</a>는 disjoint set<a href="#fn2" class="footnote-ref" id="fnref2" role="doc-noteref"><sup>2</sup></a>의 collection을 나타내는
자료구조다. 다음 3가지 연산을 지원한다.</p>
<ul>
<li><code>makeset(x)</code>: x를 유일한 원소로 두는 새로운 집합 생성. 초기화를 위해 사용.</li>
<li><code>find(x)</code>: x가 속한 집합을 반환</li>
<li><code>union(x, y)</code>: x, y가 속한 두 집합을 병합</li>
</ul>
<p>그래프의 각 컴포넌트는 하나의 disjoint set으로 볼 수 있다. 두 정점이 서로 다른
컴포넌트에 있는지 확인할 때는 <code>find</code> 연산을 사용하고, 간선 {u, v}<a href="#fn3" class="footnote-ref" id="fnref3" role="doc-noteref"><sup>3</sup></a>를 선택한
이후 두 컴포넌트를 병합하기 위해 <code>union(u, v)</code> 연산을 사용할 수 있다.</p>
<blockquote>
<p><strong>Note.</strong> Union-find 자료구조는 그래프를 완전히 표현하지 않는다. 정점과
컴포넌트의 소속 관계만 다룰 뿐, 간선 정보는 직접적으로 관리하지 않는다.</p>
</blockquote>
<h2 id="의사코드">의사코드</h2>
<p>위에서 논의한 내용을 종합하여 Kruskal 알고리즘을 단계별로 나누면 다음과 같다.</p>
<ul>
<li>그래프의 모든 컴포넌트가 하나의 정점만을 가지도록 초기화한다.</li>
<li>가중치가 작은 간선부터 순회한다.
<ul>
<li>양 끝 정점이 서로 연결되어있는지 확인한다.</li>
<li>연결되지 않았으면, 두 정점이 속하는 컴포넌트를 병합하고 간선은
저장해둔다.</li>
</ul></li>
<li>간선을 모두 방문한 이후, 저장해둔 간선들은 MST를 이룬다.</li>
</ul>
<p>다음은 알고리즘의 의사코드다.</p>
<pre><code>function Kruskal(G = (V, E))
    X := {}
    for each vertex u ∈ V
        MAKESET(u)
    for each {u, v} ∈ E ordered by weight increasing
        if FIND(u) ≠ FIND(v)
            add {u, v} to X
            UNION(u, v)
    T := (V, X)
    return T</code></pre>
<blockquote>
<p>Union-find의 의사코드는 <a href="/posts/2/union-find#pseudocode">해당 문서</a> 를 참조한다.</p>
</blockquote>
<h2 id="구현">구현</h2>
<blockquote>
<p>구현은 BOJ 문제 답안을 참고한다.</p>
</blockquote>
<ul>
<li><a href="/posts/5/boj-1197#kruskal">[BOJ] #1197 최소 스패닝 트리</a></li>
</ul>
<h2 id="기타">기타</h2>
<h3 id="kruskal-vs-prim">Kruskal vs Prim</h3>
<ul>
<li>Prim 알고리즘 문서 참조 <a href="/posts/4/prim-algorithm#kruskal-vs-prim">링크</a></li>
</ul>
<h2 id="참조">참조</h2>
<ul>
<li><a href="https://algs4.cs.princeton.edu/43mst/">프린스턴 대학 알고리즘 강의 - Minimum Spanning Trees</a></li>
<li><a href="https://rntlqvnf.github.io/lecture%20notes/algorithm-5th-week-1/">블로그 1</a></li>
</ul>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>무방향 그래프에서 두 정점 v와 u 사이에 경로가 존재하면, 이들을
<strong>연결되었다(connected)</strong>고 정의한다. 반대로, 경로가 존재하지 않으면
<strong>연결되지 않았다(disconnected)</strong>고 정의한다.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn2"><p>서로소 집합. 공통 원소가 없는 두 집합을 말한다.<a href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
<li id="fn3"><p>u, v를 양 끝 정점으로 하는 간선<a href="#fnref3" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
  </section>
</article>
]]></summary>
</entry>
<entry>
    <title>Union-find</title>
    <link href="https://blog.mskim.org/posts/2/union-find" />
    <id>https://blog.mskim.org/posts/2/union-find</id>
    <published>2024-09-14T17:00:00+09:00</published>
    <updated>2024-09-14T08:00:00Z</updated>
    <summary type="html"><![CDATA[<article>
  <section class="header">
    <span class="date">작성: <time datetime="2024-09-14T17:00:00+09:00">2024-09-14</time></span>
    
  </section>
  <section class="article-body">
    <blockquote>
<p>PS(Problem Solving) 문제 풀이를 위해 작성한 글입니다. 이론적인 설명은
배제했습니다.</p>
</blockquote>
<h2 id="개요">개요</h2>
<p>Union-find는 disjoint set<a href="#fn1" class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a>의 collection을 나타내는 자료구조다. 다르게
표현하면, <strong>집합의 파티션</strong>을 나타내는 자료구조다.</p>
<p>Union-find의 주요 개념은 다음과 같다.</p>
<ul>
<li>Union-find는 집합을 트리로 나타낸다.</li>
<li><code>union</code>은 트리를 합치는 연산이다.</li>
<li><code>find</code>는 트리의 root를 찾는 연산이다.</li>
<li>union by rank, path compression으로 최적화한다.</li>
<li>rank는 height와 유사하지만 다른 개념이다.</li>
<li>path splitting과 path halving은 메모리 효율적인 경로 압축 알고리즘이다.</li>
</ul>
<h2 id="지원하는-연산">지원하는 연산</h2>
<p>Union-find API는 다음 연산을 지원한다.</p>
<ul>
<li><code>makeset(x)</code>: x를 유일한 원소로 두는 새로운 집합 생성. 자료구조에 새로운
원소를 추가할 때 사용한다.</li>
<li><code>find(x)</code>: x가 속한 집합을 반환</li>
<li><code>union(x, y)</code>: x, y가 속한 두 집합을 병합</li>
</ul>
<p>Union-find는 <strong>집합을 트리 형태로 구현</strong>한다. <code>union</code>은 트리를 합치는 연산이고,
<code>find</code>는 트리의 root를 찾는 연산이다. 일반적으로 다음과 같이 구현한다.</p>
<ul>
<li><code>find(x)</code>는 root에 도달할 때까지 parent를 타고 올라간다.</li>
<li><code>union(x, y)</code>는 두 트리의 root를 찾아 어느 하나를 다른 하나의 자식으로
편입한다.</li>
</ul>
<h2 id="최적화-기법">최적화 기법</h2>
<p><code>find</code> 연산은 flat한 트리에서 더 빠르다. 최악의 경우 트리가 리스트와 같을 수
있으며, 이 경우 <code>find</code>는 모든 노드를 순회한다. 트리의 height를 낮게 유지하는
최적화 기법을 알아보겠다.</p>
<h3 id="union-by-rank">Union by rank</h3>
<p><code>union</code> 연산에서 rank가 작은 tree의 root를 rank가 큰 tree의 root의 자식으로
편입한다. 이로써 트리의 rank를 낮게 유지한다.</p>
<p><strong>rank</strong>는 height의 upper bound로, height와 일치하지는 않지만 효율을 위해
도입한다. 특징은 다음과 같다.</p>
<ul>
<li>새로 초기화된 node의 rank는 0이다.</li>
<li>root가 u, v인 두 트리를 병합할 때 다음을 따른다.
<ul>
<li>u, v의 rank가 다르면 작은 것을 큰 것의 자식으로 편입한다.</li>
<li>u, v의 rank가 같으면 어느 하나를 부모로 만들고 rank를 1 더한다.</li>
</ul></li>
<li>height와 달리 rank가 업데이트되지 않는 경우:
<ul>
<li>root가 아닌 node는 rank를 업데이트하지 않는다.</li>
<li><a href="#path-compression">Path compression</a> 과정에서 height가 변해도 rank를
업데이트하지 않는다.</li>
</ul></li>
</ul>
<h3 id="경로-압축path-compression">경로 압축(Path compression)</h3>
<p><code>find</code> 연산 과정에서 트리를 평탄화(flatten)하는 작업을 같이 수행한다. <code>find(x)</code>
호출 시, x와 그 조상들을 한 번에 root로 직접 연결한다. 알고리즘의 설명은
후술한다.</p>
<h2 id="의사코드">의사코드</h2>
<p>다음은 <code>makeset(x)</code>의 의사코드다.</p>
<pre><code>function makeset(x)
    x.parent := x
    x.rank := 0</code></pre>
<p>다음은 union by rank로 구현한 <code>union(x, y)</code>의 의사코드다.</p>
<pre><code>function union(x, y)
    root_x := find(x)
    root_y := find(y)
    if rank(root_x) &gt; rank(root_y)
        root_y.parent := root_x
    else
        root_x.parent := root_y
        if root_x.rank == root_y.rank
            root_y.rank := root_y.rank + 1</code></pre>
<p>다음은 <code>find(x)</code>의 의사코드다. Path compression을 적용하지 않았다.</p>
<pre><code>function find(x)
    while x ≠ x.parent
        x := x.parent
    return x</code></pre>
<p>Path compression을 구현한 <code>find</code>의 의사코드는 다음과 같다.</p>
<pre><code>function find(x)
    if x.parent ≠ x
        x.parent := find(x.parent)
        return x.parent
    else
        return x</code></pre>
<p>재귀 호출로 구현하므로 call stack이 쌓이며 메모리 사용량이 늘어날 수 있다. 재귀
없이 구현하는 방법은 다음과 같다.</p>
<pre><code>function find(x)
    root := x
    while root.parent ≠ root
        root := root.parent

    while x.parent ≠ root
        parent := x.parent
        x.parent := root
        x := parent

    return root</code></pre>
<p>메모리 사용량이 상수값으로 줄어들었다. 다만, root를 찾기 위해 첫 번째 경로
탐색이, path compression을 위한 두 번째 경로 탐색이 발생한다.</p>
<p>한 번만 탐색하는 알고리즘 또한 있다. 단 완전한 경로 압축은 아니고, 메모리 사용
측면에서의 절충안이다. path splitting과 path halving이 있다.</p>
<p>아래는 path splitting 알고리즘이다.</p>
<pre><code>function find(x)
    while x.parent ≠ x
        (x, x.parent) := (x.parent, x.parent.parent)
    return x</code></pre>
<p>아래는 path halving 알고리즘이다.</p>
<pre><code>function find(x)
    while x.parent ≠ x
        x.parent := x.parent.parent
        x = x.parent
    return x</code></pre>
<p>Path splitting은 경로상 모든 부모 노드를 조부모로 연결한다. Path halving은 모든
노드가 아니라 두 번째마다 부모를 조부모로 연결한다. path splitting이 더
공격적으로 경로를 압축하나, 구현이 아주 조금 더 복잡하고 성능 차이는 거의 없다.
따라서 path halving 또한 선호된다.</p>
<h2 id="참조">참조</h2>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure">Wikipedia - Disjoint-set data structure</a></li>
<li><a href="https://rntlqvnf.github.io/lecture%20notes/algorithm-5th-week-1/">블로그 1</a></li>
</ul>
<section id="footnotes" class="footnotes footnotes-end-of-document" role="doc-endnotes">
<hr />
<ol>
<li id="fn1"><p>서로소 집합. 공통 원소가 없는 두 집합이다.<a href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li>
</ol>
</section>
  </section>
</article>
]]></summary>
</entry>

</feed>
