<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>이즈미르의 프로그래밍</title>
    <link>https://izmirprogramming.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 17 Jun 2026 03:57:49 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>이즈미르</managingEditor>
    <image>
      <title>이즈미르의 프로그래밍</title>
      <url>https://tistory1.daumcdn.net/tistory/3423731/attach/64cf02e6f8bd49b29f1c883c5502ed6f</url>
      <link>https://izmirprogramming.tistory.com</link>
    </image>
    <item>
      <title>우선순위 큐 (Priority Queue)</title>
      <link>https://izmirprogramming.tistory.com/15</link>
      <description>&lt;p&gt;이번에는 우선순위 큐(Priority Queue)에 대해서 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선순위 큐는 큐에서 요소들에게 우선순위가 부여되며 우선순위가 높은 요소가 먼저 출력되는 큐이다.&lt;/p&gt;
&lt;p&gt;(&lt;a href=&quot;https://en.wikipedia.org/wiki/Priority_queue&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;en.wikipedia.org/wiki/Priority_queue&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 요소란 무엇을 의미할까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;요소는 일반적으로 우선순위를 결정짓는 키(key)와 그 키와 매핑이 되는 값(value)으로 구성된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;요소의 키는 일반적으로 정수를 사용하며 키 값의 대소 관계로 요소들의 우선순위를 결정짓는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;요소의 값은 애플리케이션에서 실제로 사용하는 데이터이며 정수, 실수, 문자열 등의 데이터 타입이 될 수 있으며&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여러 가지 데이터 타입들로 구성된 복합 데이터 타입(composite data type)이 될 수 있다.&lt;/p&gt;
&lt;p&gt;(&lt;a href=&quot;https://en.wikipedia.org/wiki/Composite_data_type&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;en.wikipedia.org/wiki/Composite_data_type&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 우리가 알아보고자 하는 것은 큐에서 요소의 키 값의 대소 관계로 어떻게 우선순위를 결정짓는가이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 우리는 요소의 키 값을 중점적으로 볼 것이며 값(value) 자체는 다루지 않을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 키 값이 큰 값일수록 더 높은 우선순위를 갖도록 최대 힙(max heap)을 사용하여 설명할 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;힙(heap)이란 완전 이진트리를 기반으로 하는 자료구조로 최댓값 또는 최솟값을 빠르게 찾기 위해 힙 속성을 갖고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우리가 사용할 힙은 최대 힙이고 최대 힙 속성은 부모 노드의 키 값이 자식 노드의 키 값보다 항상 크다는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉, 우선순위 큐를 구현하기 위해 요소들의 키 값들 중에서 최댓값을 빠르게 찾기 위해 최대 힙을 사용한다는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최대 힙을 완전 이진트리로 나타낸다는데 구체적으로 어떻게 나타낼까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 완전 이진트리를 설명하자면 자식 노드를 최대 2개까지 가질 수 있는 트리가 이진트리이고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이진트리의 마지막 레벨을 제외한 다른 레벨들은 노드들로 가득 차있고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막 레벨은 왼쪽부터 오른쪽 방향으로 노드들이 채워져 있어야 완전 이진트리가 된다.&lt;/p&gt;
&lt;p&gt;(&lt;a href=&quot;https://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees&lt;/a&gt;에서 complete binary tree 참고)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 완전 이진트리를 기반으로 한 최대 힙을 보통 배열로 나타내는데 아래 그림을 보면 이해하기 쉬울 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;max_heap.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2wzj3/btq2gXPHjjI/zArfIC4OlVp0KTsOLLba4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2wzj3/btq2gXPHjjI/zArfIC4OlVp0KTsOLLba4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2wzj3/btq2gXPHjjI/zArfIC4OlVp0KTsOLLba4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2wzj3%2Fbtq2gXPHjjI%2FzArfIC4OlVp0KTsOLLba4k%2Fimg.png&quot; data-filename=&quot;max_heap.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 그림은 최대 힙을 나타낸 것으로 노드 위에 있는 값은 배열의 인덱스를 나타내고 노드 안에 있는 값은 요소의 키 값을 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;트리에 있는 키 값들이 위에서부터 왼쪽에서 오른쪽으로 차례대로 배열에 저장되어 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;트리가 최대 힙 속성을 만족하고 있는지 확인해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어느 노드든 그 노드의 키 값이 항상 자식 노드의 키 값보다 크거나 같음을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이러한 최대 힙 속성을 만족시키게 하는 HeapifyUpward 함수와 HeapifyDownward 함수 두 개를 정의해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618126552882&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef int Element;
class MaxHeap
{
protected:
    std::vector&amp;lt;Element&amp;gt; _elements;
    
    void exchange(std::size_t idx1, std::size_t idx2)
    {
        Element temp = _elements[idx1];
        _elements[idx1] = _elements[idx2];
        _elements[idx2] = temp;
    }
    
    std::size_t parent(std::size_t idx)
    {
    	if(0 &amp;lt; idx)
        {
        	return (idx - 1) / 2;
        }
        return 0;
    }
    
public:
    void HeapifyDownward(std::size_t idx, std::size_t size)
    {
        std::size_t largest = idx;
        std::size_t left = idx * 2 + 1;
        std::size_t right = (idx + 1) * 2;
        
        if (size &amp;gt; left &amp;amp;&amp;amp; _elements[idx] &amp;lt; _elements[left])
        {
            largest = left;
        }
        if (size &amp;gt; right &amp;amp;&amp;amp; _elements[largest] &amp;lt; _elements[right])
        {
            largest = right;
        }
        if (idx != largest)
        {
            exchange(idx, largest);
            HeapifyDownward(largest, size);
        }
    }
    
    void HeapifyUpward(std::size_t idx)
    {
    	std::size_t pidx = parent(idx);
        while(1 &amp;lt;= idx &amp;amp;&amp;amp; _elements[pidx] &amp;lt; _elements[idx])
        {
            exchange(idx, pidx);
            idx = pidx;
            pidx = parent(idx);
        }
    }
};
            &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;HeapifyDownward 함수는 idx에 있는 노드의 키 값을 자식 노드들 중에서 더 큰 키 값을 갖고 있다면 교환한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;키 값을 교환했다면 함수명대로 트리의 아래 방향으로 진행하며 트리 전체가 최대 힙 속성을 만족시키도록 재귀적으로 호출된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;HeapifyUpward 함수는 idx에 있는 노드의 키 값이 부모 노드의 키 값보다 크다면 키 값을 교환한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;키 값을 교환한다면 트리의 윗 방향으로 진행하며 트리 전체가 최대 힙 속성을 만족시키도록 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음으로 우선순위 큐인 힙에 요소를 추가하거나 추출하는 Insert 함수와 Extract 함수 두 개를 정의해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618133428687&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef int Element;
class MaxHeap
{
protected:
    std::vector&amp;lt;Element&amp;gt; _elements;
    
    void exchange(std::size_t idx1, std::size_t idx2)
    {
        ...
    }
    
    std::size_t parent(std::size_t idx)
    {
    	...
    }
    
public:
    void HeapifyDownward(std::size_t idx, std::size_t size)
    {
        ...
    }
    
    void HeapifyUpward(std::size_t idx)
    {
    	...
    }
    
    void Insert(Element element)
    {
        _elements.push_back(element);
        HeapifyUpward(_elements.size() - 1);
    }
    
    bool Extract(Element&amp;amp; element)
    {
        const std::size_t size = _elements.size();
        if(1 &amp;gt; size)
        {
            return false;
        }
        
        element = _elements[0];
        exchange(0, size - 1);
        _elements.pop_back();
        HeapifyDownward(0, size - 1);
        return true;
    }
};
            &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Insert 함수는 힙의 가장 마지막에 요소를 넣고 최대 힙 속성을 만족시키게 하기 위해서 HeapifyUpward 함수를 호출한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Extract 함수는 힙의 루트 노드를 추출하고 힙의 마지막 요소를 루트 노드로 대체한 후&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최대 힙 속성을 만족시키도록 HeapifyDownward 함수를 호출한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 시간 복잡도를 계산해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선순위 큐의 시간 복잡도를 결정하는 함수는 HeapifyUpward 함수와 HeapifyDownward 함수이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;HeapifyUpward 함수는 리프 노드에서 루트 노드까지 반복할 때가 최악의 경우이고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;HeapifyDownward 함수는 루트 노드에서 리프 노드까지 재귀호출될 때가 최악의 경우이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;힙에 요소가 총 n개 있다면 완전 이진트리의 높이는 $\log_{2}n$이므로 시간 복잡도는 $O(\log_{2}n)$이 되겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 우선순위 큐에 대해 알아보았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;내용 출처 : Introduction to Algorithms (&lt;/span&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Introduction_to_Algorithms&quot;&gt;en.wikipedia.org/wiki/Introduction_to_Algorithms)&lt;/a&gt;&lt;/p&gt;</description>
      <category>알고리즘/study</category>
      <author>이즈미르</author>
      <guid isPermaLink="true">https://izmirprogramming.tistory.com/15</guid>
      <comments>https://izmirprogramming.tistory.com/15#entry15comment</comments>
      <pubDate>Sun, 11 Apr 2021 18:59:18 +0900</pubDate>
    </item>
    <item>
      <title>[정렬] 힙소트 (Heapsort)</title>
      <link>https://izmirprogramming.tistory.com/14</link>
      <description>&lt;p&gt;정렬 알고리즘 중에 하나인 힙 소트(Heapsort)에 대해 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;힙 소트는 힙을 이용하여 정렬하는 알고리즘이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;힙(heap)이란 무엇을 의미할까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;C언어를 좀 깊게 파봤다면 메모리 영역 중에 힙 영역이란 말을 들어 봤을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 여기서 말하는 힙은 그 힙이 아니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 말하는 힙이란 완전 이진트리(complete binary tree)를 기반으로 하는 자료구조로&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최댓값 또는 최솟값을 빠르게 찾기 위해 힙 속성이라는 것을 갖고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(출처 : &lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%9E%99_(%EC%9E%90%EB%A3%8C_%EA%B5%AC%EC%A1%B0)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ko.wikipedia.org/wiki/%ED%9E%99_(%EC%9E%90%EB%A3%8C_%EA%B5%AC%EC%A1%B0)&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;완전 이진트리는 &lt;a href=&quot;https://en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;en.wikipedia.org/wiki/Binary_tree#Types_of_binary_trees&lt;/a&gt;에 잘 설명되어 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;쉽게 말하자면 자식 노드를 최대 2개까지 가질 수 있는 트리가 이진트리이고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이진트리의 마지막 레벨을 제외한 다른 레벨들은 노드들로 가득 차있고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막 레벨은 왼쪽부터 오른쪽 방향으로 노드들이 채워져 있어야 완전 이진트리가 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 완전 이진트리의 각 노드에 키 값이 존재하고&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;부모 노드의 키 값이 자식 노드들의 키 값보다 항상 크거나 같은 속성을 가지면 최대 힙(max-heaps)이라 부르고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;부모 노드의 키 값이 자식 노드들의 키 값보다 항상 작거나 같은 속성을 가지면 최소 힙(min-hepas)라고 부른다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 그림은 최대 힙의 예시이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;max_heap.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v8z6f/btq0Drv9P2l/ukgtKr5cVuEs0OBJbEVnIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v8z6f/btq0Drv9P2l/ukgtKr5cVuEs0OBJbEVnIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v8z6f/btq0Drv9P2l/ukgtKr5cVuEs0OBJbEVnIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv8z6f%2Fbtq0Drv9P2l%2FukgtKr5cVuEs0OBJbEVnIK%2Fimg.png&quot; data-filename=&quot;max_heap.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 그림과 같이 힙은 보통 배열로 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;트리에 있는 키 값들을 위에서부터 왼쪽에서 오른쪽으로 차례대로 배열에 저장하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그러면 어떤 노드의 배열 인덱스에서 1을 빼고 2로 나누면 그 노드의 부모 노드 인덱스가 되고 (루트 노드 제외)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2를 곱하고 1을 더하면 왼쪽 자식 노드의 인덱스가, 1을 더하고 2를 곱하면 오른쪽 노드의 인덱스가 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우리는 이 힙을 정렬하는데 사용할 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최대 힙과 최소 힙은 원리가 똑같으니 최대 힙에 대해서만 설명하도록 하겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최대 힙을 사용하여 정렬하기 위해서 우리에게 필요한 작업은 두 가지가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;첫 번째로 임의의 순서로 키 값들이 나열되어 있는 배열을 입력받고 이것을 최대 힙으로 만든다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;두 번째로 최대 힙을 사용해서 정렬한다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 두 가지 작업을 하기 위해서 힙 속성을 만족시키게 하는 Heapify 함수를 정의해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 함수는 이미 최대 힙 속성을 만족하고 있는 힙에서 임의의 노드의 키 값이 변경되었을 때&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다시 힙 속성을 맞춰주기 위한 함수로 생각하면 이해하기 쉽다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615816239441&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef int Element;
class MaxHeap
{
protected:
    std::vector&amp;lt;Element&amp;gt; _elements;
    
    void exchange(Element&amp;amp; element1, Element&amp;amp; element2)
    {
        Element temp = element1;
        element1 = element2;
        element2 = temp;
    }
    
public:
    void Heapify(std::size_t idx, std::size_t size)
    {
        std::size_t largest = idx;
        std::size_t left = idx * 2 + 1;
        std::size_t right = (idx + 1) * 2;
        
        if (size &amp;gt; left &amp;amp;&amp;amp; _elements[idx] &amp;lt; _elements[left])
        {
            largest = left;
        }
        if (size &amp;gt; right &amp;amp;&amp;amp; _elements[largest] &amp;lt; _elements[right])
        {
            largest = right;
        }
        if (idx != largest)
        {
            exchange(_elements[idx], _elements[largest]);
            Heapify(largest, size);
        }
    }
};
            &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 Heapify 함수에서 부모 노드와 왼쪽 자식 노드, 오른쪽 자식 노드 중에서 키 값이 가장 큰 노드를 찾고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 노드가 부모 노드가 아니면 부모 노드와 키 값을 교환하고 다시 그 노드에 대해서 Heapify 함수를 호출한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 세 개 노드 중에서 부모 노드에 가장 큰 키 값을 놓도록 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이것은 앞서 설명한 최대 힙의 속성을 만족시키도록 만든다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 키 값이 바뀐 노드가 부모 노드로서 최대 힙 속성을 만족시키도록 그 노드에 대해 Heapify 함수를 호출해 준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;정리해보면 최대 힙 속성을 만족시키는 힙에서 임의의 노드의 키 값이 변경되었을 때&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 키 값이 위치할 적당한 노드를 찾아 이동시키는 작업이 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 작업을 가장 끝 노드에서부터 루트 노드까지 반복하면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉, 배열의 끝에 있는 요소부터 첫 번째에 있는 요소까지 Heapify 함수를 호출해 주면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우리가 하려는 첫 번째 작업인 임의의 순서로 키 값들이 나열된 배열을 최대 힙으로 만드는 작업이 완료된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 작업을 아래와 같이 Build 함수로 정의해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1616218305486&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef int Element;
class MaxHeap
{
protected:
    std::vector&amp;lt;Element&amp;gt; _elements;
    
    void exchange(Element&amp;amp; element1, Element&amp;amp; element2)
    {
        ...
    }
    
public:
    void Heapify(std::size_t idx, std::size_t size)
    {
        ...
    }
    
    void Build()
    {
        if (1 &amp;gt;= _elements.size())
        {
            return;
        }
        
        const std::size_t size = _elements.size();
        std::size_t idx = size / 2 - 1;
        while (0 &amp;lt;= idx)
        {
            Heapify(idx, size);
            
            if(0 == idx)
            {
                break;
            }
            else
            {
                idx--;
            }
        }
    }
};
            &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앞서 마지막 노드부터 Heapify 함수를 호출해 준다고 했지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자식 노드가 없는 리프 노드(leaf node)들은 Heapify 함수를 호출해 줄 필요가 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왜냐하면 부모 노드 하나만 존재하고 항상 최대 힙 속성을 만족시키기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 리프 노드들이 아닌 노드부터 루트 노드까지 Heapify 함수를 호출해 주면 된다.&lt;/p&gt;
&lt;p&gt;(리프 노드가 아닌 마지막 노드의 인덱스는 총 노드 개수 / 2 - 1로 구할 수 있다.&lt;/p&gt;
&lt;p&gt;참고 : &lt;a href=&quot;https://ita.skanev.com/06/01/07.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ita.skanev.com/06/01/07.html&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Build 함수가 왜 임의의 순서로 키 값들이 나열된 배열을 최대 힙으로 만들 수 있는지는 곰곰이 생각해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이해를 돕기 위해 아래와 같이 간단한 예시를 확인해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_step_0.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFoBFk/btq0y1e34gL/GB8kRFwaN2oFeqEL3jz170/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFoBFk/btq0y1e34gL/GB8kRFwaN2oFeqEL3jz170/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFoBFk/btq0y1e34gL/GB8kRFwaN2oFeqEL3jz170/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFoBFk%2Fbtq0y1e34gL%2FGB8kRFwaN2oFeqEL3jz170%2Fimg.png&quot; data-filename=&quot;heap_step_0.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 그림은 임의의 순서로 키 값들이 나열된 초기 상태의 배열과 트리로 표현한 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음 그림들은 위 배열에 대해 Build 함수를 호출할 때의 과정을 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;빨간색으로 표시한 숫자는 Build 함수에 있는 idx를 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 파란색으로 표시한 숫자는 Heapify 함수에 의해 교환된 키 값들을 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_step_1.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKjTQy/btq0A3P5DhQ/476yNcXYxV6NMpKUAe0oD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKjTQy/btq0A3P5DhQ/476yNcXYxV6NMpKUAe0oD1/img.png&quot; data-alt=&quot;Step 1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKjTQy/btq0A3P5DhQ/476yNcXYxV6NMpKUAe0oD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKjTQy%2Fbtq0A3P5DhQ%2F476yNcXYxV6NMpKUAe0oD1%2Fimg.png&quot; data-filename=&quot;heap_step_1.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_step_2.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7c6Tq/btq0Ao8cPtu/gcqisnv0ziBs1E9LK4kwE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7c6Tq/btq0Ao8cPtu/gcqisnv0ziBs1E9LK4kwE0/img.png&quot; data-alt=&quot;Step 2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7c6Tq/btq0Ao8cPtu/gcqisnv0ziBs1E9LK4kwE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7c6Tq%2Fbtq0Ao8cPtu%2Fgcqisnv0ziBs1E9LK4kwE0%2Fimg.png&quot; data-filename=&quot;heap_step_2.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_step_3.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m8T3j/btq0zerMOXC/B7KlQa7tvAOR5wOoNdZFdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m8T3j/btq0zerMOXC/B7KlQa7tvAOR5wOoNdZFdk/img.png&quot; data-alt=&quot;Step 3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m8T3j/btq0zerMOXC/B7KlQa7tvAOR5wOoNdZFdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm8T3j%2Fbtq0zerMOXC%2FB7KlQa7tvAOR5wOoNdZFdk%2Fimg.png&quot; data-filename=&quot;heap_step_3.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_step_4.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL7XKc/btq0z0s05ID/FjyDrOYadDPHEqF0vvzpF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL7XKc/btq0z0s05ID/FjyDrOYadDPHEqF0vvzpF0/img.png&quot; data-alt=&quot;Step 4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL7XKc/btq0z0s05ID/FjyDrOYadDPHEqF0vvzpF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL7XKc%2Fbtq0z0s05ID%2FFjyDrOYadDPHEqF0vvzpF0%2Fimg.png&quot; data-filename=&quot;heap_step_4.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 4&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위의 마지막 그림은 맨 처음 소개한 최대 힙과 다른 모습이긴 하지만 이것 또한 최대 힙 속성을 만족하므로 최대 힙이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;순서대로 잘 살펴보면 어떤 노드에 대해 Heapify 함수를 호출하기 전에&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 노드의 자식 노드들에 대해서 먼저 Heapify 함수가 호출되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉, 각 노드를 루트 노드로 하는 서브 트리(subtree)들을 모두 최대 힙으로 만들면서&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 루트 노드에 대해서 최대 힙을 만들고 배열 전체를 최대 힙으로 만들게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 두 번째 작업으로 이 힙을 이용하여 배열에 있는 키 값들이 오름차순이 되도록 정렬해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;방법은 최대 힙에서 최댓값인 루트 노드의 키 값과 최대 힙의 마지막 요소의 키 값을 교환한 후&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최대 힙의 길이를 1 감소시키고 루트 노드에 대해 Heapify 함수를 호출한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 작업을 최대 힙 길이가 1이 될 때까지 반복한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래와 같이 Sort 함수로 정의해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1616222334686&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;typedef int Element;
class MaxHeap
{
protected:
    std::vector&amp;lt;Element&amp;gt; _elements;
    
    void exchange(Element&amp;amp; element1, Element&amp;amp; element2)
    {
        ...
    }
    
public:
    void Heapify(std::size_t idx, std::size_t size)
    {
        ...
    }
    
    void Build()
    {
        ...
    }
    
    void Sort()
    {
        if (1 &amp;gt;= _elements.size())
        {
            return;
        }
        
        std::size_t size = _elements.size();
        while (1 &amp;lt; size)
        {
            exchange(_elements[0], _elements[size - 1]);
            size--;
            Heapify(0, size);
        }
    }
};
            &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위에서 봤던 최대 힙 기준으로 정렬되는 모습을 차례대로 살펴보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;초기 최대 힙은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_sort_step_0.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzJSOe/btq0zunASjG/XKnTukkn40ETlk1UGMVCR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzJSOe/btq0zunASjG/XKnTukkn40ETlk1UGMVCR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzJSOe/btq0zunASjG/XKnTukkn40ETlk1UGMVCR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzJSOe%2Fbtq0zunASjG%2FXKnTukkn40ETlk1UGMVCR1%2Fimg.png&quot; data-filename=&quot;heap_sort_step_0.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음 그림들은 위 최대 힙에 대해 Sort 함수를 호출할 때의 과정을 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;트리에서 회색으로 채운 노드는 최대 힙에 포함되지 않는 노드를 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;파란색으로 표시한 숫자는 루트 노드에 대해 Heapify 함수 호출로 교환된 키 값들을 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_sort_step_1.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPfBVi/btq0zu8ZHM0/24OoKBwqFMzrsZFCnaFnm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPfBVi/btq0zu8ZHM0/24OoKBwqFMzrsZFCnaFnm0/img.png&quot; data-alt=&quot;Step 1&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPfBVi/btq0zu8ZHM0/24OoKBwqFMzrsZFCnaFnm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPfBVi%2Fbtq0zu8ZHM0%2F24OoKBwqFMzrsZFCnaFnm0%2Fimg.png&quot; data-filename=&quot;heap_sort_step_1.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_sort_step_2.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFDKDE/btq0yZPb3DN/KXEF9gSd6dlt9T48xajK71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFDKDE/btq0yZPb3DN/KXEF9gSd6dlt9T48xajK71/img.png&quot; data-alt=&quot;Step 2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFDKDE/btq0yZPb3DN/KXEF9gSd6dlt9T48xajK71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFDKDE%2Fbtq0yZPb3DN%2FKXEF9gSd6dlt9T48xajK71%2Fimg.png&quot; data-filename=&quot;heap_sort_step_2.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_sort_step_3.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oBOtA/btq0AGAS1AQ/y31vlutljytwUudBIGi5Bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oBOtA/btq0AGAS1AQ/y31vlutljytwUudBIGi5Bk/img.png&quot; data-alt=&quot;Step 3&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oBOtA/btq0AGAS1AQ/y31vlutljytwUudBIGi5Bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoBOtA%2Fbtq0AGAS1AQ%2Fy31vlutljytwUudBIGi5Bk%2Fimg.png&quot; data-filename=&quot;heap_sort_step_3.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 3&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_sort_step_4.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yZMcQ/btq0DrCYsnk/eKKh7tqrTMovQvhkzdnit1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yZMcQ/btq0DrCYsnk/eKKh7tqrTMovQvhkzdnit1/img.png&quot; data-alt=&quot;Step 4&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yZMcQ/btq0DrCYsnk/eKKh7tqrTMovQvhkzdnit1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyZMcQ%2Fbtq0DrCYsnk%2FeKKh7tqrTMovQvhkzdnit1%2Fimg.png&quot; data-filename=&quot;heap_sort_step_4.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 4&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_sort_step_5.png&quot; width=&quot;600&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eFyvPp/btq0yZPb5hn/EbUiG12VgnmryQGn30cjp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eFyvPp/btq0yZPb5hn/EbUiG12VgnmryQGn30cjp0/img.png&quot; data-alt=&quot;Step 5&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eFyvPp/btq0yZPb5hn/EbUiG12VgnmryQGn30cjp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeFyvPp%2Fbtq0yZPb5hn%2FEbUiG12VgnmryQGn30cjp0%2Fimg.png&quot; data-filename=&quot;heap_sort_step_5.png&quot; width=&quot;600&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 5&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_sort_step_6.png&quot; width=&quot;600&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btZPlI/btq0y1e6zCu/FycDxiEnOTP7SkkDailKvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btZPlI/btq0y1e6zCu/FycDxiEnOTP7SkkDailKvK/img.png&quot; data-alt=&quot;Step 6&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btZPlI/btq0y1e6zCu/FycDxiEnOTP7SkkDailKvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtZPlI%2Fbtq0y1e6zCu%2FFycDxiEnOTP7SkkDailKvK%2Fimg.png&quot; data-filename=&quot;heap_sort_step_6.png&quot; width=&quot;600&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 6&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_sort_step_7.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kAJux/btq0y0N5hDr/CHTMaDSFL44Nz8X3A3QxP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kAJux/btq0y0N5hDr/CHTMaDSFL44Nz8X3A3QxP1/img.png&quot; data-alt=&quot;Step 7&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kAJux/btq0y0N5hDr/CHTMaDSFL44Nz8X3A3QxP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkAJux%2Fbtq0y0N5hDr%2FCHTMaDSFL44Nz8X3A3QxP1%2Fimg.png&quot; data-filename=&quot;heap_sort_step_7.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 7&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_sort_step_8.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z8gf6/btq0ApF6Ahf/sJlKnDJAiwU2uBTR0SPik1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z8gf6/btq0ApF6Ahf/sJlKnDJAiwU2uBTR0SPik1/img.png&quot; data-alt=&quot;Step 8&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z8gf6/btq0ApF6Ahf/sJlKnDJAiwU2uBTR0SPik1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz8gf6%2Fbtq0ApF6Ahf%2FsJlKnDJAiwU2uBTR0SPik1%2Fimg.png&quot; data-filename=&quot;heap_sort_step_8.png&quot; data-origin-width=&quot;1385&quot; data-origin-height=&quot;917&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Step 8&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 힙 소트의 시간 복잡도와 공간 복잡도에 대해서 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;총 노드 개수를 n이라고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 힙 소트의 공간 복잡도는 &lt;span style=&quot;color: #333333;&quot;&gt;$O(1)$이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;힙 소트는 in-place 알고리즘으로 입력 크기에 관계 없이 상수 크기만큼의 공간을 추가로 필요하기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(in-place algorithm : &lt;a href=&quot;https://en.wikipedia.org/wiki/In-place_algorithm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;en.wikipedia.org/wiki/In-place_algorithm&lt;/a&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;시간 복잡도는 함수 별로 조사해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;먼저 Heapify 함수를 실행하는 특정 노드의 높이를 h라고 했을 때&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그 노드의 Heapify 함수의 실행 시간은 $O(h)$라고 할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(리프 노드까지 도달할 때 제일 오래 걸리므로)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그렇다면 높이 0부터 (리프 노드의 높이가 0) 높이 $\lfloor\log_{2}n\rfloor$까지 차례대로&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;각 높이에 있는 노드 개수와 Heapify 함수의 실행 시간 $O(h)$를 곱한 값을 모두 더하면&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Build 함수의 시간 복잡도를 구할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;식으로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$\sum_{h=0}^{\lfloor \log_{2}n \rfloor} \lceil {n \over 2^{h+1}} \rceil O(h) = O(n \sum_{h=0}^{\lfloor \log_{2}n \rfloor} {h \over 2^{h}})$$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$\lceil {n \over 2^{h+1}} \rceil$은 높이 h에 있는 노드의 최대 개수이다.&lt;/p&gt;
&lt;p&gt;(자세한 내용은 &lt;a href=&quot;https://ita.skanev.com/06/03/03.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ita.skanev.com/06/03/03.html&lt;/a&gt; 참고)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 아래와 같은 공식이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ \sum_{k=0}^{\infty} kx^{k} = { x \over (1 - x)^{2} } \quad for \mid x \mid &amp;lt; 1 $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 공식을 사용하여 $x = 1/2$를 대입하면 아래와 같이 식이 성립한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ \sum_{h=0}^{\infty} { h \over 2^{h} } = { 1/2 \over (1 - 1/2)^{2} } = 2 $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;총 노드 개수 n이 무한히 크다고 가정하고 위의 식을 적용하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ O(n \sum_{h=0}^{\lfloor \log_{2}n \rfloor} { h \over 2^{h} }) = O(n \sum_{h=0}^{\infty} {h \over 2^{h}}) = O(n) $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 Build 함수는 $O(n)$으로 선형 시간(linear time) 복잡도를 가진다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 Sort 함수의 시간 복잡도를 알아 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;루트 노드에 있는 키 값을 마지막 노드의 키 값과 교환하는 작업을 n번 수행한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 매번 Heapify 함수를 호출하는데 Heapify 함수의 시간 복잡도를 아래와 같이 정의할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ T(n) \; \leq \; T({2n \over 3}) + \Theta(1) $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Heapify 함수에서 노드들끼리 키 값을 교환하는 작업이 $\Theta(1)$이고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;키 값을 교환하고 자식 노드에 대해서 다시 Heapify 함수를 호출할 때&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최대 $T({2n \over 3})$의 실행 시간이 걸린다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이는 최악의 경우 노드 개수 비율이 한쪽 자식 노드 개수가 다른 한쪽 자식 노드 개수보다 2배 많다는 뜻이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이를 쉽게 말하자면 트리의 마지막 레벨에 한쪽 자식 노드 쪽에는 노드들이 가득 차 있고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다른 한 쪽 노드 쪽에는 비어 있는 케이스이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들어 아래와 같이 표현할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;heap_half_full_tree.png&quot; data-origin-width=&quot;1363&quot; data-origin-height=&quot;622&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbB0QE/btq0BEiAvT8/YvITPCHgqv8NY7U35fh3g1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbB0QE/btq0BEiAvT8/YvITPCHgqv8NY7U35fh3g1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbB0QE/btq0BEiAvT8/YvITPCHgqv8NY7U35fh3g1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbB0QE%2Fbtq0BEiAvT8%2FYvITPCHgqv8NY7U35fh3g1%2Fimg.png&quot; data-filename=&quot;heap_half_full_tree.png&quot; data-origin-width=&quot;1363&quot; data-origin-height=&quot;622&quot; width=&quot;600&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;회색 노드가 현재 Heapify 함수가 호출된 대상 노드이다.&lt;/p&gt;
&lt;p&gt;(어떠한 서브 트리의 루트 노드일 수 있다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 파란색 노드가 다시 Heapify 함수가 호출된 자식 노드라고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;파란색 노드가 루트 노드인 서브 트리 기준으로 리프 노드 개수와 리프 노드가 아닌 노드들의 개수는 거의 같다.&lt;/p&gt;
&lt;p&gt;(참고 : &lt;a href=&quot;https://ita.skanev.com/06/01/07.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ita.skanev.com/06/01/07.html&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;파란색 노드 기준의 서브 트리에서 리프 노드를 제외하면 반대쪽 자식 노드의 개수와 같으므로&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;파란색 노드 기준의 서브 트리의 노드 개수가 회색 노드 기준의 서브 트리 노드 개수의 ${2 \over 3}$ 비율을 차지한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;고로 위와 같은 부등식이 성립하고 마스터 정리(master theorem)로 $T(n) = O(\log_{2}n)$임을 알 수 있다.&lt;/p&gt;
&lt;p&gt;(&lt;a href=&quot;https://en.wikipedia.org/wiki/Master_theorem_(analysis_of_algorithms)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;en.wikipedia.org/wiki/Master_theorem_(analysis_of_algorithms)&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 Sort 함수의 총 시간 복잡도는 $O(n\log_{2}n)$이 되고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;힙 소트의 총 시간 복잡도는 $ O(n) + O(n\log_{2}n) = O(n\log_{2}n)$이 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 힙 소트에 대해서 알아 보았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;내용 출처 : Introduction to Algorithms (&lt;a href=&quot;https://en.wikipedia.org/wiki/Introduction_to_Algorithms&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;en.wikipedia.org/wiki/Introduction_to_Algorithms)&lt;/a&gt;&lt;/p&gt;</description>
      <category>알고리즘/study</category>
      <category>heap sort</category>
      <category>heapsort</category>
      <category>Introduction to Algorithms</category>
      <category>sort</category>
      <category>sorting</category>
      <category>정렬</category>
      <category>힙 소트</category>
      <category>힙소트</category>
      <author>이즈미르</author>
      <guid isPermaLink="true">https://izmirprogramming.tistory.com/14</guid>
      <comments>https://izmirprogramming.tistory.com/14#entry14comment</comments>
      <pubDate>Sat, 13 Mar 2021 16:34:44 +0900</pubDate>
    </item>
    <item>
      <title>[분할 정복] Strassen algorithm</title>
      <link>https://izmirprogramming.tistory.com/13</link>
      <description>&lt;p&gt;분할 정복 알고리즘(Divide and conquer algorithm) 중에 하나인 Strassen을 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;중고등학생 때 행렬의 곱셈에 대해 배웠을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;행렬 간에 곱셈을 하기 전에 곱셈이 가능한 행과 열로 행렬들이 갖춰졌는지 확인해야 하지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우리는 정사각 행렬(Square matrix)만 다루기로 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래와 같이 각 크기가 n인 정사각 행렬 A, B가 있다고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;$$ A_{n,n} = \begin{bmatrix} a_{1,1} &amp;amp; a_{1,2} &amp;amp; \cdots &amp;amp; a_{1,n} \\ a_{2,1} &amp;amp; a_{2,2} &amp;amp; \cdots &amp;amp; a_{2,n} \\ \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \\ a_{n,1} &amp;amp; a_{n,2} &amp;amp; \cdots &amp;amp; a_{n,n} \end{bmatrix} $$&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;$$ B_{n,n} = \begin{bmatrix} b_{1,1} &amp;amp; b_{1,2} &amp;amp; \cdots &amp;amp; b_{1,n} \\ b_{2,1} &amp;amp; b_{2,2} &amp;amp; \cdots &amp;amp; b_{2,n} \\ \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \\ b_{n,1} &amp;amp; b_{n,2} &amp;amp; \cdots &amp;amp; b_{n,n} \end{bmatrix} $$&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;행렬곱 C = AB는 아래와 같이 크기 n인 정사각 행렬로 정의된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;$$ C_{n,n} = \begin{bmatrix} c_{1,1} &amp;amp; c_{1,2} &amp;amp; \cdots &amp;amp; c_{1,n} \\ c_{2,1} &amp;amp; c_{2,2} &amp;amp; \cdots &amp;amp; c_{2,n} \\ \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \\ c_{n,1} &amp;amp; c_{n,2} &amp;amp; \cdots &amp;amp; c_{n,n} \end{bmatrix} $$&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이때 행렬 C의 성분은 아래와 같이 정의된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ c_{i,j} = a_{i,1}b_{1,j} + a_{i,2}b_{2,j} + \cdots + a_{i,n}b_{n,j} = \sum_{k=1}^{n} a_{i,k}b_{k,j} $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이를 다시 정리하면 행렬 C는 아래와 같은 모습이 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ C_{n,n} = \begin{bmatrix} a_{1,1}b_{1,1}+\cdots+a_{1,n}b_{n,1} &amp;amp; a_{1,1}b_{1,2}+\cdots+a_{1,n}b_{n,2} &amp;amp; \cdots &amp;amp; a_{1,1}b_{1,n}+\cdots+a_{1,n}b_{n,n} \\ a_{2,1}b_{1,1}+\cdots+a_{2,n}b_{n,1} &amp;amp; a_{2,1}b_{1,2}+\cdots+a_{2,n}b_{n,2} &amp;amp; \cdots &amp;amp; a_{2,1}b_{1,n}+\cdots+a_{2,n}b_{n,n} \\ \vdots &amp;amp; \vdots &amp;amp; \ddots &amp;amp; \vdots \\ a_{n,1}b_{1,1}+\cdots+a_{n,n}b_{n,1} &amp;amp; a_{n,1}b_{1,2}+\cdots+a_{n,n}b_{n,2} &amp;amp; \cdots &amp;amp; a_{n,1}b_{1,n}+\cdots+a_{n,n}b_{n,n} \end{bmatrix} $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출처 : &lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%96%89%EB%A0%AC_%EA%B3%B1%EC%85%88&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ko.wikipedia.org/wiki/%ED%96%89%EB%A0%AC_%EA%B3%B1%EC%85%88&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1606457842649&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;행렬 곱셈 - 위키백과, 우리 모두의 백과사전&quot; data-og-description=&quot;위키백과, 우리 모두의 백과사전. 둘러보기로 가기 검색하러 가기 행렬 곱셈을 위해선 첫째 행렬의 열 갯수와 둘째 행렬의 행 갯수가 동일해야한다. 곱셈의 결과 새롭게 만들어진 행렬은 첫째 &quot; data-og-host=&quot;ko.wikipedia.org&quot; data-og-source-url=&quot;https://ko.wikipedia.org/wiki/%ED%96%89%EB%A0%AC_%EA%B3%B1%EC%85%88&quot; data-og-url=&quot;https://ko.wikipedia.org/wiki/%ED%96%89%EB%A0%AC_%EA%B3%B1%EC%85%88&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AYrlf/hyIoVGUW93/v6MCUKoJdk9k6K5fbOlJiK/img.png?width=1200&amp;amp;height=677&amp;amp;face=0_0_1200_677&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%96%89%EB%A0%AC_%EA%B3%B1%EC%85%88&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.wikipedia.org/wiki/%ED%96%89%EB%A0%AC_%EA%B3%B1%EC%85%88&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AYrlf/hyIoVGUW93/v6MCUKoJdk9k6K5fbOlJiK/img.png?width=1200&amp;amp;height=677&amp;amp;face=0_0_1200_677');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;행렬 곱셈 - 위키백과, 우리 모두의 백과사전&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;위키백과, 우리 모두의 백과사전. 둘러보기로 가기 검색하러 가기 행렬 곱셈을 위해선 첫째 행렬의 열 갯수와 둘째 행렬의 행 갯수가 동일해야한다. 곱셈의 결과 새롭게 만들어진 행렬은 첫째&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;ko.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;코드로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1606459589584&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int** MultiplyMatrix(int** A, int** B, std::size_t n)
{
	int** C = new int*[n];

	int sum = 0;
	for (std::size_t i = 0; i &amp;lt; n; ++i)
	{
		C[i] = new int[n];

		for (std::size_t j = 0; j &amp;lt; n; ++j)
		{
			sum = 0;
			for (std::size_t k = 0; k &amp;lt; n; ++k)
			{
				sum += (A[i][k] * B[k][j]);
			}
			C[i][j] = sum;
		}
	}

	return C;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이를 시간 복잡도와 공간 복잡도로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$time\,complexity\,:\,\Theta(n^{3})\quad space\,complexity\,:\,O(1)$$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;3중 반복문이 행렬의 크기 n만큼 돌기 때문에 $\Theta(n^{3})$의 시간 복잡도가 나오고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;새로 생성할 행렬 C의 공간을 제외하면 sum 변수 정도를 위한 공간을 사용하기 때문에 $\Theta(1)$ 공간 복잡도가 나온다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 행렬의 곱을 구하는 방법을 &lt;b&gt;[방법 1]&lt;/b&gt;이라고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;크기가 n인 정사각 행렬 곱셈의 시간 복잡도가 &lt;b&gt;[방법 1]&lt;/b&gt;보다 더 나은 것이 없다고 생각할 수 있지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Strassen 알고리즘을 사용하면 더 나은 시간 복잡도를 얻을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;원리를 간단하게 설명하자면 행렬들을 쪼개서 곱하고 더하는 과정을 재귀적으로 반복하는데&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;행렬의 덧셈이 곱셈보다 더 빠른 점을 이용하기 위해 쪼갠 행렬들의 곱셈 횟수를 줄이고 덧셈 횟수를 늘린다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(곱셈은 앞서 본 것처럼 $\Theta(n^{3})$이지만 덧셈은 $\Theta(n^{2})$으로 더 나은 시간 복잡도를 가진다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Strassen 알고리즘을 본격적으로 설명하기 앞서 재귀적으로 행렬을 쪼개어 곱하고 더하는 방법을 먼저 살펴보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;행렬을 재귀적으로 쪼개는 작업이 n을 2로 계속 나누기 때문에 사전에 행렬들의 크기를 2의 거듭제곱으로 맞춰 놓는게 좋다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 다음 행렬 A와 B, C를 아래와 같이 각각 4분할로 쪼갠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;$$ A = \begin{bmatrix} A_{1,1} &amp;amp; A_{1,2} \\ A_{2,1} &amp;amp; A_{2,2} \end{bmatrix},\, B = \begin{bmatrix} B_{1,1} &amp;amp; B_{1,2} \\ B_{2,1} &amp;amp; B_{2,2} \end{bmatrix},\, C = \begin{bmatrix} C_{1,1} &amp;amp; C_{1,2} \\ C_{2,1} &amp;amp; C_{2,2} \end{bmatrix} $$&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;쪼개진 행렬들로 다음 식들이 성립될 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ [식 1] \quad C_{1,1} = A_{1,1}B_{1,1} + A_{1,2}B_{2,1} $$&lt;/p&gt;
&lt;p&gt;$$ [식 2] \quad C_{1,2} = A_{1,1}B_{1,2} + A_{1,2}B_{2,2} $$&lt;/p&gt;
&lt;p&gt;$$ [식 3] \quad C_{2,1} = A_{2,1}B_{1,1} + A_{2,2}B_{2,1} $$&lt;/p&gt;
&lt;p&gt;$$ [식 4] \quad C_{2,2} = A_{2,1}B_{1,2} + A_{2,2}B_{2,2} $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 식들에 있는 행렬끼리의 곱에서 다시 위 과정을 재귀적으로 반복한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;행렬의 크기가 더이상 쪼개지지 않는 1까지 반복하고 1개 요소만 남았으면 그냥 곱해주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이렇게 행렬의 곱을 구하는 방법을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;[방법 2]&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;라고 하자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 아래와 같이 &lt;b&gt;[방법 2]&lt;/b&gt;의 실행 시간을 정의할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ T(n) = \begin{cases} \Theta(1), &amp;amp; \text{if }n = 1 \\ 8T(n/2) + \Theta(n^{2}), &amp;amp; \text{if }n &amp;gt; 1 \end{cases} $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[식 1~4]&lt;/b&gt;에서 쪼개진 행렬들의 곱이 8번 일어나고 그 행렬의 크기는 n/2가 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 행렬들의 덧셈이 4번 일어나는데 $4(n/2)^{2}$가 곧 $\Theta(n^{2})$가 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$\Theta(n^{2}) = cn^{2} \quad \text{where}\, c &amp;gt; 0\,$로 정의하고 T(n)에 대해서 구체적으로 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;T(n)을 트리로 나타내면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;tree1.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;230&quot; width=&quot;772&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba5S8O/btqOuHE6XDj/zlCBVuTaJK8ZrQsrsio2YK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba5S8O/btqOuHE6XDj/zlCBVuTaJK8ZrQsrsio2YK/img.png&quot; data-alt=&quot;[트리 1]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba5S8O/btqOuHE6XDj/zlCBVuTaJK8ZrQsrsio2YK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba5S8O%2FbtqOuHE6XDj%2FzlCBVuTaJK8ZrQsrsio2YK%2Fimg.png&quot; data-filename=&quot;tree1.png&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;230&quot; width=&quot;772&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[트리 1]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[트리 1]&lt;/b&gt;은 부모 노드가 각각 8개씩 자식 노드를 갖고 있는 트리이다.&lt;/p&gt;
&lt;p&gt;(...으로 표시된 노드는 다 표시하지 못한 나머지 자식 노드들임을 알아두자.&lt;/p&gt;
&lt;p&gt;그리고 첫 번째 자식 노드만 비교적 구체적으로 표현했고 나머지 노드들도 그런 식으로 자식 노드들이 있어야 한다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;부모 노드에서는 자식 노드들에서 행렬 곱셈한 결과로 나온 행렬들을 더하는 작업의 실행 시간을 나타낸다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;트리의 높이는 $\log_{2} n$이고 리프 노드의 개수는 $8^{\log_{2}n} = n^{3}$이다.&lt;/p&gt;
&lt;p&gt;(트리의 높이는 n을 1이 될 때까지 2로 몇 번 나눌 수 있는가이고&lt;/p&gt;
&lt;p&gt;리프 노드 개수는 트리 깊이가 1씩 증가할 때마다 8배씩 증가하므로 $8^{\log_{2}n} = n^{3}$이 된다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;트리의 총 실행 시간을 (리프 노드들을 제외한 모든 레벨의 총 실행 시간) + (리프 노드들의 총 실행 시간)으로 구하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ \begin{align} T(n) &amp;amp;= (cn^{2} + 8c(n/2)^{2} + 8^{2}c(n/4)^{2} + \cdots + (8/4)^{(\log_{2} n)-1}cn^{2}) + (\Theta(n^{3})) \\ &amp;amp;= \sum_{k=0}^{(\log_{2} n)-1} 2^{k}cn^{2} + \Theta(n^{3}) = {cn^{2}(2^{\log_{2}n} - 1) \over (2 - 1)} + \Theta(n^{3}) \\ &amp;amp;= cn^{2}(n - 1) + \Theta(n^{3}) = \Theta(n^{3}) \end{align} $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결국 &lt;b&gt;[방법 2]&lt;/b&gt;는 &lt;b&gt;[방법 1]&lt;/b&gt;과 같은 시간 복잡도를 갖는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 Strassen 알고리즘을 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래와 같이 7개의 행렬을 정의해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ \begin{align} &amp;amp;M_{1} = (A_{1,1} + A_{2,2})(B_{1,1} + B_{2,2}) \\ &amp;amp;M_{2} = (A_{2,1} + A_{2,2})B_{1,1} \\ &amp;amp;M_{3} = A_{1,1}(B_{1,2} - B_{2,2}) \\ &amp;amp;M_{4} = A_{2,2}(B_{2,1} - B_{1,1}) \\ &amp;amp;M_{5} = (A_{1,1} + A_{1,2})B_{2,2} \\ &amp;amp;M_{6} = (A_{2,1} - A_{1,1})(B_{1,1} + B_{1,2}) \\ &amp;amp;M_{7} = (A_{1,2} - A_{2,2})(B_{2,1} + B_{2,2}) \end{align} $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 위의 행렬들로 아래의 식들이 성립된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ \begin{align} &amp;amp;C_{1,1} = M_{1} + M_{4} - M_{5} + M_{7} \\ &amp;amp;C_{1,2} = M_{3} + M_{5} \\ &amp;amp;C_{2,1} = M_{2} + M_{4} \\ &amp;amp;C_{2,2} = M_{1} - M_{2} + M_{3} + M_{6} \end{align} $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 식들을 직접 전개해 보면 &lt;b&gt;[식 1~4]&lt;/b&gt;가 그대로 나온다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출처 : &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%8A%88%ED%8A%B8%EB%9D%BC%EC%84%BC_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ko.wikipedia.org/wiki/%EC%8A%88%ED%8A%B8%EB%9D%BC%EC%84%BC_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1606567200481&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;슈트라센 알고리즘 - 위키백과, 우리 모두의 백과사전&quot; data-og-description=&quot;위키백과, 우리 모두의 백과사전. 둘러보기로 가기 검색하러 가기 선형대수학에서 슈트라센 알고리즘은 독일의 수학자 폴커 슈트라센(Volker Strassen)이 1969년에 개발한 행렬 곱셈 알고리즘이다. &quot; data-og-host=&quot;ko.wikipedia.org&quot; data-og-source-url=&quot;https://ko.wikipedia.org/wiki/%EC%8A%88%ED%8A%B8%EB%9D%BC%EC%84%BC_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98&quot; data-og-url=&quot;https://ko.wikipedia.org/wiki/%EC%8A%88%ED%8A%B8%EB%9D%BC%EC%84%BC_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%8A%88%ED%8A%B8%EB%9D%BC%EC%84%BC_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.wikipedia.org/wiki/%EC%8A%88%ED%8A%B8%EB%9D%BC%EC%84%BC_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;슈트라센 알고리즘 - 위키백과, 우리 모두의 백과사전&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;위키백과, 우리 모두의 백과사전. 둘러보기로 가기 검색하러 가기 선형대수학에서 슈트라센 알고리즘은 독일의 수학자 폴커 슈트라센(Volker Strassen)이 1969년에 개발한 행렬 곱셈 알고리즘이다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;ko.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;앞서 간단하게 설명했던 것과 같이 행렬의 곱셈을 1번 줄이는 대신 행렬의 덧셈을 여러번 더 수행한 셈이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;행렬의 덧셈이 비록 여러번 더 수행되었지만 이는 $n^{2}$의 상수배에 달하는 시간이고 이는 결국 $\Theta(n^{2})$에 종결된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다시 실행 시간 T(n)을 정의해 보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;(굳이 다시 트리로 펼치지 않고 식으로 정의하겠다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ \begin{align} T(n) &amp;amp;= 7T(n/2) + \Theta(n^{2}) \\ &amp;amp;= (cn^{2} + 7c(n/2)^{2} + 7^{2}c(n/4)^{2} + \cdots + ({7 \over 4})^{(\log_{2} n)-1}cn^{2}) + \Theta(n^{\log_{2} 7}) \\ &amp;amp;= \sum_{k=0}^{(\log_{2} n) - 1} ({7 \over 4})^{k}cn^{2} + \Theta(n^{\log_{2} 7}) \\ &amp;amp;= {{7 \over 4}^{\log_{2} n} - 1 \over {7 \over 4} - 1}cn^{2} + \Theta(n^{\log_{2} 7}) \\ &amp;amp;= {4 \over 3}c(n^{\log_{2} 7} - n^{2}) + \Theta(n^{\log_{2} 7}) \\ &amp;amp;= \Theta(n^{\log_{2} 7}) \quad \because \, 2.80 &amp;lt; \log_{2} 7 &amp;lt; 2.81 \end{align} $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;트리의 높이는 $\log_{2} n$으로 &lt;b&gt;[방법 2]&lt;/b&gt;와 같지만 부모 노드의 자식 노드 개수가 8개에서 7개로 감소했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 리프 노드들의 총 실행 시간은 $\Theta(n^{\log_{2} 7})$이 된다.&lt;/p&gt;
&lt;p&gt;(트리의 각 레벨에 있는 노드들의 개수는 $7^{k}$(사실 k는 깊이(레벨-1)이다.)이고 마지막 레벨인 $\log_{2} n$을 $k$에 대입하면 $7^{\log_{2} n}$이 되며 이는 곧 $n^{\log_{2} 7}$이 된다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 Strassen 알고리즘은 [방법 1]과 [방법 2]보다 더 나은 시간 복잡도를 보여주고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;공간 복잡도는 어떨까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 처음 $M_{1,2, \cdots, 7}$을 위한 공간이 필요하므로 여러 개의 $(n/2)^{2}$ 크기의 공간이 필요하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 다음은 여러 개의 $(n/4)^{2}$ 크기가 필요하고 이런 식으로 $n^{2}$의 상수배 공간들이 필요하게 된다.&lt;/p&gt;
&lt;p&gt;(멀티 스레드가 아니면 어떤 노드와 그 노드의 사촌 노드를 위한 공간을 동시에 필요로 하지 않는다.&lt;/p&gt;
&lt;p&gt;즉, 실행 시간과 달리 공간은 트리의 깊이에 따라 7배씩 증가하지 않는다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 $\Theta(n^{2})$으로 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음으로 실제 실행 시간에 대해서 생각해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Strassen 알고리즘은 여러 행렬 덧셈을 수행하므로 매우 큰 $n$이 아니면 &lt;b&gt;[방법 1]&lt;/b&gt;보다 오래 걸린다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;필자도 Strassen 알고리즘이 더 빠른 $n$을 찾고자 여러 시도를 해봤는데 실행 시간이 감당이 되지 않고 메모리도 부족하여 일반 가정용 컴퓨터로 찾기에는 무리인 것으로 판단했다.&lt;/p&gt;
&lt;p&gt;(Strassen 알고리즘을 멀티 스레드로 돌려도 답이 없다.&lt;/p&gt;
&lt;p&gt;측정 가능한 $n$들에 대해 걸리는 시간을 그래프로 그리고 Strassen 알고리즘이 더 빨라지는 $n$을 추론하는 방법이 있겠다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 Strassen 알고리즘을 코드로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1606636907774&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Matrix
{
	typedef int (*Calculate)(int, int);
	static int Add(int value1, int value2)
	{
		return value1 + value2;
	}
	static int Sub(int value1, int value2)
	{
		return value1 - value2;
	}
	static int Set(int value1, int value2)
	{
		return value2;
	}

	Matrix(const Matrix&amp;amp; other, std::size_t row1, std::size_t col1, std::size_t row2, std::size_t col2, std::size_t size, Calculate cal)
	{
		_n = _size = size;
		_useStrassen = other._useStrassen; // it must be true
		_name = other._name;

		_values = new Value * [_size];
		for (std::size_t i = 0; i &amp;lt; _size; ++i)
		{
			_values[i] = new Value[_size];
			for (std::size_t j = 0; j &amp;lt; _size; ++j)
			{
				_values[i][j] = cal(other[row1 + i][col1 + j], other[row2 + i][col2 + j]);
			}
		}
	}

	Matrix(const Matrix&amp;amp; other, std::size_t row, std::size_t col, std::size_t size)
	{
		_n = _size = size;
		_useStrassen = other._useStrassen;
		_name = other._name;

		_values = new Value * [_size];
		for (std::size_t i = 0; i &amp;lt; _size; ++i)
		{
			_values[i] = new Value[_size];
			memcpy(_values[i], other[row + i] + col, _size);
		}
	}

	void Calc(const Matrix&amp;amp; other, std::size_t row, std::size_t rowCount, std::size_t col, std::size_t colCount, Calculate cal)
	{
		for (std::size_t i = 0; i &amp;lt; rowCount; ++i)
		{
			for (std::size_t j = 0; j &amp;lt; colCount; ++j)
			{
				_values[row + i][col + j] = cal(_values[row + i][col + j], other[i][j]);
			}
		}
	}

	void Strassen(const Matrix&amp;amp; other, std::size_t thisRow, std::size_t thisCol, std::size_t otherRow, std::size_t otherCol)
	{
		// reference : https://ko.wikipedia.org/wiki/%EC%8A%88%ED%8A%B8%EB%9D%BC%EC%84%BC_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98

		if (1 &amp;lt; _size)
		{
			std::size_t half = _size / 2;

			Matrix M1(*this, thisRow, thisCol, thisRow + half, thisCol + half, half, Add); // M1 = A11 + A22
			M1.Strassen(Matrix(other, otherRow, otherCol, otherRow + half, otherCol + half, half, Add), 0, 0, 0, 0); // M1 *= (B11 + B22)

			Matrix M2(*this, thisRow + half, thisCol, thisRow + half, thisCol + half, half, Add); // M2 = A21 + A22
			M2.Strassen(other, 0, 0, otherRow, otherCol); // M2 *= B11

			Matrix M3(*this, thisRow, thisCol, half); // M3 = A11
			M3.Strassen(Matrix(other, otherRow, otherCol + half, otherRow + half, otherCol + half, half, Sub), 0, 0, 0, 0); // M3 *= (B12 - B22)

			Matrix M4(*this, thisRow + half, thisCol + half, half); // M4 = A22
			M4.Strassen(Matrix(other, otherRow + half, otherCol, otherRow, otherCol, half, Sub), 0, 0, 0, 0); // M4 *= (B21 - B11)

			Matrix M5(*this, thisRow, thisCol, thisRow, thisCol + half, half, Add); // M5 = A11 + A12
			M5.Strassen(other, 0, 0, otherRow + half, otherCol + half); // M5 *= B22

			Matrix M6(*this, thisRow + half, thisCol, thisRow, thisCol, half, Sub); // M6 = A21 - A11
			M6.Strassen(Matrix(other, otherRow, otherCol, otherRow, otherCol + half, half, Add), 0, 0, 0, 0); // M6 *= (B11 + B12)

			Matrix M7(*this, thisRow, thisCol + half, thisRow + half, thisCol + half, half, Sub); // M7 = A12 - A22
			M7.Strassen(Matrix(other, otherRow + half, otherCol, otherRow + half, otherCol + half, half, Add), 0, 0, 0, 0); // M7 *= (B21 + B22)

			// for C11
			//Calc(M1, 0, half, 0, half, Set); // C11 = M1
			for (std::size_t i = 0; i &amp;lt; half; ++i)
			{
				memcpy(_values[i], M1[i], half);
			}
			Calc(M4, 0, half, 0, half, Add); // C11 += M4
			Calc(M5, 0, half, 0, half, Sub); // C11 -= M5
			Calc(M7, 0, half, 0, half, Add); // C11 += M7

			// for C12
			//Calc(M3, 0, half, half, half, Set); // C12 = M3
			for (std::size_t i = 0; i &amp;lt; half; ++i)
			{
				memcpy(_values[i] + half, M3[i], half);
			}
			Calc(M5, 0, half, half, half, Add); // C12 += M5

			// for C21
			//Calc(M2, half, half, 0, half, Set); // C21 = M2
			for (std::size_t i = 0; i &amp;lt; half; ++i)
			{
				memcpy(_values[half + i], M2[i], half);
			}
			Calc(M4, half, half, 0, half, Add); // C21 += M4

			// for C22
			//Calc(M1, half, half, half, half, Set); // C22 = M1
			for (std::size_t i = 0; i &amp;lt; half; ++i)
			{
				memcpy(_values[half + i] + half, M1[i], half);
			}
			Calc(M2, half, half, half, half, Sub); // C22 -= M2
			Calc(M3, half, half, half, half, Add); // C22 += M3
			Calc(M6, half, half, half, half, Add); // C22 += m6
		}
		else
		{
			_values[thisRow][thisCol] *= other[otherRow][otherCol];
			Progress::Proceed(_name, 1);
		}
	}

	void Strassen(const Matrix&amp;amp;&amp;amp; other, std::size_t row1, std::size_t col1, std::size_t row2, std::size_t col2)
	{
		Strassen(other, row1, col1, row2, col2);
	}
 };&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Strassen 알고리즘의 주요 코드 일부를 가져왔다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최대한 새로운 행렬을 생성하는 것을 막고자 인덱스로 행렬 성분들을 참조하도록 구현했다.&lt;/p&gt;
&lt;p&gt;(그리고 recurrence를 iteration으로 바꾸면 더욱 효율적일 것이다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 Strassen 알고리즘에 대해 알아보았다.&lt;/p&gt;</description>
      <category>알고리즘/study</category>
      <category>divide &amp;amp; conquer</category>
      <category>divide and conquer</category>
      <category>strassen</category>
      <category>Strassen algorithm</category>
      <author>이즈미르</author>
      <guid isPermaLink="true">https://izmirprogramming.tistory.com/13</guid>
      <comments>https://izmirprogramming.tistory.com/13#entry13comment</comments>
      <pubDate>Thu, 26 Nov 2020 23:56:32 +0900</pubDate>
    </item>
    <item>
      <title>[LeetCode] Longest Palindromic Substring</title>
      <link>https://izmirprogramming.tistory.com/12</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 출처&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.com/problems/longest-palindromic-substring/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;leetcode.com/problems/longest-palindromic-substring/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1602312493346&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Longest Palindromic Substring - LeetCode&quot; data-og-description=&quot;Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.&quot; data-og-host=&quot;leetcode.com&quot; data-og-source-url=&quot;https://leetcode.com/problems/longest-palindromic-substring/&quot; data-og-url=&quot;https://leetcode.com/problems/longest-palindromic-substring/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XpA5Y/hyHOq1YY3u/1fnByrLjCevzSKeodk5Ok0/img.png?width=500&amp;amp;height=260&amp;amp;face=0_0_500_260,https://scrap.kakaocdn.net/dn/nGzsQ/hyHNsUEUAq/TuvUKjpdssA7WpvPzqUrZ0/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot;&gt;&lt;a href=&quot;https://leetcode.com/problems/longest-palindromic-substring/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leetcode.com/problems/longest-palindromic-substring/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XpA5Y/hyHOq1YY3u/1fnByrLjCevzSKeodk5Ok0/img.png?width=500&amp;amp;height=260&amp;amp;face=0_0_500_260,https://scrap.kakaocdn.net/dn/nGzsQ/hyHNsUEUAq/TuvUKjpdssA7WpvPzqUrZ0/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Longest Palindromic Substring - LeetCode&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;leetcode.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Given a string s, return the longest palindromic substring in s.&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문자열 s가 주어졌을 때 가장 긴 palindromic 부분 문자열을 구하여라.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;palindromic 문자열이 무엇일까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왼쪽에서 오른쪽으로 읽으나 오른쪽에서 왼쪽으로 읽으나 같은 문자열을 말한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우리나라 말로 기러기, 토마토 등이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 1&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Input&lt;/b&gt;: s = &quot;babad&quot; &lt;br /&gt;&lt;b&gt;Output&lt;/b&gt;: &quot;bab&quot; &lt;br /&gt;&lt;b&gt;Note&lt;/b&gt;: &quot;aba&quot; is also a valid answer.&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&quot;babad&quot; 문자열에서 답은 &quot;bab&quot; 또는 &quot;aba&quot;가 될 수 있음을 보여준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;가운데 문자 기준으로 좌우 대칭임을 유의하자. (문자열 길이가 홀수일 때)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 2&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Input&lt;/b&gt;: s = &quot;cbbd&quot; &lt;br /&gt;&lt;b&gt;Output&lt;/b&gt;: &quot;bb&quot;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&quot;cbbd&quot; 문자열에서 답은 &quot;bb&quot;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;가운데 문자 없이 좌우 대칭임을 유의하자. (문자열 길이가 짝수일 때)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 3&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Input&lt;/b&gt;: s = &quot;a&quot; &lt;br /&gt;&lt;b&gt;Output&lt;/b&gt;: &quot;a&quot;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 4&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Input&lt;/b&gt;: s = &quot;ac&quot; &lt;br /&gt;&lt;b&gt;Output&lt;/b&gt;: &quot;a&quot;&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문자 1개도 palindromic 문자열이 될 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;가장 긴 palindromic 부분 문자열의 길이가 1일 때 가장 앞에 있는 문자를 구하면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제약 사항&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1 &amp;lt;= s.length &amp;lt;= 1000&lt;br /&gt;s&amp;nbsp;consist of only digits and English letters (lower-case and/or upper-case),&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 길이는 1 ~ 1000이고 문자열을 이루는 문자는 숫자 또는 영문 알파벳(&lt;span style=&quot;color: #333333;&quot;&gt;대소문자 구분)&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 어차피 문자 비교를 아스키코드값으로 하기 때문에 자동으로 대소문자 구분이 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;제일 먼저 가장 단순한 방법부터 생각해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그냥 문자열 전체를 돌면서 대칭인 부분 문자열을 찾는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문자열 길이를 n이라고 했을 때 시간 복잡도와 공간 복잡도는 아래와 같을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$[Brute\,force]\;time\,complexity\,:\,O(n^3),\,space\,complexity\,:\,O(1)$$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문제 제약 사항에 시간 복잡도 제한은 없기 때문에 위 방법으로 풀어도 답은 잘 나온다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 동적 계획법(dynamic programming) 문제를 많이 접해본 사람이면 동적 계획법으로 풀 방법이 떠오를 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;동적 계획법으로 풀기 위해 어떤 문제와 그 하위 문제 간의 관계를 먼저 파악해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 문제에서 어떤 부분 문자열이 palindromic 문자열이려면 첫 번째 문자와 마지막 문자가 같고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 두 문자를 제외한 부분 문자열이 palindromic 문자열이어야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 상위 문제와 하위 문제간의 관계는 정의됐다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;부분 문자열들에 대해 palindromic 문자열 여부를 어딘가에 저장해 놓고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;나중에 다시 해당 부분 문자열의 palindromic 문자열 여부를 알고자 할 때 바로 참고하면 될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1602316512729&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
public:
    string longestPalindrome(string s) {
    	const size_t SIZE = s.size();
        vector&amp;lt;vector&amp;lt;char&amp;gt;&amp;gt; dp(SIZE, vector&amp;lt;char&amp;gt;(SIZE, 0));
        size_t left = 0, right = 0;
        for (size_t i = 0; i &amp;lt; SIZE; ++i)
        {
            for (size_t j = 0; j &amp;lt; i; ++j)
            {
                dp[j][i] = (s[i] == s[j]) &amp;amp;&amp;amp; (i - j &amp;lt;= 2 || dp[j + 1][i - 1]);
                if (dp[j][i])
                {
                    if (i - j &amp;gt; right - left)
                    {
                        left = j;
                        right = i;
                    }
                }
            }
        }

        return s.substr(left, right - left + 1);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;변수 j는 부분 문자열 시작점을 나타내고 변수 i는 끝점을 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;길이가 작은 부분 문자열부터 큰 부분 문자열 순서로 palindromic 문자열 여부를 결정하기 때문에&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;bottom-up 접근법이 되고 상위 문제를 풀기 위해 하위 문제의 결과를 바로 참고할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 풀이 방식의 시간 복잡도와 공간 복잡도는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$\,[Dynamic\,programming]\;time\,complexity\,:\,O(n^2),\,space\,complexity\,:\,O(n^2)$$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 방법대로 배운 것을 잘 써먹어서 풀 때도 있지만 때로는 창의적으로 접근하여 쉽게 풀 때가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문자열을 순회하면서 각 문자가 중간에 위치한 것을 가정하고 가장 긴 palindromic 문자열을 찾는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;palindromic 문자열은 가운데 또는 가운데 문자 기준으로 대칭을 이루는 특징을 이용한 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1602318099937&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
private:
    void getSymmetry(string&amp;amp; s, size_t center, bool isEven, size_t&amp;amp; left, size_t&amp;amp; right)
    {
        if (isEven &amp;amp;&amp;amp; s[center] != s[center + 1])
        {
            left = center, right = center;
            return;
        }
        else
        {
            left = center, right = isEven ? center + 1 : center;
            while (0 &amp;lt; left &amp;amp;&amp;amp; right &amp;lt; s.size() &amp;amp;&amp;amp; s[left - 1] == s[right + 1])
            {
                left--;
                right++;
            }
        }
    }

public:
    string longestPalindrome(string s) {
    	if (s.empty())
        {
            return &quot;&quot;;
        }
        else if (1 == s.size())
        {
            return s;
        }

        const size_t SIZE = s.size();
        size_t min = 0, max = 0;
        for (size_t i = 0; i &amp;lt; SIZE - 1; ++i)
        {
            size_t left, right;
            // even
            getSymmetry(s, i, true, left, right);
            if (right - left &amp;gt; max - min)
            {
                min = left;
                max = right;
            }
            // odd
            getSymmetry(s, i, false, left, right);
            if (right - left &amp;gt; max - min)
            {
                min = left;
                max = right;
            }
        }

        return s.substr(min, max - min + 1);
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문자열을 순회하면서 각 문자 기준으로 2번의 대칭 확인을 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예시에서 본대로 palindromic 문자열의 길이가 짝수 또는 홀수가 될 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 풀이 방식의 시간 복잡도와 공간 복잡도는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$\,[Expand\,around\,center]\;time\,complexity\,:\,O(n^2),\,space\,complexity\,:\,O(1)$$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;동적 계획법보다 공간 복잡도가 더 좋은 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 Manacher's Algorithm으로 가장 긴 palindromic 부분 문자열을 찾을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음 게시물에서 소개하도록 하겠다.&lt;/p&gt;</description>
      <category>알고리즘/LeetCode</category>
      <category>leetcode</category>
      <category>Longest Palindromic Substring</category>
      <author>이즈미르</author>
      <guid isPermaLink="true">https://izmirprogramming.tistory.com/12</guid>
      <comments>https://izmirprogramming.tistory.com/12#entry12comment</comments>
      <pubDate>Sat, 10 Oct 2020 17:51:11 +0900</pubDate>
    </item>
    <item>
      <title>[LeetCode] Median of Two Sorted Arrays</title>
      <link>https://izmirprogramming.tistory.com/11</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 출처&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://leetcode.com/problems/median-of-two-sorted-arrays/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;leetcode.com/problems/median-of-two-sorted-arrays/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1601354191750&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Median of Two Sorted Arrays - LeetCode&quot; data-og-description=&quot;Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.&quot; data-og-host=&quot;leetcode.com&quot; data-og-source-url=&quot;https://leetcode.com/problems/median-of-two-sorted-arrays/&quot; data-og-url=&quot;https://leetcode.com/problems/median-of-two-sorted-arrays/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjYUJl/hyHEXAIvio/u6BKG2BFFE2CR0YAH3iknK/img.png?width=500&amp;amp;height=260&amp;amp;face=0_0_500_260,https://scrap.kakaocdn.net/dn/7zsx2/hyHE0xr7Za/MChgU6VNcW9eFgRou8PV21/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot;&gt;&lt;a href=&quot;https://leetcode.com/problems/median-of-two-sorted-arrays/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://leetcode.com/problems/median-of-two-sorted-arrays/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjYUJl/hyHEXAIvio/u6BKG2BFFE2CR0YAH3iknK/img.png?width=500&amp;amp;height=260&amp;amp;face=0_0_500_260,https://scrap.kakaocdn.net/dn/7zsx2/hyHE0xr7Za/MChgU6VNcW9eFgRou8PV21/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Median of Two Sorted Arrays - LeetCode&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;leetcode.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Given two sorted arrays&amp;nbsp;nums1&amp;nbsp;and&amp;nbsp;nums2&amp;nbsp;of size&amp;nbsp;m&amp;nbsp;and&amp;nbsp;n&amp;nbsp;respectively, return&amp;nbsp;the median&amp;nbsp;of the two sorted arrays.&lt;br /&gt;&lt;b&gt;Follow up&lt;/b&gt;:&amp;nbsp;The overall run time complexity should be&amp;nbsp;O(log (m+n)).&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;각 요소들이 정렬된 2개의 배열 nums1과 nums2가 주어진다.&lt;/p&gt;
&lt;p&gt;두 배열의 중간값을 구해야 한다.&lt;/p&gt;
&lt;p&gt;그리고 시간 복잡도가 O(log(m+n)) 이어야 한다는 추가 조건이 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 1&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Input&lt;/b&gt;: nums1 = [1,3], nums2 = [2] &lt;br /&gt;&lt;b&gt;Output&lt;/b&gt;: 2.00000 &lt;br /&gt;&lt;b&gt;Explanation&lt;/b&gt;: merged array = [1,2,3] and median is 2.&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 배열의 개수 합이 홀수면 그냥 가운데 요소를 찾으면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 2&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Input&lt;/b&gt;: nums1 = [1,2], nums2 = [3,4] &lt;br /&gt;&lt;b&gt;Output&lt;/b&gt;: 2.50000 &lt;br /&gt;&lt;b&gt;Explanation&lt;/b&gt;: merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5.&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 배열의 개수 합이 짝수면 가운데 두 요소의 중간값을 구하면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 3&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Input&lt;/b&gt;: nums1 = [0,0], nums2 = [0,0] &lt;br /&gt;&lt;b&gt;Output&lt;/b&gt;: 0.00000&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 4&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Input&lt;/b&gt;: nums1 = [], nums2 = [1] &lt;br /&gt;&lt;b&gt;Output&lt;/b&gt;: 1.00000&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;nums1 배열이 빈 배열일 수 있다는 것을 보여준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 5&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;Input&lt;/b&gt;: nums1 = [2], nums2 = [] &lt;br /&gt;&lt;b&gt;Output&lt;/b&gt;: 2.00000&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;nums2 배열이 빈 배열일 수 있다는 것을 보여준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제약 사항&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;nums1.length == m&lt;br /&gt;nums2.length == n&lt;br /&gt;0 &amp;lt;= m &amp;lt;= 1000&lt;br /&gt;0 &amp;lt;= n &amp;lt;= 1000&lt;br /&gt;1 &amp;lt;= m + n &amp;lt;= 2000&lt;br /&gt;10^6 &amp;lt;= nums1[i], nums2[i] &amp;lt;= 10^6&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;각 배열의 개수는 0 ~ 1000개이고 요소가 가질 수 있는 값은 int 값 범위 내이다.&lt;/p&gt;
&lt;p&gt;그리고 두 배열 모두 비어있는 상황은 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;풀이&lt;/h3&gt;
&lt;p&gt;먼저 문제를 어떻게 풀어야 할지 고민해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문제를 풀 때 가장 단순한(Brute Force) 방법부터 시작하는 것이 좋다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그냥 두 배열을 다시 정렬한 후에 중간값을 구하면 될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일반적으로 빠른 정렬을 사용하더라도 시간 복잡도는 O((m+n)log(m+n))이 될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 문제에서 정렬을 좀 더 빠르게 한다면 O(m+n)이 나올 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;병합 정렬(merge sort)에서 divide &amp;amp; conquer 후 combine하는 과정을 똑같이 따라 하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 배열의 요소들을 앞에서부터 하나씩 비교해 가며 더 작은 요소를 새로운 배열에 추가하여 정렬하는 방법 말이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래도 문제에서 요구하는 시간 복잡도를 충족시키지 못한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;시간 복잡도 조건으로 인해 문제에서 제시하는 풀이 방법은 정렬하지 말라는 것으로 해석할 수 있다.&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어떻게 해야 할까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문제의 조건으로부터 힌트를 얻을 때가 꽤 자주있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;O(log(m+n))을 어디서 많이 보지 않았는가?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이진 탐색(binary search)의 시간 복잡도이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일단 여기까지만 생각하고 다시 문제로 돌아가 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;nums1 배열과 nums2 배열은 정렬되어 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;nums1 배열의 어느 지점(i)과 nums2 배열 어느 지점(j)을 기준으로 각각 둘로 나눈다면 아래와 같을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ nums1[0], nums1[1], ... nums1[i - 1]　|　nums1[i], nums1[i + 1], ... nums1[m - 1] $$&lt;/p&gt;
&lt;p&gt;$$ nums2[0], nums2[1], ... nums2[j - 1]　|　nums2[j], nums2[j + 1], ... nums2[n - 1] $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 때 nums1[0], nums1[1], ... nums1[i - 1]과 nums2[0], nums2[1], ... nums2[j - 1]을 왼쪽 부분이라고 하고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;nums1[i], nums1[i + 1], ... nums1[m - 1]과 nums2[j], nums2[j + 1], ... nums2[n - 1]을 오른쪽 부분이라고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 왼쪽 부분의 개수가 오른쪽 부분의 개수와 같고 (또는 1개 차이가 나고)&lt;/p&gt;
&lt;p&gt;(조건 1)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왼쪽 부분에 있는 모든 요소가 항상 오른쪽 부분에 있는 모든 요소보다 값이 작으면&lt;/p&gt;
&lt;p&gt;(조건 2)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 배열을 합쳐 다시 정렬했을 때 중간에 위치할 요소가 &lt;span style=&quot;color: #333333;&quot;&gt;nums1[i - 1], nums1[i], nums2[j - 1], nums2[j] 중에&lt;/span&gt;&amp;nbsp;있게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 위 두 조건을 만족할 i와 j를 찾으면 되는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;첫 번째 조건을 먼저 식으로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ [식1]　i + j = (m - i) + (n - j) $$&lt;/p&gt;
&lt;p&gt;$$ [식2]　i + j = (m - i) + (n - j) + 1 $$&lt;/p&gt;
&lt;p&gt;$$ [식3]　i + j + 1 = (m - i) + (n - j) $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 [식1]부터 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왼쪽 항(i + j)은 왼쪽 부분의 개수를 나타내고 오른쪽 항((m - i) + (n - j))은 오른쪽 부분의 개수를 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왼쪽 항에서 i는 nums1 배열의 왼쪽 부분의 개수이고 j는 nums2 배열의 왼쪽 부분의 개수이다.&lt;/p&gt;
&lt;p&gt;(헷갈리면 m과n, i와 j에 직접 값을 넣고 세어서 확인해 보자.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오른쪽 항에서 (m - i)는 nums1 배열의 오른쪽 부분의 개수이고 (n - j)는 nums2 배열의 오른쪽 부분의 개수이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 [식2]와 [식3]도 마찬가지인 맥락이고 +1이 오른쪽 항에 붙었는지와 왼쪽 항에 붙었는지의 차이일 뿐이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[식1]은 두 배열 개수 총 합(m + n)이 짝수일 때이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다시 말해 왼쪽 부분의 개수와 오른쪽 부분의 개수가 정확히 같은 경우는 요소들 총 개수가 짝수일 때 가능하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[식2]는 왼쪽 부분에 1개가 더 많은 것을 나타내고 [식3]은 오른쪽 부분에 1개가 더 많은 경우를 나타낸다.&lt;/p&gt;
&lt;p&gt;(+1을 붙인 쪽이 더 적기 때문에 +1을 붙여주는 것이다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 [식2]와 [식3]은 요소들 총 개수가 홀수일 때이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 식들을 j 기준으로 정리하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ [식1]　j = (m + n) / 2 - i $$&lt;/p&gt;
&lt;p&gt;$$ [식2]　j = (m + n + 1) / 2 - i $$&lt;/p&gt;
&lt;p&gt;$$ [식3]　j = (m + n - 1) / 2 - i $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 바로 알 수 있는 것은 우리가 첫 번째 조건을 만족시키기 위해 i를 선택하면 j는 결정된다는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉 i를 선택하고 위 식으로 j를 구하면 첫 번째 조건을 만족시킨다는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 위에서 어떤 식을 써야 할까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 배열의 개수가 각각 짝수일 때와 홀수일 때 경우의 수를 다 구분하고 위 식 중에서 골라서 사용해야 할까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;모든 언어가 그런 것은 아니지만 C/C++ 기준으로 저 식을 계산할 때 소수점은 버려지게 된다.&lt;/p&gt;
&lt;p&gt;(m, n, i, j의 타입이 정수일 때)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 [식2]는 [식1]을 사용해야 할 경우도 포함하게 된다.&lt;/p&gt;
&lt;p&gt;([식2]에서 m + n이 짝수면 +1로 인해 분자가 홀수가 되고 2로 나누면 ~.5가 되고 이것은 버려지게 된다.&lt;/p&gt;
&lt;p&gt;결국 [식1]에서 구하는 j와 같게 된다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[식3]은 [식1]을 포함하지 못하기 때문에 [식3]으로 모든 경우를 다룰 수는 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 우리는 [식2]를 사용하여 m과 n의 짝수와 홀수 구분 없이 사용하기로 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 두 번째 조건을 식으로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ nums1[i - 1] &amp;lt;= nums2[j] &amp;amp;&amp;amp; nums2[j - 1] &amp;lt;= nums1[i] $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;nums1 배열과 nums2 배열은 이미 정렬되어 있기 때문에 두 번째 조건을 4개 요소만 사용해서 검사할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 두 조건 모두 만족하는 i와 j를 찾았고 어느 요소들이 중간에 위치할 수 있는지 알게 됐다고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;m + n이 짝수면 중간에 있는 두 요소들의 중간값을 계산하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;m + n이 홀수면 딱 중간에 있는 요소의 값이 답이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 m + n이 짝수일 때 중간에 위치할 두 요소를 찾아야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 4개 요소 중에서 쉽게 찾을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;nums1[i - 1]과 nums2[j - 1] 중에 하나가 왼쪽 부분의 최댓값이 될 것이고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;nums1[i]와 nums2[j] 중에 하나가 오른쪽 부분의 최솟값이 될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왼쪽 부분의 최댓값과 오른쪽 부분의 최솟값으로 중간값을 계산하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음으로 m + n이 홀수일 때이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;짝수일 때와 마찬가지로 왼쪽 부분의 최댓값과 오른쪽 부분의 최솟값을 구한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 둘 중 하나를 결정하면 되는데 우리가 첫 번째 조건의 [식2]를 사용할 경우를 생각해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[식2]는 왼쪽 부분에 요소가 1개 더 많은 경우를 나타낸다고 했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왼쪽 부분에 1개 더 많으니 왼쪽 최댓값과 오른쪽 최솟값 중에 왼쪽 최댓값이 딱 중간에 있는 것이므로 왼쪽 최댓값을 선택하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 i를 선택하는 방법을 생각해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그냥 0부터 m - 1까지 순서대로 조건들을 검사할 수도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 두 번째 조건을 검사하고 다음으로 검사해 볼 요소가 선택했던 요소 기준으로 왼쪽에 있는지 오른쪽에 있는지 알 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;nums1[i]가 nums2[j - 1]보다 작으면 다음으로 선택할 요소를 i 이후에 있는 요소로 선택해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;nums2[j]가 nums1[i - 1]보다 작으면 다음으로 선택할 요소를 i 이전에 있는 요소로 선택해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앞서 잠깐 언급했던 이진 탐색을 사용하면 보다 빠르게 찾을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 이 내용들을 정리하여 코드로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1601812243801&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Solution {
public:
    double findMedianSortedArrays(vector&amp;lt;int&amp;gt;&amp;amp; nums1, vector&amp;lt;int&amp;gt;&amp;amp; nums2) {
        if (nums1.size() &amp;gt; nums2.size())
        {
            nums1.swap(nums2);
        }

        const int n = nums1.size();
        const int m = nums2.size();
        int left = 0, right = n;
        while (left &amp;lt;= right)
        {
            int i = (right - left + 1) / 2 + left;
            int j = (n + m + 1) / 2 - i;
            int nums1_1 = i &amp;gt; 0 ? nums1[i - 1] : INT_MIN;
            int nums1_2 = i &amp;lt; n ? nums1[i] : INT_MAX;
            int nums2_1 = j &amp;gt; 0 ? nums2[j - 1] : INT_MIN;
            int nums2_2 = j &amp;lt; m ? nums2[j] : INT_MAX;
            if (nums1_1 &amp;gt; nums2_2)
            {
                right = i - 1;
            }
            else if (nums2_1 &amp;gt; nums1_2)
            {
                left = i + 1;
            }
            else
            {
                int leftMax = max(nums1_1, nums2_1);
                int rightMin = min(nums1_2, nums2_2);
                if (0 != (n + m) % 2)
                {
                    return leftMax;
                }
                else
                {
                    return (leftMax + rightMin) / 2.0;
                }
            }
        }

        return 0.0;
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;m &amp;lt;= n을 보장하기 위해서 처음에 swap하는 부분이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;m &amp;lt;= n이 보장되어야 nums2 배열에서 out of bounds 문제가 발생하지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 i와 j가 배열의 경계 부분에 있을 때를 위해 INT_MIN과 INT_MAX를 대신 사용한 부분도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/LeetCode</category>
      <category>leetcode</category>
      <category>Median of Two Sorted Arrays</category>
      <author>이즈미르</author>
      <guid isPermaLink="true">https://izmirprogramming.tistory.com/11</guid>
      <comments>https://izmirprogramming.tistory.com/11#entry11comment</comments>
      <pubDate>Sun, 4 Oct 2020 20:55:11 +0900</pubDate>
    </item>
    <item>
      <title>문자열 검색 알고리즘 2편 (Boyer Moore)</title>
      <link>https://izmirprogramming.tistory.com/9</link>
      <description>&lt;p&gt;저번 문자열 검색 알고리즘 1편에서 Naive, Rabin Karp, KMP를 알아보았었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://izmirprogramming.tistory.com/8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2020/04/16 - [알고리즘] - 문자열 검색 알고리즘 1편 (Naive, Rabin Karp, KMP)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1587023398194&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;문자열 검색 알고리즘 1편 (String searching algorithm)&quot; data-og-description=&quot;이번에 알아볼 알고리즘은 문자열 검색 알고리즘이다. 이름 그대로 본문 문자열(haystack)에서 찾고자 하는 특정 문자열(pattern)의 위치를 찾는 알고리즘이다. 문자열 검색 알고리즘으로 따로 언급할 만큼 많은..&quot; data-og-host=&quot;izmirprogramming.tistory.com&quot; data-og-source-url=&quot;https://izmirprogramming.tistory.com/8&quot; data-og-url=&quot;https://izmirprogramming.tistory.com/8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gASkU/hyFHNEVCA3/ka6ZknkN9KbS66kjnyHzj1/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/TIrfl/hyFGj6xnSl/QJCprSJvuKFaHH4tMVfgL1/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/ctHC9z/hyFGyJn8Hi/eWikKc3lQaWudIWBzkF3N1/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot;&gt;&lt;a href=&quot;https://izmirprogramming.tistory.com/8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://izmirprogramming.tistory.com/8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gASkU/hyFHNEVCA3/ka6ZknkN9KbS66kjnyHzj1/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/TIrfl/hyFGj6xnSl/QJCprSJvuKFaHH4tMVfgL1/img.jpg?width=800&amp;amp;height=533&amp;amp;face=0_0_800_533,https://scrap.kakaocdn.net/dn/ctHC9z/hyFGyJn8Hi/eWikKc3lQaWudIWBzkF3N1/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;문자열 검색 알고리즘 1편 (String searching algorithm)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;이번에 알아볼 알고리즘은 문자열 검색 알고리즘이다. 이름 그대로 본문 문자열(haystack)에서 찾고자 하는 특정 문자열(pattern)의 위치를 찾는 알고리즘이다. 문자열 검색 알고리즘으로 따로 언급할 만큼 많은..&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;izmirprogramming.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;이번 시간에는 Boyer Moore 알고리즘을 알아보도록 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실제로 가장 많이 사용되고 있는 문자열 검색 알고리즘이 바로 Boyer Moore 알고리즘이라고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Boyer Moore 알고리즘도 앞서 다뤘던 문자열 알고리즘처럼&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;미리 생성한 문자열 정보를 토대로 효율적인 검색을 수행한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다른 점이 몇가지 있다면 문자열을 비교할 때 뒤에서부터 비교한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;매번 본문과 패턴을 비교할 때 마지막 문자부터 시작해 첫 문자까지 비교하는 방식으로 진행한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 미리 생성한 문자열 정보를 보다 적극적으로 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;구체적으로 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 나쁜 문자(Bad character)라는 것이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다. 문자열을 비교할 때 불일치가 발생한 위치의 본문 문자를 가리킨다. (네이밍 참 단순 명쾌하다ㅋㅋ)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 예시의 빨간 문자가 나쁜 문자이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[본문]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;B&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;C&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;A&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;b&gt;[패턴]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 66.8605%; height: 5px;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;C&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;A&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 착한 접미부(Good suffix)라는 것도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문자열 비교를 뒤에서부터 시작하기 때문에 본문과 패턴의 일치하는 부분은 접미부가 되고 이를 착한 접미부라고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 예시에서는 파란색으로 표시한 &quot;CA&quot;가 되겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(여기서 용어 혼동이 있을 수 있는데 접두사 = 접두부, 접미사 = 접미부라고 생각하자)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 &lt;u&gt;착한 접미부의 접미부가 이루는 가장 긴 경계&lt;/u&gt;(필자가 지음)가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;말이 좀 길지만 해석하면 별 것 아니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;경계(Border)란 용어는 앞서 1편에서 KMP 알고리즘을 다룰 때 언급했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;간단하게 상기해보자면 문자열의 접두사와 접미사가 같은 이 쌍을 경계라고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다만 문자열 전체와 같은 접두사와 접미사가 이루는 경계는 제외한다고 했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(그냥 문자열과 접두사, 접미사 모두가 똑같은 경우를 경계로 취급하지 않는다는 얘기)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 패턴에서 예를 들자면 맨 앞의 &quot;A&quot;와 맨 끝 &quot;A&quot;가 접두사와 접미사로 경계를 이룬다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다시 돌아와서 앞서 착한 접미부는 &quot;CA&quot;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 착한 접미부의 접미부는 &quot;CA&quot;와 &quot;A&quot;가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 중 패턴에서 가장 긴 경계를 이루는 접미부를 찾으면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 예시에서는 딱 한가지 경우로 맨 앞의 &quot;A&quot;와 맨 끝 &quot;A&quot;가 경계를 이루고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이것이 &lt;u&gt;착한 접미부의 접미부가 이루는 가장 긴 경계&lt;/u&gt;가 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이것들을 어떻게 사용하는 것일까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;본문과 패턴을 비교하는 도중에 나쁜 문자가 생길 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 나쁜 문자가 패턴에 있다면 나쁜 문자가 있는 위치를 불일치가 발생한 위치와 비교해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여러 개의 나쁜 문자가 패턴에 있을 수도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럴 때는 가장 오른쪽에 있는 문자만 취급하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴 내 나쁜 문자가 불일치가 발생한 위치보다 오른쪽에 있다면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아쉽게도 나쁜 문자는 패턴을 이동시키는데 더이상 도움이 되지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왼쪽에 있다면 쓸모가 있지만 아직 바로 쓰기 전에 한 가지 더 고려해야 할 것이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;바로 착한 접미부이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴에서 이 &lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;이 착한 접미부 앞에 있는지 찾아야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들어 아래와 같이 본문과 패턴을 비교 중에 있다고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[본문]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 20px;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 20px;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 20px;&quot;&gt;&lt;span&gt;A&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;C&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;A&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;B&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;A&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 20px;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 12.5%; text-align: center; height: 20px;&quot;&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;b&gt;[패턴]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 75.2553%; height: 36px;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;A&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;B&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;A&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위에서 본문의 빨간색 문자 &quot;C&quot;가 나쁜 문자이고 파란색 문자열 &quot;ABA&quot;가 착한 접미부이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;착한 접미부 앞에 &lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;이 존재하는가?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴의 두 번째 문자부터 네 번째 문자까지 비록 일부가 겹치긴 하더라도 &lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;이 존재한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 &lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;이 여러 번 나타날 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴에서 나쁜 문자를 찾을 때와 마찬가지로 가장 오른쪽에 나타난 문자열을 취급하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 다음으로 이 문자열 위치와 패턴에 나오는 나쁜 문자의 위치를 비교해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;애초에 착한 접미부가 존재하지 않거나 &lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;이 존재하지 않거나&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;나쁜 문자의 위치가 이 문자열 위치랑 &lt;span style=&quot;color: #ee2323;&quot;&gt;같거나&lt;/span&gt; 더 앞에 있다면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴 내 나쁜 문자를 본문의 나쁜 문자의 위치에 오도록 패턴을 이동시키면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;이 패턴 내 나쁜 문자보다 더 앞에 있다면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 문자열과 착한 접미부가 일치하도록 패턴을 이동시키면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왜 이렇게 하는 것일까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일단 공통적으로 패턴과 본문의 동일한 문자/문자열을 맞춰주도록 이동시키는 것은 개념상 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이동시키는 것 즉, 몇 칸 건너 뛰는 것은 구해놓은 정보를 토대로 애초에 불일치가 발생될 것을 알기 때문에 건너 뛰는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 위와 같이 비교하는 이유는 불일치 위치 기준으로 더 멀리(왼쪽)있는 문자/문자열에 맞추기 위해서다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왜 더 멀리 있는 문자/문자열로 맞춰주는 것일까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;가까이 있는 문자/문자열로 맞춰주면 무조건 더 멀리 있는 문자/문자열이 불일치하기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 간단한 예를 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[본문]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 20px;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 20px;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 20px;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 20px;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 20px;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;D&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;A&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 20px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;B&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 20px;&quot;&gt;D&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 20px;&quot;&gt;E&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;b&gt;[패턴]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 77.7907%; height: 16px;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;A&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;B&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;D&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;E&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;A&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;B&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;나쁜 문자는 빨간색으로 표시한 &quot;D&quot;이고 착한 접미부는 파란색으로 표시한 &quot;AB&quot;이고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;은 초록색으로 표시한 &quot;AB&quot;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;더 가까이 있는 나쁜 문자 &quot;D&quot;를 맞추기 위해 패턴을 1칸 이동한다 하더라도&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;본문의 착한 접미부 &quot;AB&quot;는 패턴과 맞지 않을 것이 뻔하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왜냐하면 착한 접미부와 맞는 문자열은 훨씬 앞에 있고 이것을 이미 알고 있기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 더 멀리 있는 문자/문자열에 맞춰주는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 위에서 빨간 글씨로 &quot;같거나&quot; 라는 조건을 추가했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴에서 나쁜 문자의 위치와 &lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;의 위치가 같을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 때는 나쁜 문자가 일치하도록 패턴을 이동시키는데&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이유는 본문에서 발견되는 패턴을 최대한 앞에서 찾기 위해서다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 마지막으로 아까 얘기 하다 말았던 패턴에서 불일치 위치보다 앞에 나오는 나쁜 문자가 없을 때가 남았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 &lt;u&gt;착한 접미부의 접미부가 이루는 가장 긴 경계&lt;/u&gt;를 이용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;가장 긴 경계를 이루는 접두사를 접미부에 오도록 패턴을 이동시키면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;경계가 없으면 패턴의 길이만큼 패턴을 이동시키면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;바로 이해하기엔 아무래도 예시가 최고인 것 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[본문]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 19px;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 19px;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 19px;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 19px;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 19px;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;D&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 19px;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;D&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 19px;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;B&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 19px;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;C&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 19px;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center; height: 19px;&quot;&gt;B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;b&gt;[패턴]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 78.0233%; height: 5px;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;E&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;D&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;B&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;C&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;나쁜 문자는 빨간색으로 표시한 &quot;D&quot;이고 착한 접미부는 파란색으로 표시한 &quot;DBC&quot;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;나쁜 문자 &quot;D&quot;는 패턴에서 불일치 위치 이전에 나타나지 않고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;바로 위에 언급한 마지막 조건을 생각하지 말고 이 상황에서 패턴을 이동시킨다면 어떻게 이동시키는 것이 좋을까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앞에 보이는 &quot;BC&quot;를 뒤에 있는 &quot;BC&quot;로 맞춰주도록 패턴을 이동시키는게 좋아 보인다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다. 뒤에 있는 &quot;BC&quot;가 착한 접미부의 접미부이고 앞에 있는 &quot;BC&quot;와 경계를 이루고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 실제 구현한 코드를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1587267515669&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::size_t BoyerMoore::FindString(std::string pattern, std::size_t startIndex)
{
	if (false == CheckString(pattern, startIndex))
		return std::string::npos;

	std::string haystack = this-&amp;gt;haystack.substr(startIndex);
	const std::size_t patternSize = pattern.size();

	// preprocessing for bad char table
	std::vector&amp;lt;std::size_t&amp;gt; badCharTable(CHAR_MAX + 1, patternSize);
	for (std::size_t i = 0; i &amp;lt; patternSize; ++i)
		badCharTable[pattern[i]] = i;
	
	// preprocessing for good suffix table 1
	// good suffix table 1 index : incorrect index
	// good suffix table 1 value : left good suffix index
	std::vector&amp;lt;std::size_t&amp;gt; goodSuffixTable1(patternSize, patternSize);
	std::size_t index = 0;
	// i : beginning index of right good suffix
	for (std::size_t i = 1; i &amp;lt; patternSize; ++i)
	{
		// j : index for finding left good suffix
		for (std::size_t j = 0; j &amp;lt; i; ++j) 
		{
			// index : index for comparing those strings
			for (index = 0; index &amp;lt; patternSize - i; ++index)
				if (pattern[j + index] != pattern[i + index])
					break;
			if (patternSize - i == index)
				goodSuffixTable1[i - 1] = j;
		}
	}

	// preprocessing  for good suffix table 2
	// good suffix table 2 index : incorrect index
	// good suffix table 2 value : suffix index of the longest border
	std::vector&amp;lt;std::size_t&amp;gt; goodSuffixTable2(patternSize, patternSize);
	std::size_t longestBorderIdx = patternSize;
	// i : border length
	for (std::size_t i = 1; i &amp;lt; patternSize; ++i)
	{
		std::size_t beginningIdxOfPfx = 0;
		const std::size_t beginningIdxOfSfx = patternSize - i;
		for (; beginningIdxOfPfx &amp;lt; i; ++beginningIdxOfPfx)
			if (pattern[beginningIdxOfPfx] != pattern[beginningIdxOfSfx + beginningIdxOfPfx])
				break;
		if (i == beginningIdxOfPfx)
		{
			goodSuffixTable2[beginningIdxOfSfx - 1] = beginningIdxOfSfx;
			longestBorderIdx = beginningIdxOfSfx;
		}
		else
			goodSuffixTable2[beginningIdxOfSfx - 1] = longestBorderIdx;
	}

	int idx = 0;
	std::size_t i = 0, badCharIdx, leftGoodSfxIdx;
	while (i &amp;lt;= haystack.size() - patternSize)
	{
		for (idx = (int)(patternSize - 1); idx &amp;gt;= 0; --idx)
			if (haystack[i + idx] != pattern[idx])
				break;
		if (0 &amp;gt; idx)
			return startIndex + i;

		badCharIdx = badCharTable[haystack[i + idx]];
		leftGoodSfxIdx = goodSuffixTable1[idx];

		if (badCharIdx &amp;lt; idx)
		{
			if (badCharIdx &amp;lt;= leftGoodSfxIdx)
				i += (idx - badCharIdx);
			else
				i += ((std::size_t)idx + 1 - leftGoodSfxIdx);
		}
		else
			i += goodSuffixTable2[idx];
	}

	return std::string::npos;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;badCharTable은 나쁜 문자가 발견되었을 때 패턴에서 가장 오른쪽에 나타나는 인덱스를 저장하고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;나쁜 문자의 범위는 본문에 있는 문자들의 종류로 하면 좋겠지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;미리 조사하는 것은 비효율적이기 때문에 그냥 아스키코드 값들에 대해 모두 저장하는 것으로 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 이 테이블에 들어가는 기본값은 패턴의 크기이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉, 패턴에 없는 문자는 패턴 크기값이 들어가 있는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 goodSuffixTable1은 인덱스가 불일치가 발생된 위치이고 값은 &lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;의 위치이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마찬가지로 기본값은 패턴의 크기가 저장된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;goodSuffixTable2는 인덱스가 불일치가 발생한 위치이고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;값은 &lt;u&gt;착한 접미부의 접미부가 이루는 가장 긴 경계&lt;/u&gt;의 접미사의 위치이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;불일치가 발생한 위치를 패턴 끝(정확히는 패턴 끝에서 두번째)부터 처음으로 이동하며 값을 조사한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 조사에 실패하면 이전에 찾았던 가장 긴 경계를 재사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막 반복문(while문)에서 미리 생성한 문자열 정보를 토대로 검색을 수행한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;제일 먼저 나쁜 문자를 찾고 패턴에서의 나쁜 문자의 위치와 불일치 위치를 비교한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;나쁜 문자의 위치가 유효하다면 &lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;의 인덱스와 비교한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앞서 설명한대로 &lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;의 위치 &amp;gt;= 나쁜 문자의 위치 이면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴의 나쁜 문자를 본문의 나쁜 문자와 일치하도록 패턴을 이동시킨다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇지 않으면 &lt;u&gt;착한 접미부와 동일한 문자열&lt;/u&gt;의 위치가 착한 접미부에 오도록 패턴을 이동시킨다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 나쁜 문자가 발견된 위치가 유효하지 않으면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;착한 접미부의 접미부가 이루는 가장 긴 경계&lt;/u&gt;의 접두부가 접미부에 오도록 이동시킨다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;가장 긴 경계가 없다면 기본값으로 저장한 패턴 크기만큼 이동하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;끝으로 시간 복잡도는 O(n * m)이 되겠다. (n : 본문 길이, m : 패턴 길이)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최악의 경우는 본문이 모두 단일 문자로 되어 있고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴도 본문과 같은 문자로 이루어져 있지만 맨 앞의 문자만 다를 경우이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;-- 여담 --&lt;/p&gt;
&lt;p&gt;여러 사이트를 돌아다니며 참고해 봤는데 조금씩 내용이 다른 것 같다.&lt;/p&gt;
&lt;p&gt;그래도 가장 믿을 만한 사이트는 위키나 출처가 좀 명확한 곳들인 것 같다.&lt;/p&gt;
&lt;p&gt;위키 : &lt;a href=&quot;https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm&quot;&gt;https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1587296443187&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Boyer&amp;ndash;Moore string-search algorithm - Wikipedia&quot; data-og-description=&quot;From Wikipedia, the free encyclopedia Jump to navigation Jump to search String searching algorithm For the Boyer-Moore theorem prover, see Nqthm. In computer science, the Boyer&amp;ndash;Moore string-search algorithm is an efficient string-searching algorithm that i&quot; data-og-host=&quot;en.wikipedia.org&quot; data-og-source-url=&quot;https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm&quot; data-og-url=&quot;https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Boyer&amp;ndash;Moore string-search algorithm - Wikipedia&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;From Wikipedia, the free encyclopedia Jump to navigation Jump to search String searching algorithm For the Boyer-Moore theorem prover, see Nqthm. In computer science, the Boyer&amp;ndash;Moore string-search algorithm is an efficient string-searching algorithm that i&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;en.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;참고 사이트 : &lt;a href=&quot;https://www-igm.univ-mlv.fr/~lecroq/string/node14.html&quot;&gt;https://www-igm.univ-mlv.fr/~lecroq/string/node14.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1587296459466&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Boyer-Moore algorithm&quot; data-og-description=&quot;The Boyer-Moore algorithm is considered as the most efficient string-matching algorithm in usual applications. A simplified version of it or the entire algorithm is often implemented in text editors for the &amp;laquo;search&amp;raquo; and &amp;laquo;substitute&amp;raquo; commands. The algorithm&quot; data-og-host=&quot;www-igm.univ-mlv.fr&quot; data-og-source-url=&quot;https://www-igm.univ-mlv.fr/~lecroq/string/node14.html&quot; data-og-url=&quot;https://www-igm.univ-mlv.fr/~lecroq/string/node14.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www-igm.univ-mlv.fr/~lecroq/string/node14.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www-igm.univ-mlv.fr/~lecroq/string/node14.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;Boyer-Moore algorithm&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;The Boyer-Moore algorithm is considered as the most efficient string-matching algorithm in usual applications. A simplified version of it or the entire algorithm is often implemented in text editors for the &amp;laquo;search&amp;raquo; and &amp;laquo;substitute&amp;raquo; commands. The algorithm&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www-igm.univ-mlv.fr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사실 필자가 쓴 것과 위 두개 출처에서의 내용이 조금 다르다.&lt;/p&gt;
&lt;p&gt;필자는 나쁜 문자가 유효하지 않으면 &lt;u&gt;착한 접미부의 접미부가 이루는 가장 긴 경계&lt;/u&gt;를 사용했지만&lt;/p&gt;
&lt;p&gt;출처에서는 그 가장 긴 경계를 착한 접미부에서 착한 접미부와 동일한 문자열을 찾지 못했을 때 사용한다.&lt;/p&gt;
&lt;p&gt;그래서 나쁜 문자와 착한 접미부가 이동할 수 있는 거리 중 더 큰 값을 사용하도록 설명되어 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 밖에도 쓸 내용이 더 있다.&lt;/p&gt;
&lt;p&gt;문자열 알고리즘의 성능을 따질 때 패턴이 본문에 나타나는지 안 나타나는지의 여부도 따지고&lt;/p&gt;
&lt;p&gt;만약 나타났을 때 이어서 계속 검색하는 경우의 성능도 따져야 한다.&lt;/p&gt;
&lt;p&gt;마지막으로 테이블 생성의 시간 복잡도가 O(m)인지도 조사해봐야 한다.&lt;/p&gt;</description>
      <category>알고리즘/study</category>
      <category>boyer moore</category>
      <category>KMP</category>
      <category>rabin karp</category>
      <category>string searching algorithm</category>
      <category>문자열 검색 알고리즘</category>
      <author>이즈미르</author>
      <guid isPermaLink="true">https://izmirprogramming.tistory.com/9</guid>
      <comments>https://izmirprogramming.tistory.com/9#entry9comment</comments>
      <pubDate>Sun, 19 Apr 2020 20:52:51 +0900</pubDate>
    </item>
    <item>
      <title>문자열 검색 알고리즘 1편 (Naive, Rabin Karp, KMP)</title>
      <link>https://izmirprogramming.tistory.com/8</link>
      <description>&lt;p&gt;이번에 알아볼 알고리즘은 문자열 검색 알고리즘이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이름 그대로 본문 문자열(haystack)에서 찾고자 하는 특정 문자열(pattern)의 위치를 찾는 알고리즘이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문자열 검색 알고리즘으로 따로 언급할 만큼 많은 연구가 이뤄지고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;종류는 대표적인 것들로 아래와 같이 4가지가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Naive&lt;/li&gt;
&lt;li&gt;Rabin Karp&lt;/li&gt;
&lt;li&gt;KMP (Knuth-Morris-Pratt)&lt;/li&gt;
&lt;li&gt;Boyer Moore&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;1~3번 문자열 검색 알고리즘을 1편에서 설명하고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;4번 문자열 검색 알고리즘을 2편에서 설명하려고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 Naive부터 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. Naive&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Naive 방식은 본문 처음부터 끝까지 문자 하나하나씩 패턴과 비교하여 찾는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따로 설명하지 않아도 쉽게 구현할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1576326006009&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::size_t Naive::FindString(std::string pattern, std::size_t startIndex)
{
	if (false == CheckString(pattern, startIndex))
		return std::string::npos;

	std::string haystack = this-&amp;gt;haystack.substr(startIndex);

	std::size_t index = std::string::npos;
	for (std::size_t i = 0; i &amp;lt;= haystack.size() - pattern.size(); ++i)
	{
		for (index = 0; index &amp;lt; pattern.size(); ++index)
		{
			if (pattern[index] != haystack[i + index])
				break;
		}

		if (index == pattern.size())
			return startIndex + i;
	}

	return std::string::npos;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문자열을 검색할 때 범위를 주의해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;본문의 처음 위치부터 패턴의 크기가 들어갈 마지막 위치까지 비교해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 4가지 알고리즘에서 최악의 성능을 가지므로 실제로 많이 사용하진 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다만 다른 문자열 검색 알고리즘이 정상적으로 구현되었는지 확인하기 위한 표본으로 사용하는데 유용하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;빅오 표기법(Big-O notation)으로 표현하면 O(n * m)이 되겠다. (n : 본문 길이, m : 패턴 길이)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2. Rabin Karp&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Rabin Karp 방식은 문자의 아스키코드(ASCII Code)&amp;nbsp;값을 이용한 해시(Hash)를 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;본문에서 &lt;span style=&quot;color: #333333;&quot;&gt;차례대로&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;패턴 크기만큼 문자열의 해시값을 구하고 미리 구해놓은 패턴의 해시값을 비교해본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 알고리즘의 핵심은 문자들의 아스키코드 값을 가지고 어떻게 해시값을 만들어 내느냐가 관건이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;해시 함수(Hash function)의 수식은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ H_i = S[i] &amp;times; 2^{m-1} + S[i+1] &amp;times; 2^{m-2} + &amp;middot;&amp;middot;&amp;middot; + S[i+m-2] &amp;times; 2^1 + S[i+m-1] &amp;times; 2^{0}&amp;nbsp;$$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;i 번째 H값은 본문에서 패턴 길이 크기 m 만큼의 문자열 S에 대한 해시 값을 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그러니까 첫 번째 H는 본문 첫 문자에서 패턴의 크기만큼의 부분 문자열에 대한 해시값을 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 식은 마치 10진수로 표현된 숫자(실제로는 아스키코드 값이지만)를 2진수로 변환하는 과정과 거의 비슷하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지만 놓고 보면 앞의 Naive 방식과 별로 다를 게 없어 보인다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;본문의 문자열 하나하나씩 해시함수를 계산하면 결국 Naive와 비교 횟수가 동일하기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아니 저 복잡한 수식을 계산하느라 더 오래 걸릴 것으로 예상할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 일단 H의 i+1 번째 수식을 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ H_{i+1} = S[i+1] &amp;times; 2^{m-1} + S[i] &amp;times; 2^{m-2} + &amp;middot;&amp;middot;&amp;middot; + S[i+m-1] &amp;times; 2^1 + S[i+m] &amp;times; 2^{0} $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 H의 i 수식에 2를 곱해보면 아래와 같이 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ 2H_i = (2 &amp;times; S[i] &amp;times; 2^{m-1}) + S[i+1] &amp;times; 2^{m-1} + &amp;middot;&amp;middot;&amp;middot; + S[i+m-2] &amp;times; 2^2 + S[i+m-1] &amp;times; 2^1 $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ 2H_i = (2 &amp;times; S[i] &amp;times; 2^{m-1}) + H_{i+1} - S[i+m] &amp;times; 2^0 $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 i+1 번째 H를 i 번째 H에 대해서 정리해보면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;$$ H_{i+1} = 2(H_i - S[i] &amp;times; 2^{m-1}) + S[i+m] $$&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 수식을 토대로 우리가 알 수 있는 사실은&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;매번 본문의 문자열마다 패턴 크기만큼 문자 하나하나 계산하여 해시값을 구할 필요가 없이&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이전 해시값과 양 끝 문자들로 다음 해시값을 구할 수 있는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;한 가지 주의할 점은 제일 첫 번째(i=0일 때) H값은 위의 수식으로 구할 수 없다. (-1번째 H는 존재하지 않기 때문)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 제일 첫 번째 H값을 구할 때는 제일 처음 소개한 수식으로 구해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 해시값을 구하는 부분을 코드로 표현하면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1576394805774&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::size_t RabinKarp::MakeHash(std::size_t index, std::size_t beforeHash, std::string&amp;amp; haystack, std::size_t patternSize)
{
	std::size_t ret = 0;
	if (0 == index) // first H
	{
		for (std::size_t i = 0; i &amp;lt; patternSize; ++i)
		{
			ret += (std::size_t)(haystack[i] * pow(2.f, patternSize - i - 1));
		}
		return ret % std::numeric_limits&amp;lt;std::size_t&amp;gt;::max();
	}
	else // H of i + 1
	{
		ret = (std::size_t)(2 * (beforeHash - haystack[index - 1] * pow(2.f, patternSize - 1)) + haystack[index + patternSize - 1]);
		return ret % std::numeric_limits&amp;lt;std::size_t&amp;gt;::max();
	}

	return std::string::npos;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;첫 번째 매개변수는 다음 해시값을 구할 수식의 i이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 번째 매개변수는 이전 해시값이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;세 번째 매개변수는 본문 문자열이고 마지막 매개변수는 패턴의 크기이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 메인 알고리즘을 보면 위의 코드도 쉽게 이해할 수 있을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1576396580562&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::size_t RabinKarp::FindString(std::string pattern, std::size_t startIndex)
{
	if (false == CheckString(pattern, startIndex))
		return std::string::npos;

	std::string haystack = this-&amp;gt;haystack.substr(startIndex);

	std::size_t patternHash = MakeHash(0, 0, pattern, pattern.size());
	std::size_t haystackHash = MakeHash(0, 0, haystack, pattern.size());
	std::size_t index = 0;
	for (std::size_t i = 0; i &amp;lt;= haystack.size() - pattern.size(); ++i)
	{
		if (patternHash == haystackHash)
		{
			for (index = 0; index &amp;lt; pattern.size(); ++index)
				if (haystack[i + index] != pattern[index])
					break;
			if (pattern.size() == index)
				return startIndex + i;
		}

		if(i != haystack.size() - pattern.size())
			haystackHash = MakeHash(i + 1, haystackHash, haystack, pattern.size());
	}

	return std::string::npos;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;초기에 패턴에 대한 해시값과 본문의 제일 첫 부분 문자열의 해시값을 구한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 반복문 내부에서는 만약 두 해시값이 같다면 실제로 문자열이 같은지 직접 비교해본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 작업은 해시 특성상 꼭 필요한 부분이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다른 문자열이더라도 같은 해시값을 가질 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴과 문자열이 같지 않다면 다음 해시값을 구해서 계속 비교한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;성능은 Naive 방식보다 조금 나을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;빅오 표기법으로 표현하면 O(n+m)이 되겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;3. KMP&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Rabin Karp 방식부터 느꼈을지 모르겠지만 고급(?)스러운 알고리즘일수록 이전에 검색한 정보를 잘 활용하는 편이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Rabin Karp 방식은 이전의 해시 데이터를 사용하여 다음 해시값을 구하는데 중복되는 작업을 제거한 셈이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;KMP 방식은 해시를 사용하지 않고 다른 방법으로 이전에 검색한 정보를 활용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 그 정보를 토대로 다음 검색할 몇 개의 문자를 건너뛸 수도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 방법을 알아보기 앞서 접두사(prefix)와 접미사(suffix)에 대해 알고 넘어가자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;언어 문법을 공부하면서 많이 봤을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;접두사는 어근 앞에 접미사는 어근 뒤에 붙어서 뜻을 더하거나 강조하는 새로운 말을 만든다고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 우리가 관심있는 것은 접두사가 단어의 앞에 존재하고 접미사가 단어 뒤에 위치한다는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;KMP 방식이 접두사와 접미사를 활용하는데 이 두개가 서로 같은 경우만 취급한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다만 접두사와 접미사가 단어의 전체 길이와 같은 경우는 제외한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들면 &quot;AABCDAAB&quot; 라는 문자열이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 접두사 &quot;AAB&quot;와 접미사 &quot;AAB&quot;가 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여러 개가 같을 수도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들면 &quot;AAABBCAA&quot;는 접두사와 접미사가 같은 경우는 &quot;AA&quot;와 &quot;A&quot;가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;또 다른 예로 &quot;ABABAB&quot;가 있다면 제일 길이가 긴 경우는 &quot;ABAB&quot;일 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이처럼 접두사와 접미사가 서로 겹쳐도 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 접두사와 접미사가 같은 쌍을 가리켜 경계(border)라고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 구체적인 방법을 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;본문의 문자열과 패턴을 비교하는 방식은 Naive 방식과 거의 동일하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 Navie 방식은 무식하게 패턴을 무조건 한 칸씩 이동하면서 본문과 비교하지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;KMP 방식은 경계 정보를 바탕으로 한 칸 이상을 이동하면서 본문과 비교한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;한 가지 예를 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[본문]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;E&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;b&gt;[패턴]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;E&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;처음 본문과 패턴을 비교할 때 본문의 &quot;C&quot;와 패턴의 &quot;E&quot;의 불일치가 발생한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 불일치가 발생하기 전의 문자열 &quot;ABCAB&quot;가 동일하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이쯤만 설명해도 앞서 경계를 왜 먼저 짚고 넘어 왔는지 느낌이 올 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Naive 방식은 불일치가 발생하면 패턴을 한 칸 이동하여 다시 비교를 하지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;KMP 방식은 &lt;u&gt;본문과 패턴의 비교된 문자열의 동일한 접두사의 가장 긴 경계&lt;/u&gt;를 바탕으로 패턴을 이동시킨다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;본문과 패턴의 비교된 문자열의 동일한 접두사&lt;/u&gt;(줄여서 동일한 접두사)의 의미를 풀어 써보면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 과정에서 본문의 비교된 문자열은 &quot;ABCABC&quot;이고 패턴의 비교된 문자열은 &quot;ABCABE&quot;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 문자열들의 &lt;u&gt;동일한 접두사&lt;/u&gt;가 바로 &quot;ABCAB&quot;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 동일한 접두사의 &lt;u&gt;가장 긴 경계&lt;/u&gt;는 &quot;AB&quot;가 되겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 이 경계 기준으로 접두사가 접미사에 오도록 패턴을 이동시켜 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉 &quot;ABCAB&quot; 문자열에서 접두사 &quot;AB&quot;가 접미사 &quot;AB&quot;에 오도록 패턴을 이동시키는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[본문]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;E&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;b&gt;[패턴]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 11.1111%; text-align: center;&quot;&gt;E&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이런 방식으로 패턴을 이동시키는 것에 대해 왜 합리적인지 몇가지 고민해 봐야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;첫 번째 고민해야 할 것은 왜 &lt;u&gt;동일한 접두사&lt;/u&gt;의 경계를 기준으로 접두사를 접미사에 오도록 맞추는가? 이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이는 참으로 당연하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴을 본문과 비교하면서 맞지 않았을 때 패턴을 어떻게 얼마큼 이동해야 하는지에 대한 최적의 해이기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴의 앞부분 문자열(접두사)이 뒤에 또 나온다면 (그리고 거기까지 본문과 일치 했다면)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;거기로 바로 이동시켜서 비교해 보는게 맞지 않겠는가?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 번째 고민해야 할 것은 왜 &lt;u&gt;가장 긴 경계&lt;/u&gt;여야 하는가? 이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어떻게 보면 &lt;u&gt;가장 긴 경계&lt;/u&gt;가 패턴과 일치하는 문자열을 찾을 확률이 가장 높아 보이기도 하지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;실은 패턴과 일치하는 문자열을 본문의 제일 앞에서 찾기 위함이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들어 &lt;u&gt;동일한 접두사&lt;/u&gt; &quot;AABAA&quot;가 있다고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 경계는 &quot;A&quot;와 &quot;AA&quot;가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;경계 &quot;A&quot;를 기준으로 이동한다면 패턴을 4칸 이동하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;경계 &quot;AA&quot;를 기준으로 이동한다면 패턴을 3칸 이동하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이처럼 &lt;u&gt;가장 긴 경계&lt;/u&gt;가 패턴을 본문의 제일 앞에서 찾도록 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 고민해야 할 것은 왜 꼭 &lt;u&gt;동일한 접두사&lt;/u&gt;의 경계의 접두사와 접미사여야 하는가? 이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이를 다르게 생각해 보면 &lt;u&gt;동일한 접두사&lt;/u&gt;에서 경계가 발견되지 않았을 때&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;동일한 접두사&lt;/u&gt;의 &lt;u&gt;동일한 접두사&lt;/u&gt;의 경계를 사용하면 어떨까? 이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들어 &lt;u&gt;동일한 접두사&lt;/u&gt; &quot;ABCABD&quot;가 있다고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 경계는 없지만 눈에 띄는건 &quot;AB&quot;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어? 그렇다면 앞에 &quot;AB&quot;를 뒤에 &quot;AB&quot; 오도록 패턴을 이동시키면 안되나? 라고 생각할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(나만 바보가 되고 싶진 않다 ㅋㅋ)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그런데 한 눈에 봐도 알 수 있듯이 &quot;C&quot;와 &quot;D&quot;가 달라서 어차피 불일치가 뜰 것이 분명하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 이건 이미 경계가 구해지지 않은 것으로 불일치라는 것임을 알 수 있다. (제발 어? 했기를..ㅋㅋㅋ)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 실제로 구현할 때 이 개념을 어떻게 써먹을지에 대해 얘기해 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 &lt;u&gt;동일한 접두사&lt;/u&gt;의 경계를 패턴을 이동할 때마다 매번 구해야 하는 것일까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만약 그렇다면 최악의 경우 Naive보다 더 안좋을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴을 본문과 비교하기 앞서 패턴 내에서 &lt;u&gt;동일한 접두사&lt;/u&gt;들의 &lt;u&gt;가장 긴 경계&lt;/u&gt;를 구하여&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;패턴을 이동시킬 거리를 미리 계산해 놓는 방법이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시나리오는 패턴의 특정 인덱스에서 본문과 불일치가 발생하면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 인덱스로 미리 계산해 놓은 패턴의 이동 거리를 구하도록 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이동 거리는 &lt;b&gt;&lt;u&gt;동일한 접두사&lt;/u&gt; 길이 -&amp;nbsp; &lt;u&gt;가장 긴 경계&lt;/u&gt; 길이&lt;/b&gt; 이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;동일한 접두사&lt;/u&gt;가 없는 경우 이동 거리가 1이 되고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;경계 자체가 없으면 &lt;u&gt;가장 긴 경계&lt;/u&gt;의 길이를 0으로 취급한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 &lt;u&gt;동일한 접두사&lt;/u&gt;의 문자열 길이가 1인 경우도 이동 거리가 1이 된다. (경계가 없으므로)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들어 아래와 같은 패턴이 있다고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[패턴]&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;E&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 다음과 같이 불일치가 발생한 인덱스와 그에 따른 &lt;u&gt;동일한 접두사&lt;/u&gt; 그리고 이동 거리를 구해보자.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 114px;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;인덱스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;패턴&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;C&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;A&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;B&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 54px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 54px;&quot;&gt;
&lt;p&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;동일한 접두사&lt;/span&gt;&lt;/p&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 54px;&quot;&gt;없음&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 54px;&quot;&gt;&quot;A&quot;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 54px;&quot;&gt;&quot;AB&quot;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 54px;&quot;&gt;&quot;ABC&quot;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 54px;&quot;&gt;&quot;ABCA&quot;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 54px;&quot;&gt;&quot;ABCAB&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;이동 거리&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; text-align: center; height: 20px;&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 위 내용을 코드로 옮겨보자.&lt;/p&gt;
&lt;pre id=&quot;code_1586940499405&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::size_t KMP::FindString(std::string pattern, std::size_t startIndex)
{
	if (false == CheckString(pattern, startIndex))
		return std::string::npos;

	std::string haystack = this-&amp;gt;haystack.substr(startIndex);

	std::vector&amp;lt;std::size_t&amp;gt; skipInfo(pattern.size(), 0);
	std::size_t index = 0;
	for (int i = 0; i &amp;lt; pattern.size(); ++i) // i : incorrect index when haystack and pattern are compared
	{
		for (int j = 0; j &amp;lt; i - 1; ++j) // j : index for finding the longest border in equivalent prefix of haystack and pattern
		{
			for (index = 0; index &amp;lt;= j; ++index)
				if (pattern[index] != pattern[(std::size_t)i - 1 - j + index])
					break;
			if (index &amp;gt; j)
				skipInfo[i] = (std::size_t)i - (j + 1);
		}
		if (0 == skipInfo[i])
			skipInfo[i] = 2 &amp;gt; i ? 1 : i;
	}

	std::size_t i = 0; // i : beginning index of comparison
	while (i &amp;lt;= haystack.size() - pattern.size())
	{
		for (index = 0; index &amp;lt; pattern.size(); ++index)
			if (haystack[i + index] != pattern[index])
				break;
		if (pattern.size() == index)
			return startIndex + i;
		i += skipInfo[index];
	}

	return std::string::npos;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;첫 번째 반복문(for문)에서 패턴의 이동 거리를 구하고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;제일 바깥 인덱스(i)는 불일치가 발생한 인덱스이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 중간 인덱스(j)는 &lt;u&gt;동일한 접두사&lt;/u&gt;의 &lt;u&gt;가장 긴 경계&lt;/u&gt;를 구하기 위한 인덱스이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;중간 인덱스의 반복 범위가 0부터 바깥 인덱스보다 2 작은 값까지인 것을 유의하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;동일한 접두사&lt;/u&gt;의 길이는 불일치 인덱스보다 1만큼 작고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;경계는 &lt;u&gt;동일한 접두사&lt;/u&gt;와 같은 길이일 때 제외하므로&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(접두사 및 접미사가 단어 전체 길이와 같은 경우는 제외한다고 앞서 설명했다)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1만큼 작게 되어 총 2만큼 작은 값까지 반복한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 안쪽 인덱스(index)는 경계의 접두사와 접미사가 같은지 확인하기 위한 인덱스이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;pattern 변수에 있는 문자를 비교하는 부분에서 겁먹지 말자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그냥 경계의 접두사와 접미사 문자 하나 하나 비교하는 작업일 뿐이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;skipInfo 변수가 패턴이 이동해야 할 이동 거리 값들을 담고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;첫 번째로 할당하는 부분은 &lt;u&gt;가장 긴 경계&lt;/u&gt;를 찾았을 때 위의 이동 거리 공식대로 값을 넣고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 번째로 할당하는 부분은 경계를 찾지 못했을 때 동일한 접두사의 길이를 할당하고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 번째 반복문(while문)은 패턴의 이동 거리를 토대로 문자열을 비교한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 빅오 표기법으로 표현하면 O(n)이 되겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;나머지 Boyer Moore 알고리즘은 2편에서 알아보도록 하자.&lt;/p&gt;</description>
      <category>알고리즘/study</category>
      <category>boyer moore</category>
      <category>KMP</category>
      <category>rabin karp</category>
      <category>string searching algorithm</category>
      <category>문자열 검색 알고리즘</category>
      <author>이즈미르</author>
      <guid isPermaLink="true">https://izmirprogramming.tistory.com/8</guid>
      <comments>https://izmirprogramming.tistory.com/8#entry8comment</comments>
      <pubDate>Thu, 16 Apr 2020 11:05:54 +0900</pubDate>
    </item>
    <item>
      <title>[그래프] 최단 경로 알고리즘 (Shortest path algorithms)</title>
      <link>https://izmirprogramming.tistory.com/7</link>
      <description>&lt;p&gt;이번에 알아볼 그래프 알고리즘은 최단 경로 알고리즘(Shortest path algorithms)이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최소 신장 트리(Minimum spanning tree)는 모든 노드를 최소한의 비용을 들여 연결한 그래프의 모습이라고 했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이와 다르게 그래프의 최단 경로 알고리즘은 한 노드에서 다른 노드로 이동할 때&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;거치는 간선들의 비용의 총합을 최소한으로 하는 경로를 구한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;대표적으로 다익스트라(Dijkstra) 알고리즘과 플로이드(Floyd) 알고리즘이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;각 알고리즘을 알아보기에 앞서 그래프와 최단 경로를 어떤 자료구조를 사용할 것인지 정의하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래프를 나타낼 자료구조는 최소 신장 트리에서 소개한 것(weights 변수) 그대로 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://izmirprogramming.tistory.com/6&quot;&gt;https://izmirprogramming.tistory.com/6&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1575200772195&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[그래프] 최소 신장 트리 (minimum spanning tree)&quot; data-og-description=&quot;이번에는 그래프의 최소 신장 트리(minimum spanning tree)를 알아보자. 최소 신장 트리가 무엇일까? 먼저 그래프는 익히 알고 있을 것이다. 그래프는 노드와 간선으로 구성된다. 지하철 노선도로 비유하자면 지하..&quot; data-og-host=&quot;izmirprogramming.tistory.com&quot; data-og-source-url=&quot;https://izmirprogramming.tistory.com/6&quot; data-og-url=&quot;https://izmirprogramming.tistory.com/6&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/j29Jo/hyDWbBIEG4/rPYEhMkQOr2iSRqAhc3TP1/img.png?width=800&amp;amp;height=486&amp;amp;face=0_0_800_486,https://scrap.kakaocdn.net/dn/uLNCM/hyDVZ86QaG/92ct1JVESw9W16YBzsZuU1/img.png?width=800&amp;amp;height=486&amp;amp;face=0_0_800_486,https://scrap.kakaocdn.net/dn/b7Op8a/hyDV3XYp2g/u0ipmbCDRE2l0uSJsJ6rUK/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot;&gt;&lt;a href=&quot;https://izmirprogramming.tistory.com/6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://izmirprogramming.tistory.com/6&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/j29Jo/hyDWbBIEG4/rPYEhMkQOr2iSRqAhc3TP1/img.png?width=800&amp;amp;height=486&amp;amp;face=0_0_800_486,https://scrap.kakaocdn.net/dn/uLNCM/hyDVZ86QaG/92ct1JVESw9W16YBzsZuU1/img.png?width=800&amp;amp;height=486&amp;amp;face=0_0_800_486,https://scrap.kakaocdn.net/dn/b7Op8a/hyDV3XYp2g/u0ipmbCDRE2l0uSJsJ6rUK/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;[그래프] 최소 신장 트리 (minimum spanning tree)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;이번에는 그래프의 최소 신장 트리(minimum spanning tree)를 알아보자. 최소 신장 트리가 무엇일까? 먼저 그래프는 익히 알고 있을 것이다. 그래프는 노드와 간선으로 구성된다. 지하철 노선도로 비유하자면 지하..&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;izmirprogramming.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;초기에 weight 변수에 그래프 데이터가 세팅된 것으로 가정한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최단 경로를 나타낼 자료구조는 3차원 배열을 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;가장 바깥에 있는 배열 인덱스는 최단 경로의 시작 노드를 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;중간에 있는 배열 인덱스는 경로의 최종 목적지 노드를 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막에 있는 배열은 시작 노드로부터 최종 목적지 노드까지의 경로 중에 거치는 노드와 비용을 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지의 내용을 코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1575201030043&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Cost
{
	std::size_t _nodeNum;
	int _cost;
			
	Cost(std::size_t nodeNum, int cost)
		: _nodeNum(nodeNum), _cost(cost)
	{

	}
};
typedef std::vector&amp;lt;std::vector&amp;lt;std::vector&amp;lt;Cost&amp;gt;&amp;gt;&amp;gt; ShortestPathResult;
ShortestPathResult _result;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 다익스트라 알고리즘부터 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다익스트라 알고리즘&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;한 노드에서 시작하여 다른 나머지 노드들에 대해 최단 경로를 구한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;알고리즘 원리는 탐욕적(Greedy) 방법을 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;탐욕 알고리즘이라고 나중에 이 주제를 가지고 설명하겠지만 간단하게 설명하자면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;문제를 풀기 위해 작은 문제들의 매 순간 근시안적인 최적해를 구한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;동적 계획법과 다르게 탐욕 알고리즘의 최종해가 항상 최적해가 아님을 주의하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 어떤 문제는 최적해를 가질 수 있는데 다익스트라 알고리즘이 그렇다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(문제의 성질이나 조건에 따라 결정된다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이는 추상적으로 방법을 설명한 것이고 구체적인 방법은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;노드들을 대상으로 최단 경로를 구한 노드 집합과 그렇지 못한 집합으로 나눈다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;처음에는 시작 노드만 최단 경로를 구한 노드 집합에 포함될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 최단 경로를 구하지 못한 노드들로부터 하나씩 최단 경로를 구해서&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최단 경로를 구한 노드 집합으로 추가해 나가는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 하나씩 노드를 선택하는 우선순위 기준이 위에서 설명한 탐욕적 방법이 적용된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최단 경로를 구한 노드들 중에 가장 적은 비용으로 연결된&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최단 경로를 구하지 않은 노드가 높은 우선순위를 갖는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 구체적인 구현 방법을 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;맨 처음에 시작 노드를 정한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 시작 노드로부터 다른 나머지 노드들에 대한 각 경로 비용을 저장할 배열을 생성한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;배열의 초깃값으로 시작 노드로부터 다른 나머지 노드들의 초기 비용을 저장한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아직 시작 노드와 직접적으로 연결된 주변 노드의 비용만 유의미하고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;나머지 노드에 대한 비용은 무한대(를 의미하는) 값일 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 최단 경로를 구한 노드 집합과 그렇지 않은 집합을 구분하기 위한 배열을 생성한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지의 내용을 코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1575201253110&quot; class=&quot;c++ cpp arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define CHECKED 1
#define TO_BE_CHECKED 0

std::vector&amp;lt;Edge&amp;gt; cost;
std::vector&amp;lt;std::size_t&amp;gt; check(_vertexCount, TO_BE_CHECKED);
std::size_t checkCount = 1;

for (std::size_t i = 0; i &amp;lt; _vertexCount; ++i)
{
	Edge edge(vertexNum, i, _weights[vertexNum][i]);
	cost.push_back(edge);
}

check[vertexNum] = CHECKED;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Edge라는 자료구조는 간선을 표현한 것으로 최소 신장 트리에서 이미 소개한 적이 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;vertexNum은 시작 노드를 vertexCount는 노드의 총 개수를 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시작 노드로부터 다른 노드들로의 최단 경로 비용들을 저장할 배열(cost)과&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최단 경로를 구한 노드들과 그렇지 못한 노드들의 집합을 구분할 배열(check)을 초기화한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 메인 작업을 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최단 경로 비용들을 저장한 배열로부터 아직 최단 경로를 구한 노드가 아니면서 비용이 제일 적은 노드를 찾는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 작업이 최단 경로를 구한 노드 집합에 있는 노드들 중에 최저의 비용으로 연결된 노드를 찾는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일단 그 노드를 최단 경로를 구한 집합에 추가한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 다음 그 노드에 연결된 아직 최단 경로를 구하지 못한 노드들을 추려낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 노드들에 대한 경로 비용을 이제 알게 되었으니 최단 경로 비용들을 저장한 배열을 갱신한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;비용이 무한대였거나 새로 연결된 노드를 우회했을 때 더 적은 비용이 될 경우 갱신할 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최단 경로 비용을 저장한 배열을 갱신한 후 다시 메인 작업 초반부로 간다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;노드를 찾지 못하면 반복을 종료한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지의 작업을 코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1575724943364&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int weight;
std::size_t target = 0;
while (checkCount &amp;lt; _vertexCount)
{
	weight = INFINITY_VALUE;
	for (std::size_t i = 0; i &amp;lt; _vertexCount; ++i)
	{
		if (0 &amp;lt; cost[i].value &amp;amp;&amp;amp; TO_BE_CHECKED == check[i])
		{
			if (INFINITY_VALUE == weight || cost[i].value &amp;lt; weight)
			{
				weight = cost[i].value;
				target = i;
			}
		}
	}

	if (INFINITY_VALUE == weight)
		break;

	check[target] = CHECKED;
	checkCount++;
	std::vector&amp;lt;Cost&amp;gt;&amp;amp; targetVec = _result[vertexNum][target];
	std::vector&amp;lt;Cost&amp;gt;&amp;amp; fromVec = _result[vertexNum][cost[target].from];
	targetVec.insert(targetVec.begin(), fromVec.begin(), fromVec.end());
	targetVec.push_back(Cost(target, _weights[cost[target].from][target]));

	for (std::size_t i = 0; i &amp;lt; _vertexCount; ++i)
	{
		if (0 &amp;lt; _weights[target][i])
		{
			if (INFINITY_VALUE == cost[i].value || _weights[target][i] + cost[target].value &amp;lt; cost[i].value)
			{
				cost[i].from = target;
				cost[i].value = cost[target].value + _weights[target][i];
			}
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(글자색이 제대로 칠해지지 않는다ㅠㅠ)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결과적으로 최단 경로 비용을 저장한 배열에 시작 노드로부터 다른 노드들로의 최단 경로 비용이 저장된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;중간에 있는 vector 관련한 부분은 경로 추적을 위해 작업한 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지가 다익스트라 알고리즘이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 플로이드 알고리즘을 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플로이드 알고리즘&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다익스트라 알고리즘은 시작 노드 하나를 정하고 시작하지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;플로이드 알고리즘은 모든 노드에 대해 각 최단 경로를 구하는 것이 다른 점이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 다익스트라 알고리즘과 다르게 구현 방법이 무척 간단하다는 것도 다른 점이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;구현 방법은 모든 노드를 대상으로 3개씩 차례대로 출발, 경유, 도착 노드들을 지정하고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출발 노드에서 도착노드로의 기존 비용과 경유 노드를 거쳐서 갔을 때의 비용을 비교 후&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;더 적은 비용으로 갱신하는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이를 코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1575806212136&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Graph totalCost = *this;
Graph path = *this;
path.Reset(INFINITY_VALUE);

for (std::size_t i = 0; i &amp;lt; _vertexCount; ++i)
{
	for (std::size_t j = 0; j &amp;lt; _vertexCount; ++j)
	{
		for (std::size_t k = 0; k &amp;lt; _vertexCount; ++k)
		{
			if (INFINITY_VALUE != totalCost[j][i] &amp;amp;&amp;amp; INFINITY_VALUE != totalCost[i][k])
			{
				if (INFINITY_VALUE == totalCost[j][k] || totalCost[j][i] + totalCost[i][k] &amp;lt; totalCost[j][k])
				{
					totalCost[j][k] = totalCost[j][i] + totalCost[i][k];
					path[j][k] = i;
				}
			}
		}
	}
}

_result.clear();
_result.resize(_vertexCount);
for (std::size_t t = 0; t &amp;lt; _vertexCount; ++t)
{
	_result[t].resize(_vertexCount);

	for (std::size_t k = 0; k &amp;lt; _vertexCount; ++k)
	{
		setCost(path, t, k, t, k);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Graph 구조체는 그래프 데이터를 저장할 2차원 배열을 포함하고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;모든 노드를 대상으로 각 경로 비용을 저장할 2차원 배열 변수(totalCost)와&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;노드별 경로 추적을 위한 2차원 배열 변수(path)를 생성한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;경로 비용을 저장할 변수의 초깃값은 초기 그래프 데이터가 될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;모든 노드가 바로 연결된 노드에 대해서만 비용이 유의미하고 나머지는 무한대 값을 가질 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 3중 반복문을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;제일 바깥 반복문(변수 i)은 경유 노드를 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;중간에 있는 반복문(변수 j)은 출발 노드를 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;가장 안쪽 반복문(변수 k)은 도착 노드를 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 앞서 설명한 모든 노드들을 출발, 경유, 도착 노드로 지정하고 매번 비용을 갱신한다는 것을 이해할 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그런데 어떻게 이 작업이 모든 노드들의 최단 경로를 구하는 것일까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;필자도 이 부분을 제일 많이 고민했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일단 경유 노드 위주로 생각해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드의 조건문을 보면 출발 노드에서 경유 노드로, 경유 노드에서 도착 노드로의 비용이 유의미할 때만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;비용을 갱신해주고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉 출발 노드에서 경유 노드로, 경유 노드에서 도착 노드로 어떤 노드들을 거치는지 모르지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일단 비용이 유의미하다면 기존 비용과 비교 후 갱신한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이게 처음에는 출발, 경유, 도착 노드 3개만을 가지고 비용 갱신을 시작하지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;점점 진행할수록 여러 노드를 경유한 비용을 가지고 갱신하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;경유하는 그 여러 노드들이 앞서 지정한 경유 노드들을 포함하고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이해를 돕기 위해 간단한 예를 들어 보겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아래 [그림 1]과 같이 4개의 A, B, C, D 노드가 있다고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;우리가 알고 싶은 것은 노드 A에서 노드 D로의 최단 경로 비용이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;첫 번째 경유 노드는 B이고 출발 노드는 A, 도착 노드는 D라고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럼 [그림 1]의 붉은 색으로 표시한 간선 비용을 계산하여 갱신할 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;403&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4MKkm/btqAhp61sQM/qwWoidcCXO5yE3I6zWjWo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4MKkm/btqAhp61sQM/qwWoidcCXO5yE3I6zWjWo1/img.png&quot; data-alt=&quot;[그림 1]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4MKkm/btqAhp61sQM/qwWoidcCXO5yE3I6zWjWo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4MKkm%2FbtqAhp61sQM%2FqwWoidcCXO5yE3I6zWjWo1%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;403&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 1]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;계속 이어서 경유 노드가 B일 때 아래 [그림 2]와 같이 출발 노드가 A, 도착 노드가 C인 경우도 비용을 갱신할 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;413&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFSU3n/btqAiyPWE87/WOOWajNAkikUWKqIiPjYXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFSU3n/btqAiyPWE87/WOOWajNAkikUWKqIiPjYXk/img.png&quot; data-alt=&quot;[그림 2]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFSU3n/btqAiyPWE87/WOOWajNAkikUWKqIiPjYXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFSU3n%2FbtqAiyPWE87%2FWOOWajNAkikUWKqIiPjYXk%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;447&quot; data-origin-height=&quot;413&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 2]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 경유 노드가 C일 때를 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출발 노드 A에서 경유 노드 C까지의 최단 경로 비용은 [그림 2]처럼 이미 갱신된 상태이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉 25 + 55 비용과 25 + 15 + 5의 비용을 비교하여 더 작은 비용으로 갱신하면 되는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최종적으로 [그림 3]과 같은 경로의 비용으로 갱신할 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;408&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JgkHx/btqAhVknBWF/K4k4CwkYFPGPG6c5Wdejkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JgkHx/btqAhVknBWF/K4k4CwkYFPGPG6c5Wdejkk/img.png&quot; data-alt=&quot;[그림 3]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JgkHx/btqAhVknBWF/K4k4CwkYFPGPG6c5Wdejkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJgkHx%2FbtqAhVknBWF%2FK4k4CwkYFPGPG6c5Wdejkk%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;408&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 3]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마지막으로 경로 추적 부분을 남겨두고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;3중 반복문 내에서 비용을 갱신할 때 경유하는 노드를 path 변수에 저장하는 부분이 있었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;출발 노드에서 도착 노드로의 최단 경로는 경유 노드를 거친다는 정보를 남기고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 경로 추적을 위해 출발 노드에서 경유 노드로의, 경유 노드에서 도착 노드로의 경로 중에&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;또 다른 경유 노드가 없을 때까지 추적을 반복하면 최종적으로 경유하는 모든 노드들을 알아낼 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;추적하는 부분이 SetCost 함수인데 구현 코드는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1575810041022&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void Floyd::setCost(Graph&amp;amp; path, std::size_t first, std::size_t last, std::size_t from, std::size_t to)
{
	if (INFINITY_VALUE != path[from][to])
	{
		setCost(path, first, last, from, path[from][to]);
		setCost(path, first, last, path[from][to], to);
	}
	else // nothing between from and to
	{
		if(INFINITY_VALUE != _weights[from][to])
			_result[first][last].push_back(Cost(to, _weights[from][to]));
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이상으로 플로이드 알고리즘을 알아 보았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;플로이드 알고리즘 같은 경우 다소 헷갈릴 수 있으므로 깊게 고민해 볼 필요가 있다.&lt;/p&gt;</description>
      <category>알고리즘/study</category>
      <category>Dijkstra</category>
      <category>Floyd</category>
      <category>Shortest path</category>
      <category>다익스트라</category>
      <category>최단 경로</category>
      <category>최단 경로 알고리즘</category>
      <category>플로이드</category>
      <author>이즈미르</author>
      <guid isPermaLink="true">https://izmirprogramming.tistory.com/7</guid>
      <comments>https://izmirprogramming.tistory.com/7#entry7comment</comments>
      <pubDate>Sun, 8 Dec 2019 22:05:35 +0900</pubDate>
    </item>
    <item>
      <title>[그래프] 최소 신장 트리 (minimum spanning tree)</title>
      <link>https://izmirprogramming.tistory.com/6</link>
      <description>&lt;p&gt;이번에는 그래프의 최소 신장 트리(minimum spanning tree)를 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최소 신장 트리가 무엇일까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 그래프는 익히 알고 있을 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래프는 노드와 간선으로 구성된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지하철 노선도로 비유하자면 지하철 역이 노드이고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지하철 역과 역을 잇는 선이 간선이 될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지하철 역처럼 노드는 무엇인가를 나타내기 위해 사용하고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;간선은 노드와 노드의 관계를 나타낸다. (어느 역과 어느 역이 연결되어 있는지 등)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;간선은 보통 비용을 포함하고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;비용의 종류는 그래프의 용도에 따라 다양하게 결정된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;거리가 될 수도 있고 금전적인 진짜 비용이 될 수도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 노드들과 노드들을 제각기 잇는 간선이 있을 때&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;모든 노드들을 최소의 비용을 들여 연결한 그래프 모습을 최소 신장 트리&lt;/u&gt;라고 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 왜 트리라는 용어를 사용하는가?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최소 비용을 들여 모든 노드들을 연결하면 사이클(Cycle)을 형성하지 않고 트리 형태의 모습을 띄기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 우리가 알아볼 것은 노드와 간선의 집합이 주어졌을 때&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최소 신장 트리를 구하는 알고리즘이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;대표적으로 Kruskal과 Prim 알고리즘 두 가지가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;알고리즘을 알아보기 전에 우선순위 큐(Priority queue)와 데이터 표현 방법에 대해 알아야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우선순위 큐는 말 그대로 데이터를 저장하는 큐이지만 데이터를 차례차례 꺼냈을 때&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오름차순 또는 내림차순 순서로 정렬되도록 하는 큐이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(자세한 원리나 구현 방법은 검색하면 많이 나와 있다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 알고리즘 모두 우선순위 큐를 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 우리는 최소 비용을 원하므로 오름차순의 우선순위 큐를 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;데이터 표현 방법은 그래프를 표현하는데 어떤 자료구조를 사용할 것인가이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래프에서 노드와 간선을 표현하는 방법으로 2차원 배열이 가장 많이 사용된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;연결 리스트(Linked list)도 표현이 가능하지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;메모리를 조금 더 차지하는 대신에 원하는 간선의 비용을 바로 찾을 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본적으로 노드마다 인덱스가 주어져 있고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;배열에서 사용되는 인덱스가 노드의 인덱스를 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 배열 요소의 값은 노드와 노드를 연결하는 간선의 비용을 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들면 arr[1][2] = 10이라고 할 때&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1번 노드에서 2번 노드로의 간선 비용이 10이라는 뜻이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(여기선 무방향성 그래프를 대상으로 한다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지의 내용을 코드로 정리하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1575099264864&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct Edge
{
	std::size_t from;
	std::size_t to;
	int value;
	Edge(std::size_t from, std::size_t to, int value)
		: from(from), to(to), value(value)
	{

	}
};

std::size_t _vertexCount;
std::size_t _edgeCount;
std::vector&amp;lt;std::vector&amp;lt;int&amp;gt;&amp;gt; _weights;

#define INFINITY_VALUE -1&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Edge라는 구조체는 우선순위 큐에서 사용할 구조체이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Edge의 from은 노드를 잇는 간선의 첫 번째 노드이고 to는 두 번째 노드이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;value는 간선의 비용이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;vertexCount는 노드의 총개수를 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;edgeCount는 간선의 총개수를 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;weights는 위에서 예시로 표현한 arr과 같은 그래프를 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;INFINITY_VALUE는 노드와 노드가 연결되지 않음(간선이 없음)을 나타내기 위해서 weights 요소의 값으로 사용된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본적으로 weights 배열에 비용이 담겨 있는 상태에서 알고리즘을 구현하기로 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Kruskal 알고리즘부터 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kruskal 알고리즘&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kruskal 알고리즘은 처음에 모든 간선을 우선순위 큐에 넣고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하나씩 꺼내면서 사이클을 형성하지 않도록 노드들을 연결한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사이클을 형성하지 않게 하는 것이 매우 중요하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사이클 형성을 막기 위해 집합이라는 개념을 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;쉽게 말해 같은 집합에 속한(다른 경로를 통해 도달할 수 있는) 노드끼리 연결하면 사이클을 형성하므로&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;노드들이 속하는 집합을 만들고 이들을 구분하는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;집합을 표현하기 위한 자료구조는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1575098454963&quot; class=&quot;c++ cpp&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct SetTree
{
	SetTree* parent;
	int setNum;

	SetTree()
		: parent(nullptr), setNum(0)
	{

	}
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;멤버 변수인 setNum은 집합을 구분하기 위해 존재한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 parent 변수가 있는데 이것은 해당 집합을 포함하고 있는 상위 집합을 나타내기 위해 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉 A와 B 집합이 있고 B가 A의 부분 집합이면 B의 parent는 A를 가리키게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;parent가 널 포인터(null pointer)이면 해당 집합이 최상위 집합임을 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 질문!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왜 부분 집합 개념을 사용할까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;집합이 다르면 다른 거지 부분 집합까지 들먹이는 이유를 고민해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;답은 두 집합을 하나로 통일하기 위해 노드 하나하나마다 집합(setNum)을 바꿔주는 작업을 하지 않기 위함이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;단순하게 한 집합을 대표하는 노드의 상위 집합(parent)을 합치는 또 다른 집합으로 지정하면 되기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어떤 노드의 최상위 집합을 찾으려면 parent를 계속 타고 올라가기만 하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 알고리즘 초기에 하는 작업을 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;모든 간선 데이터를 우선순위 큐에 넣고 집합을 구성하는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;집합의 개수는 노드 개수가 될 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 두 가지 작업을 코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1575098984417&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SetTree* setTrees = new SetTree[_vertexCount];
for (std::size_t t = 0; t &amp;lt; _vertexCount; ++t)
{
	setTrees[t].parent = nullptr;
	setTrees[t].setNum = t;

	for (std::size_t u = t + 1; u &amp;lt; _vertexCount; ++u)
	{
		if (INFINITY_VALUE != _weights[t][u])
			queue.Push(new Edge(t, u, _weights[t][u]));
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드를 보면 노드 개수만큼 집합을 만들고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;노드 인덱스를 집합 구분을 위한 숫자로 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 안쪽 for문에서는 우선순위 큐에 간선 데이터를 삽입하는 모습이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;INFINITY_VALUE가 아닌 유의미한 비용일 때만 넣는 것에 주의하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;저렇게 모든 간선의 데이터를 우선순위 큐에 넣고 하나씩 추출하면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;비용이 제일 적은 간선 데이터부터 추출되는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;본격적으로 우선순위 큐에서 간선을 하나씩 추출했을 때의 작업을 보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앞서 Edge라는 간선 데이터에는 두 개의 노드 인덱스가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;각 노드의 최상위 집합을 알아낸 뒤&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;집합이 다르다면 두 집합을 합쳐주고 그 간선을 최소 신장 트리에 추가한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;집합이 같다면 그 간선은 사용하지 않고 넘어간다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇게 우선순위 큐에 데이터를 다 추출할 때까지&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;또는 최소 신장 트리의 간선 개수가 노드 개수보다 1개 작을 때까지 반복한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1575115291390&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::size_t order = 0;
Edge* edge = nullptr;
_result.Reset();
while (nullptr != (edge = (Edge*)queue.Pop()) &amp;amp;&amp;amp; order &amp;lt; _vertexCount)
{
	SetTree* from = &amp;amp;setTrees[edge-&amp;gt;from];
	while (nullptr != from-&amp;gt;parent)
		from = from-&amp;gt;parent;
	SetTree* to = &amp;amp;setTrees[edge-&amp;gt;to];
	while (nullptr != to-&amp;gt;parent)
		to = to-&amp;gt;parent;
	if (from-&amp;gt;setNum != to-&amp;gt;setNum)
	{
		_result[edge-&amp;gt;from][edge-&amp;gt;to] = ++order;
		to-&amp;gt;parent = from;
	}
	delete edge;
}

while (nullptr != (edge = (Edge*)queue.Pop()))
	delete edge;

delete[] setTrees;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;result 변수는 weights 변수와 동일한 타입이지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최소 신장 트리에 추가되는 간선과 그 순서를 저장한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 Prim 알고리즘을 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Prim 알고리즘&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Prim 알고리즘은 시작할 노드를 정하고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;비용이 적은 간선과 연결된 노드들을 하나씩 집합에 추가하는 방법을 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서도 마찬가지로 사이클을 형성하지 않는 것이 중요하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다행히도 Kruskal 알고리즘보다 덜 복잡하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;단순하게 하나의 집합에 포함되는지 아닌지만 구분하면 되기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 처음에 노드마다 집합에 추가되었는지 여부를 구분할 플레그 데이터를 초기화한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 시작 노드와 연결된 간선들만 우선순위 큐에 추가한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1575116246933&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define CHECKED 1
#define TO_BE_CHECKED 0
		
std::size_t* check = new std::size_t[_vertexCount];
memset(check, TO_BE_CHECKED, sizeof(std::size_t) * _vertexCount);
check[vertexNum] = CHECKED;

for (std::size_t t = 0; t &amp;lt; _vertexCount; ++t)
	if (t != vertexNum &amp;amp;&amp;amp; INFINITY_VALUE != _weights[vertexNum][t])
		queue.Push(new Edge(vertexNum, t, _weights[vertexNum][t]));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;vertexNum 변수는 시작할 노드 인덱스를 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;CHECKED는 최소 신장 트리에 추가된 것을 의미하며&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;TO_BE_CHECKED는 그 반대를 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시작 노드를 CHECKED로 세팅하고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;시작 노드와 연결된 간선 데이터만 우선순위 큐에 넣는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음 작업은 우선순위 큐에서 간선 데이터를 하나씩 추출하는 작업을 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Kruskal 알고리즘과 다르게 집합에 속한 노드들 중에서 가장 비용이 적은 간선의 데이터가 추출된다는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;주의할 점은 간선 데이터의 from이 집합에 이미 속한 노드이고 to가 새로 추가할 노드인 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;to에 해당하는 노드가 집합에 추가되지 않았다면 그 노드를 집합에 추가하고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 노드와 연결된 아직 집합에 추가되지 않은 노드와의 간선을 우선순위 큐에 추가한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1575117357523&quot; class=&quot;c++ cpp&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;std::size_t order = 0;
Edge* edge = nullptr;
while (nullptr != (edge = (Edge*)queue.Pop()) &amp;amp;&amp;amp; order &amp;lt; _vertexCount)
{
	if (TO_BE_CHECKED == check[edge-&amp;gt;to])
	{
		check[edge-&amp;gt;to] = CHECKED;
		_result[edge-&amp;gt;from][edge-&amp;gt;to] = ++order;
		for (std::size_t t = 0; t &amp;lt; _vertexCount; ++t)
			if (t != edge-&amp;gt;to &amp;amp;&amp;amp; INFINITY_VALUE != _weights[edge-&amp;gt;to][t])
				if (TO_BE_CHECKED == check[t])
					queue.Push(new Edge(edge-&amp;gt;to, t, _weights[edge-&amp;gt;to][t]));
	}
	delete edge;
}

while (nullptr != (edge = (Edge*)queue.Pop()))
	delete edge;

delete[] check;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지금까지 최소 신장 트리를 구현하는 두 가지 알고리즘 Kruskal과 Prim 알고리즘에 대해 알아보았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;알고리즘은 구현 방법만 아는 것과 직접 구현해 보는 것은 천지 차이이므로 꼭 구현해 보길 바란다.&lt;/p&gt;</description>
      <category>알고리즘/study</category>
      <category>Kruskal</category>
      <category>minimum spanning tree</category>
      <category>Prim</category>
      <category>최소 신장 트리</category>
      <author>이즈미르</author>
      <guid isPermaLink="true">https://izmirprogramming.tistory.com/6</guid>
      <comments>https://izmirprogramming.tistory.com/6#entry6comment</comments>
      <pubDate>Fri, 29 Nov 2019 00:09:24 +0900</pubDate>
    </item>
    <item>
      <title>레드 블랙 트리 (Red black tree)</title>
      <link>https://izmirprogramming.tistory.com/5</link>
      <description>&lt;p&gt;이번에는 레드 블랙 트리를 정리해 보도록 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사실 알고리즘보단 자료구조에 가까운 느낌이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래도 C++ stl에서 사용하고 있어서 한 번쯤은 구현해볼 가치가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이름에서 알 수 있듯이 트리(tree)를 기반으로 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이진트리(binary tree)에서 최악의 구조가 발생하지 않도록 여러 규칙들을 걸어 놓았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 규칙들을 살펴보자. (출처는 위키백과 : &lt;a href=&quot;https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Properties&quot;&gt;https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Properties&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[규칙 1] &lt;/b&gt;&lt;span&gt;Each node is either red or black. (&lt;/span&gt;노드는 레드 혹은 블랙 중의 하나이다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[규칙 2]&lt;/b&gt; &lt;span&gt;The root is black. (&lt;/span&gt;루트 노드는 블랙이다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[규칙 3]&lt;/b&gt; &lt;span&gt;All leaves (NIL) are black. (&lt;/span&gt;모든 리프 노드(NIL)들은 블랙이다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[규칙 4]&lt;/b&gt; &lt;span&gt;If a node is red, then both its children are black. (&lt;/span&gt;레드 노드의 자식 노드들은 언제나 블랙이다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[규칙 5]&lt;/b&gt; &lt;span&gt;Every path&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;from a given node to any of its descendant NIL nodes contains the same number of black nodes. (&lt;/span&gt;어떤 노드로부터 시작되어 리프 노드에 도달하는 모든 경로에는 모두 같은 개수의 블랙 노드가 있다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 규칙들을 위반할 수 있는 상황은 노드를 삽입하거나 삭제했을 때이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 규칙을 위반하지 않도록 하는 방법이 있는데&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 &lt;u&gt;왼쪽 회전&lt;/u&gt;과 &lt;u&gt;오른쪽 회전&lt;/u&gt;이라는 개념을 알고 가자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왼쪽 회전 (Left rotation)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;443&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btS6iW/btqzYqrcOlJ/PeaegvcAx0xWJ8mV9utIi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btS6iW/btqzYqrcOlJ/PeaegvcAx0xWJ8mV9utIi0/img.png&quot; data-alt=&quot;[그림 1]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btS6iW/btqzYqrcOlJ/PeaegvcAx0xWJ8mV9utIi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtS6iW%2FbtqzYqrcOlJ%2FPeaegvcAx0xWJ8mV9utIi0%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;443&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 1]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[그림 1]과 같이 1번 노드가 루트 노드인 트리가 있다고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1번 노드가 루트 노드인 트리일 수 있고 서브 트리(subtree) 일 수도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;우리가 왼쪽 회전을 수행할 &lt;u&gt;타깃&amp;nbsp;노드는 1번 노드&lt;/u&gt;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;왼쪽 회전은 말 그대로 타깃 노드 기준으로 주위 노드들과 함께 반시계 방향으로 회전하는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;회전 후 모습은 아래 [그림 2]와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;419&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GrYJc/btqzYM1Gdgs/l3R7vmaf0Z8IsolTafiik1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GrYJc/btqzYM1Gdgs/l3R7vmaf0Z8IsolTafiik1/img.png&quot; data-alt=&quot;[그림 2]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GrYJc/btqzYM1Gdgs/l3R7vmaf0Z8IsolTafiik1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGrYJc%2FbtqzYM1Gdgs%2Fl3R7vmaf0Z8IsolTafiik1%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;419&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 2]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;루트 노드였던 1번 노드가 왼쪽으로 내려가고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;원래 오른쪽 자식이었던 3번 노드가 루트 노드로 올라오게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 3번 노드의 왼쪽 자식이었던 4번 노드를 1번 노드의 오른쪽 자식으로 붙여준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1574952834822&quot; class=&quot;c++ cpp arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void RedBlackTree::leftRotate(Node* node)
{
	if (_nil == node || _nil == node-&amp;gt;_right)
		return;

	Node* temp = node-&amp;gt;_right;
	node-&amp;gt;_right = node-&amp;gt;_right-&amp;gt;_left;
	if(_nil != node-&amp;gt;_right)
		node-&amp;gt;_right-&amp;gt;_parent = node;
	temp-&amp;gt;_left = node;
	temp-&amp;gt;_parent = node-&amp;gt;_parent;
	if (nullptr != node-&amp;gt;_parent)
	{
		if (node-&amp;gt;_parent-&amp;gt;_left == node)
			node-&amp;gt;_parent-&amp;gt;_left = temp;
		else
			node-&amp;gt;_parent-&amp;gt;_right = temp;
	}
	node-&amp;gt;_parent = temp;
	if (_root == node)
		_root = temp;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오른쪽 회전도 위와 똑같은 방법이지만 한 번 확인하고 넘어가자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오른쪽 회전 (Right rotation)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;453&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQDOvq/btqzYq5PxVG/0ICFikfylyrMe5p2VFNLFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQDOvq/btqzYq5PxVG/0ICFikfylyrMe5p2VFNLFk/img.png&quot; data-alt=&quot;[그림 3]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQDOvq/btqzYq5PxVG/0ICFikfylyrMe5p2VFNLFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQDOvq%2FbtqzYq5PxVG%2F0ICFikfylyrMe5p2VFNLFk%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;453&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 3]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마찬가지로 회전을 수행할 타깃 노드는 1번 노드이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번에는 4번 노드가 2번 노드의 오른쪽 자식임을 주의하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;회전 후 모습은 아래 [그림 4]와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;432&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZroyW/btqzZcyNFin/oOWbzJjiChxlkpzKFpnhpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZroyW/btqzZcyNFin/oOWbzJjiChxlkpzKFpnhpK/img.png&quot; data-alt=&quot;[그림 4]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZroyW/btqzZcyNFin/oOWbzJjiChxlkpzKFpnhpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZroyW%2FbtqzZcyNFin%2FoOWbzJjiChxlkpzKFpnhpK%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;432&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 4]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;루트 노드였던 1번 노드가 오른쪽으로 내려가고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;원래 왼쪽 자식이었던 2번 노드가 루트 노드로 올라오게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 2번 노드의 오른쪽 자식이었던 4번 노드를 1번 노드의 왼쪽 자식으로 붙여준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1574952892187&quot; class=&quot;c++ cpp arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void RedBlackTree::rightRotate(Node* node)
{
	if (_nil == node || _nil == node-&amp;gt;_left)
		return;

	Node* temp = node-&amp;gt;_left;
	node-&amp;gt;_left = node-&amp;gt;_left-&amp;gt;_right;
	if(_nil != node-&amp;gt;_left)
		node-&amp;gt;_left-&amp;gt;_parent = node;
	temp-&amp;gt;_right = node;
	temp-&amp;gt;_parent = node-&amp;gt;_parent;
	if (nullptr != node-&amp;gt;_parent)
	{
		if (node-&amp;gt;_parent-&amp;gt;_left == node)
			node-&amp;gt;_parent-&amp;gt;_left = temp;
		else
			node-&amp;gt;_parent-&amp;gt;_right = temp;
	}
	node-&amp;gt;_parent = temp;
	if (_root == node)
		_root = temp;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 본격적으로 노드 삽입과 삭제 과정에 대해서 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드 삽입&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;노드 삽입은 기존의 이진트리의 노드 삽입 과정 이후에 추가 작업이 필요하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일단 NIL 노드는 레드 블랙 트리의 성질을 유지시키기 위해 존재하는 데이터가 없는 껍데기 노드이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 NIL 노드의 자식으로 새로운 노드를 삽입하는 것이 아니라&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기존의 NIL 노드를 없애고 새로 삽입하는 노드로 대체하고 새로 삽입한 노드의 자식 노드들로 NIL 노드를 추가한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 실제 NIL 노드는 다수개 존재하는 것이 아니라 딱 1개로 생성하여&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;NIL 노드가 필요한 여러 노드들이 다 같이 참조하는 형식으로 구현한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 새로운 노드를 삽입했다고 하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 그 노드는 레드로 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 규칙들을 검사해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;삽입한 노드는 레드이므로 [규칙 1]을 따르고 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자식 노드들이 블랙 노드인 NIL 노드들이므로 [규칙 3]은 명확히 따르지만 [규칙 4]는 따르는 듯(?)하다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 레드 노드의 삽입 자체는 [규칙 5]를 위반하지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 [규칙 4]를 위반할 수 있는 두 가지 경우가 존재한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;첫 번째로 삽입한 노드가 루트 노드가 되는 경우 [규칙 2]를 위반한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 경우는 단순하게 루트 노드를 블랙으로 바꿔주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 번째는 삽입한 노드의 부모 노드가 레드인 경우이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 부모 노드의 형제 노드인 삼촌(Uncle) 노드의 색상에 따라 해야 할 작업이 나뉜다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;참고로 부모 노드가 레드이면 부모 노드가 루트 노드일 수 없다. ([규칙 2])&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. 삼촌 노드가 레드&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;부모 노드와 삼촌 노드를 블랙으로 바꿔준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 조부모(부모의 부모) 노드를 레드로 바꿔준다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만약 조부모 노드의 부모 노드가 레드일 경우&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;조부모 노드를 새로 삽입한 노드로 취급하여 규칙들을 다시 검사한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉 반복적으로 조상 노드들을 타고 올라가면서 부모 노드가 블랙일 때까지 작업을 반복한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;루트 노드는 블랙이므로 언젠가는 멈추게 되어 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그림으로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;499&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp14wO/btqz0BdHHF7/q8fa4OKJX9QH1yGNokln2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp14wO/btqz0BdHHF7/q8fa4OKJX9QH1yGNokln2K/img.png&quot; data-alt=&quot;[그림 5] 삼촌 노드가 레드 before&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp14wO/btqz0BdHHF7/q8fa4OKJX9QH1yGNokln2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp14wO%2Fbtqz0BdHHF7%2Fq8fa4OKJX9QH1yGNokln2K%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;499&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 5] 삼촌 노드가 레드 before&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;노드 이니셜을 정리하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1) G : Grandparent node (조부모 노드)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2) P : Parent node (부모 노드)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;3) U : Uncle node (삼촌 노드)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;4) I : Inserted node (삽입된 노드)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;5) N : Nil node (NIL 노드)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;485&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHhHKJ/btqzZ1DHb7x/R3kNZSd2WuJoV4akxi7DA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHhHKJ/btqzZ1DHb7x/R3kNZSd2WuJoV4akxi7DA0/img.png&quot; data-alt=&quot;[그림 6] 삼촌 노드가 레드 after&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHhHKJ/btqzZ1DHb7x/R3kNZSd2WuJoV4akxi7DA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHhHKJ%2FbtqzZ1DHb7x%2FR3kNZSd2WuJoV4akxi7DA0%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;556&quot; data-origin-height=&quot;485&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 6] 삼촌 노드가 레드 after&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[그림 6]과 같이 작업했을 때 조부모의 부모 노드가 블랙이라고 가정하고 규칙들을 검사해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;조부모 노드와 조부모의 자식들 노드의 색상을 교환하는 것이므로 [규칙 5]를 위반하는 일은 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다른 규칙들은 한눈에 만족되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2. 삼촌 노드가 블랙&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 경우 먼저 살펴봐야 할 부분은 부모 노드와 삽입한 노드의 위치 관계이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일단 부모 노드와 삽입한 노드를 일직선상에 위치하도록 해야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 말이 무슨 말이냐면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;부모 노드가 조부모 노드의 왼쪽 자식이라면 삽입한 노드도 부모 노드의 왼쪽 자식으로 맞춰줘야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;반대로 부모 노드가 조부모 노드의 오른쪽 자식이라면 삽입한 노드도 부모 노드의 오른쪽 자식으로 맞춰야 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럼 위의 두 경우가 발생하는 모습은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[case 1]&lt;/b&gt; 부모 노드가 조부모 노드의 왼쪽 자식이고 삽입한 노드가 부모 노드의 오른쪽 자식인 경우 [그림 7]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;515&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkTnpc/btqzYpzcrZz/vxmK4B1yZkWTtxBvQi866K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkTnpc/btqzYpzcrZz/vxmK4B1yZkWTtxBvQi866K/img.png&quot; data-alt=&quot;[그림 7]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkTnpc/btqzYpzcrZz/vxmK4B1yZkWTtxBvQi866K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkTnpc%2FbtqzYpzcrZz%2FvxmK4B1yZkWTtxBvQi866K%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;515&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 7]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[case 2]&lt;/b&gt; 부모 노드가 조부모 노드의 오른쪽 자식이고 삽입한 노드가 부모 노드의 왼쪽 자식인 경우 [그림 8]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;524&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzjG3r/btqzZbUkAyF/PnrCPgkp7Ck2KiiiatPNM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzjG3r/btqzZbUkAyF/PnrCPgkp7Ck2KiiiatPNM1/img.png&quot; data-alt=&quot;[그림 8]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzjG3r/btqzZbUkAyF/PnrCPgkp7Ck2KiiiatPNM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzjG3r%2FbtqzZbUkAyF%2FPnrCPgkp7Ck2KiiiatPNM1%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;524&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 8]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 어떻게 부모 노드와 삽입한 노드를 일직선상에 위치시키는가?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 1]일 때 부모 노드를 기준으로 왼쪽 회전을 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 2]일 때 부모 노드를 기준으로 오른쪽 회전을 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 1]에 대해서만 회전 결과를 확인하면 다음과 같다. [그림 9]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;490&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oA7iH/btqzZL8WGVo/qsMKHP3vtXtKBOdhH67uTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oA7iH/btqzZL8WGVo/qsMKHP3vtXtKBOdhH67uTk/img.png&quot; data-alt=&quot;[그림 9]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oA7iH/btqzZL8WGVo/qsMKHP3vtXtKBOdhH67uTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoA7iH%2FbtqzZL8WGVo%2FqsMKHP3vtXtKBOdhH67uTk%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;636&quot; data-origin-height=&quot;490&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 9]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;부모 노드와 삽입한 노드를 일직선상에 위치시켰다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;애초에 [그림 10]과 같이 일직선 상에 위치할 수도 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;500&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tqXdE/btqzYqx7CGu/Xq6ZysSqQRfePexMI1oai0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tqXdE/btqzYqx7CGu/Xq6ZysSqQRfePexMI1oai0/img.png&quot; data-alt=&quot;[그림 10]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tqXdE/btqzYqx7CGu/Xq6ZysSqQRfePexMI1oai0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtqXdE%2FbtqzYqx7CGu%2FXq6ZysSqQRfePexMI1oai0%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;500&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 10]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[그림 9]와 [그림 10]은 회전 수행 여부에 따라 I와 P의 위치가 다름을 주의하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 [그림 10]에서 S는 부모 노드의 오른쪽 서브 트리(subtree)를 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 2]도 마찬가지로 애초에 일직선 상에 위치해 있을 수도 있다. (그림 생략)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 조부모 노드를 회전시키고 조부모 노드와 부모 노드의 색상을 교환하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 1]의 [그림 10]에 이어서 설명하겠다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;조부모 노드를 오른쪽으로 회전시킨다. 그리고 색상을 교환하면 [그림 11]과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;474&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GNElZ/btqzZbNAs9D/Ll9JCvakPJgstGnFjYkVO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GNElZ/btqzZbNAs9D/Ll9JCvakPJgstGnFjYkVO1/img.png&quot; data-alt=&quot;[그림 11]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GNElZ/btqzZbNAs9D/Ll9JCvakPJgstGnFjYkVO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGNElZ%2FbtqzZbNAs9D%2FLl9JCvakPJgstGnFjYkVO1%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;474&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 11]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[그림 11]에서 S의 위치가 바뀐 것에 주의하자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;끝으로 규칙들이, 특히 [규칙 5]를 위반하지 않았는지 확인해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 S 쪽은 부모 노드와 조부모 노드의 색상이 바뀐 것뿐이기 때문에 위반하지 않았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;삼촌 노드(U) 쪽도 상위 노드가 하나 추가되긴 하였지만 레드이므로 위반하지 않았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 2]도 마찬가지로 조부모 노드를 왼쪽으로 회전시키면 마찬가지의 결과를 얻을 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지가 노드 삽입 방법이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1574598179058&quot; class=&quot;c++ cpp arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void RedBlackTree::reconstructionAfterInsertion(Node* node, bool left)
{
	Node* parent = node-&amp;gt;_parent;
	while (nullptr != parent &amp;amp;&amp;amp; RED == parent-&amp;gt;_color)
	{
		// in loop grand parent must exist
		Node* grandParent = parent-&amp;gt;_parent;
		bool leftParent = grandParent-&amp;gt;_left == parent;
		Node* uncle = leftParent ? grandParent-&amp;gt;_right : grandParent-&amp;gt;_left;
		if (RED == uncle-&amp;gt;_color)
		{
			parent-&amp;gt;_color = BLACK;
			uncle-&amp;gt;_color = BLACK;
			grandParent-&amp;gt;_color = RED;
			node = grandParent;
			parent = grandParent-&amp;gt;_parent;
			if (nullptr != parent)
				left = parent-&amp;gt;_left == node;
		}
		else
		{
			if ((leftParent &amp;amp;&amp;amp; false == left) || (false == leftParent &amp;amp;&amp;amp; left))
			{
				leftParent ? leftRotate(parent) : rightRotate(parent);
				parent = node;
				left = !left;
			}

			parent-&amp;gt;_color = BLACK;
			grandParent-&amp;gt;_color = RED;
			left ? rightRotate(grandParent) : leftRotate(grandParent);
			break;
		}
	}
	_root-&amp;gt;_color = BLACK;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드는 이진트리의 삽입 작업 직후에 호출되는 함수이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;매개 변수는 새로 삽입한 노드와 부모 노드의 왼쪽 자식인지 오른쪽 자식인지의 여부를 나타낸다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;while 반복문의 조건은 부모 노드가 존재해야 하며 색상은 레드인 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;암묵적으로 조부모도 존재해야 한다는 조건이 따라붙는다. (부모 노드가 레드이므로)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그다음으로 삼촌 노드를 찾고 부모 노드가 조부모 노드의 왼쪽 자식인지 오른쪽 자식인지를 구별한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;삼촌 노드의 색상에 따라 위에서 설명했던 대로 작업을 수행하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;함수를 빠져나가기 전에 루트 노드를 블랙으로 바꿔주는 것도 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 노드 삭제 작업을 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;노드 삭제도 마찬가지로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이진트리의&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;노드 삭제 작업 이후에 추가 작업이 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;레드 블랙 트리에서 노드 삭제가 노드 삽입 작업보다 복잡하다고 말하지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결국엔 여러 경우로 더 나뉘고 해 줄 작업이 각각 존재한다는 것뿐이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;노드 삭제 작업에 들어가기 전에 몇 가지 확인을 하고 넘어가자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기존의 이진트리에서 노드 삭제 작업은 노드 삽입 작업과 다르게 어느 노드에서든 일어날 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(앞서 노드 삽입 작업은 항상 리프 노드에서만 일어났었다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 노드 삭제 후 해당 노드의 후임자(successor)를 구하는 작업도 한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;삭제한 노드의 자식 노드가 둘 다 존재할 경우 후임자를 구하는 방식은 두 가지가 있는데&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;필자는 왼쪽 서브 트리에서 후임자를 찾는 방식을 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;레드 블랙 트리에서 삭제한 노드 위치로 들어오는 후임자 노드는 문제 되지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;삭제한 노드의 색상으로 바꿔주면 되기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;u&gt;문제가 되는 부분은 후임자 노드가 존재했던 곳이다.&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;한 가지 다행인 것은 후임자 노드는 자식 노드가 없거나 한쪽 자식 노드만 존재한다는 것이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;필자의 경우 후임자의 오른쪽 자식 노드가 존재하지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 좀 더 구체적으로 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;후임자 노드가 레드라면 후임자의 자식 노드를 후임자의 부모 노드에 그냥 붙여주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;후임자 노드가 블랙이고 후임자의 자식 노드가 레드라면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;후임자의 자식 노드를 블랙으로 바꾸고 후임자의 부모 노드에 붙여주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 두 경우 모두 [규칙 5]를 위반하지 않는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;후임자 노드와 후임자의 자식 노드가 모두 블랙이면 [규칙 5]를 위반하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;후임자의 자식 노드 쪽으로 블랙 노드의 개수가 부족하게 되기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그래서 살펴봐야 할 노드들은 부모, 형제, 조카 노드들이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여러 경우들을 차례차례 짚고 넘어가자. 총 5가지 경우가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[case 1]&lt;/b&gt; 부모 노드가 레드이고 형제, 조카 노드들이 블랙인 경우 [그림 12]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;392&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dL0jqB/btqzZ1ScYYB/RbWWYvOPDomUs0KLTNQR9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dL0jqB/btqzZ1ScYYB/RbWWYvOPDomUs0KLTNQR9K/img.png&quot; data-alt=&quot;[그림 12]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dL0jqB/btqzZ1ScYYB/RbWWYvOPDomUs0KLTNQR9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdL0jqB%2FbtqzZ1ScYYB%2FRbWWYvOPDomUs0KLTNQR9K%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;392&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 12]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 노드의 이니셜을 정리하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1) P : Parent node (부모 노드)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;2) C : Child node (자식 노드)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;3) S : Sibling node (형제 노드)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;4) N1 : Nephew 1 node (조카 1 노드)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;5) N2 : Nephew 2 node (조카 2 노드)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앞서 설명한 후임자 노드의 자리를 메꾼 후임자의 자식 노드가 그림의 자식 노드(C)이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자식 노드는 NIL이든 NIL이 아니든 상관없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 1]부터 [case 5]까지 모두 자식 노드가 부모 노드의 왼쪽에 있는 모습을 나타냈지만&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 반대 모습일 수도 있다. (C 노드가 오른쪽에 있고 형제와 조카 노드들이 왼쪽에 있을 수 있다.)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;설명은 위의 모습을 토대로 하지만 반대의 모습일 때는 회전만 반대 방향으로 해주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 질문!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;형제 노드가 NIL일 수 있을까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;한 번 고민해보고 넘어가길 바란다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;답은 NIL일 수 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 5가지 경우까지 오게 된 사유를 상기해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;후임자 노드와 후임자의 자식 노드가 모두 블랙일 때이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉 [규칙 5] 때문에 형제 노드가 NIL이 아님을 보장받는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 [case 1]일 때 수행할 작업을 알아보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 1]은 단순하게 부모 노드와 형제 노드의 색상을 교환하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자식 노드 쪽은 블랙 노드 개수가 늘어났고 조카 노드들 쪽은 변함이 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 [규칙 5]를 위반하지 않게 되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;작업 후 모습은 [그림 13]과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;377&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tAZQk/btqzZ2jhJ4g/ZewhKPyr9UqvgPFnEGYAuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tAZQk/btqzZ2jhJ4g/ZewhKPyr9UqvgPFnEGYAuk/img.png&quot; data-alt=&quot;[그림 13]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tAZQk/btqzZ2jhJ4g/ZewhKPyr9UqvgPFnEGYAuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtAZQk%2FbtqzZ2jhJ4g%2FZewhKPyr9UqvgPFnEGYAuk%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;377&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 13]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[case 2]&lt;/b&gt; 형제 노드가 블랙이고 멀리 있는 조카 노드가 레드인 경우 [그림 14]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;379&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWa2Nj/btqz12bhq4v/2RydcIRAXHdRZzkScpZYnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWa2Nj/btqz12bhq4v/2RydcIRAXHdRZzkScpZYnK/img.png&quot; data-alt=&quot;[그림 14]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWa2Nj/btqz12bhq4v/2RydcIRAXHdRZzkScpZYnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWa2Nj%2Fbtqz12bhq4v%2F2RydcIRAXHdRZzkScpZYnK%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;379&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 14]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;레드와 블랙으로 칠해지지 않은 빈 노드는 무슨 색상의 노드가 오든 상관없음을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;[case 2]는 부모 노드를 왼쪽으로 회전하고 부모 노드와 형제 노드의 색상을 교환한 후&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;멀리 있는 조카의 색상을 블랙으로 바꾼다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;작업한 모습은 [그림 15]과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;300&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;368&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FfDSW/btqz13gX9L0/Smv7NSm4zyDkBjKtQA6kg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FfDSW/btqz13gX9L0/Smv7NSm4zyDkBjKtQA6kg0/img.png&quot; data-alt=&quot;[그림 15]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FfDSW/btqz13gX9L0/Smv7NSm4zyDkBjKtQA6kg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFfDSW%2Fbtqz13gX9L0%2FSmv7NSm4zyDkBjKtQA6kg0%2Fimg.png&quot; width=&quot;300&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;368&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 15]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자식 노드 쪽은 블랙 노드가 한 개 늘어났고 조카 노드들 쪽은 변함이 없으므로 [규칙 5]를 위반하지 않았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[case 3]&lt;/b&gt; 형제 노드가 블랙이고 가까이 있는 조카 노드가 레드, 멀리 있는 조카 노드가 블랙인 경우 [그림 16]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;394&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sz4JM/btqzZc7HJdI/nAyXlVT2Ng7Qfk4xsjK7Q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sz4JM/btqzZc7HJdI/nAyXlVT2Ng7Qfk4xsjK7Q0/img.png&quot; data-alt=&quot;[그림 16]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sz4JM/btqzZc7HJdI/nAyXlVT2Ng7Qfk4xsjK7Q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsz4JM%2FbtqzZc7HJdI%2FnAyXlVT2Ng7Qfk4xsjK7Q0%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;394&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 16]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 3]은 형제 노드를 오른쪽으로 회전하고 가까이 있던 조카와 형제 노드의 색상을 교환한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;작업한 모습은 [그림 17]과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;474&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5XVSS/btqz03pr2cG/KmshE1QNCUo4xx9KkL5XPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5XVSS/btqz03pr2cG/KmshE1QNCUo4xx9KkL5XPk/img.png&quot; data-alt=&quot;[그림 17]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5XVSS/btqz03pr2cG/KmshE1QNCUo4xx9KkL5XPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5XVSS%2Fbtqz03pr2cG%2FKmshE1QNCUo4xx9KkL5XPk%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;474&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 17]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[그림 17]의 모습은 [case 2]와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 2]에서 했던 작업을 해주면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[case 4]&lt;/b&gt; 부모, 형제, 조카 노드들이 모두 블랙인 경우 [그림 18]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;414&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuv0mi/btqz03hJyrI/c0OgzeWkpuEASNkDpLDtj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuv0mi/btqz03hJyrI/c0OgzeWkpuEASNkDpLDtj0/img.png&quot; data-alt=&quot;[그림 18]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuv0mi/btqz03hJyrI/c0OgzeWkpuEASNkDpLDtj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcuv0mi%2Fbtqz03hJyrI%2Fc0OgzeWkpuEASNkDpLDtj0%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;414&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 18]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 4]는 형제 노드의 색상을 레드로 바꿔주면&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;자식 노드 쪽으로만 블랙 노드의 개수가 부족했던 문제가&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;부모 노드 쪽으로 블랙 노드의 개수가 부족해지는 것으로 확장된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;즉 자식 노드의 문제가 부모 노드의 문제로 올라가게 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앞서 노드 삽입할 때에도 조상 노드로 문제를 올려 보내서 해결했던 것처럼 해결하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;작업한 모습은 [그림 19]와 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;383&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SSlI6/btqz2Ea8i5A/jpeO5AgoX7BYqOVVqDXnp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SSlI6/btqz2Ea8i5A/jpeO5AgoX7BYqOVVqDXnp1/img.png&quot; data-alt=&quot;[그림 19]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SSlI6/btqz2Ea8i5A/jpeO5AgoX7BYqOVVqDXnp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSSlI6%2Fbtqz2Ea8i5A%2FjpeO5AgoX7BYqOVVqDXnp1%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;511&quot; data-origin-height=&quot;383&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 19]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;[case 5]&lt;/b&gt; 형제 노드가 레드이고 부모, 조카 노드들이 블랙인 경우 [그림 20]&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;375&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dVDgA5/btqz02XmBpP/EwsX7EJ9FNsrpD1Kf0BNA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dVDgA5/btqz02XmBpP/EwsX7EJ9FNsrpD1Kf0BNA1/img.png&quot; data-alt=&quot;[그림 20]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dVDgA5/btqz02XmBpP/EwsX7EJ9FNsrpD1Kf0BNA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdVDgA5%2Fbtqz02XmBpP%2FEwsX7EJ9FNsrpD1Kf0BNA1%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;375&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 20]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;[case 5]는 부모 노드를 왼쪽으로 회전하고 부모 노드와 형제 노드의 색상을 교환한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;작업한 모습은 [그림 21]과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;399&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TFnf0/btqz0AOAv0w/aMfmpNBGIa6xivAbXZhytk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TFnf0/btqz0AOAv0w/aMfmpNBGIa6xivAbXZhytk/img.png&quot; data-alt=&quot;[그림 21]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TFnf0/btqz0AOAv0w/aMfmpNBGIa6xivAbXZhytk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTFnf0%2Fbtqz0AOAv0w%2FaMfmpNBGIa6xivAbXZhytk%2Fimg.png&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;399&quot; width=&quot;300&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[그림 21]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 잠시 [규칙 5]를 검사해보자.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;가까웠던 사촌인 N1 노드 쪽은 변함이 없어서 위반하지 않았다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;멀리 있던 사촌인 N2 노드 쪽도 마찬가지이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 여전히 자식 노드 쪽은 블랙 노드 개수가 부족한 상황이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 상황은 부모 노드의 색상이 바뀌고 N1 조카 노드가 형제 노드가 되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이는 형제 노드 기준 서브 트리의 규모가 작아진 셈이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 상황에서 다시 [case 1]부터 [case 3]까지의 경우를 검사하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 또다시 질문!&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;조카 노드들이 NIL일 수 있을까?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;답은 NIL일 수 없다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;후임자 노드가 블랙 노드였기 때문에 [규칙 5]에 의하여 NIL이 아님을 보장받는다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지가 노드 삭제 작업이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1574779115064&quot; class=&quot;c++ cpp arduino&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void RedBlackTree::reconstructionAfterDeletion(Node* child, Node* parent)
{
	while (nullptr != parent)
	{
		bool left = parent-&amp;gt;_left == child;
		Node* sibling = left ? parent-&amp;gt;_right : parent-&amp;gt;_left;
		if (RED == parent-&amp;gt;_color)
		{
			if (BLACK == sibling-&amp;gt;_left-&amp;gt;_color &amp;amp;&amp;amp; BLACK == sibling-&amp;gt;_right-&amp;gt;_color)
			{
				// case 1
				// parent : red, sibling : black, sibling left : black, sibling right : black
				sibling-&amp;gt;_color = RED;
				parent-&amp;gt;_color = BLACK;
				break; 
			}
		}
		else if(BLACK == sibling-&amp;gt;_left-&amp;gt;_color &amp;amp;&amp;amp; BLACK == sibling-&amp;gt;_right-&amp;gt;_color)
		{
			if (BLACK == sibling-&amp;gt;_color)
			{
				// case 4
				// parent : black, sibling : black, sibling left : black, sibling right : black
				sibling-&amp;gt;_color = RED;
				child = parent;
				parent = parent-&amp;gt;_parent;
				continue; 
			}
			else // RED == sibling-&amp;gt;_color
			{
				// case 5
				// parent : black, sibling : red, sibling left : black, sibling right : black
				parent-&amp;gt;_color = RED;
				sibling-&amp;gt;_color = BLACK;
				if (left)
				{
					leftRotate(parent);
					sibling = parent-&amp;gt;_right;
				}
				else
				{
					rightRotate(parent);
					sibling = parent-&amp;gt;_left;
				}
				continue;
			}
		}

		if (BLACK == sibling-&amp;gt;_color &amp;amp;&amp;amp;
			((left &amp;amp;&amp;amp; RED == sibling-&amp;gt;_left-&amp;gt;_color &amp;amp;&amp;amp; BLACK == sibling-&amp;gt;_right-&amp;gt;_color)
				|| (!left &amp;amp;&amp;amp; BLACK == sibling-&amp;gt;_left-&amp;gt;_color &amp;amp;&amp;amp; RED == sibling-&amp;gt;_right-&amp;gt;_color)))
		{
			// case 3
			// parent : all, sibling : black, sibling left : red, sibling right : black
			if (left)
			{
				rightRotate(sibling);
				parent-&amp;gt;_right-&amp;gt;_color = BLACK;
				parent-&amp;gt;_right-&amp;gt;_right-&amp;gt;_color = RED;
				sibling = parent-&amp;gt;_right;
			}
			else
			{
				leftRotate(sibling);
				parent-&amp;gt;_left-&amp;gt;_color = BLACK;
				parent-&amp;gt;_left-&amp;gt;_left-&amp;gt;_color = RED;
				sibling-&amp;gt;_parent-&amp;gt;_left;
			}
		}
		
		if (BLACK == sibling-&amp;gt;_color &amp;amp;&amp;amp; ((left &amp;amp;&amp;amp; RED == sibling-&amp;gt;_right-&amp;gt;_color) || (!left &amp;amp;&amp;amp; RED == sibling-&amp;gt;_left-&amp;gt;_color)))
		{
			// case 2
			// parent : all, sibling : black, sibling left : all, sibling right : red
			left ? leftRotate(parent) : rightRotate(parent);
			NODE_COLOR temp = parent-&amp;gt;_color;
			parent-&amp;gt;_color = sibling-&amp;gt;_color;
			sibling-&amp;gt;_color = temp;
			left ? sibling-&amp;gt;_right-&amp;gt;_color = BLACK : sibling-&amp;gt;_left-&amp;gt;_color = BLACK;
			break; 
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;위 코드는 이진트리의 삭제 작업 후에 후임자 노드와 후임자의 자식 노드가 블랙일 때 호출되는 함수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;매개 변수는 후임자 자식 노드와 그 부모 노드이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;while 반복문의 조건은 자식 노드가 루트 노드가 아닐 때까지 반복한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;위에서 설명한 각 case들을 주석으로 나타냈다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;자식 노드가 부모 노드의 왼쪽이냐 오른쪽이냐에 따라 회전 방향이 다르다는 점을 주의하자.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;위 코드에는 없지만 함수 끝나고 루트 노드의 색상을 블랙으로 바꿔주는 부분이 꼭 추가되어야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 레드 블랙 트리에 대한 설명이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고 사이트 &lt;a href=&quot;https://itstory.tk/entry/%EB%A0%88%EB%93%9C%EB%B8%94%EB%9E%99-%ED%8A%B8%EB%A6%ACRed-black-tree&quot;&gt;https://itstory.tk/entry/%EB%A0%88%EB%93%9C%EB%B8%94%EB%9E%99-%ED%8A%B8%EB%A6%ACRed-black-tree&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1574950080834&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;article&quot; data-og-title=&quot;레드블랙 트리(Red-black tree)&quot; data-og-description=&quot;이진검색트리는 저장과 검색에 평균 &amp;Theta;( )시간이 소요되지만 운이 나쁘면 트리의 모양이 균형을 잘 이루지 못한다. 균형이 많이 깨지면 &amp;Theta;(n)에 근접한 시간이 소요될 수도 있다. 그래서 고안해 낸 것이 균형잡힌..&quot; data-og-host=&quot;itstory.tk&quot; data-og-source-url=&quot;https://itstory.tk/entry/%EB%A0%88%EB%93%9C%EB%B8%94%EB%9E%99-%ED%8A%B8%EB%A6%ACRed-black-tree&quot; data-og-url=&quot;https://itstory.tk/entry/%EB%A0%88%EB%93%9C%EB%B8%94%EB%9E%99-%ED%8A%B8%EB%A6%ACRed-black-tree&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/coiZq8/hyDTQKB5II/nukhtfkFKy4c2ez1Fda5vk/img.png?width=650&amp;amp;height=312&amp;amp;face=0_0_650_312,https://scrap.kakaocdn.net/dn/q52Bt/hyDSvVC2Cp/lLxq2uJTZUvGK6R9ZicJ0k/img.png?width=650&amp;amp;height=312&amp;amp;face=0_0_650_312,https://scrap.kakaocdn.net/dn/JJGV3/hyDSJ0C1yF/oQ7hhjNEiqXvX2qM8bAvl1/img.png?width=650&amp;amp;height=312&amp;amp;face=0_0_650_312&quot;&gt;&lt;a href=&quot;https://itstory.tk/entry/%EB%A0%88%EB%93%9C%EB%B8%94%EB%9E%99-%ED%8A%B8%EB%A6%ACRed-black-tree&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://itstory.tk/entry/%EB%A0%88%EB%93%9C%EB%B8%94%EB%9E%99-%ED%8A%B8%EB%A6%ACRed-black-tree&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/coiZq8/hyDTQKB5II/nukhtfkFKy4c2ez1Fda5vk/img.png?width=650&amp;amp;height=312&amp;amp;face=0_0_650_312,https://scrap.kakaocdn.net/dn/q52Bt/hyDSvVC2Cp/lLxq2uJTZUvGK6R9ZicJ0k/img.png?width=650&amp;amp;height=312&amp;amp;face=0_0_650_312,https://scrap.kakaocdn.net/dn/JJGV3/hyDSJ0C1yF/oQ7hhjNEiqXvX2qM8bAvl1/img.png?width=650&amp;amp;height=312&amp;amp;face=0_0_650_312');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;레드블랙 트리(Red-black tree)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;이진검색트리는 저장과 검색에 평균 &amp;Theta;( )시간이 소요되지만 운이 나쁘면 트리의 모양이 균형을 잘 이루지 못한다. 균형이 많이 깨지면 &amp;Theta;(n)에 근접한 시간이 소요될 수도 있다. 그래서 고안해 낸 것이 균형잡힌..&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;itstory.tk&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;참고 서적은 한빛미디어의 &quot;뇌를 자극하는 알고리즘&quot;이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;생각보다 간단하지 않으므로 한 번쯤은 구현해보는 것도 나쁘지 않다.&lt;/p&gt;</description>
      <category>알고리즘/study</category>
      <category>red black tree</category>
      <category>레드 블랙 트리</category>
      <category>레드블랙트리</category>
      <author>이즈미르</author>
      <guid isPermaLink="true">https://izmirprogramming.tistory.com/5</guid>
      <comments>https://izmirprogramming.tistory.com/5#entry5comment</comments>
      <pubDate>Mon, 18 Nov 2019 22:30:37 +0900</pubDate>
    </item>
  </channel>
</rss>