<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>seokyoung</title>
    <link>https://seokyoungg.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 13:06:46 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>seok-young</managingEditor>
    <image>
      <title>seokyoung</title>
      <url>https://tistory1.daumcdn.net/tistory/5703178/attach/d2e2e3bffd6f4b18b891c85141f31c39</url>
      <link>https://seokyoungg.tistory.com</link>
    </image>
    <item>
      <title>[Algorithm] 이분 탐색 - 심화</title>
      <link>https://seokyoungg.tistory.com/111</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이분 탐색을 활용한 Parametric Search 문제에서 정말 다양한 코드들이 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 종료 조건에 따라,&amp;nbsp; &lt;s&gt;st &amp;lt; en&lt;/s&gt;과 &lt;s&gt;st &amp;lt;= en&lt;/s&gt;으로 분리된다.&amp;nbsp;&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;s&gt;st = mid + 1&lt;/s&gt;, &lt;s&gt;st = mid&lt;/s&gt; 혹은 &lt;s&gt;en = mid - 1&lt;/s&gt;, &lt;s&gt;en = mid&lt;/s&gt; 과 같은 다양한 코드들을 마주하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;실제로 PS에서 이런 종료 조건들에 의해 답이 틀리는 경우도 있었으며, 무한 루프에 빠지는 경험을 자주 하곤 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 따라 이들의 차이점에 대해 정리하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 나오는 코드들은 Swift로 작성되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이분 탐색&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;이분 탐색&lt;/b&gt;&quot;은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;정렬되어 있는 원소&lt;/b&gt;&lt;/span&gt;들에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;탐색 범위를 절반으로 줄여&lt;/b&gt;&lt;/span&gt;가며 탐색하는 알고리즘이다.&lt;/p&gt;
&lt;pre id=&quot;code_1737441131635&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func binarySearch&amp;lt;T: Comparable&amp;gt;(elements: [T], target: T) -&amp;gt; Int? {
  var st = 0
  var en = elements.count - 1
  
  while st &amp;lt;= en {
    let mid = (st + en) / 2
    
    if elements[mid] == target {
      return mid
    } else if elements[mid] &amp;gt; target {
      en = mid - 1
    } else {
      st = mid + 1
    }
  }
  
  return nil
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;예를 들어, [st, en]의 범위에서 mid가 작거나 크다면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mid를 포함한 나머지 구간은 탐색에서 제외시켜 버린다.&amp;nbsp;&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;이렇게 매 횟수마다 절반씩 줄여나가기 때문에, 시간복잡도는 $O(logN)$이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Parametric Search&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;&lt;b&gt;Parametric Search&lt;/b&gt;&amp;rdquo;란 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;특정 조건을 만족하는 값 중 최적값&lt;/span&gt;&lt;/b&gt;을 찾는 알고리즘이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이분 탐색에서는 특정 조건을 만족하는 값을 찾는 문제인 반면, 파라메트릭 서치는 이 중 최적값을 찾는 문제이다.&amp;nbsp;&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;파라메트릭 서치의 핵심은 &lt;b&gt;최적화 문제&lt;/b&gt;를 &lt;b&gt;결정 문제&lt;/b&gt;로 바꾸어 푼다는 점이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최적화 문제: 가능한 해 중 가장 최적의 해를 찾는 것 (3보다 큰 최초의 수)&lt;/li&gt;
&lt;li&gt;결정 문제: 주어진 조건에 해가 존재하는지 여부를 판단하는 것 (3보다 작다 크다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, [1, 2, 3, 4, 4, 4, 5] 의 배열에서 최초의 4가 등장하는 인덱스를 찾는다고 가정해 보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 결정 문제로 바꾸게 되면, &quot;k번째 인덱스가 4보다 큰가?&quot;라는 문제로 바뀌게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 K에 대해서 이분 탐색으로 바꿔서 풀게 되면 결정문제로 해를 구할 수 있게 된다.&amp;nbsp;&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;Parametric Search는 이분 탐색과 다르게 원하는 조건과 동일한 값이 나오더라도,&amp;nbsp;&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;위의 예시에서 5번 인덱스의 4를 먼저 발견했다고 해서, 최초의 4를 보장할 수 없듯이 말이다.&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;이러한 Parametric Search를 활용하는 대표적인 것에는 lowerBound, upperBound 등등 이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구간의 범위&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Parametric Search문제에서는 구간을 어떻게 설정하느냐에 따라, 종료 조건 혹은 탐색의 범위를 줄여나가는 방식에 차이가 있을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;b&gt;닫힌 구간&lt;/b&gt;: [st, en]&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반열린 구간&lt;/b&gt;: [st, en)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;열린 구간&lt;/b&gt;: (st, en)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 닫힌 구간, 반열린 구간을 많이 사용하기에 해당 포스팅에선 2가지 방식만 다룰 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 사용하는 예시들은 lowerBound를 기준으로 코드를 작성했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;닫힌 구간&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;닫힌 구간에서의 코드를 우선적으로 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1737442036088&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func lowerBound&amp;lt;T: Comparable&amp;gt;(elements: [T], target: T) -&amp;gt; Int {
  var st = 0
  var en = elements.count - 1
  
  while st &amp;lt;= en {
    let mid = (st + en) / 2
    
    if elements[mid] &amp;lt; target {
      st = mid + 1
    } else {
      en = mid - 1
    }
  }
  
  return st
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;닫힌 구간이기 때문에, &lt;s&gt;mid&lt;/s&gt;를 기준으로 다음과 같이 2개의 닫힌 구간으로 분할된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[st, mid]&lt;/li&gt;
&lt;li&gt;[mid, en]&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이분 탐색과 마찬가지로, mid에서의 탐색을 한 후, 다음 탐색에선 mid를 제외시켜 버리게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;[st, mid - 1]: &lt;s&gt;en = mid - 1&lt;/s&gt;로 업데이트&lt;/li&gt;
&lt;li&gt;[mid + 1, en]: &lt;s&gt;st = mid + 1&lt;/s&gt;로 업데이트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;종료 조건&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;닫힌 구간에서 종료 조건은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737442237904&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while st &amp;lt;= en&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는, 닫힌 구간이기에, [k, k]의 범위에서도 k라는 숫자가 존재한다. 즉, 검사해주어야 하는 숫자가 존재한다.&amp;nbsp;&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;또한, 탐색의 끝 값도 유효한 해가 될 수 있기에 검사를 해주어야 한다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주의할 점&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;닫힌 구간에서 주의할 점은 탐색이 종료된 후, &lt;s&gt;st != en&lt;/s&gt; 이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 문제 조건에 맞게 st를 리턴할지, en을 리턴할지 혹은 st - 1, en -1을 리턴할지 고려해야 한다.&amp;nbsp;&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;예를 들면 lowerBound의 경우에는 st를 리턴했던 반면, upperBound에서는 en을 리턴해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737442394667&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func upperBound&amp;lt;T: Comparable&amp;gt;(elements: [T], target: T) -&amp;gt; Int {
  var st = 0
  var en = elements.count - 1
  
  while st &amp;lt;= en {
    let mid = (st + en) / 2
    
    if elements[mid] &amp;lt;= target {
      st = mid + 1
    } else {
      en = mid - 1
    }
  }
  
  return en
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반열린 구간&lt;/h3&gt;
&lt;pre id=&quot;code_1737442435803&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func lowerBound&amp;lt;T: Comparable&amp;gt;(elements: [T], target: T) -&amp;gt; Int {
  var st = 0
  var en = elements.count
  
  while st &amp;lt; en {
    let mid = (st + en) / 2
    
    if elements[mid] &amp;lt; target {
      st = mid + 1
    } else {
      en = mid
    }
  }
  
  return en
}&lt;/code&gt;&lt;/pre&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;s&gt;en = elements.count&lt;/s&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;반열린 구간에서는 [st, en)이 &lt;s&gt;mid&lt;/s&gt;값을 기준으로 다음과 같이 2개의 열린구간으로 분할된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[st, mid)&lt;/li&gt;
&lt;li&gt;[mid, en)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반열린 구간 역시 이분 탐색과 마찬가지로, mid 검사 이후 다음 탐색에서 mid는 구간에서 제외되어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;[st, mid): &lt;s&gt;en = mid&lt;/s&gt; 로 업데이트&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;[mid + 1, en): &lt;s&gt;st = mid + 1&lt;/s&gt;로 업데이트, mid를 제외시켜야 하기 때문에 위와 같이 업데이트한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;종료 조건&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반열린 구간에서의 종료 조건은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737442694446&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while st &amp;lt; en&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 [k, k)를 만족하는 자연수 k는 존재하지 않는다. 따라서, 두 값이 같을 때의 별도의 탐색을 필요 없게 된다.&amp;nbsp;&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;s&gt;st == en&lt;/s&gt;이기 때문에 어떠한 값을 리턴해도 상관없다는 장점이 있다.&lt;/p&gt;</description>
      <category>CS/Algorithm</category>
      <category>Algorithm</category>
      <category>parametric search</category>
      <category>이분 탐색</category>
      <author>seok-young</author>
      <guid isPermaLink="true">https://seokyoungg.tistory.com/111</guid>
      <comments>https://seokyoungg.tistory.com/111#entry111comment</comments>
      <pubDate>Tue, 21 Jan 2025 16:01:45 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] Coordinator - Refactoring</title>
      <link>https://seokyoungg.tistory.com/110</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Coordinator에서는 몇 가지 문제점들과 불편한 점들을 겪고 이들을 리팩토링의 필요성을 느꼈다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 버전의 Coordinator는 여기서 확인해볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://seokyoungg.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://seokyoungg.tistory.com/100&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1736926688707&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Design Pattern] Coordinator 패턴&quot; data-og-description=&quot;&amp;quot;Coordinator패턴&amp;quot;은 ViewController로부터 화면 전환의 부담을 줄여주기 위한 패턴이다.&amp;nbsp;이를 통해ViewController 간의 결합도를 낮춰주게 된다.&amp;nbsp;&amp;nbsp;&amp;nbsp;만약 ViewController에서 다음과 같이 화면 전환 로직을 담&quot; data-og-host=&quot;seokyoungg.tistory.com&quot; data-og-source-url=&quot;https://seokyoungg.tistory.com/100&quot; data-og-url=&quot;https://seokyoungg.tistory.com/100&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/5QtFk/hyX0lrNhVT/NbT2DiFHLuXudBqYGb5fv1/img.png?width=800&amp;amp;height=233&amp;amp;face=0_0_800_233,https://scrap.kakaocdn.net/dn/SlEMc/hyX4sJzups/kufwddlJNE3TPIaWu9Vnh1/img.png?width=800&amp;amp;height=233&amp;amp;face=0_0_800_233,https://scrap.kakaocdn.net/dn/cn3A6i/hyX0xy0BzK/5xntS0jaH0U2BANeH1MEJK/img.png?width=1230&amp;amp;height=687&amp;amp;face=0_0_1230_687&quot;&gt;&lt;a href=&quot;https://seokyoungg.tistory.com/100&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seokyoungg.tistory.com/100&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/5QtFk/hyX0lrNhVT/NbT2DiFHLuXudBqYGb5fv1/img.png?width=800&amp;amp;height=233&amp;amp;face=0_0_800_233,https://scrap.kakaocdn.net/dn/SlEMc/hyX4sJzups/kufwddlJNE3TPIaWu9Vnh1/img.png?width=800&amp;amp;height=233&amp;amp;face=0_0_800_233,https://scrap.kakaocdn.net/dn/cn3A6i/hyX0xy0BzK/5xntS0jaH0U2BANeH1MEJK/img.png?width=1230&amp;amp;height=687&amp;amp;face=0_0_1230_687');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Design Pattern] Coordinator 패턴&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&quot;Coordinator패턴&quot;은 ViewController로부터 화면 전환의 부담을 줄여주기 위한 패턴이다.&amp;nbsp;이를 통해ViewController 간의 결합도를 낮춰주게 된다.&amp;nbsp;&amp;nbsp;&amp;nbsp;만약 ViewController에서 다음과 같이 화면 전환 로직을 담&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seokyoungg.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과물을 SPM을 통해 배포했기 때문에, 사용법 및 코드들은 아래서 확인해 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jungseokyoung-cloud/Coordinator&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/jungseokyoung-cloud/Coordinator&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1737096226980&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - jungseokyoung-cloud/Coordinator: Coordinator 패턴&quot; data-og-description=&quot;Coordinator 패턴 . Contribute to jungseokyoung-cloud/Coordinator development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jungseokyoung-cloud/Coordinator&quot; data-og-url=&quot;https://github.com/jungseokyoung-cloud/Coordinator&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eauxe2/hyX4sQAxdh/vOkupwC0pJgR4QqLVtYzN0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/fBqUj/hyX0nDqA8U/ktBehGWlDEw8bbxzA90Hm0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jungseokyoung-cloud/Coordinator&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jungseokyoung-cloud/Coordinator&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eauxe2/hyX4sQAxdh/vOkupwC0pJgR4QqLVtYzN0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/fBqUj/hyX0nDqA8U/ktBehGWlDEw8bbxzA90Hm0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - jungseokyoung-cloud/Coordinator: Coordinator 패턴&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Coordinator 패턴 . Contribute to jungseokyoung-cloud/Coordinator development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 Coordinator 구조의 문제점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 Coordinator 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Coordinator는 다음과 같은 BaseCoordinator가 존재하여, 필요한 곳에서 이를 채택해 활용했다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736926881190&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// 화면 전환 로직 및, `ViewController`와 `ViewModel`생성을 담당하는 객체입니다.
public protocol Coordinating: AnyObject {
  var navigationController: UINavigationController? { get set }
  var children: [Coordinating] { get }
  
  func start(at navigationController: UINavigationController?)
  func stop()
  func addChild(_ coordinator: Coordinating)
  func removeChild(_ coordinator: Coordinating)
}

open class Coordinator: Coordinating {
  public var navigationController: UINavigationController?
  public final var children: [Coordinating] = []
  
  public init() { }
  
  public init(_ navigationController: UINavigationController?) {
    self.navigationController = navigationController
  }
  
  open func start(at navigationController: UINavigationController?) {
    self.navigationController = navigationController
  }
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;활용&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사용방법은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, Coordinator를 채택해주게 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736927327916&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
import Core

final class SignUpCoordinator: Coordinator, SignUpCoordinatable {
  ...
  
  override func start(at navigationController: UINavigationController?) {
    super.start(at: navigationController)
    attachEnterEmail()
    // navigationController.pushViewController(viewController)
  }
}&lt;/code&gt;&lt;/pre&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;s&gt;start(at:)&lt;/s&gt;메서드에 구현해 주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, &lt;s&gt;SignUpCoordinator&lt;/s&gt;와 같이 Coordinator의 구현체들을 Internal타입이기 때문에, 외부 모듈로 노출되지 않게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;활용 - 부모 coordinator&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했은 Coordinator들의 구현체들은 Internal타입이기 때문에, 부모 Coordinator에선 이를 &lt;s&gt;Coordinating&lt;/s&gt;이라는 프로토콜 타입으로 알고 있게 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736927479480&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class LogInCoordinator: Coordinator {
  ...
  
  private var signUpCoordinator: Coordinating?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모에선 이를 통해 Routing을 진행하고 싶다면,&amp;nbsp; 아래와 같이 진행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1736927518437&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MARK: - SignUp
func attachSignUp() {
  guard signUpCoordinator == nil else { return }

  let coordinater = signUpContainable.coordinator(listener: self)
  addChild(coordinater)
  
  self.signUpCoordinator = coordinater
  // 이를 호출하면, navigationController에 띄어지게 됨.
  coordinater.start(at: self.navigationController) 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 중복되어 View가 Present되는 것을 방지하기 위해 guard문을 통해 확인해 준다.&amp;nbsp;&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;최종적으로 자식 Coordinator의 &lt;s&gt;start(at:)&lt;/s&gt;메서드를 호출한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선, 구조로 프로젝트에서 4~5개월간 사용해 보면서 다음과 같은 문제점을 느끼게 되었다.&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;한 가지 방식으로만 Routing이 가능한 문제&amp;nbsp;&lt;/li&gt;
&lt;li&gt;사용하는 곳이 아닌 사용되는 Coordinator에서 Routing방식을 결정하는 문제점&amp;nbsp;&lt;/li&gt;
&lt;li&gt;UIKit에 대한 의존성 문제&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한 가지 방식으로만 Routing할 수 있는 문제점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 가장 큰 문제점은 한가지 방식으로만 Routing 할 수 있다는 문제점이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736928508809&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class SignUpCoordinator: Coordinator, SignUpCoordinatable {
  ...
  
  override func start(at navigationController: UINavigationController?) {
    super.start(at: navigationController)
    attachEnterEmail()
    // navigationController.pushViewController(viewController)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;앞서 말했는 부모는 자식을 Coordinating으로만 알고 있게 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736928543403&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class LogInCoordinator: Coordinator {
  ...
  
  private var signUpCoordinator: Coordinating?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 다른 Routing방식을 위해 메서드를 추가가 불가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고, Interafce에 Routing 방식마다 메서드를 추가해 놓자니, 구체 타입에서 구현을 하지 않게 되는 메서드들이 생기게 된다. 즉, LSP에 위반하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용하는 곳이 아닌 사용되는 Coordinator에서 Routing 방식을 결정하는 문제점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째는 사용하는 곳이 아닌 사용되는 Coordinator에서 아래와 같이 Routing방식을 결정하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736928810572&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override func start(at navigationController: UINavigationController?) {
  super.start(at: navigationController)
  
  navigationController?.present(viewController, animated: true)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;'LogIn' 화면에서 &quot;회원가입하기&quot; 버튼을 누르게 되면 'SignUp'으로 넘어가게 된다.&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;이때, Routing방식은 실제 해당 View를 띄우는 'Login'에서 결정하는 것이 아닌 'SignUp'에서 결정 나게 된다.&amp;nbsp;&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;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;해당 문제점은 앞서 말한, &quot;한가지 방법으로만 Routing이 가능한 문제점&quot;과 더불어, 결론적으로 View의 재사용성을 저하시키게 된다.&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;또한, 'Login'에서 'SignUp'이 띄어지는 방식이 modal 방식으로 변경되었다고 가정해 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면,&amp;nbsp; 'Login'을 수정하는 것이 아닌, 'SignUp'을 수정해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 변경의 범위가 'SignUp'까지 확대되는 문제가 발생한다.&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;추가적으로 UISegmentControl에서 segment가 바뀔 때마다 ViewController가 변경된다고 가정해 보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, UISegmentControl를 가지고 있는 ViewController에선 각 segment별로 ViewController를 알고 있어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 사용되는 Coordinator에서 Routing방식을 결정하기 때문에, ViewController를 알지 못한다.&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;지금까지는 이를 해결하기 위해 임시방편으로 Coordinator의 ViewController프로퍼티를 다음과 같이 Internal로 구현해 놓았다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1736931000496&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 부모
final class ChallengeCoordinator ... {
 func attachSegments() {
   let feedSegmentCoordinator = feedSegmentContainer.coordinator(listener: self)
   // 부모는 Coordinating으로 알고 있기에 타입 캐스팅이 필요함.
   guard let feedSegmentCoordinator = feedSegmentCoordinator as? FeedSegmentCoordinator else { return }
   self.feedSegmentCoordinator = feedSegmentCoordinator
   viewController.attachViewControllers(feedSegmentCoordinator.viewController)
 }
}

// 자식
final class FeedSegmentCoordinator ... {
  let viewController: FeedSegmentViewController
  ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 부모 자식이 모두 같은 모듈에 존재한다면 internal로 해결이 가능하지만, 다른 모듈일 경우엔 불가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewController 프로퍼티를 public으로 변경한다 해도, Coordinator는 Internal타입이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;UIKit에 대한 의존성 문제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로는 Coordinator에서 UINavigationController로 인해 UIKit에 대한 의존성이 생긴다는 문제점이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리팩토링&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;한 가지 방식으로만 routing 할 수 있는 문제&lt;/li&gt;
&lt;li&gt;사용되는 Coordinator에서 Routing방식을 결정하는 문제&lt;/li&gt;
&lt;li&gt;UIKit에 대한 의존성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해, 가장 핵심은 ViewController를 외부에서 접근할 수 있게 하는 것이다. 이렇게 되면, 부모 Coordinator에서 어떤 방식으로 Routing 할지 결정할 수 있어 재사용성이 증가하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UIKit에 대한 의존성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ViewController를 외부로 분리하기에 앞서, UIKit에 대한 의존성을 제거해 보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 달성하기 위해 다음과 같은 2개의 타입들을 추가했다.&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;s&gt;ViewControllerable&lt;/s&gt;: UIViewController를 랩핑 한 프로토콜 타입이다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;NavigationControllerable&lt;/s&gt;: UINavigationController를 랩핑한 객체이다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ViewControllerable&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 RIBs 아키텍쳐의 ViewControllerable을 참고했다.&lt;/p&gt;
&lt;pre id=&quot;code_1737096175551&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public protocol ViewControllerable: AnyObject {
  var uiviewController: UIViewController { get }
}

public extension ViewControllerable where Self: UIViewController {
  var uiviewController: UIViewController { return self }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이는 다음과 같은 방식으로 present를 하기 때문에 코드가 복잡해진다는 단점이 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737096259581&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let viewController = coordinator.viewControllerable.uiViewController 
viewController.modalPresentationStyle = .fullScreen 
self.viewControllerable.present(viewController, animated: true)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 편리성을 위해 extension을 통해 다음과 같이 구현해 주었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737096284581&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MARK: - Present Methods
public extension ViewControllerable {
  func present(
    _ viewControllable: ViewControllerable,
    animated: Bool,
    completion: (() -&amp;gt; Void)? = nil
  ) {
    self.uiviewController.present(
      viewControllable.uiviewController,
      animated: animated,
      completion: completion
    )
  }
  
  func present(
    _ viewControllable: ViewControllerable,
    animated: Bool,
    modalPresentationStyle: UIModalPresentationStyle,
    completion: (() -&amp;gt; Void)? = nil
  ) {
    viewControllable.uiviewController.modalPresentationStyle = modalPresentationStyle
    self.uiviewController.present(
      viewControllable.uiviewController,
      animated: animated,
      completion: completion
    )
  }
...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, present뿐만이 아닌 push방식을 고려하여 다음과 같이 추가해 주었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737096419323&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MARK: - Push Methods
public extension ViewControllerable {
  func pushViewController(_ viewControllable: ViewControllerable, animated: Bool) {
    if let nav = self.uiviewController as? UINavigationController {
      nav.pushViewController(viewControllable.uiviewController, animated: animated)
    } else {
      self.uiviewController
        .navigationController?
        .pushViewController(viewControllable.uiviewController, animated: animated)
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NavigationControllerable&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, Coordinator에서 다음 View를 Navigation으로 감싸 present하고 싶은 경우 &lt;s&gt;UINavigationController&lt;/s&gt;를 생성해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 이를 랩핑 해주는 NavigationControllerable을 추가해 주었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1737096511095&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public extension ViewControllerable where Self: NavigationControllerable {
  var uiviewController: UIViewController { return self.navigationController }
}

@MainActor
public class NavigationControllerable: ViewControllerable {
  public let navigationController: UINavigationController
  
  // MARK: - Initializers
  public init(navigationController: UINavigationController) {
    self.navigationController = navigationController
  }
  
  public init(_ rootViewControllerable: ViewControllerable) {
    self.navigationController = UINavigationController(rootViewController: rootViewControllerable.uiviewController)
  }
  
  public init() {
    self.navigationController = UINavigationController()
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ViewController를 외부로 노출시키기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, 위의 ViewControllerable을 활용해 Coordinator에서 ViewController를 외부로 노출시켜 보자.&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;이에 앞서, 특정 Coordinator에선 ViewController가 필요 없는 경우가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, SignUp에는 여러 단계들이 포함될 수 있다. (아이디 입력, 비밀번호 입력 &amp;hellip;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들을 묶어주는 SignUp에는 별도의 ViewController가 필요 없어지게 된다.&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;따라서, 이들을 분리해 주기 위해 Coordinator를 2가지 타입으로 분리했다.&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;s&gt;Coordinator&lt;/s&gt;: View가 없는 Coordinator&lt;/li&gt;
&lt;li&gt;&lt;s&gt;ViewableCoordinator&lt;/s&gt;: View가 있는 Coordinator&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Coordinator&lt;/h4&gt;
&lt;pre id=&quot;code_1737096671553&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// 화면 전환 로직을 담당하는 객체입니다..
public protocol Coordinating: AnyObject {
  var children: [Coordinating] { get }
  
  func start()
  func stop()
  func addChild(_ coordinator: Coordinating)
  func removeChild(_ coordinator: Coordinating)
}

open class Coordinator: Coordinating {
  public final var children: [Coordinating] = []
    
  /// 부모에게 attach되었을 때 원하는 동작을 해당 메서드에 구현하면 됩니다.
  open func start() { }
  
  /// 부모에게 제거되었을 때 원하는 동작을 해당 메서드에 구현하면 됩니다.
  open func stop() {
    self.removeAllChild()
  }
  
  public init() { }
  
  public final func addChild(_ coordinator: Coordinating) {
    guard !children.contains(where: { $0 === coordinator }) else { return }
    
    children.append(coordinator)
    
    coordinator.start()
  }
  
  public final func removeChild(_ coordinator: Coordinating) {
    guard let index = children.firstIndex(where: { $0 === coordinator }) else { return }
    
    children.remove(at: index)
    
    coordinator.stop()
  }
  
  private func removeAllChild() {
    children.forEach { removeChild($0) }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View가 없기 때문에, 기존의 Coordinator와 NavigatoinController가 없다는 것 외에 차이점이 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ViewableCoordinator&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 View가 있는 경우의 Coordinator이다.&lt;/p&gt;
&lt;pre id=&quot;code_1737096735815&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public protocol ViewableCoordinating: Coordinating {
  var viewControllerable: ViewControllerable { get }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스는 &lt;s&gt;viewControllerable&lt;/s&gt;가 추가되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 부모에서는 Routing을 담당하게 된다.&amp;nbsp;&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;또한, Coordinator에서 ViewController로 이벤트를 전달할 경우가 발생한다. 이전 방식에선 이를 구체타입으로 해결했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하고자 다음과 같이 구현했다.&lt;/p&gt;
&lt;pre id=&quot;code_1737096809231&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;open class ViewableCoordinator&amp;lt;PresenterType&amp;gt;: Coordinator, ViewableCoordinating

public let viewControllerable: ViewControllerable
  /// 내부적으로 `Coordinator`에서 `ViewController`로 이벤트를 전달할 경우 사용합니다.
public let presenter: PresenterType

public init(_ viewController: ViewControllerable) {
  self.viewControllerable = viewController
  
  guard let presenter = viewController as? PresenterType else {
    fatalError(&quot;\(viewController) should conform to \(PresenterType.self)&quot;)
  }
  
  self.presenter = presenter
  super.init()
  bind()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewControllerable은 부모에서 Routing을 하기 위해 사용된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, presenter는 내부에서 ViewController로 이벤트를 전달하기 위해 사용한다.&amp;nbsp;&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;이때, 부모는 ViewableCoordinating이라는 인터페이스로만 알기 때문에, presenter에 접근할 수 없다.&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS/Pattern</category>
      <category>Coordinator</category>
      <category>design pattern</category>
      <author>seok-young</author>
      <guid isPermaLink="true">https://seokyoungg.tistory.com/110</guid>
      <comments>https://seokyoungg.tistory.com/110#entry110comment</comments>
      <pubDate>Fri, 17 Jan 2025 15:55:57 +0900</pubDate>
    </item>
    <item>
      <title>[iOS] Custom 영상길이 조절 SliderBar 구현하기</title>
      <link>https://seokyoungg.tistory.com/109</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에선 아래와 같이 iPhone에서 제공하는 SliderBar를 직접 구현해보자!&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screen Recording Dec 13 2024.gif&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;1920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEJohp/btsLjvmc07k/VRkxdvNp1ob9lU2ud2IgS0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEJohp/btsLjvmc07k/VRkxdvNp1ob9lU2ud2IgS0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEJohp/btsLjvmc07k/VRkxdvNp1ob9lU2ud2IgS0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bEJohp/btsLjvmc07k/VRkxdvNp1ob9lU2ud2IgS0/img.gif&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; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1084&quot; data-filename=&quot;Screen Recording Dec 13 2024.gif&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 것은 해당 링크에서 확인해볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jungseokyoung-cloud/VideoTrimmingSliderBar&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/jungseokyoung-cloud/VideoTrimmingSliderBar&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734105972373&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - jungseokyoung-cloud/VideoTrimmingSliderBar&quot; data-og-description=&quot;Contribute to jungseokyoung-cloud/VideoTrimmingSliderBar development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jungseokyoung-cloud/VideoTrimmingSliderBar&quot; data-og-url=&quot;https://github.com/jungseokyoung-cloud/VideoTrimmingSliderBar&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/o6V5k/hyXKwULbRk/4E6Q0pxxGk3eFtGg8Q9hPk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/niyuS/hyXOrjUXX5/AHrhaRGhKvsMI62KrBZgJk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jungseokyoung-cloud/VideoTrimmingSliderBar&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jungseokyoung-cloud/VideoTrimmingSliderBar&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/o6V5k/hyXKwULbRk/4E6Q0pxxGk3eFtGg8Q9hPk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/niyuS/hyXOrjUXX5/AHrhaRGhKvsMI62KrBZgJk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - jungseokyoung-cloud/VideoTrimmingSliderBar&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to jungseokyoung-cloud/VideoTrimmingSliderBar development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;s&gt;VideoTrimmingSliderBar&lt;/s&gt;를 State를 정의할 수도 있고, send-Action을 활용할 수 있어, UIControl로 구현해주었다.&lt;/p&gt;
&lt;pre id=&quot;code_1733708711252&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class VideoTrimmingSliderBar: UIControl { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 프로퍼티들부터 보자면, 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734069011343&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/// 현재 slider의 최솟값
private var minimumValue: Double = 0
/// 현재 slider의 최댓값
private var maximumValue: Double = 100

/// 현재 slider에서 선택학 영역의 최솟값
private(set) var lowerValue: Double = 0.0 {
  didSet {
    updateThumbFrame(lowerSlider)
    delegate?.lowerValueDidChanged(self, value: lowerValue)
  }
}

/// 현재 slider에서 선택한 영역의 최댓값
private(set) var upperValue: Double = 100 {
  didSet {
    updateThumbFrame(upperSlider)
    delegate?.upperValueDidChanged(self, value: upperValue)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;영상 길이 조절 기능&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상길이를 조절할 수 있는 것은 다음과 같이 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 좌우에서 영상 길이를 조절할 수 있는 &lt;s&gt;sliderThumb&lt;/s&gt;와&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;sliderThumb&lt;/s&gt;&amp;nbsp;사이 길이에 맞춰 길이가 늘어나고 줄어드는 ViewA가 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 3.png&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zCG1o/btsLjSg1PcL/fZibpWTMT4iWjufiJC8q11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zCG1o/btsLjSg1PcL/fZibpWTMT4iWjufiJC8q11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zCG1o/btsLjSg1PcL/fZibpWTMT4iWjufiJC8q11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzCG1o%2FbtsLjSg1PcL%2FfZibpWTMT4iWjufiJC8q11%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;599&quot; height=&quot;200&quot; data-filename=&quot;Group 3.png&quot; data-origin-width=&quot;599&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&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;해당 ViewA와 좌우의 &lt;s&gt;sliderThumb&lt;/s&gt;를 하나로 묶을 수 있지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 움직이는 곳은 &lt;s&gt;VideoTrimmingSliderBar&lt;/s&gt;이기 때문에,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;묶게 되면 좌 우 &lt;s&gt;sliderThumb&lt;/s&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;따라서, VideoTrimmingSliderBar안에 다음과 같이 2개의 Thumb를 별도로 구성해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734069965465&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private let lowerThumb = SliderThumb(tintColor: .systemYellow)
private let upperThumb = SliderThumb(tintColor: .systemYellow)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;View A의 UI&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 ViewA를 그려보자. 이때 View를 그리는 방법에는 크게 3가지로 구상했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;UIView&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간단한 방법의 UIView로 구성하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말그대로 너무나 간단하고 동작역시 매끄럽다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734071240155&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private let topView = UIView()
private let bottomView = UIView()

func updateThumbFrame(_ thumb: SliderThumb) {
  ...
  updateTopAndBottomView()
}


func updateTopAndBottomView() {
  topView.frame = CGRect(
    x: lowerThumb.center.x,
    y: 0,
    width: upperThumb.center.x - lowerThumb.center.x,
    height: 5
  )
  
  bottomView.frame = CGRect(
    x: lowerSlider.center.x,
    y: bounds.height - 5,
    width: upperThumb.center.x - lowerThumb.center.x,
    height: 5
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Core Graphics&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 방법으로는 CPU기반 렌더링 방식인 Core Graphics다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734072434658&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MARK: - Draw
override func draw(_ rect: CGRect) {
  super.draw(rect)
  guard let context = UIGraphicsGetCurrentContext() else { return }
  
  let maskedRect = CGRect(
    x: lowerThumb.center.x,
    y: 0,
    width: upperThumb.center.x - lowerThumb.center.x,
    height: 5
  )
  
  context.setFillColor(UIColor.systemYellow.cgColor)
  context.fill(maskedRect)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것 역시 매끄럽고 잘 동작한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Core Animation&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Core Animation방식이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Core Animation에선 다음과 같이 구성된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734072585385&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func updateThumbFrame(_ thumb: SliderThumb) {
  ...
  updateTopAndBottomLayer()
}


func updateTopAndBottomLayer() {
  topLayer.frame = CGRect(
    x: lowerThumb.center.x,
    y: 0,
    width: upperThumb.center.x - lowerThumb.center.x,
    height: 5
  )
  
  bottomLayer.frame = CGRect(
    x: lowerThumb.center.x,
    y: bounds.height - 5,
    width: upperThumb.center.x - lowerThumb.center.x,
    height: 5
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 해당 방식에서는 다음과 같은 Delay가 존재하게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Core Animation.gif&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFz1Hy/btsLhsdhSZT/8mYkSmrTO7MZ7JHTdstVH0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFz1Hy/btsLhsdhSZT/8mYkSmrTO7MZ7JHTdstVH0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFz1Hy/btsLhsdhSZT/8mYkSmrTO7MZ7JHTdstVH0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dFz1Hy/btsLhsdhSZT/8mYkSmrTO7MZ7JHTdstVH0/img.gif&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; loading=&quot;lazy&quot; width=&quot;295&quot; height=&quot;640&quot; data-filename=&quot;Core Animation.gif&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU 기반의 랜더링인 Core Animation이 CPU기반의 Core Graphics보다 빠르면 더 빨랐지, 느릴일은 없다고 생각했다. 하지만 위와 같은 딜레이를 맞이하고 stackOverFlow에 질문글을 올리게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/79264407/why-does-core-animation-have-a-delay-compared-to-core-graphics&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/79264407/why-does-core-animation-have-a-delay-compared-to-core-graphics&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734072879224&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Why does Core Animation have a delay compared to Core Graphics?&quot; data-og-description=&quot;I am trying to implement a custom UISlider on an iOS device. First, the ThumbSlider is positioned on both ends, and the desired behavior is as follows: When the ThumbSlider moves left or right, the&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/79264407/why-does-core-animation-have-a-delay-compared-to-core-graphics&quot; data-og-url=&quot;https://stackoverflow.com/questions/79264407/why-does-core-animation-have-a-delay-compared-to-core-graphics&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/m4WNY/hyXKzcM3aE/Asz4OWPceNUWblJHGs1TeK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/79264407/why-does-core-animation-have-a-delay-compared-to-core-graphics&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/79264407/why-does-core-animation-have-a-delay-compared-to-core-graphics&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/m4WNY/hyXKzcM3aE/Asz4OWPceNUWblJHGs1TeK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Why does Core Animation have a delay compared to Core Graphics?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I am trying to implement a custom UISlider on an iOS device. First, the ThumbSlider is positioned on both ends, and the desired behavior is as follows: When the ThumbSlider moves left or right, the&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 답변에 의하면, Core Animation에서는 implicit하게 animation을 실행하는 것들이 있으며, frame이 이에 해당된다. 따라서, 위의 딜레이는 Animation에 의한 딜레이이고, 이를 해결하기 위해선 다음과 같이 코드를 작성해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734073051481&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func updateTopAndBottomLayer() {
  CATransaction.begin()
  CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
  topLayer.frame = CGRect(
    x: lowerThumb.center.x,
    y: 0,
    width: upperThumb.center.x - lowerThumb.center.x,
    height: 5
  )
  
  bottomLayer.frame = CGRect(
    x: lowerThumb.center.x,
    y: bounds.height - 5,
    width: upperThumb.center.x - lowerThumb.center.x,
    height: 5
  )
  CATransaction.commit()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Core Animation 선정 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선적으로, 해당 View는 아무런 이벤트가 없기 때문에, Responder Chain을 활용할 이유가 없다고 생각했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, UIView는 Core Animation과 Core Graphics보다 무겁다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, UIView의 방식은 우선적으로 배제했다.&amp;nbsp;&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;다음으로 Core Graphics방식과 Core Animation 방식이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 ViewA는 ThumbSlider가 움직일때마다, drawing이 일어나게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 자주 일어나기 때문에, CPU기반보단 GPU기반이 성능적의 이점이 있다고 판단했다.&amp;nbsp;&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;실제 테스트 해봤을 때의 결과는 CPU 사용률은 1~2퍼 정도만 차이가 났다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고, 조금이나마의 성능 최적화를 위해 Core Animation으로 선정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Core Graphics (최대 7)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;CoreGraphics MAX7.gif&quot; data-origin-width=&quot;2982&quot; data-origin-height=&quot;1822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be3H9z/btsLiBAllFs/dLYKdqEmYvKZ7YSV5QM3ck/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be3H9z/btsLiBAllFs/dLYKdqEmYvKZ7YSV5QM3ck/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be3H9z/btsLiBAllFs/dLYKdqEmYvKZ7YSV5QM3ck/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/be3H9z/btsLiBAllFs/dLYKdqEmYvKZ7YSV5QM3ck/img.gif&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; loading=&quot;lazy&quot; width=&quot;2982&quot; height=&quot;1822&quot; data-filename=&quot;CoreGraphics MAX7.gif&quot; data-origin-width=&quot;2982&quot; data-origin-height=&quot;1822&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Core Animation (최대 6)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;CALAyer MAX6.gif&quot; data-origin-width=&quot;2982&quot; data-origin-height=&quot;1822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SVRk7/btsLhtQDl51/9VdfDf8UGD2V8F3bCbblF1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SVRk7/btsLhtQDl51/9VdfDf8UGD2V8F3bCbblF1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SVRk7/btsLhtQDl51/9VdfDf8UGD2V8F3bCbblF1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/SVRk7/btsLhtQDl51/9VdfDf8UGD2V8F3bCbblF1/img.gif&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; loading=&quot;lazy&quot; width=&quot;2982&quot; height=&quot;1822&quot; data-filename=&quot;CALAyer MAX6.gif&quot; data-origin-width=&quot;2982&quot; data-origin-height=&quot;1822&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UIView (최대 8)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;UIView방식 MAX8.gif&quot; data-origin-width=&quot;2982&quot; data-origin-height=&quot;1822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btKTV4/btsLhrS2Ifj/1Uv7dmCJcrxtcG5YMkK3j1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btKTV4/btsLhrS2Ifj/1Uv7dmCJcrxtcG5YMkK3j1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btKTV4/btsLhrS2Ifj/1Uv7dmCJcrxtcG5YMkK3j1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/btKTV4/btsLhrS2Ifj/1Uv7dmCJcrxtcG5YMkK3j1/img.gif&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; loading=&quot;lazy&quot; width=&quot;2982&quot; height=&quot;1822&quot; data-filename=&quot;UIView방식 MAX8.gif&quot; data-origin-width=&quot;2982&quot; data-origin-height=&quot;1822&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;위치 계산&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 &lt;s&gt;ThumbSlider&lt;/s&gt;를 움직였을 때의 위치 변환이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt; View 좌표계를 초 단위로 변경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 살펴보았듯,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;s&gt;ThumbSlider&lt;/s&gt;의 값들은 lowerValue, upperValue로 들고 있으며 이들은 초단위이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만, 이때 움직이는 것은 View의 좌표계 단위로 움직이기 때문에, 이들을 초단위로 매핑해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 다음과 같이 &lt;s&gt;UIControl&lt;/s&gt;의 &lt;s&gt;continueTacking(_:with:)&lt;/s&gt;&amp;nbsp;메서드에 다음을 추가해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1734074645089&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override func continueTracking(_ touch: UITouch, with event: UIEvent?) -&amp;gt; Bool {
  let location = touch.location(in: self)
  defer { previousLocation = location }
  
  guard let thumb = currentHighlightedThumb else { return false }
  
  let thumbDeltaValue = deltaValue(from: previousLocation, to: location)
  
  
  if thumb === lowerThumb {
    self.lowerValue = updatedLowerValue(moved: thumbDeltaValue)
  } else if thumb === upperThumb {
    self.upperValue = updatedUpperValue(moved: thumbDeltaValue)
  }
  sendActions(for: .valueChanged)
  
  return true
}&lt;/code&gt;&lt;/pre&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;pre id=&quot;code_1734075295878&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let sliderDeltaValue = deltaValue(from: previousLocation, to: location)

func deltaValue(from previous: CGPoint, to current: CGPoint) -&amp;gt; Double {
  let deltaLocation = Double(current.x - previous.x)
  
  return (maximumValue - minimumValue) * deltaLocation / Double(totalLength)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;움직인 거리를 초단위로 변환해준다.&amp;nbsp;&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;pre id=&quot;code_1734075398685&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func updatedLowerValue(moved delta: Double) -&amp;gt; Double {
  return (lowerValue + delta).bound(lower: minimumValue, upper: upperValue - gapBetweenSliders)
}

func updatedUpperValue(moved delta: Double) -&amp;gt; Double {
  return (upperValue + delta).bound(lower: lowerValue + gapBetweenSliders, upper: maximumValue)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;초 단위를 View의 좌표계로 변경&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 이 lowerValue, upperValue를 가지고 실제 View에서의 위치를 표기해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 4.png&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AgUlW/btsLjp0HrAH/lKDjv6sbM16O46xAfUssRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AgUlW/btsLjp0HrAH/lKDjv6sbM16O46xAfUssRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AgUlW/btsLjp0HrAH/lKDjv6sbM16O46xAfUssRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAgUlW%2FbtsLjp0HrAH%2FlKDjv6sbM16O46xAfUssRk%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;622&quot; height=&quot;218&quot; data-filename=&quot;Group 4.png&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 imageFrame이 위치하는 곳은 파란색 부분이다. 하지만,  SliderThumb는 ImageFrame보다 바깥쪽에서 움직일 수 있게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 위와 같이 왼쪽 기준을 각각의 value가 위치하는 곳이라 치면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2개의 Slider가 맞닿으면, 실제로 SliderThumb의 넓이만큼의 초가 남아있게 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734076587970&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func updateSliderFrame(_ slider: EditSlider) {
  let width = Constants.sliderWidth
  
  let leading = slider === lowerSlider ? leading(of: lowerValue) : leading(of: upperValue)
  
  slider.frame = CGRect(
    x: leading,
    y: 0,
    width: width,
    height: bounds.height
  )
  updateTopAndBottomLayer()
}

func leading(of value: Double) -&amp;gt; Double {
  return totalLength * value / maximumValue // totalLength는 움직일 수 있는 실제 거리
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드로 실제 view의 frame에 매핑을 해주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;영상 위치 조절 기능&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 영상 위치를 나타내는 seekThumb를 구현해보자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 1.png&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biZb6V/btsLhiPzAVU/r7cH1hcpdsbq9lHgiPeiz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biZb6V/btsLhiPzAVU/r7cH1hcpdsbq9lHgiPeiz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biZb6V/btsLhiPzAVU/r7cH1hcpdsbq9lHgiPeiz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiZb6V%2FbtsLhiPzAVU%2Fr7cH1hcpdsbq9lHgiPeiz0%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;185&quot; data-filename=&quot;Group 1.png&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1734076772936&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private let seekThumb = SliderThumb(tintColor: .white, hightlightedColor: .systemGray4)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 seekValue를 초단위이다.&lt;/p&gt;
&lt;pre id=&quot;code_1734078658913&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private(set) var seekValue: Double = 0 {
  didSet {
    updateSeekThumbFrame()
    delegate?.seekValueDidChanged(self, value: seekValue)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;위치 계산&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;View의 좌표계에서 초 단위로 변환&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lowerThumb, upperThumb와 마찬가지로, Devide상에서의 위치 변화를 초단위인 seekValue로 변환해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734079015913&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override func continueTracking(_ touch: UITouch, with event: UIEvent?) -&amp;gt; Bool {
  ...

  let seekThumbDeltaValue = deltaValue(from: previousLocation, to: location)
  
  if thumb === seekThumb {
    self.seekValue = updatedSeekValue(moved: seekThumbDeltaValue)
  }
  ...
  
  sendActions(for: .valueChanged)
  return true
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734079045687&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func updatedSeekValue(moved delta: Double) -&amp;gt; Double {
  return (seekValue + delta).bound(lower: lowerValue, upper: upperValue)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 주의할점은 seekValue의 경우에는 bound가 lowerValue서부터, upperValue까지이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;초 단위를 view의 좌표계으로 변환&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 seekValue를 view의 좌표값 단위로 변경해주어야 한다.&amp;nbsp;&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;이때 주의 할점은 seekThumb와 lowerThumb, upperThumb의 움직일 수 있는 범위가 다르다는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 1.png&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OPAQ8/btsLjmiqCMF/77aX9gouJnjjiKGaXBsJT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OPAQ8/btsLjmiqCMF/77aX9gouJnjjiKGaXBsJT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OPAQ8/btsLjmiqCMF/77aX9gouJnjjiKGaXBsJT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOPAQ8%2FbtsLjmiqCMF%2F77aX9gouJnjjiKGaXBsJT1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;564&quot; height=&quot;226&quot; data-filename=&quot;Group 1.png&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, seekThumb의 leading을 구해주는 코드는 lowerValue로부터 떨어져 있는 값을 실제 Device에서의 위치로 변환을 해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1734079821225&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func leadingForSeek(of value: Double) -&amp;gt; Double {
  let range = upperValue - lowerValue
  guard range &amp;gt; 0 else { return 0 }
  
  let proportion = (value - lowerValue) / range
  return Double(upperThumb.frame.minX - lowerThumb.frame.maxX) * proportion
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt; Frame이미지 생성&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 ImageFrame을 생성하는 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 처음으로 생각했던 방법은 다음과 같이 ImageView를 Frame 갯수만큼 생성해주어,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 넣어주는 방식이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 2.png&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GUTZ1/btsLiDyQ1FS/bjRExmo7p8RpXhlWWlI8Q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GUTZ1/btsLiDyQ1FS/bjRExmo7p8RpXhlWWlI8Q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GUTZ1/btsLiDyQ1FS/bjRExmo7p8RpXhlWWlI8Q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGUTZ1%2FbtsLiDyQ1FS%2FbjRExmo7p8RpXhlWWlI8Q0%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;563&quot; height=&quot;185&quot; data-filename=&quot;Group 2.png&quot; data-origin-width=&quot;563&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, Frame 갯수만큼 ImageView가 생성되기 때문에, 최적화를 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Frame의 이미지를 하나로 합치기로 결정했다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734098999128&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func frameImage(
  with generator: AVAssetImageGenerator,
  times: [CMTime],
  frameWidth: CGFloat
) async -&amp;gt; UIImage {
  var resultImages = Array(repeating: UIImage(), count: Constants.frameCount)
  
  await withTaskGroup(of: Void.self) { group in
    for (index, time) in times.enumerated() {
      group.addTask {
        guard let image = try? await generator.generateUIImage(at: time) else { return }
        resultImages[index] = image
      }
    }
  }
  // 이미지를 하나로 합치기
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, frame 갯수에 맞는 이미지들을 &lt;s&gt;AVAssetImageGenerator&lt;/s&gt;를 통해 이를 생성한다.&amp;nbsp;&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;이때, 합치는 방법에는 크게 CPU기반과 GPU기반이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘의 시간 측정을 하기 위해서 영상길이를 4분 4초이고, frame 갯수는 500개로 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CPU 기반 랜더링&amp;nbsp;&lt;/h3&gt;
&lt;pre id=&quot;code_1734099550995&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension Array where Element: UIImage {
  func concatImagesHorizontaly() -&amp;gt; UIImage {
    let maxWidth = self.compactMap { $0.size.width }.max()
    let maxHeight = self.compactMap { $0.size.height }.max()
    
    let maxSize = CGSize(width: maxWidth ?? 0, height: maxHeight ?? 0)
    let totalSize = CGSize(width: maxSize.width * (CGFloat)(self.count), height: maxSize.height)
    
    return UIGraphicsImageRenderer(size: totalSize).image { context in
      for (index, image) in self.enumerated() {
        
        let rect = CGRect(
          x: maxSize.width * CGFloat(index),
          y: 0,
          width: maxSize.width,
          height: maxSize.height
        )
        
        image.draw(in: rect)
      }
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 위와 같은 CPU기반 랜더링 방식에선, 7초가 소요되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GPU 기반 랜더링&lt;/h3&gt;
&lt;pre id=&quot;code_1734101073458&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension Array where Element: UIImage {
  func concatImagesHorizontallyGPU() -&amp;gt; UIImage {
    let context = CIContext(options: [CIContextOption.useSoftwareRenderer: false])

    let ciImages = self.compactMap { CIImage(image: $0) }

    guard !ciImages.isEmpty else { return UIImage() }

    let maxWidth = ciImages.compactMap { $0.extent.width }.max() ?? 0
    let maxHeight = ciImages.compactMap { $0.extent.height }.max() ?? 0
    
    let maxSize = CGSize(width: maxWidth, height: maxHeight)
    let totalSize = CGSize(
      width: maxSize.width * (CGFloat)(self.count),
      height: maxSize.height
    )
    let finalRect = CGRect(origin: .zero, size: totalSize)
    
    let outputImage = ciImages.enumerated().reduce(CIImage()) { result,  element in
      let (index, image) = element
      let xOffset = maxWidth * CGFloat(index)
      
      let translatedImage = image.transformed(by: CGAffineTransform(
          translationX: xOffset,
          y: (maxHeight - image.extent.height) / 2
        )
      )
      return result.composited(over: translatedImage)
    }
    
    // 결과를 UIImage로 변환
    guard let cgImage = context.createCGImage(outputImage, from: finalRect) else { return UIImage() }
    return UIImage(cgImage: cgImage)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 GPU 랜더링 방식에선, 동일한 조건에서 0.3초가 소요됐다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이미지 블러 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 이미지의 블러처리이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 사진과 같이 lowerThumb와 upperThumb밖에 있는 영역에 있는 이미지는 위와 같이 Blur처리를 해주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screen Recording Dec 13 2024.gif&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;1920&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEJohp/btsLjvmc07k/VRkxdvNp1ob9lU2ud2IgS0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEJohp/btsLjvmc07k/VRkxdvNp1ob9lU2ud2IgS0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEJohp/btsLjvmc07k/VRkxdvNp1ob9lU2ud2IgS0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bEJohp/btsLjvmc07k/VRkxdvNp1ob9lU2ud2IgS0/img.gif&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; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;1084&quot; data-filename=&quot;Screen Recording Dec 13 2024.gif&quot; data-origin-width=&quot;886&quot; data-origin-height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 처리 하기 위해서, VideoTrimmingSliderBar 최상단에 BlurView를 하나 추가했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 BlurView는 다음과 같이 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734101824955&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class ImageFrameBlurView: UIView {
	var selectedRect: CGRect = .zero {
		didSet { setNeedsDisplay() }
	}
	
	init() {
		super.init(frame: .zero)
		self.backgroundColor = .clear
	}
	
	override func draw(_ rect: CGRect) {
		super.draw(rect)
		guard let context = UIGraphicsGetCurrentContext() else { return }
		
		context.setFillColor(UIColor.black.withAlphaComponent(0.4).cgColor)
		context.fill(self.bounds)
		
		context.setBlendMode(.clear)
		context.fill(selectedRect)
		
		context.setBlendMode(.normal)
	}
	...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, selectedRect 외의 영역은 blur처리로 fill을 해주고, 외의 영역을 clear로 처리했다.&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS/iOS</category>
      <category>ios</category>
      <category>sliderbar</category>
      <author>seok-young</author>
      <guid isPermaLink="true">https://seokyoungg.tistory.com/109</guid>
      <comments>https://seokyoungg.tistory.com/109#entry109comment</comments>
      <pubDate>Sat, 14 Dec 2024 01:07:57 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] 그래프 탐색 알고리즘 - BFS &amp;amp; DFS</title>
      <link>https://seokyoungg.tistory.com/108</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;그래프 탐색&lt;/b&gt;&quot;이란 &lt;u&gt;&lt;b&gt;그래프에서 하나의 노드를 시작으로 다수의 노드를 방문하는 알고리즘&lt;/b&gt;&lt;/u&gt;을 말한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 방문하는 노드는 &lt;u&gt;&lt;b&gt;딱 한 번씩만 방문&lt;/b&gt;&lt;/u&gt;하는 것이 특징이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 자료구조 그래프에 대한 내용이 궁금하다면 아래 포스팅을 참고 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://seokyoungg.tistory.com/94&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://seokyoungg.tistory.com/94&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723612985134&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Data Structure] Graph&quot; data-og-description=&quot;&amp;quot;그래프&amp;quot;(Graph)는 각 데이터와 그들을 잇는 선들로 이루어진 ADT이다.&amp;nbsp;각 데이터들을 &amp;quot;정점&amp;quot;(vertex) 혹은 &amp;quot;노드&amp;quot;(node)라 부르며,&amp;nbsp;이들을 잇는 선을 &amp;quot;간선&amp;quot;(edge)라 부른다.&amp;nbsp;즉, 그래프는 유한한 개수의&quot; data-og-host=&quot;seokyoungg.tistory.com&quot; data-og-source-url=&quot;https://seokyoungg.tistory.com/94&quot; data-og-url=&quot;https://seokyoungg.tistory.com/94&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bFxRUr/hyWOkmIp1r/zU0yuoIi5PoHYLgsQ0GlM1/img.png?width=800&amp;amp;height=561&amp;amp;face=0_0_800_561,https://scrap.kakaocdn.net/dn/fzYe3/hyWOlFU7q5/FuyDqCghrnPc784TbA4nc0/img.png?width=800&amp;amp;height=561&amp;amp;face=0_0_800_561&quot;&gt;&lt;a href=&quot;https://seokyoungg.tistory.com/94&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seokyoungg.tistory.com/94&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bFxRUr/hyWOkmIp1r/zU0yuoIi5PoHYLgsQ0GlM1/img.png?width=800&amp;amp;height=561&amp;amp;face=0_0_800_561,https://scrap.kakaocdn.net/dn/fzYe3/hyWOlFU7q5/FuyDqCghrnPc784TbA4nc0/img.png?width=800&amp;amp;height=561&amp;amp;face=0_0_800_561');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Data Structure] Graph&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&quot;그래프&quot;(Graph)는 각 데이터와 그들을 잇는 선들로 이루어진 ADT이다.&amp;nbsp;각 데이터들을 &quot;정점&quot;(vertex) 혹은 &quot;노드&quot;(node)라 부르며,&amp;nbsp;이들을 잇는 선을 &quot;간선&quot;(edge)라 부른다.&amp;nbsp;즉, 그래프는 유한한 개수의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seokyoungg.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 그래프 탐색 알고리즘에는 2가지가 있다.&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;b&gt;BFS&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DFS&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BFS&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;BFS&lt;/b&gt;&quot;는 &lt;b&gt;Breadth-first Search&lt;/b&gt;의 약자로 말 그대로 &lt;u&gt;&lt;b&gt;너비 우선 탐색&lt;/b&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Animated_BFS.gif&quot; data-origin-width=&quot;187&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/85CDW/btsI3QAu6uM/fKKzY5kSV5tQO6gzd1Bqb0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/85CDW/btsI3QAu6uM/fKKzY5kSV5tQO6gzd1Bqb0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/85CDW/btsI3QAu6uM/fKKzY5kSV5tQO6gzd1Bqb0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/85CDW/btsI3QAu6uM/fKKzY5kSV5tQO6gzd1Bqb0/img.gif&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; loading=&quot;lazy&quot; width=&quot;187&quot; height=&quot;175&quot; data-filename=&quot;Animated_BFS.gif&quot; data-origin-width=&quot;187&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BFS는 &lt;u&gt;&lt;b&gt;하나의 정점을 시작으로 인접한 모든 정점들을 우선 방문하는 방법&lt;/b&gt;&lt;/u&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, BFS는 &lt;b&gt;자료구조 Queue를 사용&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 자료구조 Queue에 대한 설명과 구현방법이 궁금하다면, 해당 포스팅을 참고 바란다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://seokyoungg.tistory.com/90&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://seokyoungg.tistory.com/90&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723613651194&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Data Structure] Queue&quot; data-og-description=&quot;&amp;quot;큐&amp;quot;(Queue)는 선형 자료구조로 2가지 Main Operation을 기본적으로 제공하는 ADT이다. enqueue(push) : 원소를 추가한다. dequeue(pop) : 가장 처음에 추가된 원소를 제거한다. 지난 포스팅에서 다루었던 스택은&quot; data-og-host=&quot;seokyoungg.tistory.com&quot; data-og-source-url=&quot;https://seokyoungg.tistory.com/90&quot; data-og-url=&quot;https://seokyoungg.tistory.com/90&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vElIF/hyWOnX2wp9/MVeN6BlLohLKMbwHJ6ty50/img.jpg?width=405&amp;amp;height=265&amp;amp;face=0_0_405_265,https://scrap.kakaocdn.net/dn/dovNwl/hyWOp2D6ib/eAyisJh3D8bLEYHdFmIKR0/img.jpg?width=405&amp;amp;height=265&amp;amp;face=0_0_405_265,https://scrap.kakaocdn.net/dn/dhaoTT/hyWOkAd5gr/Rzktk2Nzes5jaZ1Z0z7X1K/img.png?width=1982&amp;amp;height=588&amp;amp;face=0_0_1982_588&quot;&gt;&lt;a href=&quot;https://seokyoungg.tistory.com/90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seokyoungg.tistory.com/90&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vElIF/hyWOnX2wp9/MVeN6BlLohLKMbwHJ6ty50/img.jpg?width=405&amp;amp;height=265&amp;amp;face=0_0_405_265,https://scrap.kakaocdn.net/dn/dovNwl/hyWOp2D6ib/eAyisJh3D8bLEYHdFmIKR0/img.jpg?width=405&amp;amp;height=265&amp;amp;face=0_0_405_265,https://scrap.kakaocdn.net/dn/dhaoTT/hyWOkAd5gr/Rzktk2Nzes5jaZ1Z0z7X1K/img.png?width=1982&amp;amp;height=588&amp;amp;face=0_0_1982_588');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Data Structure] Queue&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&quot;큐&quot;(Queue)는 선형 자료구조로 2가지 Main Operation을 기본적으로 제공하는 ADT이다. enqueue(push) : 원소를 추가한다. dequeue(pop) : 가장 처음에 추가된 원소를 제거한다. 지난 포스팅에서 다루었던 스택은&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seokyoungg.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래프를 구현하는 방법에는 크게 2가지가 있다.&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;인접 행렬&amp;nbsp;&lt;/li&gt;
&lt;li&gt;인접 리스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 2가지 방식별로 코드와 시간복잡도는 달라지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인접 행렬&lt;/h4&gt;
&lt;pre id=&quot;code_1723617257672&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func bfs&amp;lt;T&amp;gt;(graph: AdjacencyMatrixGraph&amp;lt;T&amp;gt;, startNode: Node&amp;lt;T&amp;gt;) {
	var queue = Queue&amp;lt;Node&amp;lt;T&amp;gt;&amp;gt;()
	var isVis = [Node&amp;lt;T&amp;gt;: Bool]()
	
	isVis[startNode] = true
	queue.enqueue(startNode)
	
	while let current = queue.dequeue() {
		// 모든 vertex와 연결되어 있는지 정보를 확인한다.
		let allVertexs = graph.allVertexs()
		for vertex in allVertexs {
			if !graph.isConnect(from: current, to: vertex) { continue }
			if isVis[vertex] != nil { continue }
			
			isVis[vertex] = true
			queue.enqueue(vertex)
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 앞서 말했듯 Queue자료구조와 방문했는지 여부를 확인하기 위한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;s&gt;isVis&lt;/s&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;배열도 선언한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&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;초기 지점을 Queue에 넣어주고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;s&gt;isVis&lt;/s&gt;를 통해 방문 여부를 체크해 준다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이후, 아래 과정을 Queue가 비어 있을 때까지 반복한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;graph내의 모든 정점들에 대해, 현재 정점과 연결되어 있는지 확인한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;만약 연결되어 있고, 방문 이력이 없다면 방문한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;방문할 땐, 큐에 넣어주고 &lt;s&gt;isVis&lt;/s&gt;를 true로 체크해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인접 리스트&amp;nbsp;&lt;/h4&gt;
&lt;pre id=&quot;code_1723614713679&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func bfs&amp;lt;T&amp;gt;(graph: AdjacencyListGraph&amp;lt;T&amp;gt;, startNode: Node&amp;lt;T&amp;gt;) {
	var queue = Queue&amp;lt;Node&amp;lt;T&amp;gt;&amp;gt;()
	var isVis = [Node&amp;lt;T&amp;gt;: Bool]()
	
	isVis[startNode] = true
	queue.enqueue(startNode)
	
	while let current = queue.dequeue() {
		// 현재 노드를 기준으로 인접 노드만 확인한다.
		for vertex in graph.adjacentVertex(for: current) {
			if isVis[vertex] != nil { continue }
			
			isVis[vertex] = true
			queue.enqueue(vertex)
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;하지만, 모든 노드에 대해 연결된 노드를 직접 찾아야 했다면,&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시간복잡도&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정점의 개수가 $ V$, 간선의 갯수가 $E$라고 가정해 보자.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인접 행렬&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인접행렬의 경우, 현재 노드에 대해 모든 노드와 연결되어 있는지 확인 후 방문을 진행했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, $O(V^2)$의 시간복잡도가 소요된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인접 리스트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인접 리스트의 경우, 현재 노드에 대해 연결된 노드의 개수만큼 확인 후 방문하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 모든 노드를 돌게 되며 ($V$번), 이때 그래프 내의 모든 간선의 개수만큼 확인하게 된다. $E$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 시간복잡도는 $O(V + E)$가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인접 행렬 vs. 인접 리스트&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간선의 수가 적은 Sparse Graph의 경우에는 메모리적인 측면에서도 시간복잡도 측면에서도 인접리스트가 유리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 간선이 많은 그래프(Dense Graph)에선 인접행렬을 사용하는 것이 유리하다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;활용 사례&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최단 거리 문제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 대표적으로 최단 경로 문제를 해결할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 간선의 가중치가 모두 동일해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;가중치가 다른 경우의 최단 거리는 &quot;다익스트라&quot;, &quot;벨만 포드&quot;등등 다른 알고리즘을 통해 해결해야 한다.&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;BFS는 너비 우선 탐색이다. 따라서, 목적지까지 가장 먼저 도착한 경로가 최단 거리가 된다.&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;s&gt;isVis&lt;/s&gt;를 통해 거리를 확인해 주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(백준 문제의 경우, 대부분 BFS문제에서 2차원 배열을 graph로 활용하기 때문에, graph를 2차원 배열로 변경했다.)&lt;/p&gt;
&lt;pre id=&quot;code_1723615760057&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func bfs&amp;lt;T&amp;gt;(graph: [[T]], startPoint: Point, endPoint: Point) -&amp;gt; Int {
	var queue = Queue&amp;lt;Point&amp;gt;()
	var isVis = Array(
		repeating: Array(repeating: 0, count: graph[0].count),
		count: graph.count
	)
	
	isVis[startPoint.x][startPoint.y] = 0
	queue.enqueue(startPoint)
	
	while let current = queue.dequeue() {
		let currentDistance = isVis[current.x][current.y]
		
		if current == endPoint {
			return currentDistance
		}
		
		for direction in (0..&amp;lt;4) {
			let nextX = current.x + disX[direction]
			let nextY = current.y + disY[direction]
			
			if nextX &amp;lt; 0 || nextX &amp;gt;= graph.count || nextY &amp;lt; 0 || nextY &amp;gt;= graph[0].count { continue }
			if isVis[nextX][nextY] != 0 { continue }
			
			isVis[nextX][nextY] = currentDistance + 1
			queue.enqueue(.init(x: nextX, y: nextY))
		}
	}
	
	return -1
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DFS&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;DFS&lt;/b&gt;&quot;는 &lt;b&gt;Depth- First Search&lt;/b&gt;의 약자로 &lt;u&gt;&lt;b&gt;깊이 우선 탐색&lt;/b&gt;&lt;/u&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Depth-First-Search.gif&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mhfi6/btsI5AwWreb/d2CC22VJpcd2p79hO3CpAK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mhfi6/btsI5AwWreb/d2CC22VJpcd2p79hO3CpAK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mhfi6/btsI5AwWreb/d2CC22VJpcd2p79hO3CpAK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/mhfi6/btsI5AwWreb/d2CC22VJpcd2p79hO3CpAK/img.gif&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; loading=&quot;lazy&quot; width=&quot;440&quot; height=&quot;440&quot; data-filename=&quot;Depth-First-Search.gif&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&quot;&lt;b&gt;DFS&lt;/b&gt;&quot;는 &lt;u&gt;&lt;b&gt;하나의 노드를 시작해서 다음 분기로 넘어가기 전에 해당 분기를 완벽하게 탐색하는 방식&lt;/b&gt;&lt;/u&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;BFS와의 탐색 과정 차이를 보게 되면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;rQplhMj5-FQ1XMSZQv2rUNmXxxTAfpt4BgXXhDXWwav0oV_-fxxfNcNpuXUgJXcygNNu4OwdmSZBq9-b6xSOUg.gif&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E2Hbe/btsI4oxyvwJ/uzNkmOKXSWJsgFwxQX7Ej0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E2Hbe/btsI4oxyvwJ/uzNkmOKXSWJsgFwxQX7Ej0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E2Hbe/btsI4oxyvwJ/uzNkmOKXSWJsgFwxQX7Ej0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/E2Hbe/btsI4oxyvwJ/uzNkmOKXSWJsgFwxQX7Ej0/img.gif&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; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;333&quot; data-filename=&quot;rQplhMj5-FQ1XMSZQv2rUNmXxxTAfpt4BgXXhDXWwav0oV_-fxxfNcNpuXUgJXcygNNu4OwdmSZBq9-b6xSOUg.gif&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 DFS에서는 &lt;b&gt;스택&lt;/b&gt; 자료구조를 사용하게 되는데, 스택에 대한 자세한 설명은 아래 링크를 참고 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://seokyoungg.tistory.com/89&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://seokyoungg.tistory.com/89&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1723771115975&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Data Structure] Stack&quot; data-og-description=&quot;&amp;quot;스택&amp;quot;(Stack)은 말 그대로 &amp;quot;무언가를 쌓아 올린 것&amp;quot;을 의미한다.대표적으로 프링글스 통이 있다. 프링글스 통 안에는 과자가 쌓여있고, 최근에 들어간 과자가 가장 먼저 나오는 구조이다.&amp;nbsp;이러한 &quot; data-og-host=&quot;seokyoungg.tistory.com&quot; data-og-source-url=&quot;https://seokyoungg.tistory.com/89&quot; data-og-url=&quot;https://seokyoungg.tistory.com/89&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/WYgtE/hyWOgSHzZy/WShcqRrqhKbOUxf1S3S6Hk/img.png?width=525&amp;amp;height=367&amp;amp;face=0_0_525_367,https://scrap.kakaocdn.net/dn/dE3XmG/hyWOjWcA7w/tkq5j1LW0aVsKNKOkOgYXK/img.png?width=525&amp;amp;height=367&amp;amp;face=0_0_525_367,https://scrap.kakaocdn.net/dn/b6vNVv/hyWOgd5VMG/nIiMO0lJBpqJSV28QR5fKk/img.png?width=525&amp;amp;height=367&amp;amp;face=0_0_525_367&quot;&gt;&lt;a href=&quot;https://seokyoungg.tistory.com/89&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seokyoungg.tistory.com/89&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/WYgtE/hyWOgSHzZy/WShcqRrqhKbOUxf1S3S6Hk/img.png?width=525&amp;amp;height=367&amp;amp;face=0_0_525_367,https://scrap.kakaocdn.net/dn/dE3XmG/hyWOjWcA7w/tkq5j1LW0aVsKNKOkOgYXK/img.png?width=525&amp;amp;height=367&amp;amp;face=0_0_525_367,https://scrap.kakaocdn.net/dn/b6vNVv/hyWOgd5VMG/nIiMO0lJBpqJSV28QR5fKk/img.png?width=525&amp;amp;height=367&amp;amp;face=0_0_525_367');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Data Structure] Stack&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&quot;스택&quot;(Stack)은 말 그대로 &quot;무언가를 쌓아 올린 것&quot;을 의미한다.대표적으로 프링글스 통이 있다. 프링글스 통 안에는 과자가 쌓여있고, 최근에 들어간 과자가 가장 먼저 나오는 구조이다.&amp;nbsp;이러한&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seokyoungg.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;이러한 DFS는 &lt;b&gt;재귀&lt;/b&gt;로도 해결할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;사실, 재귀함수의 호출 형태가 내부적으로는 Stack자료구조에 해당 함수의 Stack Frame을 쌓는 구조이기 때문에, 유사하다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BFS와 마찬가지로 인접 행렬, 인접리스트 둘 다 활용이 가능하다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인접 행렬&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, Stack의 방식이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723772217451&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func dfsStack&amp;lt;T&amp;gt;(graph: AdjacencyMatrixGraph&amp;lt;T&amp;gt;, startNode: Node&amp;lt;T&amp;gt;) {
	var stack = Stack&amp;lt;Node&amp;lt;T&amp;gt;&amp;gt;()
	var isVis = [Node&amp;lt;T&amp;gt;: Bool]()
	
	while let current = stack.pop() {
		let allVertexs = graph.allVertexs()
		for vertex in allVertexs {
			if !graph.isConnect(from: current, to: vertex) { continue }
			if isVis[vertex] != nil { continue }
			
			isVis[vertex] = true
			stack.push(vertex)
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보게 되면, BFS와 유사하다. 다만, 차이점은 Stack과 Queue의 자료구조의 차이점에 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 재귀호출의 방식이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723772605742&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func dfsReculsive&amp;lt;T&amp;gt;(
	graph: AdjacencyMatrixGraph&amp;lt;T&amp;gt;,
	currentNode: Node&amp;lt;T&amp;gt;,
	isVis: [Node&amp;lt;T&amp;gt;: Bool]
) {
	guard isVis[currentNode] == nil else { return }
	var isVis = isVis
	isVis[currentNode] = true
	
	for vertex in graph.allVertexs() {
		if !graph.isConnect(from: currentNode, to: vertex) { continue }

		dfsReculsive(graph: graph, currentNode: vertex, isVis: isVis )
	}
	isVis[currentNode] = false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀 호출 방식도 상당히 유사하다. 다만 Stack 내부에서 구현했던 코드를 붙여 넣은 게 전부이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;인접 리스트&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, stack의 구현방식이다.&lt;/p&gt;
&lt;pre id=&quot;code_1723773095343&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func dfsStack&amp;lt;T&amp;gt;(graph: AdjacencyListGraph&amp;lt;T&amp;gt;, startNode: Node&amp;lt;T&amp;gt;) {
	var stack = Stack&amp;lt;Node&amp;lt;T&amp;gt;&amp;gt;()
	var isVis = [Node&amp;lt;T&amp;gt;: Bool]()
	
	while let current = stack.pop() {
		for vertex in graph.adjacentVertex(for: current) {
			if isVis[vertex] != nil { continue }
			
			isVis[vertex] = true
			stack.push(vertex)
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 BFS의 방식가 유사하나 사용한 자료구조가 Queue가 아닌 Stack이라는 차이점이 있다.&amp;nbsp;&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;pre id=&quot;code_1723773329244&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func dfsReculsive&amp;lt;T&amp;gt;(
	graph: AdjacencyListGraph&amp;lt;T&amp;gt;,
	currentNode: Node&amp;lt;T&amp;gt;,
	isVis: [Node&amp;lt;T&amp;gt;: Bool]
) {
	guard isVis[currentNode] == nil else { return }
	var isVis = isVis
	
	isVis[currentNode] = true
	
	for vertex in graph.adjacentVertex(for: currentNode) {
		dfsReculsive(graph: graph, currentNode: vertex, isVis: isVis )

	}
	
	isVis[currentNode] = false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시간복잡도&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS 역시 마찬가지로 시간복잡도가 동일하다.&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;b&gt;인접 행렬&lt;/b&gt;: $O(V^2)$&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인접 리스트&lt;/b&gt;: $O(V + E)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, DFS를 재귀로 구현하는 경우, Address Space의 Stack 메모리가 다 차게 되는 Stack OverFlow가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;활용 사례&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BFS와 마찬가지로 최단거리 탐색에 사용될 수 있지만, BFS와 다른 점은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BFS에선 최초로 목적지에 도달한 지점이 최단거리임을 보장했지만, DFS에선 보장하지 않는다.&amp;nbsp;&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;따라서, 최단 거리 문제에선 BFS가 대부분 유리하다.&amp;nbsp;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;References&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위키백과&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Breadth-first_search&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Breadth-first search&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Depth-first_search&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Depth-first search&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS/Algorithm</category>
      <category>Algorithm</category>
      <category>BFS</category>
      <category>DFS</category>
      <author>seok-young</author>
      <guid isPermaLink="true">https://seokyoungg.tistory.com/108</guid>
      <comments>https://seokyoungg.tistory.com/108#entry108comment</comments>
      <pubDate>Fri, 16 Aug 2024 11:31:05 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] 정렬 알고리즘(2) - 머지 소트, 퀵 소트, 기수 정렬</title>
      <link>https://seokyoungg.tistory.com/107</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 포스팅에서 살펴본 정렬 알고리즘들은 평균, 최악의 경우 $O(n^2)$의 시간복잡도를 가지게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;병합 정렬 (Merage Sort)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;병합 정렬&lt;/b&gt;&quot;(또는 합병 정렬)은 문제를 분할하고, 문제를 정복하여 합치는 &lt;u&gt;&lt;b&gt;분할정복을 기반으로 한 정렬 알고리즘&lt;/b&gt;&lt;/u&gt;이다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정렬 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 분할정복은 &quot;&lt;b&gt;하향식&lt;/b&gt;&quot;과 &quot;&lt;b&gt;상향식&lt;/b&gt;&quot;구현으로 구분할 수 있는데,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;병합 정렬에선&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;&lt;b&gt;하향식 구현방식을 대부분 사용&lt;/b&gt;&lt;/u&gt;한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하향식(TopDown)방식으로 구현한 병합 정렬의 과정은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(아래 그림은 &lt;span style=&quot;text-align: center;&quot;&gt;정확한 Top-Down 방식은 아니지만, 분할되고 정복되는 순서만 차이 있을 뿐, 방법은 동일하다.&lt;/span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;123.gif&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCNkro/btsIvkBe2VE/wygEmTksUDbZBygTTtzEGk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCNkro/btsIvkBe2VE/wygEmTksUDbZBygTTtzEGk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCNkro/btsIvkBe2VE/wygEmTksUDbZBygTTtzEGk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bCNkro/btsIvkBe2VE/wygEmTksUDbZBygTTtzEGk/img.gif&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; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;210&quot; data-filename=&quot;123.gif&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리스트를 절반으로 나누어 두 부분 리스트로 나눈다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이를 부분 리스트의 길이가 1이하가 될 때까지 재귀적으로 위의 과정을 반복한다. (길이가 1인 부분리스트는 정렬되었다고 볼 수 있다.)&lt;/li&gt;
&lt;li&gt;이후, 정렬된 두 부분리스트를 합칠 때, &quot;임시 저장공간&quot;에 정렬된 결과를 저장한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이후, 원본 리스트에 정렬 결과를 복사한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시간 복잡도 증명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;분할 정복&quot;의 시간복잡도는 최선, 최악, 평균의 경우 모두 $O(n \cdot logn)$을 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 $O(n \cdot logn)$알아보기 위해, 각 분할 과정과 정복 과정을 따로따로 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;분할 과정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력의 갯수가 $n$이라고 가정해 보자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNLdHM/btsItH5MIMY/VTkNBFAtHofcJuTMQlt8vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNLdHM/btsItH5MIMY/VTkNBFAtHofcJuTMQlt8vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNLdHM/btsItH5MIMY/VTkNBFAtHofcJuTMQlt8vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNLdHM%2FbtsItH5MIMY%2FVTkNBFAtHofcJuTMQlt8vk%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;1398&quot; height=&quot;692&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;/figure&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;각 총 $k$라 가정하고 Depth별로 분할 횟수를 살펴보면, 위에서부터 순차적으로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$1, 2, 4, 8, ..., 2^k$회씩 분할한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, Depth는 $log(n)$ 이므로 $k = log(n)$이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 이때 지수 로그 공식에 따라 $2^{logn} = n$으로 대체가 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 최종적으로 $1, 2, 4, 8, ..., n$회씩 분할한다.&amp;nbsp;&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;이때, 각 분할작업에 들어가는 시간복잡도는 입력에 비례하지 않기에 상수시간이라 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, $T(n) = c(1 + 2 + 4 + .. + n) = c(2n - 1) = O(n)$이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정복 과정&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebL8RD/btsIuuSb9xv/B8o2iG2dauKo8ItO0efNKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebL8RD/btsIuuSb9xv/B8o2iG2dauKo8ItO0efNKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebL8RD/btsIuuSb9xv/B8o2iG2dauKo8ItO0efNKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebL8RD%2FbtsIuuSb9xv%2FB8o2iG2dauKo8ItO0efNKk%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;1398&quot; height=&quot;776&quot; data-origin-width=&quot;1398&quot; data-origin-height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Depth마다 모든 원소를 비교하면서 병합을 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했듯, Depth는 총 $log(n)$이며, 각 Depth마다 총 $n$개의 원소를 비교하는 작업을 진행한다.&amp;nbsp;&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;따라서, $O(n \cdot logn)$이다.&amp;nbsp;&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;이때, 분할 과정과 정복과정은 독립적으로 수행되기 때문에, 둘의 시간복잡도는 곱이 아닌 합연산이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$O(n \cdot logn + n)$이게 되고, 점근적 표기에서 최고차항 외에는 생략하므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 병합 정렬은 $O(n \cdot logn)$의 시간복잡도를 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;수식적으로 증명&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$T(n)$을 n개의 원소를 분할 정복할 때의 시간복잡도라고 가정해 보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 앞서 말했듯 분할 정복 기법이기 때문에 2개의 부분 리스트로 분할하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합칠 때는 총 $n$개의 원소를 비교하기 때문에, $n$의 시간복잡도가 든다.&amp;nbsp;&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;따라서,$T(n) = 2 \cdot T(n/2) + n$ 으로 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$T(n) = 2(2 \cdot T(n/4) + n/2) + n = 4 \cdot T(n/4) + 2n$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$T(n) = n \cdot T(1) + n \cdot logn$&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;$T(1) = 1$이므로, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;$T(n) = n +&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;n \cdot logn$ 이 완성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, $g(n) = n \cdot logn$이라 가정할 때, $n_0 \geq 10$이고 $c =2$일 때,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$n \geq n_0$인 모든 자연수에 대하여 $g(n) \geq T(n)$을 만족한다.&amp;nbsp;&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;점근적 상한의 정의에 의해 $T(n) = O(n \cdot logn)$가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&amp;nbsp;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제자리 정렬 X&lt;/li&gt;
&lt;li&gt;안정 정렬&lt;/li&gt;
&lt;li&gt;시간복잡도가 $O(n \cdot logn)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병합 정렬은 앞서 살펴봤다시피, 병합하는 과정에서 새로운 메모리 공간에 병합을 하고, 이를 원본 메모리에 복사한다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현&lt;/h3&gt;
&lt;pre id=&quot;code_1720675211674&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension Array where Element: Comparable {
	func mergeSort(
		sortedBy: (Element, Element) -&amp;gt; Bool = { $0 &amp;lt; $1 }
	) -&amp;gt; [Element] {
		return mergeSort(self, st: 0, en: self.count, sortedBy: sortedBy)
	}
	
	private func mergeSort(
		_ array: [Element],
		st: Int,
		en: Int,
		sortedBy: (Element, Element) -&amp;gt; Bool
	) -&amp;gt; [Element] {
		guard st &amp;lt; en - 1 else { return array }

		let mid = (st + en) / 2
		var array = array
		
		array = mergeSort(array, st: st, en: mid, sortedBy: sortedBy)
		array = mergeSort(array, st: mid, en: en, sortedBy: sortedBy)
		
		return merge(array, st: st, en: en, sortedBy: sortedBy)
	}
	
	private func merge(
		_ array: [Element],
		st: Int,
		en: Int,
		sortedBy: (Element, Element) -&amp;gt; Bool
	) -&amp;gt; [Element] {
		var array = array
		
		let mid = (st + en) / 2
		var index1 = st
		var index2 = mid
		
		var temp = [Element]()
		
		for _ in st..&amp;lt;en {
			if index1 == mid {
				temp.append(array[index2])
				index2 += 1
			} else if index2 == en {
				temp.append(array[index1])
				index1 += 1
			} else if sortedBy(array[index1], array[index2]) {
				temp.append(array[index1])
				index1 += 1
			} else {
				temp.append(array[index2])
				index2 += 1
			}
		}
		
		for i in 0..&amp;lt;temp.count {
			array[i + st] = temp[i]
		}
		
		return array
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;퀵 정렬&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;퀵 정렬&lt;/b&gt;&quot;은 &lt;u&gt;&lt;b&gt;제자리 정렬 알고리즘&lt;/b&gt;&lt;/u&gt;으로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;분할 정복을 기반&lt;/b&gt;&lt;/u&gt;으로 수행되며 &lt;b&gt;매우 빠른 속도&lt;/b&gt;를 자랑한다.&amp;nbsp;&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;분할 정복을 기반으로 한다는 점에서 병합 정렬과 유사해 보이지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀵정렬은 &lt;u&gt;&lt;b&gt;제자리 정렬 알고리즘&lt;/b&gt;&lt;/u&gt;이란 차이점이 있다.&amp;nbsp;&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;a href=&quot;https://seokyoungg.tistory.com/106#1.2.%20%EC%A0%9C%EC%9E%90%EB%A6%AC%20%EC%A0%95%EB%A0%AC%EC%9D%B8%EC%A7%80%20%EC%95%84%EB%8B%8C%EC%A7%80%C2%A0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 포스팅&lt;/a&gt;에서 말했지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;제자리 정렬&lt;/b&gt;&quot;은 추가적인 메모리 공간의 할당을 필요로 하지 않기 때문에,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공간 지역성(Spatical Locality)&lt;/b&gt;에 Cache Hit비율이 높아, 좋은 성능을 자랑한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정렬 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;퀵 정렬&quot;은 &lt;u&gt;&lt;b&gt;분할 정복을 통해 리스트를 정렬&lt;/b&gt;&lt;/u&gt;한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Quicksort-example.gif&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgUntn/btsIwUbmcbO/IoIRFPkGWl42PK6oZi3I8K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgUntn/btsIwUbmcbO/IoIRFPkGWl42PK6oZi3I8K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgUntn/btsIwUbmcbO/IoIRFPkGWl42PK6oZi3I8K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bgUntn/btsIwUbmcbO/IoIRFPkGWl42PK6oZi3I8K/img.gif&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; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;180&quot; data-filename=&quot;Quicksort-example.gif&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리스트에서 하나의 원소를 선택한다. 이 원소를 &quot;&lt;b&gt;피벗&lt;/b&gt;&quot;이라 부른다.&lt;/li&gt;
&lt;li&gt;현재 리스트를 순회하면서, 피벗보다 작은 원소는 피벗 앞으로, 피벗보다 큰 원소는 피벗 뒤로 위치시킨다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;위의 과정으로 &quot;피벗의 위치&quot;가 결정 나므로, 피벗을 기준으로 리스트를 둘로 &lt;b&gt;분할&lt;/b&gt;하여 위의 과정을 반복한다.&lt;/li&gt;
&lt;li&gt;해당 과정을 재귀적으로 반복하여, 리스트의 크기가 0 혹은 1이 될 때까지 반복한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 재귀 호출이 이뤄질 때마다, 피벗의 위치가 결정 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 최소한 하나의 원소의 위치가 결정 나게 되기 때문에, 재귀 호출은 반드시 끝난다는 것을 보증할 수 있으며, 최종적으로는 정렬된다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시간 복잡도 증명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀵소트에서 $n$개의 원소를 정렬한다고 가정해 보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택된 피벗의 위치가 $m$번째 원소라고 가정했을 때, (단, $m$은 n 이하의 자연수)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬과정을 수행 후, $m-1$과 $n-m$개의 원소로 분할되어 재귀적으로 반복한다.&amp;nbsp;&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;이때, 피벗에 대하여 피벗을 비교하는 연산과 Swap 하는 연산이 이루어지는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 연산을 $C(n)$이라 가정했을 때, $\Theta(n)$이라 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(둘은 독립적으로 수행되며 모두 n에 비례하기 때문에 가능하다.)&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;p data-ke-size=&quot;size16&quot;&gt;$T(n) = T(m-1) + T(n-m) + \Theta(n)$ $\leq T(m-1) + T(n-m) + c \cdot n$&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;$c \geq 3$일때 위의 식은 성립한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최악의 경우 (Worst Case)&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최악의 경우에는 피봇이 맨 앞의 원소 혹은 맨 뒤의 원소를 선택할 경우이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 경우에는 균등하게 분할되지 않아 $O(n^2)$의 시간복잡도를 갖게 된다.&amp;nbsp;&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;즉, 위의 식에서 $m = 1$이거나$m = n$인 경우이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;24&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMDMjG/btsIwPhgXtu/UmSODGahxYhnzqJH9QIrik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMDMjG/btsIwPhgXtu/UmSODGahxYhnzqJH9QIrik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMDMjG/btsIwPhgXtu/UmSODGahxYhnzqJH9QIrik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMDMjG%2FbtsIwPhgXtu%2FUmSODGahxYhnzqJH9QIrik%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;24&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;24&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, $T(0) = 0$ 이므로, 다음과 같이 표기할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;25&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn1CZu/btsIv5ZfInk/81TFq3BxDgDXwivjsiKKkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn1CZu/btsIv5ZfInk/81TFq3BxDgDXwivjsiKKkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn1CZu/btsIv5ZfInk/81TFq3BxDgDXwivjsiKKkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn1CZu%2FbtsIv5ZfInk%2F81TFq3BxDgDXwivjsiKKkk%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;25&quot; data-filename=&quot;edited_edited_blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;25&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이때, 이를 반복적으로 해보게 되면,&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSFxPJ/btsIwr17Vvj/GKONJc9tITvR9v5MxrU3i1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSFxPJ/btsIwr17Vvj/GKONJc9tITvR9v5MxrU3i1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSFxPJ/btsIwr17Vvj/GKONJc9tITvR9v5MxrU3i1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSFxPJ%2FbtsIwr17Vvj%2FGKONJc9tITvR9v5MxrU3i1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;123&quot; data-filename=&quot;edited_edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;측 $T(n) \leq c\cdot n^2$가 성립하므로&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$T(n) = O(n^2)$이다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최선의 경우 (Best Case)&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최선의 경우에는 피봇이 항상 균등하게 분할하는 경우이기에, 다음과 같은 식이 성립한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;29&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zcT9C/btsIxfTU2Fw/oOr159CfyIBaOoAUDwTCy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zcT9C/btsIxfTU2Fw/oOr159CfyIBaOoAUDwTCy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zcT9C/btsIxfTU2Fw/oOr159CfyIBaOoAUDwTCy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzcT9C%2FbtsIxfTU2Fw%2FoOr159CfyIBaOoAUDwTCy1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;29&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;29&quot;/&gt;&lt;/span&gt;&lt;/figure&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;이때, $n/2$에 대해서 진행해보면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;26&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byfkmp/btsIxKe2Npn/NrXK37H6JR4mSyylSWw3tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byfkmp/btsIxKe2Npn/NrXK37H6JR4mSyylSWw3tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byfkmp/btsIxKe2Npn/NrXK37H6JR4mSyylSWw3tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbyfkmp%2FbtsIxKe2Npn%2FNrXK37H6JR4mSyylSWw3tk%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;26&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;26&quot;/&gt;&lt;/span&gt;&lt;/figure&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;p data-ke-size=&quot;size16&quot;&gt;이를 통해 위의 수식을 일반화하게 되면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GKaBT/btsIwVOUCZQ/69nOLzoIpRkmnsCGHgOIAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GKaBT/btsIwVOUCZQ/69nOLzoIpRkmnsCGHgOIAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GKaBT/btsIwVOUCZQ/69nOLzoIpRkmnsCGHgOIAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGKaBT%2FbtsIwVOUCZQ%2F69nOLzoIpRkmnsCGHgOIAK%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;102&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이때 $k = logn$이므로, 지수 로그 법칙에 의해 $2^k = n$이 성립한다. 따라서 위의 식은 최종적으로 다음과 같이 성립한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;20&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k9wsf/btsIwW1k3n9/E1iCiPnEeykvOfBMd4VlWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k9wsf/btsIwW1k3n9/E1iCiPnEeykvOfBMd4VlWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k9wsf/btsIwW1k3n9/E1iCiPnEeykvOfBMd4VlWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk9wsf%2FbtsIwW1k3n9%2FE1iCiPnEeykvOfBMd4VlWk%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;20&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;20&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 최종적으로 $T(n) = O(n \cdot logn)$이 성립하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;평균의 경우 (Average Case)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, &lt;b&gt;&lt;u&gt;피봇을 랜덤하게 선택&lt;/u&gt;&lt;/b&gt;한다고, 가정했을 때, 각 원소들이 피봇이 될 확률을 $\frac{1}{n}$이다.&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;29&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd6XE0/btsIvP3CHGI/AEFBguAvudjVjXa3zaAVX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd6XE0/btsIvP3CHGI/AEFBguAvudjVjXa3zaAVX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd6XE0/btsIvP3CHGI/AEFBguAvudjVjXa3zaAVX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd6XE0%2FbtsIvP3CHGI%2FAEFBguAvudjVjXa3zaAVX0%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;29&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;29&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, $\sum_{m=1}^{n}T(m-1) = \sum_{m=1}^{n}T(n-m)$이기 때문에 아래와 같이 표현할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;31&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc1H5w/btsIx2tvvG9/pf02nJeqVm0EJkuXP4Vb51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc1H5w/btsIx2tvvG9/pf02nJeqVm0EJkuXP4Vb51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc1H5w/btsIx2tvvG9/pf02nJeqVm0EJkuXP4Vb51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc1H5w%2FbtsIx2tvvG9%2Fpf02nJeqVm0EJkuXP4Vb51%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;31&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;31&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 양변에 $n$을 곱해주게 되면 다음과 같은 수식을 얻게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;($n$은 자연수이기 때문에 부등호의 방향은 바뀌지 않는다)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_edited_edited_blob&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;27&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHAEFX/btsIxiKMoil/NmMuxNKB7SgeoyhV9Lh2F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHAEFX/btsIxiKMoil/NmMuxNKB7SgeoyhV9Lh2F0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHAEFX/btsIxiKMoil/NmMuxNKB7SgeoyhV9Lh2F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHAEFX%2FbtsIxiKMoil%2FNmMuxNKB7SgeoyhV9Lh2F0%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;27&quot; data-filename=&quot;edited_edited_edited_edited_blob&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;27&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 $n = n-1$을 대입해 보게 되면, 다음과 같은 식을 얻게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;33&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X8mxM/btsIwRM2gLd/pRtmQe92dGcz7zviSvYncK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X8mxM/btsIwRM2gLd/pRtmQe92dGcz7zviSvYncK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X8mxM/btsIwRM2gLd/pRtmQe92dGcz7zviSvYncK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX8mxM%2FbtsIwRM2gLd%2FpRtmQe92dGcz7zviSvYncK%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;33&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;33&quot;/&gt;&lt;/span&gt;&lt;/figure&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;이때 (A)-(B)를 하게 되면, 다음과 같은 식을 얻게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjfAcH/btsIwWOrWuI/K4135e65bOijxymY0UHOw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjfAcH/btsIwWOrWuI/K4135e65bOijxymY0UHOw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjfAcH/btsIwWOrWuI/K4135e65bOijxymY0UHOw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjfAcH%2FbtsIwWOrWuI%2FK4135e65bOijxymY0UHOw1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;1896&quot; height=&quot;166&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양 변의 항들을 다음게 되면 최종적으로 다음과 같은 식을 얻게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;23&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9th35/btsIv5ZRuAB/6dcUUKKU54fd7yw7RD72vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9th35/btsIv5ZRuAB/6dcUUKKU54fd7yw7RD72vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9th35/btsIv5ZRuAB/6dcUUKKU54fd7yw7RD72vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9th35%2FbtsIv5ZRuAB%2F6dcUUKKU54fd7yw7RD72vk%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;23&quot; data-filename=&quot;edited_edited_edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;23&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 항상 $T(n) \geq T(n-1)$이 성립하기 때문에, 다음과 같이 식을 변경할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_스크린샷 2024-07-12 오후 2.27.45.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;24&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p71ud/btsIybxmOJ1/AfkkOoBnfrCJNN5zt9Tft0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p71ud/btsIybxmOJ1/AfkkOoBnfrCJNN5zt9Tft0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p71ud/btsIybxmOJ1/AfkkOoBnfrCJNN5zt9Tft0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp71ud%2FbtsIybxmOJ1%2FAfkkOoBnfrCJNN5zt9Tft0%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;24&quot; data-filename=&quot;edited_edited_스크린샷 2024-07-12 오후 2.27.45.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;24&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 최종적으로 다음과 같은 식을 얻게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_edited_edited_스크린샷 2024-07-12 오후 2.55.34.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;23&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOXWv4/btsIyKsopIc/yx19nyKblwOnM4nW3xj0mK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOXWv4/btsIyKsopIc/yx19nyKblwOnM4nW3xj0mK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOXWv4/btsIyKsopIc/yx19nyKblwOnM4nW3xj0mK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOXWv4%2FbtsIyKsopIc%2Fyx19nyKblwOnM4nW3xj0mK%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;23&quot; data-filename=&quot;edited_edited_edited_edited_스크린샷 2024-07-12 오후 2.55.34.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;23&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 때, 양 변을 $frac{1}{n(n+1)}$로 나누게 되면 다음과 같은 식을 얻는다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_edited_edited_blob&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEHPqr/btsIyw2jEDx/ptIzqTbWcCmNFI9oNUJ5Ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEHPqr/btsIyw2jEDx/ptIzqTbWcCmNFI9oNUJ5Ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEHPqr/btsIyw2jEDx/ptIzqTbWcCmNFI9oNUJ5Ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEHPqr%2FbtsIyw2jEDx%2FptIzqTbWcCmNFI9oNUJ5Ek%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;470&quot; height=&quot;125&quot; data-filename=&quot;edited_edited_edited_edited_blob&quot; data-origin-width=&quot;470&quot; data-origin-height=&quot;125&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 식에서 $n = n-1$을 대입하게 되면 다음과 같은 식을 얻게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DbssW/btsIwpqdy76/bXmepwLdwyaqHSBB9PaSwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DbssW/btsIwpqdy76/bXmepwLdwyaqHSBB9PaSwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DbssW/btsIwpqdy76/bXmepwLdwyaqHSBB9PaSwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDbssW%2FbtsIwpqdy76%2FbXmepwLdwyaqHSBB9PaSwK%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;50&quot; data-filename=&quot;edited_edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당식의 결과를 위의 식의 $\frac{T(n-1)}{n}$ 대입하게 되면 다음과 같은 결과를 갖는다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R7bRc/btsIyuQYMbq/ZhkrkTO9ftv9abJVzuUDfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R7bRc/btsIyuQYMbq/ZhkrkTO9ftv9abJVzuUDfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R7bRc/btsIyuQYMbq/ZhkrkTO9ftv9abJVzuUDfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR7bRc%2FbtsIyuQYMbq%2FZhkrkTO9ftv9abJVzuUDfK%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;127&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;127&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 대입하던 과정을 $T(n-2), T(n-3), ... T(1)$까지 반복하게 되면 최종적으로 다음과 같은 식을 얻게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_edited_edited_edited_blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AZ2P0/btsIx4kYxz0/7wZGgQgdghttgQP0zL30Ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AZ2P0/btsIx4kYxz0/7wZGgQgdghttgQP0zL30Ik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AZ2P0/btsIx4kYxz0/7wZGgQgdghttgQP0zL30Ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAZ2P0%2FbtsIx4kYxz0%2F7wZGgQgdghttgQP0zL30Ik%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;52&quot; data-filename=&quot;edited_edited_edited_edited_edited_blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;52&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_edited_edited_edited_스크린샷 2024-07-12 오후 3.10.23.png&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;53&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WjaX0/btsIytxK4Cw/5hE97llOFeGiZdG64EjKVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WjaX0/btsIytxK4Cw/5hE97llOFeGiZdG64EjKVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WjaX0/btsIytxK4Cw/5hE97llOFeGiZdG64EjKVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWjaX0%2FbtsIytxK4Cw%2F5hE97llOFeGiZdG64EjKVk%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;650&quot; height=&quot;53&quot; data-filename=&quot;edited_edited_edited_edited_edited_스크린샷 2024-07-12 오후 3.10.23.png&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;53&quot;/&gt;&lt;/span&gt;&lt;/figure&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;이를 정리해 보게 되면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_edited_edited_edited_blob&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/THiDO/btsIwQ8IYSF/iKElqHNFc0xdNdptPd3D3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/THiDO/btsIwQ8IYSF/iKElqHNFc0xdNdptPd3D3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/THiDO/btsIwQ8IYSF/iKElqHNFc0xdNdptPd3D3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTHiDO%2FbtsIwQ8IYSF%2FiKElqHNFc0xdNdptPd3D3K%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;252&quot; data-filename=&quot;edited_edited_edited_edited_edited_blob&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 다음과 같은 식이 성립한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGX972/btsIwwbVAkm/qw7q4Z6yJwrWHD5NSsXFB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGX972/btsIwwbVAkm/qw7q4Z6yJwrWHD5NSsXFB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGX972/btsIwwbVAkm/qw7q4Z6yJwrWHD5NSsXFB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGX972%2FbtsIwwbVAkm%2Fqw7q4Z6yJwrWHD5NSsXFB1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;94&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 최종적으로 다음과 같은 식을 도출해낼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c28Hrm/btsIwZdyTKt/pnZ5HNtVeCuHPV7FkyzX1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c28Hrm/btsIwZdyTKt/pnZ5HNtVeCuHPV7FkyzX1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c28Hrm/btsIwZdyTKt/pnZ5HNtVeCuHPV7FkyzX1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc28Hrm%2FbtsIwZdyTKt%2FpnZ5HNtVeCuHPV7FkyzX1k%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;61&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;61&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 양변을 다시 $n+1$로 곱하게 되면, 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;33&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4k4EI/btsIwXtjUbC/K0gYAqxtb8Pubwfp5ePYbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4k4EI/btsIwXtjUbC/K0gYAqxtb8Pubwfp5ePYbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4k4EI/btsIwXtjUbC/K0gYAqxtb8Pubwfp5ePYbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4k4EI%2FbtsIwXtjUbC%2FK0gYAqxtb8Pubwfp5ePYbK%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;33&quot; data-filename=&quot;edited_edited_blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;33&quot;/&gt;&lt;/span&gt;&lt;/figure&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;마지막으로, 빅-오 점근 표기법 공식에 따라 $T(n) = O(n \cdot log_2n)$의 시간복잡도를 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;최악의 경우 $O(n^2)$, 최선, 평균의 경우는 $O(n \cdot logn)$의 시간복잡도를 갖는다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;제자리 정렬이기에 속도적으로 이점이 있다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;불안정 정렬에 속한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&amp;nbsp;&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;또한, 3개의 피벗 후보를 선택해 그중, 중앙값을 선택하는 방법을 사용하기도 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 1대 99 비율로 계속 쪼개지더라고 시간 복잡도를 $O(n \cdot logn)$이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현&lt;/h3&gt;
&lt;pre id=&quot;code_1721011043107&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MARK: - QuickSort
extension Array where Element: Comparable {
	enum PivotType {
		case random
		case first
		case middle // 랜덤으로 선택된 3개의 값들 중 중앙값
	}
	
	func quickSort(
		sortedBy: (Element, Element) -&amp;gt; Bool
	) -&amp;gt; [Element] {
		guard
			self.count &amp;gt; 1,
			let pivot = self.pivot(.random)
		else { return self }
		
		let left = self.filter { sortedBy($0, pivot) }
		var right = self.filter { !sortedBy($0, pivot) }
		if let firstIndex = right.firstIndex(of: pivot) {
			right.remove(at: firstIndex)
		}
		
		
		let sortedLeft = left.quickSort(sortedBy: sortedBy)
		let sortedRight = right.quickSort(sortedBy: sortedBy)
		
		return sortedLeft + [pivot] + sortedRight
	}
	
	private func pivot(_ type: PivotType) -&amp;gt; Element? {
		switch type {
			case .first:
				return self.first
				
			case .random:
				return self.randomElement()
				
			case .middle:
				if let randoms = self.randomElements(count: 3) {
					return randoms.sorted()[1]
				} else {
					return self.first
				}
		}
	}
	
	private func randomElements(count: Int) -&amp;gt; [Element]? {
		guard self.count &amp;gt;= count else { return nil }
		
		var flags = Array&amp;lt;Bool&amp;gt;(repeating: false, count: self.count)
		var numbers = [Element]()
		
		while numbers.count != count {
			let randomIndex = Int.random(in: 0..&amp;lt;self.count)
			if !flags[randomIndex] {
				flags[randomIndex] = true
				numbers.append(self[randomIndex])
			}
		}
		
		return numbers
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기수 정렬&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &quot;&lt;b&gt;기수 정렬&lt;/b&gt;&quot;은 &lt;u&gt;&lt;b&gt;안정정렬&lt;/b&gt;&lt;/u&gt;로,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;우선 정렬 로직은 다음과 같다.&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;1의 자릿수부터 시작해 각 자릿수에 숫자에 해당하는 Bucket에 숫자를 넣는다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이후, Bucket에서 순차적으로 뺸 다면, 1의 자릿수의 정렬이 완료된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;해당 과정을 10의 자리, 100의 자리 가장 높은 차수까지 진행한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실행 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 배열이 있다고 가정해 보자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;38&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YHJ7D/btsI0SdYHr1/MNKhZ3Prh8DAIQ7hDDNop1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YHJ7D/btsI0SdYHr1/MNKhZ3Prh8DAIQ7hDDNop1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YHJ7D/btsI0SdYHr1/MNKhZ3Prh8DAIQ7hDDNop1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYHJ7D%2FbtsI0SdYHr1%2FMNKhZ3Prh8DAIQ7hDDNop1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;38&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;38&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서, 자릿수를 기준으로 정렬한다 했기에 총 0~9까지 10개의 bucket이 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0Jbcx/btsI0K74I2E/vsqaIjojcLPd3oQ0Ho1Pc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0Jbcx/btsI0K74I2E/vsqaIjojcLPd3oQ0Ho1Pc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0Jbcx/btsI0K74I2E/vsqaIjojcLPd3oQ0Ho1Pc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0Jbcx%2FbtsI0K74I2E%2FvsqaIjojcLPd3oQ0Ho1Pc1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;116&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 1의 자릿수별로 다음과 같이 buket에 담는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnGcjp/btsI1Frt0s5/jVkGvD0KZrKbcNfg9y9TV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnGcjp/btsI1Frt0s5/jVkGvD0KZrKbcNfg9y9TV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnGcjp/btsI1Frt0s5/jVkGvD0KZrKbcNfg9y9TV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnGcjp%2FbtsI1Frt0s5%2FjVkGvD0KZrKbcNfg9y9TV0%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;116&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, Bucket에 담기는 숫자는 순서대로 진행한다. 따라서, 안정 정렬이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;38&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1O77l/btsI1Wme60P/H0W3L1U3wd7TBzaUcC8Gm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1O77l/btsI1Wme60P/H0W3L1U3wd7TBzaUcC8Gm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1O77l/btsI1Wme60P/H0W3L1U3wd7TBzaUcC8Gm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1O77l%2FbtsI1Wme60P%2FH0W3L1U3wd7TBzaUcC8Gm1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;38&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;38&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, 이를 순서대로 Bucket에서 빼내면서 1의 자릿수 정렬을 마친다.&amp;nbsp;&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;다음으로 10 자릿수 정렬을 시작한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRiFXQ/btsI2u38FWl/kom4adLJ2kzO1IbaSd2kT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRiFXQ/btsI2u38FWl/kom4adLJ2kzO1IbaSd2kT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRiFXQ/btsI2u38FWl/kom4adLJ2kzO1IbaSd2kT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRiFXQ%2FbtsI2u38FWl%2Fkom4adLJ2kzO1IbaSd2kT1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;116&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 마찬가지로, Bucket에서 빼내면서 10의 자릿수 정렬을 마친다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;38&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tRkPt/btsI0GFbWI1/bvL0cN19cdgNHLpjs6tYa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tRkPt/btsI0GFbWI1/bvL0cN19cdgNHLpjs6tYa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tRkPt/btsI0GFbWI1/bvL0cN19cdgNHLpjs6tYa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtRkPt%2FbtsI0GFbWI1%2FbvL0cN19cdgNHLpjs6tYa1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;38&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;38&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 100의 자릿수를 기준으로 정렬을 시작한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqZMfb/btsI2e8hoGD/PGY7ieJVpMFpL29s65nZxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqZMfb/btsI2e8hoGD/PGY7ieJVpMFpL29s65nZxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqZMfb/btsI2e8hoGD/PGY7ieJVpMFpL29s65nZxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqZMfb%2FbtsI2e8hoGD%2FPGY7ieJVpMFpL29s65nZxk%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;358&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&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;위와 같은 과정을 진행하게 되면 정렬이 완료되게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 가장 큰 특징은 시간복잡도가 $O(w\cdot n)$이라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(n개의 숫자 중 가장 높은 차수를 w라고 가정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, $w$가 작다면, 시간복잡도 측면에선 어떠한 알고리즘보다 빠르다.&amp;nbsp;&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;하지만, 2141125....909등과 같이 차수가 매우 높은 숫자가 있다면, 해당 알고리즘의 시간복잡도는 $O(n^2)$에 수렴할 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현&lt;/h3&gt;
&lt;pre id=&quot;code_1723446571146&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MARK: - Radix Sort
extension Array where Element: BinaryInteger {
	func radixSort(
		sortedByAscending: Bool = true
	) -&amp;gt; [Element] {
		let radix = 10
		var done = false
		var index: Element = 1
		
		var returnArray = self
		
		while !done {
			done = true
			var buckets: [[Element]] = .init(repeating: [], count: radix)
			
			returnArray.forEach {
				let remainPart = $0 / index
				let digit = remainPart % Element(radix)
				let digitIndex = Int(digit)
				
				buckets[digitIndex].append($0)
				
				remainPart &amp;gt; 0 ? done = false : ()
			}

			sortedByAscending ? (returnArray = buckets.flatMap { $0 }) : (returnArray = buckets.reversed().flatMap { $0 })
			
			index *= Element(radix)
		}
		
		return returnArray
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;References&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위키백과&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Merge_sort&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Merge sort&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Quicksort&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Quick sort&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Radix_sort&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Radix sort&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS/Algorithm</category>
      <category>기수정렬</category>
      <category>머지소트</category>
      <category>알고리즘</category>
      <category>퀵소트</category>
      <author>seok-young</author>
      <guid isPermaLink="true">https://seokyoungg.tistory.com/107</guid>
      <comments>https://seokyoungg.tistory.com/107#entry107comment</comments>
      <pubDate>Mon, 12 Aug 2024 16:12:01 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] 정렬 알고리즘(1) - 버블정렬, 선택정렬, 삽입정렬</title>
      <link>https://seokyoungg.tistory.com/106</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;정렬 알고리즘&lt;/b&gt;&quot;은 &lt;u&gt;&lt;b&gt;일련의 원소들에 대해 정해진 규칙에 맞게 재배열하는 알고리즘&lt;/b&gt;&lt;/u&gt;이다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정렬 알고리즘 분류 기준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬 알고리즘은 여러 기준으로 분류된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;안정 정렬 vs. 불안정 정렬&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;안정 정렬&lt;/b&gt;&quot;이란, &lt;u&gt;&lt;b&gt;정렬조건이 동일한 원소&lt;/b&gt;&lt;/u&gt;가 여러개 있을 때, &lt;u&gt;&lt;b&gt;입력 때와 동일한 순서로 정렬&lt;/b&gt;&lt;/u&gt;되는 정렬 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, &quot;&lt;b&gt;불안정 정렬&lt;/b&gt;&quot; 입력 때와 동일한 순서로 졍렬되는 것을 보장하지 않는다.&amp;nbsp;&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;&quot;안정 정렬&quot;은 정렬조건이 동일한 원소들의 순서를 보장해야할 때, 유용하게 사용될 수 있다.&amp;nbsp;&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;u&gt;등급순&lt;/u&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제자리 정렬인지 아닌지&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;제자리 정렬(In-place)&lt;/b&gt;&quot; &lt;u&gt;&lt;b&gt;추가적인 자료구조를 사용하지 않고 정렬이 가능&lt;/b&gt;&lt;/u&gt;한 정렬 알고리즘이다. 다만, 추가적인 변수를 위해 약간의 추가 공간은 허용한다.&amp;nbsp;&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;제자리 정렬은 다음과 같은 장점을 갖는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, &lt;u&gt;&lt;b&gt;추가적인 메모리 공간을 필요로하지 않는다&lt;/b&gt;&lt;/u&gt;는 점에서 메모리 측면으로 이득이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 거의 모든 정렬 알고리즘은 배열을 자료구조로 사용하며, 배열은 메모리 공간에 연속적으로 할당되기 때문에, Cache hit 비율이 높다. 이러한 하나의 배열내에서 정렬알고리즘을 수행할 수 있다면, &lt;u&gt;&lt;b&gt;공간 지역성에 의해 상대적으로 좋은 성능&lt;/b&gt;&lt;/u&gt;을 가지게 될 것이다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;버블 정렬&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;버블 정렬&lt;/b&gt;&quot;은 &lt;u&gt;&lt;b&gt;제자리 정렬&lt;/b&gt;&lt;/u&gt;로,&amp;nbsp;&amp;nbsp;&lt;u&gt;&lt;b&gt;두 원소의 대소관계를 비교하여 위치를 변경&lt;/b&gt;&lt;/u&gt;을 반복하는 정렬 알고리즘이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 인접한 원소와의 대소관계를 비교하기 때문에, &lt;u&gt;&lt;b&gt;안정정렬&lt;/b&gt;&lt;/u&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;자세한 절차는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720507883097&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension Array where Element: Comparable {
	func bubbleSort(
		sortedBy: (Element, Element) -&amp;gt; Bool = { $0 &amp;lt; $1 }
	) -&amp;gt; [Element] {
		var sortedArray = self
		
		for i in (0..&amp;lt;self.count) {
			var isChanged = false
			
			for j in (0..&amp;lt;self.count-i-1) {
				if !sortedBy(sortedArray[j], sortedArray[j + 1]) {
					sortedArray.swapAt(j, j + 1)
					isChanged = true
				}
			}
			
			if !isChanged { break }
		}
		
		return sortedArray
	}
}

print([2, 5, 3, 1].bubbleSort()) // [1, 2, 3, 5]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1번째 원소부터 시작하여, 오른쪽의 원소와 대소관계를 비교한 후 필요하면 위치를 변경한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;해당 과정을 n번째 원소까지 완료한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;한번의 순회가 완료되면, 1번과정을 1부터 n-1번째 원소까지 반복한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이를 배열에 아무 변화가 없을때까지 반복한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 볼 수 있겠지만, 2중 포문을 돌기 때문에 최악, 평균의 경우 모두 $O(n^2)$이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 모든 원소가 정렬되어 있다면 for문이 바로 종료되기 때문에, 최선의 경우 $O(n)$이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;선택 정렬&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;선택 정렬&lt;/b&gt;&quot;은 &lt;u&gt;&lt;b&gt;제자리 정렬&lt;/b&gt;&lt;/u&gt;로, 다음과 같이 수행한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배열에서 제일 작은 원소를 탐색한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;그 값을 맨앞에 위치한 값과 swap한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;맨앞의 위치의 원소를 제외하고 위의 내용을 반복한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 매 시행마다 앞자리가 하나씩 결정난다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720587602590&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension Array where Element: Comparable {
	func selectionSort(
		sortedBy: (Element, Element) -&amp;gt; Bool = { $0 &amp;lt; $1 }
	) -&amp;gt; [Element] {
		var sortedArray = self
		
		for i in 0..&amp;lt;self.count {
			
			var minIndex = i
			for j in i..&amp;lt;self.count {
				sortedBy(sortedArray[i], sortedArray[j]) ? () : (minIndex = j)
			}
			
			sortedArray.swapAt(i, minIndex)
		}
		
		return sortedArray
	}
}

print([2,3,1,4,7].selectionSort(sortedBy: &amp;lt;)) // [1, 2, 3, 4, 7]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 볼 수 있겠지만, 선택 정렬은 최선, 최악, 평균의 경우 모두 $O(n^2)$이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빈번하게 swap연산이 있었던 버블정렬과 비교해보자면, 매 시행마다 딱 한번 swap연산이 일어나기 때문에, &lt;b&gt;일반적으로 버블정렬보다 성능이 좋다&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;.&amp;nbsp;&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;b&gt;선택 정렬은 불안정정렬&lt;/b&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;span style=&quot;color: #666666;&quot;&gt;(이때, 2와 2.은 같은 원소이고, 둘을 구분하기 위해 &quot;!&quot;을 붙였다.)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720588379809&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;2 2! 1 3&lt;/code&gt;&lt;/pre&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과 2의 위치가 변경되게 되면, 정렬은 종료된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720588564348&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1 2! 2 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 정렬전에는 2 2! 이었지만, 정렬후에는 2! 2로 순서가 변경되었다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;삽입 정렬&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;삽입 정렬&lt;/b&gt;&quot;은 &lt;u&gt;&lt;b&gt;제자리 정렬&lt;/b&gt;&lt;/u&gt;로,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 &lt;u&gt;&lt;b&gt;모든 요소를 앞에서부터 차례대로 이미 정렬된 부분과 비&lt;/b&gt;&lt;b&gt;교&lt;/b&gt;&lt;/u&gt;하여, 자신의 위치를 찾아 삽입하는 정렬알고리즘이다.&amp;nbsp;&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;즉, 다음과 같다.&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;2번째 원소부터 시작해, 1번째 원소와 비교해 삽입될 위치를 결정한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;3번째 원소를 1, 2번째와 비교해 삽입될 위치를 결정한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;이를 마지막 원소까지 진행하면된다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1720589309635&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extension Array where Element: Comparable {
	func insertionSort(
		sortedBy: (Element, Element) -&amp;gt; Bool = { $0 &amp;lt; $1 }
	) -&amp;gt; [Element] {
		guard self.count &amp;gt; 1 else { return self }
		var sortedArray = self
		
		for i in 1..&amp;lt;self.count {
			let key = sortedArray[i]
			
			var j = i-1
			// key값보다, 뒤로 밀어야 한다면 뒤로 밀고 남는 공간에 key를 삽입
			while j &amp;gt;= 0 &amp;amp;&amp;amp; sortedBy(key, sortedArray[j]) {
				sortedArray[j+1] = sortedArray[j]
				j -= 1
			}
			sortedArray[j+1] = key
		}
		
		return sortedArray
	}
}

print([2,3,1,4,7].insertionSort(sortedBy: &amp;lt;)) // [1, 2, 3, 4, 7]&lt;/code&gt;&lt;/pre&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;u&gt;&lt;b&gt;안정 정렬&lt;/b&gt;&lt;/u&gt;이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다 정렬되어 있는 경우(최선의 경우) $O(n)$이고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균, 최악의 경우 $O(n^2)$이다.&amp;nbsp;&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;또한, 버블 정렬과 선택 정렬과 비교했을 때 가장 성능이 좋은 정렬 알고리즘이다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;References&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;a href=&quot;https://en.wikipedia.org/wiki/Sorting_algorithm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Sorting Algorithm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Bubble_sort&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Bubble Sort&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Selection_sort&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Selection Sort&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Insertion_sort&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Insertion Sort&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS/Algorithm</category>
      <category>버블정렬</category>
      <category>삽입정렬</category>
      <category>선택정렬</category>
      <category>알고리즘</category>
      <author>seok-young</author>
      <guid isPermaLink="true">https://seokyoungg.tistory.com/106</guid>
      <comments>https://seokyoungg.tistory.com/106#entry106comment</comments>
      <pubDate>Wed, 10 Jul 2024 17:25:06 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] 알고리즘이란?</title>
      <link>https://seokyoungg.tistory.com/105</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;수학 혹은 컴퓨터 과학(Computer Science)에서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;알고리즘&lt;/b&gt;&lt;/span&gt;&quot;이란 &lt;u&gt;&lt;b&gt;어떠한 문제를 풀기 위한&lt;/b&gt;&lt;/u&gt; &quot;&lt;u&gt;&lt;b&gt;유한한 갯수의 일련의 시퀀스&lt;/b&gt;&lt;/u&gt;&quot;이다.&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;이때, 알고리즘은 컴퓨터 프로그램으로 대부분 구현되나, 전기 회로 혹은 기계 등등 다른 수단으로도 구현될 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;특성&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 알고리즘을 &quot;&lt;u&gt;&lt;b&gt;유한한 갯수의 일련의 시퀀스&lt;/b&gt;&lt;/u&gt;&quot;라고 정의했지만, 여기서 몇가지 조건이 더 붙는다.&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;b&gt;입력&lt;/b&gt; : 잘 정의된 입력들을 받아들일 수 있어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;출력&lt;/b&gt; : 정확성을 추론할 수 있는 출력이 있어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명확성&lt;/b&gt; : 각 단계는 명확하게 정의되어야 한다. 각 단계에서 수행할 작업을 정확하게 지정할 수 있어야 한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유한성&lt;/b&gt; : 유한한 수의 명령어의 시퀀스가 수행되면 반드시 종료되어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유효성&lt;/b&gt; : 모든 명령어는 실행 가능해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;알고리즘 평가&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;유한한 갯수의 시퀀스&quot;가 몇달 혹은 몇년이 걸린다면 &quot;효율적&quot;으로 문제를 풀었다고 할 수 없다. 즉, &lt;b&gt;&lt;u&gt;알고리즘은 효율성을 고려할 필요&lt;/u&gt;&lt;/b&gt;가 있으며, 이를 &quot;&lt;b&gt;복잡도&lt;/b&gt;&quot;를 통해 평가할 수 있다.&amp;nbsp;&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;&quot;&lt;b&gt;복잡도&lt;/b&gt;&quot;는 &lt;u&gt;&lt;b&gt;시간&lt;/b&gt;&lt;/u&gt;과 &lt;u&gt;&lt;b&gt;공간&lt;/b&gt;&lt;/u&gt;을 얼마나 소모하는지를 중점으로 보는데, 이를 각각&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;u&gt;&lt;b&gt;시간 복잡도&lt;/b&gt;&lt;/u&gt;, &lt;u&gt;&lt;b&gt;공간 복잡도&lt;/b&gt;&lt;/u&gt;라 부른다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시간 복잡도&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시간 복잡도&lt;/b&gt;는 &quot;&lt;u&gt;&lt;b&gt;알고리즘의 소요시간을 측정&lt;/b&gt;&lt;/u&gt;&quot;하는데 사용된다.&amp;nbsp;&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;만약 다음과 같은 알고리즘 있다고 가정해보자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1719488073139&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int func(int arr[], int n) {
	int cnt = 0;
	
	for(int i = 0; i &amp;lt; n; i++) {
		if(arr[i] % 5 == 0) cnt ++;
	}
	
	return cnt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 알고리즘의 시간을 측정하기에는 너무나 많은 변수가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터마다 하드웨어 혹은 OS차이로 인한 실행시간의 차이가 있을 수가 있으며,&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, &quot;&lt;b&gt;시간 복잡도&lt;/b&gt;&quot;는 문제를 해결하는데 걸리는 시간을&amp;nbsp;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;u&gt;&lt;b&gt;입력에 대한 연산횟수&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;를 통해 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 명령어를 본다면, 시간 복잡도는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1720340306601&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int cnt = 0; // 지역 변수 초기화: 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, &lt;s&gt;cnt&lt;/s&gt; 변수의 값을 0으로 선언하고 값을 넣는 과정에서 1번의 연산이 소요된다.&amp;nbsp;&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;다음으로 for문은 수행하기 전에 &lt;s&gt;i&lt;/s&gt; 변수를 초기화하는 연산을 1회 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720340876985&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// i 초기화 연산: 1
for(int i = 0; i &amp;lt; n; i++) {
	// 매 iteration 시작 전 i &amp;lt; n 확인: 1
	
	// arr[i]가 5의 배수인지 비교: 2
	// cnt 증감 연산: 1
	if(arr[i] % 5 == 0) cnt ++;
	// 한번의 iteration이 끝난 후 i 증가: 1
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, for문은 총 n번 반복하게 되는데, 다음과 같은 연산들을 반복한다.&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;매 iteration 시작 전 &lt;s&gt;i &amp;lt; n&lt;/s&gt; 을 만족하는 지 확인하는 연산 1회&lt;/li&gt;
&lt;li&gt;&lt;s&gt;arr[i]&lt;/s&gt;를&amp;nbsp;5로 나누는 연산 1회&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;arr[i]&lt;/s&gt;를 5로 나누는 결과를 0과 비교하는 연산 1회&lt;/li&gt;
&lt;li&gt;&lt;s&gt;arr[i]&lt;/s&gt;가 5의 배수라면 &lt;s&gt;cnt&lt;/s&gt;를 증가시키는 연산 1회&lt;/li&gt;
&lt;li&gt;매 iteration 완료 후, &lt;s&gt;i++&lt;/s&gt; 하는 연산 1회&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 매 iteration마다 5회의 연산이 진행되며, iteration은 n번 반복하기 때문에 5n + 1번의 연산이 수행된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720340897114&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return cnt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 return 문제에서 1회 연산이 수행된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 연산은 n개의 입력에 대해 총 5n + 3번의 연산이 수행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공간 복잡도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공간 복잡도&lt;/b&gt;는 &quot;&lt;u&gt;&lt;b&gt;알고리즘이 사용하는 메모리 양을 측정&lt;/b&gt;&lt;/u&gt;&quot;하는데 사용된다.&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;다음과 같은 문자열을 예시로 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1720405987669&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var str = &quot;12345&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8bit의 ASCII를 사용하는 언어에서는 해당 문자열을 널문자를 포함해 6byte로 특정할 수 있지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swift와 같이 Unicode를 사용하는 언어에서는 UTF8이냐 UTF16이냐 등등 하나의 문자의 길이가 가변적이다.&amp;nbsp;&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;즉, 언어마다 OS마다 변수하나를 저장하는데 사용하는 메모리양이 다를 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 시간복잡도와 마찬가지로 &quot;&lt;u&gt;&lt;b&gt;입력에 비례한 메모리 양&lt;/b&gt;&lt;/u&gt;&quot;을 측정한다.&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;대다수의 경우 시간복잡도와 공간복잡도가 반비례한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거와 달리, 현재 메모리값이 상당히 저렴하기 때문에 시간복잡도를 많은 경우에 우선적으로 고려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;점근 표기법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 살펴본 시간복잡도와 공간복잡도는 &quot;&lt;u&gt;&lt;b&gt;입력에 비레하여 측정&lt;/b&gt;&lt;/u&gt;&quot;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 모든 알고리즘마다 매번 이렇게 입력에 비례한 메모리양 혹은 연산횟수를 직접 세기에는 쉽지않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 이러한 &lt;u&gt;&lt;b&gt;시간복잡도와 공간복잡도를 표현할때는 점근 표기법을 사용&lt;/b&gt;&lt;/u&gt;한다.&amp;nbsp;&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;&quot;&lt;b&gt;점근 표기법&lt;/b&gt;&quot;은 &lt;b&gt;&lt;u&gt;어떤 함수의 증가 양상을 다른 함수와의 비교로 표현하는 방법&lt;/u&gt;&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확히는 입력 n에 대해서 n을 무한대로 보냈을 때, 수렴하게 되는 새로운 함수로 표기하는 방법이다.&amp;nbsp;&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;앞선 예시에서 5n + 3은 n을 무한대로 보내게 되면 n에 수렴하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 쉽게 말해 &lt;u&gt;&lt;b&gt;가장 영항력 있는 항으로 표기하는 방법&lt;/b&gt;&lt;/u&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;이러한 점근 표기법에는 여러가지가 있지만, 알고리즘에서는 크게 3가지를 사용한다.&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;b&gt;빅-오 표기법&lt;/b&gt;: 점근적 상한&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빅-오메가 표기법&lt;/b&gt;: 점근적 하한&lt;/li&gt;
&lt;li&gt;&lt;b&gt;빅-세타 표기법&lt;/b&gt;: 점근적 평균&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;빅-오(Big-O) 표기법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;빅-오 표기법&quot;은 &lt;b&gt;점근적 상한&lt;/b&gt;에 대한 정의이며, 수학적인 정의는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;$n_0 \leq n$인 모든 자연수 $n$에 대하여, $f(n) \leq c \cdot g(n)$를 만족하는 양의 상수 $c$와 $n_0$가 존재하면, $f(n) = O(g(n))$ 이다.&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;예시를 통해 살펴보자. $f(n) = 2n^4 + 3n^3 + n + 1$ 라고 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, $g(n) = n^4$이라면, $c \geq 3$일때, 기울기가 더 가파르기 때문에, 위의 조건을 만족하는 자연수 $n_0$은 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, $O(n^4)$으로 표기할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게도 $O(n^5)$, $O(n^6)$, $O(2^n)$ 등등 가능하지만, 점근적 상한을 너무 높게 잡아버리게 되면 알고리즘의 성능을 파악하기 힘들기 때문에 가능한 가장 작은 점근적 상한을 잡는것이 좋다.&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;b&gt; &lt;/b&gt;&lt;u&gt;&lt;b&gt;적어도 해당 복잡도 안에 든다는 것&lt;/b&gt;&lt;/u&gt;을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;빅-오메가(Big-$\Omega$)표기법&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;빅-오메가 표기법&quot;은 &lt;b&gt;점근적 하한&lt;/b&gt;에 대한 정의이며, 수학적인 정의는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;$n_0 \leq n$인 모든 자연수 $n$에 대하여, $f(n) \geq c \cdot g(n)$를 만족하는 양의 상수 $c$와 $n_0$가 존재하면, $f(n) = \Omega(g(n))$ 이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 예시의 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;$f(n) = 2n^4 + 3n^3 + n + 1$를 다시 한번 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여기서, $g(n) = n^4$이라면, $c \leq 2$일때, 위의 조건을 만족하는 자연수 $n_0$는 존재한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;따라서, $\Omega(n^4)$으로 표기할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;마찬가지로 $\Omega(n^3)$, $\Omega(n)$, $\Omega(1)$ 등등 가능하지만, 점근적 하한을 너무 낮게 잡으면 성능을 파악하기 어려워진다.&amp;nbsp;&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;span style=&quot;text-align: left;&quot;&gt;쉽게 말해, &lt;u&gt;&lt;b&gt;적어도 해당 복잡도보다는 크다는 것&lt;/b&gt;&lt;/u&gt;을 의미한다.&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;빅-세타(Big-$\Theta$)표기법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&quot;빅-세타 표기법&quot;은 &lt;b&gt;점근적 평균&lt;/b&gt;에 대한 정의이며, 수학적인 정의는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;$n_0 \leq n$인 모든 자연수 $n$에 대하여, $c_1 \cdot g(n) \leq f(n) \leq c_2 \cdot g(n)$를 만족하는 양의 상수 $c_1$, $c_2$와 $n_0$가 존재하면, $f(n) = \Theta(g(n))$ 이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;마찬가지로 $f(n) = 2n^4 + 3n^3 + n + 1$를 예시로 들어보자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여기서, $g(n) = n^4$이라면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;$c_1 \leq 2$이고, $c_2 \geq 3$일때, 위의 조건을 만족하는 자연수 $n_0$는 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;따라서, $\Theta(n^4)$으로 표기할 수 있다. &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;span style=&quot;text-align: left;&quot;&gt;&lt;u&gt;&lt;b&gt;평균적으로 해당 복잡도 안에 든다는 것&lt;/b&gt;&lt;/u&gt;을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;빅-오, 빅-오메가, 빅-세타는 최악, 최선, 평균을 의미하지 않는다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;수많은 곳에서 빅-오는 최악의 경우, 빅-오메가는 최선의 경우, 빅-세타는 평균의 경우라고 정의하는 경우가 많다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;하지만, &lt;u&gt;&lt;b&gt;이러한 해석은 틀린 해석&lt;/b&gt;&lt;/u&gt;이다.&amp;nbsp;&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;pre id=&quot;code_1720404484808&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func index(of target: Int, in array: [Int]) -&amp;gt; Int {
	for idx in (0..&amp;lt;array.count) {
		if arr[idx] == target { return idx }
	}
	
	return -1
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;앞서, 점근적 상한인&amp;nbsp;&lt;/span&gt;&lt;b&gt;빅-오&lt;/b&gt;는 &quot;&lt;u&gt;&lt;b&gt;적어도 이 복잡도 안에 든다는 것&lt;/b&gt;&lt;/u&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&quot;을 의미한다고 했지만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;u&gt;&lt;b&gt;이때의 상한은 최악의 경우를 의미하지 않는다&lt;/b&gt;&lt;/u&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;text-align: left;&quot;&gt;만약 &lt;s&gt;target&lt;/s&gt;이 1번째 인덱스에 존재하는 경우, 해당 함수는 1번만에 for문이 진행될것이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;하지만, 최악의 경우 맨끝 인덱스에 존재하는 경우, &lt;s&gt;array&lt;/s&gt;의 원소 갯수 만큼 for문을 돌게 될것이다.&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;span style=&quot;text-align: left;&quot;&gt;시간복잡도를 예시살펴보자면, 다음과 같이 최선, 최악의 경우 각기 다른 시간복잡도를 가지게 될 것이다. &lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;즉, &lt;u&gt;&lt;b&gt;복잡도를 논하기 위해서는 최선, 최악, 평균에 대한 시나리오&lt;/b&gt;&lt;/u&gt;부터 고려해야 한다.&amp;nbsp;&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;b&gt;빅-오&lt;/b&gt; 표기법은 &quot;&lt;u&gt;&lt;b&gt;최선, 최악 혹은 평균의 경우 적어도 이 복잡도 안에 든다는 것&lt;/b&gt;&lt;/u&gt;&quot;을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 위의 알고리즘은 최선의 경우 $O(1)$이고, 최악, 평균은 $O(n)$에 동작한다고 말할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 예시로 퀵소트 알고리즘의 경우, 최선, 평균의 경우 $O(n logn)$이며, 최악의 경우 $O(n^2)$으로 동작한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 최선과 평균적인 입력에 대하여 적어도 $n logn$안에 동작하며, 최악의 경우에는 적어도 $n^2$안에 든다는 의미이지,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 $\Omega(n logn)$, $\Theta(n logn)$, $O(n^2)$으로 표기하지는 않는 이유이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위키백과&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Algorithm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Algorithm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Time_complexity&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Time Complexity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Space_complexity&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Space Complexity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS/Algorithm</category>
      <category>Algorithm</category>
      <category>공간복잡도</category>
      <category>시간복잡도</category>
      <author>seok-young</author>
      <guid isPermaLink="true">https://seokyoungg.tistory.com/105</guid>
      <comments>https://seokyoungg.tistory.com/105#entry105comment</comments>
      <pubDate>Mon, 8 Jul 2024 13:00:18 +0900</pubDate>
    </item>
    <item>
      <title>[PS] BOJ 2457 (공주님의 정원)</title>
      <link>https://seokyoungg.tistory.com/104</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-23 오후 8.55.01.png&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0Y2Z0/btsH9YE1WrJ/Hk7J7SzQApFwuDZ5Ojrl11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0Y2Z0/btsH9YE1WrJ/Hk7J7SzQApFwuDZ5Ojrl11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0Y2Z0/btsH9YE1WrJ/Hk7J7SzQApFwuDZ5Ojrl11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0Y2Z0%2FbtsH9YE1WrJ%2FHk7J7SzQApFwuDZ5Ojrl11%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;1033&quot; height=&quot;780&quot; data-filename=&quot;스크린샷 2024-06-23 오후 8.55.01.png&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;780&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제부터 살펴보면 3월 1일부터 11월 30일까지 꽃이 지지 않게 하는 것이 핵심이다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1번째 접근&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 가장 먼저 생각나는 것은 &lt;u&gt;&lt;b&gt;특정 날짜 이전에 피는 꽃 중 가장 늦게 피는 꽃&lt;/b&gt;&lt;/u&gt;을 매 시행시 구하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 매 시행 시 $N$개의 꽃을 살펴보게 되기 때문에, 시작 복잡도는 $O(N^2)$이 된다. 해당 문제에서 $N$은 최대 100,000이기 때문에, $O(N^2)$은 시간 초과가 발생하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2번째 접근&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 시행시 &lt;u&gt;&lt;b&gt;특정 날짜 이전에 피는 꽃 중 가장 늦게 피는 꽃&lt;/b&gt;&lt;/u&gt;을 상수 혹은 로그 시간에 구할 수만 있게 된다면, 시간초과가 발생하지 않고 문제를 해결할 수 있게 된다.&amp;nbsp;&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;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;&lt;u&gt;꽃이 지는 날짜&lt;/u&gt;는 다음 시행에서 &lt;u&gt;선택할 꽃의 피는 날짜&lt;/u&gt;의 마지노선&lt;/b&gt;이기 때문에, 꽃이 피는 날짜를 기준으로 정렬을 해준다.&amp;nbsp;&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;u&gt;&lt;b&gt;꽃이 지는 날짜&lt;/b&gt;&lt;/u&gt;를 기준으로 정렬하는 우선순위 큐에 선택할 수 있는 꽃을 삽입한 후,&amp;nbsp;&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;우선순위 큐에서 원소의 삽입, 삭제는 $O(logN)$이므로, 총 시간 복잡도는 $O(NlogN)$이 된다.&amp;nbsp;&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;이를 코드로 옮겨보자면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;풀이&amp;nbsp;&lt;/h3&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;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://seokyoungg.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://seokyoungg.tistory.com/91&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719147962947&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Data Structure] Priority Queue&quot; data-og-description=&quot;&amp;quot;우선순위 큐&amp;quot;(Priority Queue)는 다음과 같은 2개의 Main Operation을 명세하는 ADT이다.enqueue(push): 원소를 추가한다.dequeue(pop): 우선순위가 높은 원소를 제거한다.&amp;nbsp;&amp;nbsp;지난 포스팅에서 다루었던 &amp;quot;큐&amp;quot;(Queue)&quot; data-og-host=&quot;seokyoungg.tistory.com&quot; data-og-source-url=&quot;https://seokyoungg.tistory.com/91&quot; data-og-url=&quot;https://seokyoungg.tistory.com/91&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kzhiu/hyWrR5n7Di/QaWjvBh6hATw29oghWKdvK/img.png?width=800&amp;amp;height=399&amp;amp;face=0_0_800_399,https://scrap.kakaocdn.net/dn/iUvVY/hyWoK1a54N/wCqlOVw76vHMbT0KK9VKKk/img.png?width=800&amp;amp;height=399&amp;amp;face=0_0_800_399,https://scrap.kakaocdn.net/dn/Nddto/hyWoMkl3Qc/ZsVEDTfZe7nL67bLPKbfY0/img.png?width=4870&amp;amp;height=1452&amp;amp;face=0_0_4870_1452&quot;&gt;&lt;a href=&quot;https://seokyoungg.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://seokyoungg.tistory.com/91&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kzhiu/hyWrR5n7Di/QaWjvBh6hATw29oghWKdvK/img.png?width=800&amp;amp;height=399&amp;amp;face=0_0_800_399,https://scrap.kakaocdn.net/dn/iUvVY/hyWoK1a54N/wCqlOVw76vHMbT0KK9VKKk/img.png?width=800&amp;amp;height=399&amp;amp;face=0_0_800_399,https://scrap.kakaocdn.net/dn/Nddto/hyWoMkl3Qc/ZsVEDTfZe7nL67bLPKbfY0/img.png?width=4870&amp;amp;height=1452&amp;amp;face=0_0_4870_1452');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Data Structure] Priority Queue&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&quot;우선순위 큐&quot;(Priority Queue)는 다음과 같은 2개의 Main Operation을 명세하는 ADT이다.enqueue(push): 원소를 추가한다.dequeue(pop): 우선순위가 높은 원소를 제거한다.&amp;nbsp;&amp;nbsp;지난 포스팅에서 다루었던 &quot;큐&quot;(Queue)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;seokyoungg.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1719147987019&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 시작날짜 기준으로 정렬
let sortedFlowers = flowers.sorted {
	$0.stDate &amp;lt; $1.stDate
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 시작날짜 기준으로 정렬해 준 뒤,&lt;/p&gt;
&lt;pre id=&quot;code_1719148126597&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while currentEnDate &amp;lt; targetEnDate {
	// 현재의 꽃이 지는날보다, 먼저 피는 꽃들을 우선순위 큐에 삽입한다.
	while
		flowerIndex &amp;lt; sortedFlowers.count &amp;amp;&amp;amp;
		sortedFlowers[flowerIndex].stDate &amp;lt;= currentEnDate {
		
		priorityQueue.push(sortedFlowers[flowerIndex])
		flowerIndex += 1
	}

	// 이후, 가장 늦게 지는 꽃을 선택한다.
	if let date = priorityQueue.pop() {
		currentEnDate = date.enDate
		result += 1
	} else {
		break
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 시행마다, 선택 가능한 꽃들을 우선순위 큐에 삽입한 뒤, 가장 늦게 지는 꽃을 선택하면 된다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 코드&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Swift&lt;/h3&gt;
&lt;pre id=&quot;code_1719148207292&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation

struct Heap&amp;lt;T: Comparable&amp;gt; {
	enum HeapType {
		case min, max
	}
	
	private var nodes = [T]()
	private let sort: (T, T) -&amp;gt; Bool
	
	var isEmpty: Bool { nodes.isEmpty }
	var count: Int { nodes.count }
	var top: T? {
		guard !isEmpty else { return nil }
		return nodes[0]
	}
	
	init(type: HeapType) {
		switch type {
			case .min:
				sort = { $0 &amp;lt; $1 }
			case .max:
				sort = { $0 &amp;gt; $1 }
		}
	}
	
	mutating func insert(_ element: T) {
		nodes.append(element)
		shiftUp()
	}
	
	mutating func pop() -&amp;gt; T? {
		guard !isEmpty else { return nil }
		
		defer {
			nodes.swapAt(0, nodes.count - 1)
			nodes.removeLast()
			shiftDown()
		}
		
		return nodes[0]
	}
}

private extension Heap {
	mutating func shiftUp() {
		var now = nodes.count - 1
		var parent = getParent(of: now)
		
		while now &amp;gt; 0 &amp;amp;&amp;amp; sort(nodes[now], nodes[parent]) {
			nodes.swapAt(now, parent)
			now = parent
			parent = getParent(of: now)
		}
	}
	
	mutating func shiftDown() {
		var now = 0
		
		while true {
			let (left, right) = getChilds(of: now)
			var candidate = now
			
			if left &amp;lt; nodes.count &amp;amp;&amp;amp; sort(nodes[left], nodes[now]) {
				candidate = left
			}
			if right &amp;lt; nodes.count &amp;amp;&amp;amp; sort(nodes[right], nodes[now]) &amp;amp;&amp;amp; sort(nodes[right], nodes[left]) {
				candidate = right
			}
			
			if candidate == now { return }
			nodes.swapAt(now, candidate)
			now = candidate
		}
	}
	
	func getParent(of node: Int) -&amp;gt; Int {
		return (node - 1) / 2
	}
	
	func getChilds(of node: Int) -&amp;gt; (left: Int, right: Int) {
		return (node * 2 + 1, node * 2 + 2)
	}
}

struct PriorityQueue&amp;lt;T: Comparable&amp;gt; {
	private var heap: Heap&amp;lt;T&amp;gt;
	
	var isEmpty: Bool { heap.isEmpty }
	var count: Int { heap.count }
	var top: T? { heap.top }
	
	init(type: Heap&amp;lt;T&amp;gt;.HeapType) {
		self.heap = Heap(type: type)
	}
	
	mutating func push(_ element: T) {
		heap.insert(element)
	}
	
	@discardableResult
	mutating func pop() -&amp;gt; T? {
		heap.pop()
	}
}

struct FlowerDate: Comparable {
	let month: Int
	let day: Int
	
	static func &amp;lt; (lhs: FlowerDate, rhs: FlowerDate) -&amp;gt; Bool {
		if lhs.month == rhs.month {
			return lhs.day &amp;lt; rhs.day
		} else {
			return lhs.month &amp;lt; rhs.month
		}
	}
}

struct Flower: Comparable {
	let stDate: FlowerDate
	let enDate: FlowerDate
	
	init(_ stMonth: Int, _ stDay: Int, _ enMonth: Int, _ enDay: Int) {
		self.stDate = FlowerDate(month: stMonth, day: stDay)
		self.enDate = FlowerDate(month: enMonth, day: enDay)
	}
	
	init(_ arr: [Int]) {
		self.init(arr[0], arr[1], arr[2], arr[3])
	}
	
	static func &amp;lt; (lhs: Flower, rhs: Flower) -&amp;gt; Bool {
		lhs.enDate &amp;lt; rhs.enDate
	}
}

func solution() {
	let n = readInteger()
	var result = 0
	var flowers = [Flower]()
	var currentEnDate = FlowerDate(month: 3, day: 1)
	let targetEnDate = FlowerDate(month: 12, day: 1)
	var priorityQueue = PriorityQueue&amp;lt;Flower&amp;gt;(type: .max)
	
	for _ in (0..&amp;lt;n) {
		let informations = readIntergers()
		flowers.append(.init(informations))
	}
	
	// 시작날짜 기준으로 정렬
	let sortedFlowers = flowers.sorted {
		$0.stDate &amp;lt; $1.stDate
	}

	var flowerIndex = 0
	
	while currentEnDate &amp;lt; targetEnDate {
		// 현재의 꽃이 지는날보다, 먼저 피는 꽃들을 우선순위 큐에 삽입한다.
		while
			flowerIndex &amp;lt; sortedFlowers.count &amp;amp;&amp;amp;
			sortedFlowers[flowerIndex].stDate &amp;lt;= currentEnDate {
			
			priorityQueue.push(sortedFlowers[flowerIndex])
			flowerIndex += 1
		}

		// 이후, 가장 늦게 지는 꽃을 선택한다.
		if let date = priorityQueue.pop() {
			currentEnDate = date.enDate
			result += 1
		} else {
			break
		}
	}
	
	currentEnDate &amp;gt;= targetEnDate ? print(result) : print(0)
}

func readInteger() -&amp;gt; Int {
	Int(readLine()!)!
}

func readIntergers() -&amp;gt; [Int] {
	return readLine()!.split(separator: &quot; &quot;).map { Int($0)! }
}

solution()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C++&lt;/h3&gt;
&lt;pre id=&quot;code_1719148232523&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;vector&amp;gt;

using namespace std;

#define FAST ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define endl '\n'

struct Date {
	int month = 0, day = 0;
};

int N;
vector&amp;lt;pair&amp;lt;Date, Date&amp;gt;&amp;gt; arr;

bool compareDate(Date a, Date b) {
	if(a.month == b.month) return a.day &amp;lt; b.day;
	return a.month &amp;lt; b.month;
}

bool compare1(pair&amp;lt;Date, Date&amp;gt; a, pair&amp;lt;Date, Date&amp;gt; b) {
	return compareDate(a.first, b.first);
}

bool compare2(pair&amp;lt;Date, Date&amp;gt; a, pair&amp;lt;Date, Date&amp;gt; b) {
	return compareDate(a.second, b.second);
}

void input() {
	cin &amp;gt;&amp;gt; N;
	
	Date date1, date2;
	for(int i = 0; i &amp;lt; N; i++) {
		cin &amp;gt;&amp;gt; date1.month &amp;gt;&amp;gt; date1.day &amp;gt;&amp;gt; date2.month &amp;gt;&amp;gt; date2.day;
		arr.push_back({date1, date2});
	}
}

bool isFinish(Date &amp;amp;a) {
	Date temp;
	temp.month = 11;
	temp.day = 30;
	
	return compareDate(temp, a);
}

int solve() {
	sort(arr.begin(), arr.end(), compare1);
	
	vector&amp;lt;pair&amp;lt;Date, Date&amp;gt;&amp;gt; temp;
	
	Date currentDate;
	currentDate.month = 3; currentDate.day = 1;
	int index = 0;
	int cnt = 0;
	while(index &amp;lt; N) {
		if(isFinish(currentDate)) return cnt;

		while(index &amp;lt; N &amp;amp;&amp;amp; !compareDate(currentDate, arr[index].first)) {
			temp.push_back(arr[index ++]);
		}

		if(temp.size() == 0) return 0;
		sort(temp.begin(), temp.end(), compare2);
		currentDate = temp[temp.size()-1].second;
		cnt++;
		
		temp.clear();
	}

	if(!isFinish(currentDate)) return 0;
	return cnt;
}

int main() {
	FAST;
	input();
	cout &amp;lt;&amp;lt; solve() &amp;lt;&amp;lt; endl;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS/Algorithm</category>
      <author>seok-young</author>
      <guid isPermaLink="true">https://seokyoungg.tistory.com/104</guid>
      <comments>https://seokyoungg.tistory.com/104#entry104comment</comments>
      <pubDate>Sun, 23 Jun 2024 22:26:40 +0900</pubDate>
    </item>
    <item>
      <title>[PS] BOJ 1539(이진 검색 트리)</title>
      <link>https://seokyoungg.tistory.com/103</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-18 오후 9.49.48.png&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;882&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHWB6S/btsH4pizCcK/IiVzclN5LJPJNsQwsTkzz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHWB6S/btsH4pizCcK/IiVzclN5LJPJNsQwsTkzz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHWB6S/btsH4pizCcK/IiVzclN5LJPJNsQwsTkzz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHWB6S%2FbtsH4pizCcK%2FIiVzclN5LJPJNsQwsTkzz1%2Fimg.png&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; loading=&quot;lazy&quot; width=&quot;946&quot; height=&quot;882&quot; data-filename=&quot;스크린샷 2024-06-18 오후 9.49.48.png&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;882&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1539&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/1539&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 접근&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;가장 먼저 생각할 수 있는 방법은 BST를 직접 구현해, 삽입될 때마다 위치를 확인하는 방법이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만,&amp;nbsp;N = 250,000이고, BST가 편향 트리가 되는 경우 탐색 시간은 $O(logN)$이 아닌, $O(N)$이 되게 되어,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 시간 복잡도는 $O(N^2)$으로 시간초과가 발생하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, Self-Balancing 트리를 사용하면서 BST상에서 노드의 높이를 알 수 있어야만 한다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, &lt;b&gt;Self-Balancing 트리&lt;/b&gt;에서 &lt;u&gt;&lt;b&gt;새로운 노드가 삽입되는 부모 노드&lt;/b&gt;&lt;/u&gt;를 알아야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리에서 새로운 노드는 &quot;&lt;u&gt;&lt;b&gt;자신보다 바로 작거나 큰 값을 가진 노드&lt;/b&gt;&lt;/u&gt;&quot;의 &lt;b&gt;자식&lt;/b&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, &quot;&lt;b&gt;자신보다 바로 큰 값을 가진 노드&lt;/b&gt;&quot;의 자식으로 삽입되는 상황을 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 경우에는 &lt;b&gt;왼쪽 자식으로 삽입&lt;/b&gt;되게 될 것이다.&amp;nbsp;&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;u&gt;&lt;b&gt;왼쪽 자식이 있는 경우&lt;/b&gt;&lt;/u&gt;라면,&amp;nbsp;&quot;&lt;b&gt;자신보다 바로 작은 값을 가진 노드&lt;/b&gt;&quot;의 자식으로 삽입되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 경우에는 &lt;b&gt;오른쪽 자식으로 삽입&lt;/b&gt;되게 될 것이다.&amp;nbsp;&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;그렇다면, 오른쪽 자식이 있는 경우라면 어떻게 될까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론부터 말하자면, &quot;&lt;u&gt;&lt;b&gt;자신보다 바로 큰 값을 가진 노드&lt;/b&gt;&lt;/u&gt;&quot;부터 확인한다면, 해당 경우는 존재하지 않는다.&amp;nbsp;&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;만약, 삽입되는 노드를 X라고 가정하고, 부모 노드를 Y라고 가정하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드 X와 노드 Y의 값들을 각각 $x$, $y$라고 가정한다면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;&lt;b&gt;자신보다 바로 작은 값을 가진 노드&lt;/b&gt;&quot;의 자식으로 삽입되었기 때문에, 항상 $y &amp;lt; x$를 만족한다.&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;이때, Y의 오른쪽 자식을 K라고 가정해 보고, 값을 $k$라고 가정해 보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 $y &amp;lt; k$가 되며, 노드 K는 &quot;X보다 바로 큰 값을 가진 노드&quot;가 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;노드 Y가 &quot;X보다 바로 작은 값을 가진 노드&quot;이기 때문에, $y &amp;lt; k &amp;lt; x$는 성립할 수 없다.&amp;nbsp;&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;하지만, 해당 케이스는 우선적으로 &quot;자신보다 바로 큰 값을 가진 노드&quot;부터 확인한 경우라면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Y의 오른쪽 자식이 존재할 수 있는 경우는 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제 풀이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이를 코드로 옮겨보자면 다음과 같다.&amp;nbsp;&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;우선, lower_bound를 통해 &quot;&lt;b&gt;자신보다 바로 큰 값을 가진 노드&lt;/b&gt;&quot;를 확인한다.&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;하지만, 자신보다 바로 큰 값이 없는 경우, 해당 경우는 삽입되는 노드가 가장 큰 값이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, &quot;&lt;b&gt;자신보다 바로 작은 값을 가진 노드&lt;/b&gt;&quot;의 오른쪽 자식으로 삽입한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718719536378&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;auto iter = node.lower_bound(num);

if(iter == node.end()) {
	advance(iter, -1);
	
	iter-&amp;gt;second.hasRight = true;
	node[num] = {false, false, iter-&amp;gt;second.height + 1};
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;다음으로, &quot;&lt;b&gt;자신보다 바로 큰 값을 가진 노드&lt;/b&gt;&quot;의 왼쪽 자식이 이미 존재하는 경우에는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&quot;&lt;b&gt;자신보다 바로 작은 값을 가진 노드&lt;/b&gt;&quot;의 오른쪽 자식으로 삽입한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대의 경우 왼쪽 자식으로 그대로 삽입하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718719610936&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;else {
	// 왼쪽이 있는 경우
	if(iter-&amp;gt;second.hasLeft) {
		advance(iter, -1);
		
		iter-&amp;gt;second.hasRight = true;
		node[num] = {false, false, iter-&amp;gt;second.height + 1};
		// 왼쪽이 없는 경우
	} else {
		iter-&amp;gt;second.hasLeft = true;
		node[num] = {false, false, iter-&amp;gt;second.height + 1};
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 코드&lt;/h2&gt;
&lt;pre id=&quot;code_1718719435667&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;map&amp;gt;

using namespace std;

#define FAST ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define endl '\n'
#define ll long long

struct NodeInfo {
	bool hasLeft, hasRight;
	int height;
};

int N;
map&amp;lt;int, NodeInfo&amp;gt; node;

ll solve() {
	ll total = 0;
	cin &amp;gt;&amp;gt; N;
	
	int num;
	for(int i = 0; i &amp;lt; N; i++) {
		cin &amp;gt;&amp;gt; num;
		if(node.size() == 0) {
			node[num] = {false, false, 1};
			continue;
		}
		
		auto iter = node.lower_bound(num);
		
		if(iter == node.end()) {
			advance(iter, -1);
			
			iter-&amp;gt;second.hasRight = true;
			node[num] = {false, false, iter-&amp;gt;second.height + 1};
		} else {
			// 왼쪽이 있는 경우
			if(iter-&amp;gt;second.hasLeft) {
				advance(iter, -1);
				
				iter-&amp;gt;second.hasRight = true;
				node[num] = {false, false, iter-&amp;gt;second.height + 1};
			// 왼쪽이 없는 경우
			} else {
				iter-&amp;gt;second.hasLeft = true;
				node[num] = {false, false, iter-&amp;gt;second.height + 1};
			}
		}
	}
	
	for(auto res: node) {
		total += res.second.height;
	}
	
	return total;
}

int main(){
	FAST;
	cout &amp;lt;&amp;lt; solve() &amp;lt;&amp;lt; endl;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>CS/Algorithm</category>
      <author>seok-young</author>
      <guid isPermaLink="true">https://seokyoungg.tistory.com/103</guid>
      <comments>https://seokyoungg.tistory.com/103#entry103comment</comments>
      <pubDate>Tue, 18 Jun 2024 23:08:26 +0900</pubDate>
    </item>
    <item>
      <title>[Data Structure] Tree(5) - Red-Black Tree Implementation</title>
      <link>https://seokyoungg.tistory.com/102</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에선 &quot;Red-Black Tree&quot;를 Swift로 구현할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 개념에 대한 내용이 궁금하다면 &lt;a href=&quot;https://seokyoungg.tistory.com/101&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;해당 포스팅&lt;/a&gt;을 참고 바란다.&amp;nbsp;&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;자세한 구현은 아래 링크에서 확인해 볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/jungseokyoung-cloud/Data-Structure/tree/main/Data-Structure/Tree.playground/Sources/Red-BlackTree&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/jungseokyoung-cloud/Data-Structure/tree/main/Data-Structure/Tree.playground/Sources/Red-BlackTree&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718537192291&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Data-Structure/Data-Structure/Tree.playground/Sources/Red-BlackTree at main &amp;middot; jungseokyoung-cloud/Data-Structure&quot; data-og-description=&quot;Data Structures implemented with Swift. Contribute to jungseokyoung-cloud/Data-Structure development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/jungseokyoung-cloud/Data-Structure/tree/main/Data-Structure/Tree.playground/Sources/Red-BlackTree&quot; data-og-url=&quot;https://github.com/jungseokyoung-cloud/Data-Structure/tree/main/Data-Structure/Tree.playground/Sources/Red-BlackTree&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0BsPH/hyWoIm9DBZ/XQoE5qiVOMMj5MG9SuN7z1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/jungseokyoung-cloud/Data-Structure/tree/main/Data-Structure/Tree.playground/Sources/Red-BlackTree&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/jungseokyoung-cloud/Data-Structure/tree/main/Data-Structure/Tree.playground/Sources/Red-BlackTree&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0BsPH/hyWoIm9DBZ/XQoE5qiVOMMj5MG9SuN7z1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Data-Structure/Data-Structure/Tree.playground/Sources/Red-BlackTree at main &amp;middot; jungseokyoung-cloud/Data-Structure&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Data Structures implemented with Swift. Contribute to jungseokyoung-cloud/Data-Structure development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RedBlack Node&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, RedBlackNode는 다음과 같이 구현해 준다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718537636673&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final class RedBlackNode&amp;lt;T: Comparable&amp;gt;: Node&amp;lt;T&amp;gt; {
	var isRed: Bool = true
	
	required init(value: T) {
		super.init(value: value)
	}
	
	required init(value: T, parent: Node&amp;lt;T&amp;gt;?) {
		super.init(value: value, parent: parent)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;색깔을 구분하기 위 한&amp;nbsp;&lt;s&gt;isRed&lt;/s&gt; 프로퍼티를 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RedBlack Tree&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RB 트리에서 NIL노드는 별도의 메모리를 할당하지 않고, 단순히 &lt;s&gt;nil&lt;/s&gt;로 지정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;insert(_:)&lt;/h3&gt;
&lt;pre id=&quot;code_1718538382608&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public override func insert(_ value: T) {
	guard let node = super.performInsert(value) as? RedBlackNode&amp;lt;T&amp;gt; else { return }
	
	insertBalancing(for: node)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;insert(_:)&lt;/s&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;메서드는 insert수행한 후, &lt;s&gt;insertBalancing(for:)&lt;/s&gt;&amp;nbsp;메서드를 수행한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;insertBalancing(for:)&lt;/h4&gt;
&lt;pre id=&quot;code_1718538582511&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@discardableResult
func insertBalancing(for node: RedBlackNode&amp;lt;T&amp;gt;) -&amp;gt; RedBlackNode&amp;lt;T&amp;gt;? {
	guard node !== root else {
		node.isRed = false
		return node
	}
	
	// 연속해서 빨간색이 나오지 않는 경우, 종료
	guard
		let parent = node.parent as? RedBlackNode&amp;lt;T&amp;gt;,
		parent.isRed
	else { return node }
	
	if let uncle = node.uncle as? RedBlackNode&amp;lt;T&amp;gt;, uncle.isRed {
		return recoloring(for: node)
	} else {
		return restructuring(for: node)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;insertBalancing(for:)&lt;/s&gt;메서드는 restructuring을 할지 recoloring을 할지 결정하는 메서드이다.&amp;nbsp;&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;우선, 부모노드가 빨간색인 경우 #4 조건을 위반하게 되어, self-balancing이 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;#4: 빨간색 노드의&amp;nbsp;자식은&amp;nbsp;검은색이다.&amp;nbsp; (즉, 2 연속으로 빨간색이 나올 수 없다.)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718539404682&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 연속해서 빨간색이 나오지 않는 경우, 종료
guard
	let parent = node.parent as? RedBlackNode&amp;lt;T&amp;gt;,
	parent.isRed
else { return node }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 다음과 같이 부모 노드가 검은색일 경우, 다음과 같이 종료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718539519208&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if let uncle = node.uncle as? RedBlackNode&amp;lt;T&amp;gt;, uncle.isRed {
	return recoloring(for: node)
} else {
	return restructuring(for: node)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로, 삼촌 노드가 빨간색일 경우 recoloring을 수행하고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검은색일 경우, restructing을 수행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;restructuring(for:)&lt;/h4&gt;
&lt;pre id=&quot;code_1718539723047&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@discardableResult
func restructuring(for node: RedBlackNode&amp;lt;T&amp;gt;) -&amp;gt; RedBlackNode&amp;lt;T&amp;gt;? {
	guard
		let parent = node.parent as? RedBlackNode&amp;lt;T&amp;gt;,
		let grandParent = parent.parent as? RedBlackNode&amp;lt;T&amp;gt;
	else { return node }
	grandParent.isRed = true

	// LL Case
	if node.isLeftChild &amp;amp;&amp;amp; parent.isLeftChild {
		parent.isRed = false
		return rightRotate(for: grandParent, pivot: parent)
		
	// RR Case
	} else if node.isRightChild &amp;amp;&amp;amp; parent.isRightChild {
		parent.isRed = false
		return leftRotate(for: grandParent, pivot: parent)
		
	// LR Case
	} else if node.isRightChild &amp;amp;&amp;amp; parent.isLeftChild {
		node.isRed = false
		let pivot = leftRotate(for: parent, pivot: node)
		return rightRotate(for: grandParent, pivot: pivot)
		
	// RL Case
	} else {
		node.isRed = false
		let pivot = rightRotate(for: parent, pivot: node)
		return leftRotate(for: grandParent, pivot: pivot)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RR Case, LL Case의 경우 부모 노드와 조부모 노드의 색상을 변경해 주고, Rotation 작업을 수행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, RL Case, LR Case의 경우 새로운 노드와 조부모 노드의 색상을 변경한 후, Rotation 작업을 수행한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, NIL노드가 삼촌노드일수도 있기 때문에, 다음과 같이 optional 바인딩을 진행해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;recoloring(for:)&lt;/h4&gt;
&lt;pre id=&quot;code_1718540249884&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@discardableResult
func recoloring(for node: RedBlackNode&amp;lt;T&amp;gt;) -&amp;gt; RedBlackNode&amp;lt;T&amp;gt;? {
	guard
		let parent = node.parent as? RedBlackNode&amp;lt;T&amp;gt;,
		let grandParent = parent.parent as? RedBlackNode&amp;lt;T&amp;gt;,
		let uncle = node.uncle as? RedBlackNode&amp;lt;T&amp;gt;
	else { return node }
	
	grandParent.isRed = true
	parent.isRed = false
	uncle.isRed = false
	
	return insertBalancing(for: grandParent)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;recoloring(for:)&lt;/s&gt;메서드는 다음과 같이 부모노드와, 삼촌 노드를 검은색으로 변경한다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 조부모 노드에서 다시 #4조건을 위반할 수 있기 때문에, 재귀적으로 &lt;s&gt;insertBalancing(for:)&lt;/s&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;remove(_:)&lt;/h3&gt;
&lt;pre id=&quot;code_1718551269428&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public override func remove(_ value: T) -&amp;gt; T? {
	guard let removeNode = search(value) else { return nil }

	let removedReturnType = super.performRemove(node: removeNode)
	removeBalancing(
		removedNode: removedReturnType.removedNode,
		replaceNode: removedReturnType.replaceNode,
		parentNode: removedReturnType.parentNode
	)
	
	return removeNode.value
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;remove(_:)&lt;/s&gt;메서드는 &lt;s&gt;removeBalancing(removedNode:replaceNode:parentNode:)&lt;/s&gt;를 통해 균형을 잡는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;removeBalancing(removedNode:replaceNode:parentNode:)&lt;/h4&gt;
&lt;pre id=&quot;code_1718552211345&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func removeBalancing(
	removedNode: Node&amp;lt;T&amp;gt;?,
	replaceNode: Node&amp;lt;T&amp;gt;?,
	parentNode: Node&amp;lt;T&amp;gt;?
) {
	guard let node = removedNode as? RedBlackNode&amp;lt;T&amp;gt;, !node.isRed else { return }
	
	// 삭제된 노드가 검정색이라면, Extra-Black을 부여
	removeExtraBlack(parent: parentNode, replaceNode: replaceNode)
	
	(root as? RedBlackNode&amp;lt;T&amp;gt;)?.isRed = false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;removeBalancing&lt;/s&gt;은 삭제되는 노드가 검은색이라면, 문제가 발생한다. 따라서, guard문을 통해 삭제되는 노드가 검은색인지 확인한다.&amp;nbsp;&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;s&gt;removeExtraBlack&lt;/s&gt;메서드를 통해 ExtraBlack을 제거한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;removeExtraBlack(parent:replaceNode:)&lt;/h4&gt;
&lt;pre id=&quot;code_1718552721308&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func removeExtraBlack(parent: Node&amp;lt;T&amp;gt;?, replaceNode: Node&amp;lt;T&amp;gt;?) {
	// Red-And-Black Node
	if let replaceNode = replaceNode as? RedBlackNode&amp;lt;T&amp;gt;, replaceNode.isRed {
		replaceNode.isRed = false
	} else {
		removeDoublyBlack(parent: parent, doublyBlack: replaceNode)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, &lt;s&gt;replaceNode&lt;/s&gt;는 삭제된 노드의 위치에 위치한 노드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;replaceNode&lt;/s&gt;가 빨간색이라면, Red-And-Black이므로 검은색으로 변경해 주면 되지만,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Double-Black이라면 여러 분기로 나뉘기 때문에, &lt;s&gt;removeDoublyBlack&lt;/s&gt;메서드를 통해 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;removeDoublyBlack(parent:doublyBlack:)&lt;/h4&gt;
&lt;pre id=&quot;code_1718552893493&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func removeDoublyBlack(parent: Node&amp;lt;T&amp;gt;?, doublyBlack: Node&amp;lt;T&amp;gt;?) {
	guard
		let parent = parent as? RedBlackNode&amp;lt;T&amp;gt;,
		let sibling = (parent.left === doublyBlack ? parent.right : parent.left) as? RedBlackNode&amp;lt;T&amp;gt;
	else { return }
	
	if !sibling.isRed {
		if
			let sibilingChild = (sibling.isLeftChild ? sibling.left : sibling.right) as? RedBlackNode&amp;lt;T&amp;gt;,
			sibilingChild.isRed {
			removeCase4(
				parent: parent,
				sibling: sibling,
				child: sibilingChild
			)
		} else if
			let sibilingChild = (sibling.isLeftChild ? sibling.right : sibling.left) as? RedBlackNode&amp;lt;T&amp;gt;,
			sibilingChild.isRed {
			removeCase3(
				parent: parent,
				sibling: sibling,
				child: sibilingChild
			)
		} else {
			removeCase2(parent: parent, sbiling: sibling)
		}
	} else {
		removeCase1(parent: parent, sibling: sibling, node: doublyBlack as? RedBlackNode&amp;lt;T&amp;gt;)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분기가 복잡해 보이지만 살펴보면 단순하다.&amp;nbsp;&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;우선, 형제노드의 색깔이 검은색이고, 형제노드와 같은 위치의 형제노드의 자식(LL 혹은 RR)이 빨간색인 경우,&lt;/p&gt;
&lt;pre id=&quot;code_1718553105465&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if !sibling.isRed {
	if
		let sibilingChild = (sibling.isLeftChild ? sibling.left : sibling.right) as? RedBlackNode&amp;lt;T&amp;gt;,
		sibilingChild.isRed {
		removeCase4(
			parent: parent,
			sibling: sibling,
			child: sibilingChild
		)
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;case4에 해당되기 때문에, &lt;s&gt;removeCase4&lt;/s&gt;메서드를 통해 Doubly-Black을 제거해 준다.&amp;nbsp;&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;다음으로, 형제노드의 색깔이 검은색이고, 형제노드와는 반대편 위치에 존재하는 형제노드의 자식(LR 혹은 RL)이 빨간색이고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 위치의 형제노드는(LL 혹은 RR)이 검은색인 경우,&lt;/p&gt;
&lt;pre id=&quot;code_1718553169288&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if !sibling.isRed {
	...
	} else if
		let sibilingChild = (sibling.isLeftChild ? sibling.right : sibling.left) as? RedBlackNode&amp;lt;T&amp;gt;,
		sibilingChild.isRed {
		removeCase3(
			parent: parent,
			sibling: sibling,
			child: sibilingChild
		)
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;case3에 해당하여 &lt;s&gt;removeCase3&lt;/s&gt;메서드를 통해 Doubly-Black을 제거해 준다.&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;만약, 형제노드가 검은색이고, 형제노드의 두 자식 다 검은색이라면,&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1718553342518&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if !sibling.isRed {
	...
	} else {
		removeCase2(parent: parent, sbiling: sibling)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;case2이므로, &lt;s&gt;removeCase2&lt;/s&gt;메서드를 통해 Doubly-Black을 제거해 준다.&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;마지막으로 형제노드가 빨간색인 경우는 case1에 해당되므로 &lt;s&gt;removeCase1&lt;/s&gt;메서드를 호출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1718553667087&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;else {
	removeCase1(parent: parent, sibling: sibling, node: doublyBlack as? RedBlackNode&amp;lt;T&amp;gt;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;removeCase4(parent:sibling:child:)&lt;/h4&gt;
&lt;pre id=&quot;code_1718553989994&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func removeCase4(
	parent: RedBlackNode&amp;lt;T&amp;gt;,
	sibling: RedBlackNode&amp;lt;T&amp;gt;,
	child: RedBlackNode&amp;lt;T&amp;gt;
) {
	sibling.isRed = parent.isRed
	
	child.isRed = false
	parent.isRed = false
	
	if sibling.isLeftChild {
		rightRotate(for: parent, pivot: sibling)
	} else {
		leftRotate(for: parent, pivot: sibling)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형제노드는 부모노드의 색상으로 변경하고, 형제노드의 자식노드와 부모노드를 검은색으로 변경한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, 각 상황에 맞게 rotation을 수행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;removeCase3(parent:sbling:child:)&lt;/h4&gt;
&lt;pre id=&quot;code_1718554290746&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func removeCase3(
	parent: RedBlackNode&amp;lt;T&amp;gt;,
	sibling: RedBlackNode&amp;lt;T&amp;gt;,
	child: RedBlackNode&amp;lt;T&amp;gt;
) {
	sibling.isRed = true
	child.isRed = false
	
	if sibling.isLeftChild {
		leftRotate(for: sibling, pivot: child)
	} else {
		rightRotate(for: sibling, pivot: child)
	}
	
	guard
		let newChild = (sibling.isLeftChild ? child.left : child.right) as? RedBlackNode&amp;lt;T&amp;gt;
	else { return }
	
	removeCase4(parent: parent, sibling: child, child: newChild)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 경우에는 형제노드를 빨간색으로, 형제의 자식노드를 검은색으로 변경한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, Rotation 작업을 진행해 주게 되면 case4가 되기 때문에 &lt;s&gt;removeCase4&lt;/s&gt;를 호출해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;removeCase2(parent:sibling:)&lt;/h4&gt;
&lt;pre id=&quot;code_1718554627497&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func removeCase2(parent: RedBlackNode&amp;lt;T&amp;gt;, sibling: RedBlackNode&amp;lt;T&amp;gt;) {
	sibling.isRed.toggle()
	// 부모에게 Extra-Black을 위임
	removeExtraBlack(parent: parent.parent, replaceNode: parent)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형제노드의 색상을 변경해 주고, Extra-Black을 부모에게 넘겨준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 부모노드에게 ExtraBlack을 위임한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;removeCase1(parent:sibling:node:)&lt;/h4&gt;
&lt;pre id=&quot;code_1718554961958&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func removeCase1(parent: RedBlackNode&amp;lt;T&amp;gt;, sibling: RedBlackNode&amp;lt;T&amp;gt;, node: RedBlackNode&amp;lt;T&amp;gt;?) {
	parent.isRed = true
	sibling.isRed = false
	
	if sibling.isLeftChild {
		rightRotate(for: parent, pivot: sibling)
	} else {
		leftRotate(for: parent, pivot: sibling)
	}
	
	removeDoublyBlack(parent: parent, doublyBlack: node)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;case1의 경우에는 부모 노드와 형제노드의 색상을 변경한 후, rotation 작업을 수행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 case2, case3, case4에 맞춰서 DoublyBlack을 해결하면 된다.&amp;nbsp;&lt;/p&gt;</description>
      <category>CS/Data Structure</category>
      <category>DataStructure</category>
      <category>RB tree</category>
      <category>SWIFT</category>
      <author>seok-young</author>
      <guid isPermaLink="true">https://seokyoungg.tistory.com/102</guid>
      <comments>https://seokyoungg.tistory.com/102#entry102comment</comments>
      <pubDate>Mon, 17 Jun 2024 01:42:50 +0900</pubDate>
    </item>
  </channel>
</rss>