<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>나름 개발자의 IT블로그</title>
    <link>https://riroan.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 12 Apr 2026 12:09:33 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>riroan</managingEditor>
    <image>
      <title>나름 개발자의 IT블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/4182334/attach/3035ba17259b496ea58fb537812e75ef</url>
      <link>https://riroan.tistory.com</link>
    </image>
    <item>
      <title>[알고리즘] 2025 카카오 하반기 코딩테스트 문제 풀어보기</title>
      <link>https://riroan.tistory.com/189</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/challenges?order=recent&amp;amp;partIds=94316%2C94315&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;2025년 하반기 카카오 공채에 사용된 1, 2차 알고리즘 코딩테스트 문제가 프로그래머스에 올라와서 풀어보았다. 최근 알고리즘을 많이 놓기도 했지만 지금까지 경험해본 코테중에 가장 어려운 난이도에 속한 것 같다...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-06 오후 9.14.26.png&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;1608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdOMDY/dJMcaibPhoW/vhKoAI9PFE4JbWUh67I3V0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdOMDY/dJMcaibPhoW/vhKoAI9PFE4JbWUh67I3V0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdOMDY/dJMcaibPhoW/vhKoAI9PFE4JbWUh67I3V0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdOMDY%2FdJMcaibPhoW%2FvhKoAI9PFE4JbWUh67I3V0%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;574&quot; height=&quot;556&quot; data-filename=&quot;스크린샷 2026-04-06 오후 9.14.26.png&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;1608&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;1차 중요한 단어를 스포방지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오 코테의 전통일수도 있지만 1번문제는 쉬운 구현문제이다. 스포방지 구간에 포함되는 단어들을 set, map 등으로 관리한 뒤 스포방지가 해제될 때마다 해당 단어들이 얼마나 존재하는지를 더하면 쉽게 풀 수 있다.&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;1차 노란불 신호등&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lcm, gcd 같은 느낌이 들지만 제한이 충분히 작아 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;1차 리프 노드 수 최대화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리의 모양이 위에서부터 깊이에 따라 2, 2, 2, 2, ... , 3, 3, 3 또는 3, 3, 3, 3, ..., 2, 2, 2 꼴이 될 줄 알고 열심히 구현했으나 구현실수를 했는지 WA 를 받았다. 트리 배치를 랜덤하게 구성해서 2, 3, 3, 2, ... 같은 모양도 나오게 해서 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;1차 바이러스 파이프&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코테 단골 소재인 완전탐색 문제이다. $k \le 10$ 이므로 처음부터 A 가 열렸을때, B 가 열렸을때, C 가 열렸을때 모든 경우에 대해 각각 탐색하면 $3^k$ 의 경우의 수가 나온다. 각 경우마다 $O(n)$ 에 bfs 하면 $O(n 3^k)$ 에 넉넉히 풀 수 있다.&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;1차 카카오 앱 정리하기&lt;/h3&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;1차 발전소 회로 복구&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제 재미있었다. 각 패널이 모두 연결된 인접 행렬로 그래프를 그려서 가중치를 거리로 둔다. 패널의 개수가 최대 15개이므로 bit-dp 를 사용할 수 있다. 예를 들어 상태가 1000 (3번 패널이 활성화됨)일 때 1001 로 갈 수 있는지 확인한다. 즉, 0번 패널을 활성화 시키는데 1번, 2번 패널이 이미 활성화 되어있지 않아도 되는지 확인한다. 가능하다면 1000 의 상태값 + 3번 -&amp;gt; 0번 이동 비용을 추가한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 전이는 1000 -&amp;gt; 1001 도 가능하고 0001 -&amp;gt; 1001 도 가능하므로 각각 조건에 위배되지 않는지 확인하고 이동 후 더 작은 값으로 설정한다. 모든 경우에 대해 위 과정을 진행하고 모든 패널이 활성화된 상태(1111)의 값을 반환한다. 모든 패널을 안전하게 활성화 가능한 경우만 주어지므로 (사이클이 존재하지 않으므로) 위상정렬을 통해 가능성을 판별하지 않아도 된다.&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;코테에서 bit-dp 가 나와서 신기했다. 다른 방법도 있으려나?&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;1차 최고 속도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interested_point 를 정의하자. 이 점은 교점, 과속카메라가 있는 점, 도시가 있는 점을 포함한다. 그 후 점들끼리 도로로 이동할 수 있으면 연결해서 그래프를 만든다. 가중치는 과속카메라가 있다면 해당 과속카메라의 속도, 없다면 무한으로 둔다. 그렇게 하면 문제는 &lt;b&gt;A 부터 B 까지 가는 경로에서 최소 가중치의 최댓값을 구하는 문제&lt;/b&gt;가 된다. 이는 &lt;a href=&quot;https://www.acmicpc.net/problem/1939&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2차 힌트 스테이지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제도 $O(2^n)$ 완전탐색 문제이다. 각 step 에서 힌트를 쓴다/안쓴다로 모든 경우를 나누어 계산하면 쉽게 답을 구할 수 있다.&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;2차 보물 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신기한 인터랙티브 문제이다. 나는 못풀었지만 &lt;a href=&quot;https://www.acmicpc.net/problem/11049&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;행렬곱셈순서 dp&lt;/a&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;2차 선인장 숨기기&lt;/h3&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;2차 제곱 개수 배열&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 코포틱한 run length encoding 문제이다. K 를 구하는 것은 제곱수의 누적합을 이분탐색하여 쉽게 할 수 있다. C 를 못구해서 못풀었다 ㅠㅠㅠ 슬라이딩 윈도우를 하면 되려나..?&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;2차 기차 선로&lt;/h3&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;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;이번 공채 코딩테스트는 너무 어렵고 실수할 부분이 많아 실제로 응시했다면 떨어졌을 것 같다. ㅠㅠㅠ AI 때문인지 실력 상향평준화 때문인지 코테가 날이 갈수록 어려워지는 것 같다.&lt;/p&gt;</description>
      <category>프로그래밍/알고리즘</category>
      <category>알고리즘</category>
      <category>카카오</category>
      <category>코딩테스트</category>
      <author>riroan</author>
      <guid isPermaLink="true">https://riroan.tistory.com/189</guid>
      <comments>https://riroan.tistory.com/189#entry189comment</comments>
      <pubDate>Mon, 6 Apr 2026 21:47:18 +0900</pubDate>
    </item>
    <item>
      <title>2025년을 되돌아보며</title>
      <link>https://riroan.tistory.com/188</link>
      <description>&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;이제 만 2년이 되어간다. 도메인 지식은 아직 알아야 할 부분이 많지만 대부분 알게 되었고 혼자서 이슈해결을 상당수 할 수 있을 정도가 되었다. 또한 부서이동등으로 인해 새로운 팀원이 합류하면 온보딩도 시켜줄 수 있는 수준이다. 올해는 IaaS 를 담당하면서 VM 생성속도를 개선하는데 대부분 시간을 쏟은 것 같다. 이 과정에서 내가 알고 있는 알고리즘 지식을 사용할 수 있어서 좋았고 실제로 벤치마킹 결과가 예상처럼 개선돼서 의미가 있었다. 언젠가 카카오 테크 블로그에 기재할 수도..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무에 필요한 CS 지식도 꾸준히 학습중이다. 학부에서 배웠지만 시간이 흘러 잊을만한 내용들이 종종 등장해서 감을 잊지 않기 위해 전공책을 보는 중이다. 대표적으로 CPU Pinning, NUMA Topology, noise neighbours problem 등이 있다. 내가 제일 약했던 컴퓨터 구조랑 운영체제에 대해 다시 봐야할 것 같다.. ㅠㅠ 개발보다 이론적인 부분에서 많이 성장한 것 같다.&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;올해 초에 세웠던 목표중 하나인 해외여행을 다녀왔다. 지금까지 해외로 나가본 적이 없어서 목표로 넣긴 했는데 결국엔 이루었다! 사실 지금까진 언어에 대한 걱정때문에 못 나간 게 컸는데 생각보다 사용하는 말은 많이 없고 같이 간 사람이 외국어를 잘해서인지 꽤 재밌게 다녀왔다. 일본과 상하이를 다녀왔는데 일본 여행에 대한 경험이 너무 좋았어서 올해만 도쿄, 오사카, 후쿠오카로 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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-12-26 오전 11.18.23.png&quot; data-origin-width=&quot;2292&quot; data-origin-height=&quot;592&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GtP7P/dJMcaiPqJ0t/f9M2OKizMAkSMIuKlKtJkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GtP7P/dJMcaiPqJ0t/f9M2OKizMAkSMIuKlKtJkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GtP7P/dJMcaiPqJ0t/f9M2OKizMAkSMIuKlKtJkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGtP7P%2FdJMcaiPqJ0t%2Ff9M2OKizMAkSMIuKlKtJkk%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;2292&quot; height=&quot;592&quot; data-filename=&quot;스크린샷 2025-12-26 오전 11.18.23.png&quot; data-origin-width=&quot;2292&quot; data-origin-height=&quot;592&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 끊어지지 않아 계속 풀고있다. ㅋㅋ 하루에 한 문제씩은 꼬박꼬박 풀고 있고 대회가 있다면 가끔씩은 참여하고 있다. 지금까진 아침에 한 문제 풀고 출근하는게 루틴이었는데 점점 힘들어져서 한 30일치 문제를 풀어서 카톡같은 곳에 저장해놓고 일어나면 한 문제를 제출하는 것으로 루틴을 바꿨다. 좀 삶의 질이 나아진 것 같다. 덕분에 실력이 점점 떨어지는 것은 덤. 그래도 오프라인 행사가 있으면 최대한 참여하려고 하는 편이다. 올해도 Hello BOJ 2025 와 Good Bye 2025 에 오프라인으로 참가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해는 건국대학교에서 Hello Alkon 이랑 KUPC 2025 가 열려서 둘 다 검수를 했고 후원도 좀 했다. 벌써 4년째 이어지는 대회여서 신기하기도 하다. 동아리 차기 회장도 염두했다고 하니 오랫동안 이어졌으면 좋겠다.&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;올해 목표에는 없었지만 해외여행을 다녀오고 친구가 외국어로 소통하는 모습이 좀 멋있어보여서 나도 공부를 시작했다. 가장 먼저 일본어를 준비했고 12월에 JLPT N4 를 응시했다. 결과는 1월 말에 나올 예정. 연습한 것보다 어렵게 나와서 걱정되기도 한다. ㅋㅋ N4 는 일본인 유치원생 수준이긴 하지만 히라가나도 몰랐던 나에겐 적당히 도전할만한 난이도였던 것 같다. 계속 공부해서 혼자 일본여행가서 대화할 수준까지 되고싶다. 이 목표를 이루기 위해서는 시험을 위한 공부를 하면 안 될 것 같다고 느꼈다. 뭔가 시험을 위해 언어를 공부하면 문제를 맞힐 수 있는 꼼수만 늘고 언어는 잘 안 느는 것 같다고 느꼈기 때문이다. 어느정도 소통할 수준이 된다면 최종적으로 일본에서 한 달 살기 같은 것도 해보고 싶다.&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;2026년엔 무엇을 할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작년 글에 쓴 것처럼 올해는 행복이 무엇인가에 대해 꽤 많은 고민을 했던 것 같지만 아직 확실한 답은 내리지 못했다. 하지만 주변 사람들의 여러 의견을 들었는데 좀 도움이 된 것 같다. 나와 나잇대가 비슷한 사람들에게 언제 행복하냐고 물어보면 &quot;일을 조금할 때&quot;, &quot;돈 쓸 때&quot; 같은 답을 많이 얻었고 한 40대정도 되는 나이차이가 있는 사람들에게 물어보면 &quot;오늘도 되풀이되는 일상을 느낄 수 있을 때&quot;, &quot;건강한 하루를 맞이할 때&quot; 같은 답을 많이 얻었다. 확실히 살아갈수록 행복에 대한 기준이 달라지는 것 같다. 나도 질문을 받는다면 나잇대가 비슷한 사람들에게 받았던 답변과 비슷하게 말할 것 같다. 2026 년에도 나의 행복을 찾기 위해 고민할 것 같다.&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;그리고 외국어공부도 더 하고 싶다. 해외여행을 하면서 언어를 넓고 얕게 아는 것도 도움이 되는 것 같다. 일본어는 개인적으로 좋고 재밌어서 좀 깊게 할 예정이지만 다른 나라 언어도 공부해보고 싶다. 아마 하게 된다면 중국어가 될 것 같다. 같은 한자권 언어이기도 하고 거의 기억은 나지 않지만 중학교 때 배웠던 기억이 있어서 진입하기 쉬울 것 같기 때문이다. 그래서 내년엔 HSK 낮은 등급도 한번 도전해볼 것 같다. 그리고 스페인어도 찍먹해볼 것 같다. 하고 싶은 것은 많지만 그만큼 의지가 따라올 지는 모르겠다 ㅠㅠ&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;2026년도 파이팅&lt;/p&gt;</description>
      <category>기타</category>
      <category>개발</category>
      <category>일상</category>
      <category>회고</category>
      <author>riroan</author>
      <guid isPermaLink="true">https://riroan.tistory.com/188</guid>
      <comments>https://riroan.tistory.com/188#entry188comment</comments>
      <pubDate>Wed, 31 Dec 2025 23:59:01 +0900</pubDate>
    </item>
    <item>
      <title>[분산 시스템] 1. 분산 시스템이란?</title>
      <link>https://riroan.tistory.com/187</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현대사회에서 대부분의 사람은 PC나 모바일 기기등 통신이 가능한 기기를 하나 이상은 가지고 있다. 이 말은 통신이 가능한 기기를 가진 사람들은 인터넷에 접속하여 원하는 정보를 얻고 전자 상거래를 하며 게임을 할 수 있는 환경에 있다는 뜻이다. 서버의 입장에서는 이러한 많은 사람들의 요청을 문제없이 수행할 수 있도록 해야한다. 이를 가능하게 하기 위해선 단일 노드보다는 다중 노드에서 애플리케이션을 구동시키려는 시도가 필요하다.&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;분산 시스템(Distributed system)&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;스크린샷 2025-05-04 01-24-15.png&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lk6FI/btsNLtMT76S/ZBE8myMD4ZVI8k3WKPMrLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lk6FI/btsNLtMT76S/ZBE8myMD4ZVI8k3WKPMrLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lk6FI/btsNLtMT76S/ZBE8myMD4ZVI8k3WKPMrLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flk6FI%2FbtsNLtMT76S%2FZBE8myMD4ZVI8k3WKPMrLK%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;361&quot; height=&quot;239&quot; data-filename=&quot;스크린샷 2025-05-04 01-24-15.png&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application과 DB간에 CPU나 디스크 자원 간섭을 받지 않기 위해 노드를 분리하여 네트워크로 통신하는 모델이다. 또는 대규모 트래픽을 감당하기 위해 애플리케이션을 다중화하는 모델도 생각할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-05-04 01-27-10.png&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;652&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIwSy2/btsNJVqh4x0/GXLkQRJyX6Nn9rVubVuBKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIwSy2/btsNJVqh4x0/GXLkQRJyX6Nn9rVubVuBKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIwSy2/btsNJVqh4x0/GXLkQRJyX6Nn9rVubVuBKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIwSy2%2FbtsNJVqh4x0%2FGXLkQRJyX6Nn9rVubVuBKk%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;555&quot; height=&quot;396&quot; data-filename=&quot;스크린샷 2025-05-04 01-27-10.png&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;652&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우엔 하나의 도메인으로 다중 애플리케이션에 접근하기 위해 로드밸런서를 앞에 두어야 한다. 이 외에도 Master-slave구조 DB, DB 다중화, 샤딩 등 다양한 영역에서 분산 시스템을 설계할 수 있다. 큰 범위에서 보면 분산 시스템에서 수행하는 일은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다중화 (복제)&lt;/li&gt;
&lt;li&gt;파티셔닝&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;단일 컴퓨팅 노드로 해결할 수 없는 규모의 문제를 해결한다. (대규모 트래픽, 분산 알고리즘 ...)&lt;/li&gt;
&lt;li&gt;이미 다중 컴퓨팅 환경을 구성했기 때문에 확장성에 있어서 유연하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;노드간 통신이 네트워크로 이루어지기 때문에 네트워크 이슈가 발생하면 시스템 전체에 위협이 된다.&lt;/li&gt;
&lt;li&gt;MSA와 마찬가지로 노드가 분산되어있기 때문에 트러블슈팅하기 어렵다.&lt;/li&gt;
&lt;li&gt;다수의 노드를 관리하기 때문에 공격 루트가 많아져 보안에 신경을 더 써야한다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/분산시스템</category>
      <author>riroan</author>
      <guid isPermaLink="true">https://riroan.tistory.com/187</guid>
      <comments>https://riroan.tistory.com/187#entry187comment</comments>
      <pubDate>Sun, 4 May 2025 01:41:38 +0900</pubDate>
    </item>
    <item>
      <title>[개발] 카카오 입사 1주년 후기</title>
      <link>https://riroan.tistory.com/186</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 3월 18일은 내가 카카오에 정규직으로 입사한지 1년이 되는 날이다. 즉 2024년 3월 18일에 정규직으로 첫 출근을 했다!! 이를 기념하여 지금까지 어떻게 살아왔는 지 남겨보려고 한다. 인턴 생활은 &lt;a href=&quot;https://riroan.tistory.com/172&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;h3 data-ke-size=&quot;size23&quot;&gt;입사하고 나서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인턴때와 같은 팀으로 입사했기 때문에 인턴때의 자리로 돌아왔고 본격적으로 다른 팀원들이 하는 것과 동일한 업무를 시작하게 된다. 하는 일은 &lt;a href=&quot;https://riroan.tistory.com/173&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;에서 설명한 카카오의 IaaS인 &lt;a href=&quot;https://zdnet.co.kr/view/?no=20190721085551&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Krane&lt;/a&gt;을 개발하는 업무를 한다. 한 3개월간의 부서 온보딩을 받고 그 이후부터가 진짜 시작이다. 온보딩에는 부서 코드를 숙지하고 코어 도메인에 대해서 학습하게 된다.&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;스크린샷 2025-03-18 19-18-36.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SMLvF/btsMPM0HIQb/MkMyRfmk6rZBabcIfSXrh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SMLvF/btsMPM0HIQb/MkMyRfmk6rZBabcIfSXrh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SMLvF/btsMPM0HIQb/MkMyRfmk6rZBabcIfSXrh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSMLvF%2FbtsMPM0HIQb%2FMkMyRfmk6rZBabcIfSXrh1%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;719&quot; height=&quot;409&quot; data-filename=&quot;스크린샷 2025-03-18 19-18-36.png&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&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 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;스크린샷 2025-03-18 20-10-27.png&quot; data-origin-width=&quot;863&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP2o6z/btsMPntstx5/MlprgzwMlR4xKlPw2zOHPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP2o6z/btsMPntstx5/MlprgzwMlR4xKlPw2zOHPk/img.png&quot; data-alt=&quot;Openstack nova architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP2o6z/btsMPntstx5/MlprgzwMlR4xKlPw2zOHPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP2o6z%2FbtsMPntstx5%2FMlprgzwMlR4xKlPw2zOHPk%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;774&quot; height=&quot;624&quot; data-filename=&quot;스크린샷 2025-03-18 20-10-27.png&quot; data-origin-width=&quot;863&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Openstack nova architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 메인으로 다루는 서비스인 &lt;a href=&quot;https://docs.openstack.org/nova/latest/admin/architecture.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Openstack Nova의 구조&lt;/a&gt;이다. 이것만 봐도 알 수 있듯이 기본이 MSA로 구성돼있고 하이퍼바이저를 직접 다루게 된다. 이를 통해 MSA에서 고민해야 하는 것들을 고민하게 되고 트러블슈팅을 할 때도 이 흐름을 알아야한다. 그러다보면 코드분석할 일이 많이 생기고 거기서 insight를 얻기도 한다.&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;color: #333333; text-align: start;&quot;&gt;팀에서 다루는 범위가 AWS로 비유하면 EC2, EBS, ELB정도가 될 것 같은데 이는 개발 뿐만 아니라 CS지식도 꽤 깊게 알고 있어야 하는 부분이다. 대학때 알고리즘을 제외한 CS과목들을 어려워했는데 그 대가를 지금 톡톡히 치루고 있다. ㅠㅠ 그래도 이젠 필요한 CS범위가 정해졌으니 다시 차근차근 공부하고 있다. 마음같아서는 운영체제, 컴퓨터구조, 네트워크 실력을 알고리즘 수준까지 끌어올리고 싶지만 시간이 한정돼있으니 필요한 부분 위주로 채워나가려고 한다.&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;동호회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;너무 고여있지 않을 것&lt;/li&gt;
&lt;li&gt;활동이 강제되지 않을 것 (ex. 숙제)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 만족하는 동호회에 들고 싶었다. 이를 모두 만족하는 동호회가 있었으니 바로 &lt;b&gt;마술 동호회&lt;/b&gt;였다! 이번에 처음 만들어진 동호회였고 동기가 홍보하길래 따라 들어가게 되었다. 들어가기 전에 &lt;a href=&quot;https://www.youtube.com/@%EA%B9%80%EC%A4%80%ED%91%9C&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;김준표&lt;/a&gt;나 &lt;a href=&quot;https://www.youtube.com/@kimtrickster52&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 동호회로 클라이밍 동호회를 들어가고 싶었는데 잘 안하기도 하고 열심히 동호회비 기부만 할까봐 아직 가입은 안했다. &quot;클라이밍화를 사면 들어가겠다&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;사실 내심 알고리즘 동호회가 있기를 바랬지만 과거에 있다가 사라진 것 같다. 간간이 &lt;a href=&quot;https://www.acmicpc.net/school/ranklist/631&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;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/개발</category>
      <author>riroan</author>
      <guid isPermaLink="true">https://riroan.tistory.com/186</guid>
      <comments>https://riroan.tistory.com/186#entry186comment</comments>
      <pubDate>Tue, 18 Mar 2025 20:15:46 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 점근적 표기법 A to Z</title>
      <link>https://riroan.tistory.com/185</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;점근적 표기법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;점근적 표기법(Asymtotic notation)&lt;/b&gt;은 함수의 증감 추세를 비교하는 표기법이다. 다른 말로 &lt;b&gt;란다우 표기법(Landau notation)&lt;/b&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;Big-$O$: $O(g(n))$&lt;/li&gt;
&lt;li&gt;Big-$\Omega$: $\Omega(g(n))$&lt;/li&gt;
&lt;li&gt;Big-$\Theta$: $\Theta(g(n))$&lt;/li&gt;
&lt;li&gt;little-$o$: $o(g(n))$&lt;/li&gt;
&lt;li&gt;little-$\omega$: $\omega(g(n))$&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;We often want to know a quantity approximately, instead of exactly, in order to compare it to another.&lt;br /&gt;- Donald Knuth (TAOCP vol. 1)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Big-$O$ 표기법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 함수 $f(n)$이 다음 조건을 만족하는 집합에 있으면 $f(n) = O(g(n))$이라고 한다. 여기서 $=$는 &quot;같다&quot;의 의미가 아니라 $\in$정도로 이해하는 것이 좋다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$O(g(n)) = \{f(n) : n_0 \le n $인 모든 $n$에 대하여 $0 \le f(n) \le c \cdot g(n)$인 양의 상수 $c, n_0$이 존재하는 $f(n) \}$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 $f(n) = n$라고 하자. 이 $f(n)$이 들어갈 수 있는 $g(n)$의 종류에는 $n, n^2, 2^n$등등이 있다. 이 말인 즉슨 $f(n) = O(n),\ f(n) = O(n^2),\ f(n)=O(2^n)$이라고 나타낼 수 있다. 하지만 $O(\sqrt{n})$, $O(\log{n})$이라고는 나타낼 수 없다. 이를 일반화하면 가능한 $g(n)$은 다음 조건을 만족한다고도 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$\lim\limits_{n \to \infty} \frac{f(n)}{g(n)} &amp;lt; \infty$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Big-$O$ 표기법이 나타내는 의미는 어떤 함수 $f(n)$이 정확히 어떻게 돼 있는지는 모르겠지만 충분히 큰 $n$에서 $g(n)$을 넘지 않는다는 상한을 의미한다.&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;Big-$\Omega$ 표기법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 함수 $f(n)$이 다음 조건을 만족하는 집합에 있으면 $f(n) = \Omega(g(n))$이라고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$\Omega(g(n)) = \{f(n) : n_0 \le n $인 모든 $n$에 대하여 $0 \le c \cdot g(n) \le f(n)$인 양의 상수 $c, n_0$이 존재하는 $f(n) \}$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Big-$\Omega$는 Big-$O$에서 $f, g$의 위치가 바꼈다. 따라서 Big-$O$와는 반대로 $f(n) = n$일 때, $f(n) = \Omega(\sqrt{n}),\ f(n) = \Omega(\log{n})$이라고 나타낼 수 있고, $\Omega(n^2),\ \Omega(2^n)$이라고 나타낼 수 없다. 이를 마찬가지로 일반화하면 가능한 $g(n)$은 다음 조건을 만족한다고도 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$\lim\limits_{n \to \infty} \frac{g(n)}{f(n)} &amp;gt; 0$&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Big-$\Omega$ 표기법이 나타내는 의미는 어떤 함수 $f(n)$이 충분히 큰 $n$에서 $g(n)$보다 크다는 하한을 나타낸다. Big-$O$와 Big-$\Omega$는 서로 다른 영역의 함수를 나타내므로 $O(g(n)) \cup \Omega(g(n))$은 함수 전체 집합을 의미한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다고 $O(g(n)) \cap \Omega(g(n)) \neq \varnothing$인데 위의 예시에서 $n = O(n), n = \Omega(n)$을 동시에 만족한다. 이를 나타내는 표기법은 따로 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;Big-$\Theta$ 표기법&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 함수 $f(n)$이 다음 조건을 만족하는 집합에 있으면 $f(n) = \Theta(g(n))$이라고 한다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;$\Theta(g(n)) = \{f(n) : n_0 \le n $인 모든 $n$에 대하여 $0 \le c_1 \cdot g(n) \le f(n) \le c_2 \cdot g(n)$인 양의 상수 $c_1, c_2, n_0$이 존재하는 $f(n) \}$&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 나온 세 가지 표기법 중 가장 엄격한 조건을 제시한다. $\Theta(g(n)) = O(g(n)) \cap \Omega(g(n))$이 성립하고 상한과 하한을 동시에 표현한다. 일반화하면 가능한 $g(n)$은 다음 조건을 만족한다고도 볼 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$ 0 &amp;lt; \lim\limits_{n \to \infty} \frac{g(n)}{f(n)} &amp;lt; \infty$&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;$f(n)$이 다항함수라면 $g(n)$의 차수는 같을 수밖에 없다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;little-$o, \omega $표기법&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;정의는 비슷하고 등호만 뺀 것이니 간략하게 정의만 적고 넘어간다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$o(g(n)) = \{f(n) : n_0 \le n $인 모든 $n$에 대하여 $0 \le f(n) \lt c \cdot g(n)$인 양의 상수 $c, n_0$이 존재하는 $f(n) \}$&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;$\omega(g(n)) = \{f(n) : n_0 \le n $인 모든 $n$에 대하여 $0 \le c \cdot g(n) \lt f(n)$인 양의 상수 $c, n_0$이 존재하는 $f(n) \}$&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;점근적 표기법의 성질&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음이 성립한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$f(n) = O(f(n))$&lt;/li&gt;
&lt;li&gt;$c \cdot O(f(n)) = O(f(n))$&lt;/li&gt;
&lt;li&gt;$O(f(n)) + O(f(n)) = O(f(n))$&lt;/li&gt;
&lt;li&gt;$O(O(f(n))) = O(f(n))$&lt;/li&gt;
&lt;li&gt;$O(f(n))O(g(n)) = O(f(n)g(n))$&lt;/li&gt;
&lt;li&gt;$O(f(n)g(n)) = f(n)O(g(n))$&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;또한 테일러 급수(매크로린 급수)에서도 점근적 표기법을 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;$e^x = \displaystyle\sum_{n=0}^\infty \frac{1}{n!} x^n = 1 + x + \frac{1}{2!}x^2 + \cdots + \frac{1}{m!}x^m + O(x^{m + 1})$&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;오해와 진실&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;점근적 표기법은 알고리즘의 시간복잡도를 분석하기 위해 만들어진 표기법이다.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;알고리즘을 가장 처음 배울 때 시간복잡도를 접하며 점근적 표기법을 배우게 된다. 사실 지금까지 점근적 표기법을 설명하면서 알고리즘에 대한 내용은 하나도 없었다. 점근적 표기법은 &lt;b&gt;알고리즘의 시간복잡도만을 위한 것은 아니고&lt;/b&gt; 범용적으로 쓰이는 개념이다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Big-O는 최악, Big-$\Omega$는 최선 Big-$\Theta$는 평균 시간복잡도를 의미한다.&lt;/blockquote&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;가장 쉬운 예시로 삽입정렬을 생각해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1741263938061&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def insertion_sort(arr):
    cnt = 0
    n = len(arr)
    for i in range(1, n):
        cnt += 1
        key = arr[i]
        j = i - 1
        while j &amp;gt;= 0 and arr[j] &amp;gt; key:
            cnt += 1
            arr[j + 1] = arr[j]
            j -= 1
        arr[j + 1] = key
    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;삽입정렬은 이미 정렬이 되어있는 경우 가장 최선의 입력이라는 것이 알려져 있다. 실제로 최선의 입력인 정렬된 데이터를 넣고 수행 시간을 분석하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;a.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wjhxw/btsMEm8WXho/O87nEzjiBoM2zNyfod2q9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wjhxw/btsMEm8WXho/O87nEzjiBoM2zNyfod2q9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wjhxw/btsMEm8WXho/O87nEzjiBoM2zNyfod2q9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwjhxw%2FbtsMEm8WXho%2FO87nEzjiBoM2zNyfod2q9K%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;300&quot; data-filename=&quot;a.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빨간색 그래프가 실제 결과 $f(n)$인데 각 점근적 표기법의 정의에 따르면 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$c = 2, n_0 = 10$으로 정하면 Big-$O$의 정의를 만족하므로 $f(n) = O(n)$으로 볼 수 있다.&lt;/li&gt;
&lt;li&gt;$c = 0.5, n_0 = 10$으로 정하면 Big-$\Omega$의 정의를 만족하므로 $f(n) = \Omega(n)$으로 볼 수 있다.&lt;/li&gt;
&lt;li&gt;$c_1 = 0.5, c_2 = 2, n_0 = 10$으로 정하면 Big-$\Theta$의 정의를 만족하므로 $f(n) = \Theta(n)$으로 볼 수 있다.&lt;/li&gt;
&lt;/ul&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;a.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdJUdE/btsMD0ytE9y/WmmIijyyr4PjykZ5uO27K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdJUdE/btsMD0ytE9y/WmmIijyyr4PjykZ5uO27K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdJUdE/btsMD0ytE9y/WmmIijyyr4PjykZ5uO27K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdJUdE%2FbtsMD0ytE9y%2FWmmIijyyr4PjykZ5uO27K0%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;300&quot; data-filename=&quot;a.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;480&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;$c = 1, n_0 = 10$으로 정하면 Big-$O$의 정의를 만족하므로 $f(n) = O(n^2)$으로 볼 수 있다.&lt;/li&gt;
&lt;li&gt;$c = 0.2, n_0 = 10$으로 정하면 Big-$\Omega$의 정의를 만족하므로 $f(n) = \Omega(n^2)$으로 볼 수 있다.&lt;/li&gt;
&lt;li&gt;$c_1 = 0.2, c_1 = 2, n_0 = 10$으로 정하면 Big-$\Theta$의 정의를 만족하므로 $f(n) = \Theta(n^2)$으로 볼 수 있다.&lt;/li&gt;
&lt;/ul&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;a.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beEnI1/btsMBZVxtr8/xKmLqjL37ZLGqAlX1pquk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beEnI1/btsMBZVxtr8/xKmLqjL37ZLGqAlX1pquk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beEnI1/btsMBZVxtr8/xKmLqjL37ZLGqAlX1pquk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeEnI1%2FbtsMBZVxtr8%2FxKmLqjL37ZLGqAlX1pquk0%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;300&quot; data-filename=&quot;a.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;480&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$c = 1, n_0 = 10$으로 정하면 Big-$O$의 정의를 만족하므로 $f(n) = O(n^2)$으로 볼 수 있다.&lt;/li&gt;
&lt;li&gt;$c = 0.2, n_0 = 10$으로 정하면 Big-$\Omega$의 정의를 만족하므로 $f(n) = \Omega(n^2)$으로 볼 수 있다.&lt;/li&gt;
&lt;li&gt;$c_1 = 0.2, c_1 = 2, n_0 = 10$으로 정하면 Big-$\Theta$의 정의를 만족하므로 $f(n) = \Theta(n^2)$으로 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 최선, 최악, 평균에 대해 Big-$O, \Omega, \Theta$를 정의할 수 있다. 뇌피셜이긴 하지만 주어진 알고리즘에 조건문이 없고 반복문이 입력의 크기에만 의존한다면 모든 상황에서 시간복잡도가 같아질 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 Big-$O$를 많이 쓸까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 알고리즘을 설명할 때 $O(n), O(n \log{n})$같이 Big-$O$표기법을 많이 사용한다. 여기에 대해 여러 사람들의 의견을 구했는데 다음과 같은 결론이 나왔다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;셋 중 유일하게 아스키코드에 들어있어서 타이핑하기 쉬웠다.&lt;/li&gt;
&lt;li&gt;최악의 입력에 대한 수행시간의 상한이 다른 모든 입력을 커버할 수 있기 때문에 상한을 나타내는 Big-$O$를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 Big-$O$로 표현할 때는 위와 같이 최선, 최악, 평균에 대해 모두 정의할 수 있기 때문에 엄밀하게는 매번 &quot;merge-sort는 최악 $O(n\log{n})$이다.&quot;, &quot;floyd-warshall은 최악 $O(n^3)$이다.&quot; 라고 말해야 하지만 워낙 관습적으로 사용하기도 하고 많이 표현하기 때문에 &quot;최악&quot;을 제외하고 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&quot;merge-sort는 $O(n\log{n})$이다.&quot;라고 편하게 표현하는 것 같다.&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;그럼 little-$o, \omega$는 언제 쓸까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;편의상 little-$o$만 보자. 좀 심연으로 들어가면 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%8A%88%ED%8A%B8%EB%9D%BC%EC%84%BC_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이런 거&lt;/a&gt;나 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%B9%B4%EB%9D%BC%EC%B6%94%EB%B0%94_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이런 것&lt;/a&gt; 같이 이상한 시간복잡도가 나온다. 널리 알려져있는 $n \times n$ 행렬 두 개의 곱은 $O(n^3)$이지만 이 보다 빠른 알고리즘인 링크에 있는 슈트라센 알고리즘도 $O(n^{2.81})$이라고는 하지만 복잡하니 $o(n^3)$으로 볼 수 있다. 물론 $O(n^3)$도 맞는 말. 주로 $O(n^{3 - \epsilon})$같은 알고리즘을 찾을 때 $o(n^3)$이라고 쓰는 것 같다.&lt;/p&gt;</description>
      <category>프로그래밍/알고리즘</category>
      <category>알고리즘</category>
      <category>점근적 표기법</category>
      <author>riroan</author>
      <guid isPermaLink="true">https://riroan.tistory.com/185</guid>
      <comments>https://riroan.tistory.com/185#entry185comment</comments>
      <pubDate>Thu, 6 Mar 2025 22:13:38 +0900</pubDate>
    </item>
    <item>
      <title>2024년을 되돌아보며</title>
      <link>https://riroan.tistory.com/184</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;정신을 차려보니 벌써 2024년이 다 갔다. &lt;a href=&quot;https://riroan.tistory.com/167&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;&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;가장 큰 변화이다. 2024년 카카오 인턴을 거쳐 &lt;a href=&quot;https://riroan.tistory.com/172&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;정직원 전환 후 본격적으로 도메인에 대해 학습했다. 도메인은 클라우드 컴퓨팅이었는데 상당히 폭 넓은 분야를 알아야 했다. AWS제품으로 보면 EC2, VPC, ELB, EBS 정도를 다룬다. 개발팀이기 때문에 웹 개발 지식은 물론 서비스를 다루기 위한 컴퓨터 구조, 운영체제, 컴퓨터 네트워크 같은 CS지식도 폭 넓게 필요하다. 나는 실무에 사용되는 알고리즘에 대해서는 자신감이 있었지만 다른 과목에 대해서는 그 정도는 아니었기 때문에 개인적으로 아래 전공책을 되돌아보며 학습을 하곤 했다.&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 style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;Abraham Silberschatz (a.k.a. 공룡책)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: center;&quot;&gt;컴퓨터 네트워킹 하향식 접근&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000; text-align: center;&quot;&gt;컴퓨터 구조, William Stallings&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한 팀 내에서도 &lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000061897997&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;소프트웨어 아키텍처 The Hard Parts&lt;/a&gt;, Virtualization 같은 스터디를 진행하며 지식을 쌓을 수 있었다. 뛰어난 실력을 가진 팀원들로부터 많은 것을 배울 수 있었다. 아직 도메인을 완전히 파악한 것은 아니지만 그래도 입사 초기보다는 많은 것을 알 수 있었고 사용자의 불편에 어느 정도 대응할 수 있는 정도로 성장했다. 물론 개발 지식도 많이 습득했다.&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_KakaoTalk_Photo_2024-12-28-17-00-59.jpeg&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/exzSal/btsLz3qGNWB/drQo1r0StYGgq6JVEtT0hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/exzSal/btsLz3qGNWB/drQo1r0StYGgq6JVEtT0hk/img.png&quot; data-alt=&quot;카카오의 밤 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/exzSal/btsLz3qGNWB/drQo1r0StYGgq6JVEtT0hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FexzSal%2FbtsLz3qGNWB%2FdrQo1r0StYGgq6JVEtT0hk%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;687&quot; height=&quot;916&quot; data-filename=&quot;edited_KakaoTalk_Photo_2024-12-28-17-00-59.jpeg&quot; data-origin-width=&quot;1038&quot; data-origin-height=&quot;956&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;카카오의 밤 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;블로그&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;올해는 블로그에 글을 많이 쓰지 않은 것 같다. 쓰더라도 대부분 알고리즘 관련된 글이고 개발 관련된 글은 거의 없었는데 사실 쓸 내용은 많았는데 주제에 대해 깊이있게 알고 있다고 생각하지 않아서 좀 더 공부하고 쓰려고 미뤘던 것 같다. 그렇게 미뤄둔 주제는 아래와 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CQRS&lt;/li&gt;
&lt;li&gt;Event Store&lt;/li&gt;
&lt;li&gt;MSA&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;&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;a href=&quot;https://kth990303.tistory.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 친구&lt;/a&gt;가 재밌게 하는 걸 보고 나도 &quot;언젠간 해보고 싶다.&quot; 라는 마음만 가지고 미루고 미루다가 카카오 동기와 함께 처음 맛보기로 시작했다. 처음 갈 땐 한 두번 해보고 흥미를 못 느껴 그만둘 줄 알았으나 생각보다 너무 재밌어서 꾸준히 가고 있다. 판교 손상원 클라이밍 짐에 10회권까지 끊었다!! 클라이밍화도 빨리 사야되는데 경황이 없어 못사고 있었다. 이미 손익분기점을 넘긴 상태 ㅠㅠ&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카카오에 클라이밍 동호회가 있는데 동호회비만 내면 월 1회 무료로 클라이밍을 갈 수 있다고 한다. 동호회비가 클라이밍 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;&lt;a href=&quot;https://www.instagram.com/stories/highlights/18327420367182644/&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;&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;클라이밍과 별개로 헬스를 다시 시작했다. 작년 8월경에 이사를 하면서 다니던 헬스를 그만 뒀는데 그 이후로 새로운 헬스장을 찾지 못하다가 올해 9월쯤 회사 근처로 다니기 시작했다. 같은 회사 동기들이 다니는 곳이라 같이 하고 있다. 역시 같이 갈 사람이 있으니 억지로라도 꾸준히 가게 되는 것 같아서 좋았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운동의 목표는 다이어트와 근육량 증가이다. 올해 맛있는 것을 너무 많이 먹었는지 짧은 시간만에 살이 많이 찌게 되었다... 그래서 이러한 과거의 나태함을 청산하기 위해 운동을 열심히 해야한다. 단기적으로는 인바디를 측정했을 때 체중 / 근육량 / 체지방 수치가 C자를 하고 있는데 (근육량이 체중 / 체지방보다 높은 상태) 이를 D자형으로 만드는 것이 목표이다. (근육량이 체중 / 체지방보다 높은 상태)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다이어트를 위해 달리기도 시작했다. 원래는 밖에서 뛰었지만 지금은 추워서 헬스장의 런닝머신을 사용하고 있다. 카카오 동기중에 마라톤을 뛸 정도로 달리기를 잘하는 동기가 있어서 많은 것을 배우고 적용하려고 노력했다. 한 달정도는 매 주말마다 달리기를 약 3~4km정도 하고 평일 점심을 샐러드로만 채우니 눈에 띄는 체중감량이 있었다!&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-28 16-39-10.png&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2vpep/btsLBtaD3e3/vmmTGIPtK4XkBWReNiBLT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2vpep/btsLBtaD3e3/vmmTGIPtK4XkBWReNiBLT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2vpep/btsLBtaD3e3/vmmTGIPtK4XkBWReNiBLT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2vpep%2FbtsLBtaD3e3%2FvmmTGIPtK4XkBWReNiBLT1%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;1480&quot; height=&quot;506&quot; data-filename=&quot;스크린샷 2024-12-28 16-39-10.png&quot; data-origin-width=&quot;1480&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 요즘은 알고리즘을 열심히 하진 않는다. 그래도 스트릭이 끊어지지 않게 유지는 하고 있지만 탈출하지 못한 굴레때문에 하는 것이지 예전만큼 흥미가 있는 것 같지는 않다. 해결한 문제는 대부분 브론즈이며 심지어 3월에는 한번 끊어질 뻔 하기도 했다. 코드포스는 &lt;a href=&quot;https://riroan.tistory.com/163&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;그래도 내가 참가할 수 있는 대회라면 가리지 않고 나가고는 있다. 올해도 &lt;a href=&quot;https://riroan.tistory.com/174&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;서울대학교 대회&lt;/a&gt;나 &lt;a href=&quot;https://riroan.tistory.com/175&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;이러한 알고리즘 경험을 가지고 실무에서 알고리즘 지식을 사용해야 할 때나 코딩테스트를 평가할 때 많은 기여를 할 수 있었다. 앞으로도 감을 잃지 않을 정도로 유지해야겠다.&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;자취를 시작했다. 원래 살던 집이 중랑구여서 판교까지 출퇴근하려면 1시간 좀 넘게 걸렸는데 이번에 정자동쪽에서 자취를 시작해서 출퇴근 시간이 10분으로 줄어들었다! 전부터 해보고 싶기도 했고 회사에서 지원 나오는 것도 있으니 이 기회에 시작해봐도 좋을 것이라고 생각해서 시작했다. 집은 8월부터 구하기 시작했는데 신분당선 라인으로 논현 ~ 강남 쪽으로 보고 있었는데 마음에 드는 집을 구하기 힘들었다. 그러다 팀원의 &quot;서울이 정답은 아닐 수 있다.&quot;라는 말을 듣고 경기도 쪽으로도 알아봤다. 나는 살면서 훈련소때 외에는 서울에서 벗어나 살아본 적이 없어서 큰 결심이 필요했다. 찾다가 정자쪽에 넓고 괜찮은 오피스텔이 있어서 바로 계약하여 12월부터 살고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 자취하면 하고 싶었던 방 꾸미기나 친구 부르기, 요리하기 등 버킷리스트를 채우면서 너무 잘 살고 있다. 혼자 살면 재밌을 것이라고 생각했지만 살짝 외로움을 타는 것 같았다. 그래서 주말이나 쉬는 날 사람보러 나가고 친구들을 집에 초대하는 등 사람을 많이 만나려고 하는 것 같다. 원래는 쉬는 날이면 집밖에 절대 안나가고 알고리즘 문제풀이나 코딩 같은 것을 하곤 했는데 성격이 참 많이 바뀐 것 같다. 실제로 회사생활을 하면서 T -&amp;gt; F로 바뀌기도 했다.&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;2025년은 무엇을 할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 회고를 1년마다 하지만 다른 사람들의 이야기를 들어보면 일기를 쓰는 사람도 있고 1달마다 하는 사람도 있고 안하는 사람도 있다. 이러한 이야기를 듣다보면 나도 뭔가 회고의 주기를 좀 줄여볼까 하는 마음이 생기기도 한다. (실제로 지키는 지는 다른 이야기이지만) 일기쓰는 사람의 이야기를 들어보면 매일매일이 같은 일이 반복된다고 생각해도 하루에 일어난 일을 생각하며 쓰다보면 아무리 사소한 것이라도 다른 점이 있다고 한다. 또한 전에 쓴 일기를 돌아보면 추억하기도 좋다고 한다. 그래서 회고의 주기를 줄여볼까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 내년에는 뭔가 취미를 하나 가지는 게 좋을 것 같다. 지금도 클라이밍이라는 취미가 있지만 가는 주기가 길기도 하고 다른 취미가 하나 더 있으면 좋을 것 같다고 생각하긴 했다. 아직 구체적으로 정한 건 아니지만 일단 목표엔 넣어놓은 상태!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 자기계발에 좀 힘을 쏟을 것 같다. 요즘 인문학에 좀 빠져서 관련 책들을 많이 읽고 있는데 &quot;좋은 사람이 된다는 것&quot;은 정말 추상적이고 다방면으로 어려운 일이다. 아직 깊은 것을 깨닫진 못했지만 다양한 노력을 하여 많은 사람에게 좋은 사람이 되는 것을 목표로 잡았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 의미에서 인생에서 행복도 찾고 싶다. 요즘 팀원들과 이런 철학적인 이야기를 많이 하는데 가장 흥미로웠던 주제가 &quot;행복이란 무엇인가?&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;2025년도 파이팅!&lt;/p&gt;</description>
      <category>기타/기타</category>
      <category>개발</category>
      <category>일상</category>
      <category>회고</category>
      <author>riroan</author>
      <guid isPermaLink="true">https://riroan.tistory.com/184</guid>
      <comments>https://riroan.tistory.com/184#entry184comment</comments>
      <pubDate>Sat, 28 Dec 2024 17:16:38 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] Splay Tree를 사용해서 해결할 수 있는 유형</title>
      <link>https://riroan.tistory.com/183</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://riroan.tistory.com/182&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Splay Tree&lt;/a&gt; 를 사용하면 백준 태그에 Splay Tree가 붙은 문제 외에도 많은 문제를 해결할 수 있다. 여기에서 해결할 수 있는 문제들을 유형별로 정리하고 풀이를 간단하게 소개한다. 문제의 순서는 난이도 순이 아닐 수 있다.&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;Splay Tree는 값의 insert, delete, find연산을 지원한다. 이를 사용하여 해결할 수 있는 문제들이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11723&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;11723. 집합&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 비트마스킹 연습하라고 메모리가 적게 세팅된 문제이지만 Splay Tree를 잘 구현하면 제한 안에 통과할 수 있다. Splay Tree를 연습하기 좋다. insert와 delete, find같은 기본 연산만으로 해결할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1269&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;1269. 대칭 차집합&lt;/b&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11723을 해결하며 toggle을 잘 구현했다면 toggle만으로 해결할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1822&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1822. 차집합&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Splay Tree를 구성하여 $A$의 모든 원소를 insert하고 $B$의 모든 원소를 erase한 후 남은 원소들을 출력하면 된다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://www.acmicpc.net/problem/8834&quot;&gt;8834. Kolejka&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전에 들어온 사람 인덱스를 기억하여 해당 인덱스 뒤에 insert하면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://www.acmicpc.net/problem/31590&quot;&gt;&lt;b&gt;31590. Candy Compress&lt;/b&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://www.acmicpc.net/problem/9548&quot;&gt;9548. 무제&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리에서 소개하는대로 삽입하라는 곳에 삽입하고 제거하라는 곳에 제거하면 된다. 구간 삽입/제거이지만 naive하게 해도 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/16586&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;16586. Linked List&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 결과만 출력하라고 했다면 문제 제목대로 Linked List를 사용하여 해결하면 되지만 거리까지 구하라고 해서 Splay Tree를 사용해야한다. 그냥 $a$가 있는 위치의 노드를 erase하고 $b$의 오른쪽에 붙여준 후 두 인덱스의 차이를 구해주면 된다. 인덱스는 find_kth한 후 왼쪽 서브트리의 크기와 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Data Structure 유형&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/10828&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;10828. 스택&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/10845&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;10845. 큐&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/10866&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;10866. 덱&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1927&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1927. 최소 힙&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/7662&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;7662. 이중 우선순위 큐&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Splay Tree는 스택, 큐, 덱같은 선형 자료구조는 물론, 우선순위 큐, 이중 우선순위 큐같은 일부 2차원 자료구조도 해결할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Segment Tree 유형&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Splay Tree의 구간을 한 Node로 모으면 구간 쿼리를 처리할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2042&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2042. 구간 합 구하기&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2357&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;2357. 최솟값과 최댓값&lt;/b&gt;&lt;/a&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/11505&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;11505. 구간 곱 구하기&lt;/b&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Segment Tree를 처음 배울 때 접하게 되는 구간 쿼리 유형이다. 단일 업데이트이므로 find_kth를 사용하여 루트로 올린 후 업데이트하고 Node를 모아 구간 쿼리를 처리한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/10999&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;10999. 구간 합 구하기 2&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/14245&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;14245. XOR&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1395&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1395. 스위치&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lazy propagation을 처음 배울 때 접하게 되는 문제들이다. 구간 업데이트이므로 Node를 먼저 모아 lazy를 setting하고 lazy가 있는 노드들을 rotate할때마다 push하여 업데이트하고 Node를 모아 구간 쿼리를 처리한다.&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;BBST 유형&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Splay Tree도 PBDS와 마찬가지로 특정 값을 가진 원소의 인덱스 찾기, 특정 위치에 있는 원소의 값 찾기 연산을 각각 지원하므로 PBDS로 해결할 수 있는 문제를 해결할 수 있다. Segment Tree로도 해결할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://www.acmicpc.net/problem/1849&quot;&gt;1849. 순열&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.acmicpc.net/problem/10090&quot;&gt;&lt;b&gt;10090. Counting Inversions&lt;/b&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BBST의 특성을 가장 잘 사용할 수 있는 Inversion Counting 유형이다. Splay Tree를 BBST로 사용하면 현재 노드의 오른쪽 서브트리에 현재 노드 값보다 큰 값들만 모여있기 때문에 앞에서부터 Splay Tree에 insert하면서 오른쪽 서브트리의 개수를 더해주면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/1168&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;1168. 요세푸스 문제 2&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/30853&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;30853. Black Box&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/31865&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;31865. 수열 만들기&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요세푸스 유형이다. 다음 제거 대상인 인덱스를 찾는 것은 쉽게 구할 수 있다. 해당 인덱스를 삭제하면 되기 때문에 Splay Tree를 구성한 후 erase만으로 해결할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Small to Large trick 유형&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;small to large는 집합을 합칠 때 작은 것을 큰 것에 합쳐서 시간복잡도의 이득을 보는 트릭이다. Splay Tree는 두 집합을 합칠 때 포인터 연산을 하기 때문에 상수시간에 합칠 수 있다. 작은 것에 큰 것을 합쳐도 시간복잡도가 똑같다!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/28277&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;28277. 뭉쳐야 산다&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 Splay Tree를 합치는 연산을 구현하여 1번 쿼리를 해결할 수 있다. 이는 위에서 언급한 것 처럼 트리의 루트를 다른 트리의 왼쪽 끝이나 오른쪽 끝에 붙여서 구현할 수 있다. 2번 쿼리는 트리의 크기를 출력하면 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;reverse, shift 유형&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적으로 변화하는 Splay Tree의 특성을 사용하여 구간 뒤집기와 shift를 하여 문제를 해결할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/16994&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;16994. 로프와 쿼리&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/14131&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;14131. Okret&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13159&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;13159. 배열&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에서 소개하는대로 정직하게 reverse와 shift를 구현하여 해결하면 된다. 난이도 차이가 왜 있는지는 모르겠다. 배열 문제는 구간 쿼리나 점쿼리가 포함되어 있지만 위의 문제들을 해결했다면 어렵지 않게 해결할 수 있는 문제&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/19589&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;19589. 카드 셔플&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1, 2는 단순 shift쿼리이고 3번은 제한이 1000이기때문에 dfs를 사용해서 해결해도 시간이 넉넉해서 통과된다.&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;기본 lazy propagation은 구간에 같은 값을 더하거나 대입하지만 등차수열로 업데이트하라는 문제가 있다. lazy값을 push할 때 초항과 공차를 잘 push하면 된다. 초항이 $a$이고 공차가 $d$인 등차수열을 구간에 업데이트하려면 아래 그림과같이 하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-24 22-45-41.png&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bitXgA/btsKiphgTud/bpPukdSFK22mp1BVcULCi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bitXgA/btsKiphgTud/bpPukdSFK22mp1BVcULCi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bitXgA/btsKiphgTud/bpPukdSFK22mp1BVcULCi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbitXgA%2FbtsKiphgTud%2FbpPukdSFK22mp1BVcULCi0%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;786&quot; height=&quot;486&quot; data-filename=&quot;스크린샷 2024-10-24 22-45-41.png&quot; data-origin-width=&quot;1096&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구간 합을 기록하려면 등차수열 합 공식인 $S = n \frac{a + l}{2}$를 더해주면 된다. $a$는 구간 초항, $l$은 구간 마지막 항&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/17353&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;17353. 하늘에서 떨어지는 1, 2, ..., R-L+1개의 별&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/20305&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;20305. 피보나치와 수열과 쿼리&lt;/b&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 업데이트를 잘 했다면 이 두 문제를 해결할 수 있다. 20305는 값을 더할 때 값에 대응하는 피보나치 수를 더해주면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/26087&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;26087. 피보나치와 마지막 수열과 쿼리&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정해는 20305와 많이 다르지만 Splay Tree를 사용하면 한 줄만 바꿔서 통과할 수 있다. 20305번에서 값을 업데이트할 때 한 덧셈연산을 대입연산으로 바꾸기만하면 해결할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2844&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2844. 자료 구조&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1, 3, 4번쿼리는 위에서 많이 나온 유형이고 2번은 등차수열을 업데이트하라는 쿼리이므로 위 문제들과 유사하게 해결하면 된다.&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/16993&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;16993. 연속합과 쿼리&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/17346&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;17346. Maintaining a Sequence&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Splay Tree에서 위 방법으로 구간 최대 부분합을 구할 수 있다면 위 문제를 해결할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/17607&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;17607. 수열과 쿼리 31&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번은 단순 reverse쿼리이다. 2번 쿼리를 해결하기 위해 연속 최대 부분합에서 조건을 단순 합이 아닌 1의 개수를 세는 것으로 바꾸면 해결할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Splay Tree로 해결할 수 있는 고난도의 문제가 많아서 제대로 이해했다면 다이아까지는 문제 없이 달성할 수 있을 것이다...&lt;/p&gt;</description>
      <category>프로그래밍/알고리즘</category>
      <category>splay tree</category>
      <category>알고리즘</category>
      <author>riroan</author>
      <guid isPermaLink="true">https://riroan.tistory.com/183</guid>
      <comments>https://riroan.tistory.com/183#entry183comment</comments>
      <pubDate>Thu, 24 Oct 2024 23:01:14 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] PS를 위한 Splay Tree</title>
      <link>https://riroan.tistory.com/182</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Splay Tree는 Balanced Binary Search Tree의 한 종류로 데이터의 삽입, 삭제, 탐색을 $O(\log N)$에 준하는 속도로 처리할 수 있도록 하는 자료구조이다. 보통 학부과정에선 저런 기본 연산들을 배우게 되는데 Splay Tree를 사용해야만 해결할 수 있는 알고리즘 문제에서는 극한의 테크닉을 사용하여 많은 쿼리를 처리할 수 있다. 이 글에서는 Splay Tree가 어떤 자료구조인지 소개하기보다는 해결할 수 있는 application을 중점으로 소개한다. Splay Tree는 1985년에 Self-Adjusting Binary Search Tree라는 논문으로 소개가 되었고 여기에도 Tarjan이 저자로 들어가있다. 정말 많은 공헌을 하신 분이다.&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;Basic&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Splay Tree에는 Rotate와 Splay 연산이 들어있는데 이는 간략하게 짚고 넘어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Rotate&lt;/b&gt; 연산은 현재 노드를 부모 위치로 옮기는 연산이다. 그림을 보면 쉽게 이해할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wWCGk/btsJT9kZMZy/TewDngxhXp0U3N9OZxJluk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wWCGk/btsJT9kZMZy/TewDngxhXp0U3N9OZxJluk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;918&quot; data-filename=&quot;스크린샷 2024-10-03 10-39-13.png&quot; width=&quot;532&quot; height=&quot;458&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wWCGk/btsJT9kZMZy/TewDngxhXp0U3N9OZxJluk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwWCGk%2FbtsJT9kZMZy%2FTewDngxhXp0U3N9OZxJluk%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;1067&quot; height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E3CrH/btsJTfTXJUe/7eGnIRCzRfJz9ExbUZKi91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E3CrH/btsJTfTXJUe/7eGnIRCzRfJz9ExbUZKi91/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;918&quot; data-filename=&quot;스크린샷 2024-10-03 10-40-52.png&quot; width=&quot;438&quot; height=&quot;377&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E3CrH/btsJTfTXJUe/7eGnIRCzRfJz9ExbUZKi91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE3CrH%2FbtsJTfTXJUe%2F7eGnIRCzRfJz9ExbUZKi91%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;1067&quot; height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;Rotate&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 여전히 BBST의 특징인 중위순회 했을 때 정렬된 순서가 유지되고 특정 노드의 위치를 바꿀 수 있다. 링크드리스트를 구현하듯이 C++의 포인터를 사용하여 구현하면 편하다. 파이썬으로는 안해봤는데 조금 불편하지만 가능할 것 같다.&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;Splay&lt;/b&gt; 연산은 특정 노드를 Rotate연산을 통해 루트로 옮기는 연산이다. Rotate 연산을 하면 현재 노드를 부모의 위치로 옮기니까 적당한 횟수를 수행하면 root에 도달하게 된다. 여기에서 Rotate하는 데 규칙이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조부모-부모, 부모-현재 노드의 방향이 같다. -&amp;gt; 부모를 먼저 Rotate하고 현재 노드를 Rotate한다. (zig-zig step)&lt;/li&gt;
&lt;li&gt;조부모-부모, 부모-현재 노드의 방향이 다르다. -&amp;gt; 현재 노드를 두 번 Rotate한다. (zig-zag step)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방향이 같다는 것은 부모가 조부모의 왼쪽 노드, 현재 노드가 부모의 왼쪽 노드인 경우를 말한다. 대부분 쿼리를 처리하기 전에 Splay연산을 한 후 진행하게 된다. Splay 트리는 AVL 트리와 다르게 트리 자체가 balancing 하진 않다. 좌우 서브트리 높이 차이가 클 수도 있으며 연산을 진행하며 편향된 모양의 트리가 나오기도 한다. 그래서 언제나 $O(\log N)$이 되는 건 아니고 가끔 편향된 상태로 Splay를 수행하면 $O(N)$이 걸릴 수도 있다. 다만 편향된 상태에서 Splay를 진행하면 높이가 절반정도로 줄어들고 다음 연산부터는 다시 $O(\log N)$에 수행할 수 있다. 이러한 특성 때문에 Splay tree의 연산들은 amortized $O(\log 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;그럼 이제 Splay tree를 사용하여 처리할 수 있는 쿼리들을 알아보자.&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;삽입연산은 BBST에서 탐색하듯이 내려가는 현재 노드 값보다 크면 오른쪽, 작으면 왼쪽으로 이동하며 삽입할 위치를 찾으면 된다. 찾았다면 그 위치에 노드를 추가하고 해당 노드를 Splay하여 다음 연산에 용이하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제연산은 삭제할 노드를 Splay하여 루트로 올리고 삭제한 후 양쪽 서브트리를 합쳐주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탐색연산은 삽입할 때처럼 BBST탐색하여 노드를 찾고 Splay하여 루트로 올려준다.&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;이 연산들은 set, map에서도 지원하는 기능이라 이 쿼리만을 위해 Splay tree를 사용할 필요는 없다.&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;$K$번째 값 찾기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 노드에 서브트리의 크기를 추가로 저장하면 K번째 값을 찾을 수 있다. 그리고 Rotate 할 때마다 서브트리의 크기가 바뀌기때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업데이트해야하는데 이는 Rotate 대상이 되는 현재 노드와 부모 노드의 값만 업데이트하면 된다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Wlnr/btsJUZPp0PB/clkr5syoaIcvc4RinWHkjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Wlnr/btsJUZPp0PB/clkr5syoaIcvc4RinWHkjK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1059&quot; data-origin-height=&quot;958&quot; data-filename=&quot;스크린샷 2024-10-03 13-37-41.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Wlnr/btsJUZPp0PB/clkr5syoaIcvc4RinWHkjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Wlnr%2FbtsJUZPp0PB%2Fclkr5syoaIcvc4RinWHkjK%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;1059&quot; height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BYGtW/btsJSJHQqeN/xrLSoGT3CmGLJpjjBseKAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BYGtW/btsJSJHQqeN/xrLSoGT3CmGLJpjjBseKAK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1059&quot; data-origin-height=&quot;958&quot; data-filename=&quot;스크린샷 2024-10-03 13-46-00.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BYGtW/btsJSJHQqeN/xrLSoGT3CmGLJpjjBseKAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBYGtW%2FbtsJSJHQqeN%2FxrLSoGT3CmGLJpjjBseKAK%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;1059&quot; height=&quot;958&quot;/&gt;&lt;/span&gt;&lt;/div&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, C$라고 할 때 현재 노드를 루트로 하는 서브트리의 크기는 $A + B + 1$에서 $A + B + C + 2$가 된다. 즉 Rotate할 때 현재 서브트리 크기 값에서 $C + 1$을 더해준다. 부모 노드를 루트로 하는 서브트리의 크기는 $A + B + C + 2$에서 $B + C + 1$가 된다. 부모의 서브트리 크기 값에서 $A + 1$을 빼면 된다.&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$번째 값을 찾으려면 다음 과정을 루트부터 재귀적으로 수행하면 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$K$가 왼쪽 서브트리의 크기보다 작거나 같으면 왼쪽으로 내려간다.&lt;/li&gt;
&lt;li&gt;$K$가 왼쪽 서브트리의 크기 + 1이라면 현재 노드의 값이 $K$번째 값이다.&lt;/li&gt;
&lt;li&gt;$K$가 왼쪽 서브트리의 크기 + 1보다 크다면 $K$에서 왼쪽 서브트리의 크기 + 1 을 빼고 오른쪽으로 내려간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$K$번째 값에 해당하는 노드를 찾았다면 Splay연산을 통해 루트로 보낸다. 이 작업을 수행하는 함수를 앞으로 find_kth(K)라고 부를 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리도 &lt;a href=&quot;https://riroan.tistory.com/63&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PBDS&lt;/a&gt;를 사용하면 쉽게 해결할 수 있으므로 Splay tree를 사용할 필요는 없다.&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;세그먼트트리로 해결할 수 있는 구간쿼리문제(구간합, 최대/최소)의 대부분은 Splay tree로도 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구간 합 쿼리를 예로 들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 주어진 배열을 Splay tree로 변환해야한다. 하지만 BBST의 특성상 수열을 그냥 insert하면 정렬시켜버리기 때문에 구간쿼리를 처리할 수 없다. 그래서 이러한 쿼리를 처리할 때는 insert함수를 수정하여 무조건 가장 오른쪽에만 insert하도록 수정한다. 그렇다면 인덱스 접근은 어떻게 하는가? 배열의 $i$번째 인덱스를 Splay tree에서 접근하려면 위에서 소개한 find_kth(i)를 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구간 합 쿼리를 수행하기 위해 각 정점에 sum값을 저장한다. sum값은 현재 노드를 루트로 하는 서브트리 값의 총 합이다. 이는 find_kth에서 서브트리 크기를 관리한 것과 동일하게 관리하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 $[L, R]$범위에 포함된 구간 합을 구하는 쿼리를 하려면 어떻게 할까? 굉장히 단순하다!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$R+1$번째 노드를 Splay한다.&lt;/li&gt;
&lt;li&gt;$L-1$번째 노드를 Splay한다.&lt;/li&gt;
&lt;li&gt;구하고자 하는 값은 루트의 오른쪽 자식의 왼쪽 자식의 sum값이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QCZc2/btsJU17xCmo/BpVxOeKCTVzQGU8clw8bsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QCZc2/btsJU17xCmo/BpVxOeKCTVzQGU8clw8bsk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1225&quot; data-origin-height=&quot;978&quot; data-filename=&quot;스크린샷 2024-10-03 14-25-01.png&quot; style=&quot;width: 36.5925%; margin-right: 10px;&quot; data-widthpercent=&quot;37.46&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QCZc2/btsJU17xCmo/BpVxOeKCTVzQGU8clw8bsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQCZc2%2FbtsJU17xCmo%2FBpVxOeKCTVzQGU8clw8bsk%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;1225&quot; height=&quot;978&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6KEtZ/btsJSLTdpCK/QcApUNeZ7Sr5kiMGFtZ5Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6KEtZ/btsJSLTdpCK/QcApUNeZ7Sr5kiMGFtZ5Uk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;997&quot; data-filename=&quot;스크린샷 2024-10-03 14-25-06.png&quot; style=&quot;width: 29.097%; margin-right: 10px;&quot; data-widthpercent=&quot;29.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6KEtZ/btsJSLTdpCK/QcApUNeZ7Sr5kiMGFtZ5Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6KEtZ%2FbtsJSLTdpCK%2FQcApUNeZ7Sr5kiMGFtZ5Uk%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;993&quot; height=&quot;997&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6ziFa/btsJUihS2sJ/cEaAJh4vOfJMjm4L19jirk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6ziFa/btsJUihS2sJ/cEaAJh4vOfJMjm4L19jirk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;833&quot; data-filename=&quot;스크린샷 2024-10-03 14-25-12.png&quot; style=&quot;width: 31.9849%;&quot; data-widthpercent=&quot;32.75&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6ziFa/btsJUihS2sJ/cEaAJh4vOfJMjm4L19jirk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6ziFa%2FbtsJUihS2sJ%2FcEaAJh4vOfJMjm4L19jirk%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;912&quot; height=&quot;833&quot;/&gt;&lt;/span&gt;&lt;/div&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;두 번의 Splay 과정을 통해 $[L, R]$범위가 한 곳으로 모였다. 루트의 오른쪽 자식의 왼쪽 자식의 sum 값은 해당 범위에 존재하는 값들의 합이므로 구하고자하는 값이 된다. $L-1 \rightarrow R+1$순서대로 Splay하고 루트의 왼쪽 자식의 오른쪽 자식의 sum값을 구해도 같은 결과가 나온다. 구간 최대/최소 쿼리도 유사하게 진행하면 답을 얻을 수 있다.&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;Lazy 연산 수행하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 $[L, R]$구간을 모으고 나면 해당 노드에 lazy를 뿌려주어 lazy 갱신도 할 수 있다. 탐색하면서 내려가는 도중에 대상 노드에 lazy가 있다면 자식에게 뿌려주고 탐색하면 된다.&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;여기까진 Splay tree 없이도 가능한 쿼리들이었다. 그럼 이제 Splay tree를 사용해야만 처리할 수 있는 쿼리를 알아보자!&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;$i$번째 자리에 값 추가/삭제하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;find_kth(i)를 수행하면 왼쪽 서브트리의 크기는 $i-1$이 된다. 그렇다면 find_kth(i)를 수행하고 왼쪽 자식노드의 가장 오른쪽에 노드를 추가하면 $i$번째 자리에 값을 추가한 것과 같아진다. 추가하고 나면 sum값이나 서브트리의 크기를 잘 업데이트해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삭제도 마찬가지로 find_kth(i)를 한 다음에 처음에 설명한 삭제 연산을 수행하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 배열이면 $O(N)$이 걸렸을 연산을 Splay tree로 amortized $O(\log N)$에 수행했다!&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;Splay tree의 꽃이다. 세그먼트트리와 다르게 Splay tree는 트리 모양이 동적으로 변하기 때문에 뒤집기연산이 가능하다. $[L, R]$구간을 뒤집는다고 하자. 구간쿼리 수행할 때처럼 구간을 모아주고 구간을 뒤집어야하는 지 여부(is_flip)를 추가로 저장한다. 이는 lazy와 동일한 형태로 동작하며 탐색할때마다 is_flip이 활성화돼있다면 좌우 서브트리 위치를 swap하고 자식에게 is_flip을 전파한다. 서브트리 swap이지만 포인터 교환이기때문에 $O(1)$에 가능하다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bE6rde/btsJUxy77Rv/7Jh17zK8yho3lb3zdY6Es0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bE6rde/btsJUxy77Rv/7Jh17zK8yho3lb3zdY6Es0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;643&quot; data-filename=&quot;스크린샷 2024-10-03 14-27-04.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bE6rde/btsJUxy77Rv/7Jh17zK8yho3lb3zdY6Es0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbE6rde%2FbtsJUxy77Rv%2F7Jh17zK8yho3lb3zdY6Es0%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;755&quot; height=&quot;643&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HvYR4/btsJTCnUCUG/xYywP4bvzwXN6Kqb0MMpo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HvYR4/btsJTCnUCUG/xYywP4bvzwXN6Kqb0MMpo0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;643&quot; data-filename=&quot;스크린샷 2024-10-03 14-27-16.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HvYR4/btsJTCnUCUG/xYywP4bvzwXN6Kqb0MMpo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHvYR4%2FbtsJTCnUCUG%2FxYywP4bvzwXN6Kqb0MMpo0%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;755&quot; height=&quot;643&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구간 shift&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구간 뒤집기가 가능하면 구간 shift도 가능하다. 예를 들어 $[1, 2, 3, 4, 5, 6]$인 배열을 오른쪽으로 $2$만큼 shift한다고 하자. 결과는 $[5, 6, 1, 2, 3, 4]$가 될 것이다. 전체를 뒤집으면 $[6, 5, 4, 3, 2, 1]$이 되고 앞에서 2만큼 뒤집으면 $[5, 6, 4, 3, 2, 1]$, 남은 구간을 뒤집으면 $[5, 6, 1, 2, 3, 4]$가 되므로 뒤집기 연산만으로 shift를 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반화하면 $[L, R]$구간을 오른쪽으로 $K$만큼 shift하는 연산은 $[L, R]$을 뒤집고 $[L, L+K-1]$과 $[L+K, R]$을 순차적으로 뒤집으면 된다. 왼쪽으로 shift하는 것도 유사하게 수행하면 된다.&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;Splay Tree로 해결할 수 있는 쿼리 목록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두 $O(\log N)$에 가능하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중간/앞/뒤 값 하나 삽입, 삭제, 갱신&lt;/li&gt;
&lt;li&gt;$K$번째 값 찾기 (오름차순, 내림차순, 랜덤 액세스)&lt;/li&gt;
&lt;li&gt;구간 삭제&lt;/li&gt;
&lt;li&gt;구간 쿼리 (최대, 최소, 합, gcd, ...)&lt;/li&gt;
&lt;li&gt;Lazy Propagation&lt;/li&gt;
&lt;li&gt;구간 뒤집기&lt;/li&gt;
&lt;li&gt;구간 shift (좌, 우)&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;&lt;a href=&quot;https://www.acmicpc.net/problem/2844&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/2844&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13159&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/13159&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13543&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/13543&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/17607&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/17607&lt;/a&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;Additional Question&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Splay tree로 Segment tree beats문제도 해결할 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/17474&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/17474&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래밍/알고리즘</category>
      <category>Segment Tree</category>
      <category>splay tree</category>
      <category>스플레이 트리</category>
      <category>알고리즘</category>
      <category>자료구조</category>
      <author>riroan</author>
      <guid isPermaLink="true">https://riroan.tistory.com/182</guid>
      <comments>https://riroan.tistory.com/182#entry182comment</comments>
      <pubDate>Thu, 3 Oct 2024 14:33:50 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] Pollard rho 알고리즘</title>
      <link>https://riroan.tistory.com/181</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;소인수분해를 하는 가장 쉬운 방법은 약수를 구하듯이 $\sqrt N$까지 검사하며 나누어질 경우 인수로 가져가는 방식이 있을 것이다. 이러한 방법도 괜찮지만 $N \le 10^{18}$정도 된다면 TLE가 나는 느린 방법이다. 위 방법의 시간복잡도는 $\sqrt N$이지만 입력이 숫자이기 때문에 복잡도상으로 보면 지수시간을 가지는 느린 알고리즘이다. 소인수분해는 그만큼 어려운 문제이기 때문에 &lt;a href=&quot;https://riroan.tistory.com/112&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;암호&lt;/a&gt;에도 많이 쓴다. 위 방법보다 빠르고 $10^{18}$범위까지 해결할 수 있는 &lt;b&gt;Pollard rho 알고리즘&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;Pollard rho를 이해하기 위해 몰라도 되지만 알면 좋은 사전지식이 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Birthday Paradox&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;사람들이 얼마나 모여야 생일이 같은 사람이 존재할 수 있을까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://riroan.tistory.com/101&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;비둘기집의 원리&lt;/a&gt;에 의하면 윤년이 아닌 경우 366명만 모이면 무조건 존재한다. 하지만 이건 너무 재미없기 때문에 다음 문제를 생각해보자.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;사람들이 얼마나 모여야 생일이 같은 사람이 존재할 확률이 50%를 넘을 수 있을까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 좀 흥미롭다. 단순한 계산을 통해 위 문제를 해결할 수 있다. $k$명의 사람들이 모였을 때 생일이 겹치는 경우가 있을 확률을 $Q_k$, 생일이 겹치는 경우가 없는 확률을 $P_k$라고 하자. $Q_k + P_k = 1$이다. 여기에서 $P_k$를 구하는 것은 쉽다. 1년의 일 수를 $n$이라고 하자. 적절한 조합을 사용하면 $P_k = \frac{n(n-1)(n-2)\cdots(n-k+1)}{n^k} = \prod_{i=0}^{k-1} (1 - \frac{i}{n})$임을 알아낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 $1 + x \le e^x$라는 사실을 사용하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$P_k \le \prod_{i=0}^{k-1} e ^ {-\frac{i}{n}} = e^{\sum_{i=0}^{k-1} -\frac{i}{n}} = e^{-\frac{1}{n} \frac{k(k-1)}{2}}$ 라는 사실을 얻을 수 있다. 우리가 원하는 것은 $Q_k \ge \frac{1}{2}$인 $k$를 찾는 것이므로 $P_k \le \frac{1}{2}$인 $k$를 찾는 것과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$e^{-\frac{1}{n} \frac{k(k-1)}{2}} \le \frac{1}{2} = e^{ - \ln 2}$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$\frac{1}{n} \frac{k(k-1)}{2} \ge \ln 2$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$k^2 - k - 2n \ln 2\ge 0$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$k = \frac{1 \pm \sqrt{1+8n \ln 2}}{2}$를 경계로 하고 음수가 될 수는 없으므로 $k \ge \frac{1 + \sqrt{1+8n \ln 2}}{2}$이라면 생일이 같은 사람이 존재할 확률이 50%가 넘게 된다. $n = 365$를 넣어 계산하면 $k \ge 22.9999431 \dots$이 나오게 된다. 즉, 23명만 모여도 생일이 같은 사람이 존재할 확률은 50%가 넘게 된다.&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;Birthday Paradox&lt;/b&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;b&gt;tortoise &amp;amp; hare algorithm&lt;/b&gt;이라고 알려진 이 알고리즘은 connected functional graph에서 사이클이 시작하는 노드를 찾는 알고리즘이다. 여기서 functional graph는 모든 노드의 나가는 간선이 오직 한 개인 그래프이다. 보통의 알고리즘 문제라면 indegree가 2인 노드를 찾으면 되기 때문에 굉장히 쉽게 풀리지만 Pollard rho에서는 중요하게 쓰인다. 그래프 전체가 사이클이라면 사이클의 시작 노드를 찾을 수 없기 때문에 전체가 사이클이면 안된다. 알고리즘은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;indegree가 0인 노드에 $A, B$를 둔다.&lt;/li&gt;
&lt;li&gt;$A$는 간선을 따라 2칸씩 이동하고 $B$는 1칸씩 이동한다.&lt;/li&gt;
&lt;li&gt;2번을 반복하다보면 두 포인터는 어떤 노드에서 만나게 된다. 이 때 $A$는 indegree가 0인 노드로 옮기고 $B$는 그대로 둔다.&lt;/li&gt;
&lt;li&gt;$A$와 $B$가 만날때까지 간선을 따라 1칸씩 이동한다.&lt;/li&gt;
&lt;li&gt;둘이 만난 노드가 사이클이 시작하는 노드이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간복잡도는 $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;이제 사전지식은 끝났고 Pollard rho 알고리즘을 알아보자.&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$이 주어졌을 때 &lt;b&gt;어떠한 방법&lt;/b&gt;으로 $N$의 약수 $v$를 구하여 $\frac{N}{v}, v$에 각각 동일한 알고리즘을 적용하여 소인수분해를 진행하는 방식이다. 여기에서 그 &lt;b&gt;어떠한 방법&lt;/b&gt;을 알아보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Naive&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$2 \le x \le&amp;nbsp; N$를 랜덤하게 뽑아 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;$\gcd(x, N) &amp;gt; 1$가 된다면&lt;/span&gt;&amp;nbsp;괜찮은 확률로 $v = \gcd(x, 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;a href=&quot;https://www.acmicpc.net/problem/11653&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 11653 문제&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;http://boj.kr/b2fe08a587414943934db0d7890e9224&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 11653 풀이&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pollard rho&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 2차 이상의 단순한 다항식을 하나 선택한다. 1차 다항식은 알고리즘 속도가 너무 느려지기때문에 고려하지 않는다. 보통 차수가 높으면 속도가 계산하느라 느려질 수 있으므로 단순한 2차로 선택한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$f(x) = x^2 + c$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$c \neq 0$이 좋은데 계산하다가 값이 $0$이 나와버리면 그대로 무한루프에 빠지기 때문이다.. 그리고 $0 \le x_0 \le N - 1$을 선택한다. 그렇게 $x_{i+1} = f(x) \mod N$을 수행하면 언젠가 $x_i = x_j \mod N$인 지점이 생긴다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 $N = 10, x_0 = 3, f(x) = x^2 +1$라고 하자. 수열 $x_n = \{3, 0, 1, 2, 5, 6, 7, 0, \dots\}$가 생기게 되고 이를 그래프로 표현하면 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-09-27 23-54-36.png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfsrJO/btsJQImFxuz/vHIedkL2Xji5Oa6Fz2pEm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfsrJO/btsJQImFxuz/vHIedkL2Xji5Oa6Fz2pEm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfsrJO/btsJQImFxuz/vHIedkL2Xji5Oa6Fz2pEm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfsrJO%2FbtsJQImFxuz%2FvHIedkL2Xji5Oa6Fz2pEm0%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;445&quot; height=&quot;356&quot; data-filename=&quot;스크린샷 2024-09-27 23-54-36.png&quot; data-origin-width=&quot;701&quot; data-origin-height=&quot;561&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모양이 그리스어 $\rho$(rho)같이 생겼다고 해서 Pollard rho라는 이름이 붙었다. 그럼 여기서 의문이 생긴다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;수가 엄청 크면 rho의 크기도 엄청 커질 것 같은데?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$f$를 통과할때마다 적당한 난수가 추출되므로 Birthday Paradox에 의하면 표본이 꽤 적어도 겹치는 수가 나올 확률이 높다. 하지만 $f$로 1차 다항식을 선택하면 Birthday Paradox와 관계없이 rho크기는 커진다!&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_i, x_j$를 보며 $v = \gcd (|x_i - x_j|, N) &amp;gt; 1$인지 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$v = N$이라면 $x_0, c$를 다시 정해 처음부터 다시 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$v &amp;lt; N$이라면 Naive때와 마찬가지로 약수를 하나 구한 것이므로 $\frac{N}{v}, v$에 대해 알고리즘을 각각 재귀적으로 수행한다.&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;구해진 rho 크기를 $P$라고 하자. Birthday Paradox에 의해 $P$가 적당히 작다는 것은 알지만 모든 $x_i, x_j$에 대해 확인하는 방법은 $O(P^2)$의 시간이 걸리므로 비효율적이다. 그래서 적당히 $j = 2i$를 선택하여 $x_i, x_{2i}$만 확인하여 체크하는 방법이 좋다. 이 방법의 좋은 점은 토끼와 거북이 알고리즘에 의해 $O(P)$번 안에 무조건 같은 노드에 도착하게 된다는 사실이 보장된다. 같은 노드에 도착하게 되면 $v = \gcd(0, N) = 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;시간복잡도는 복잡한 분석에 의해 $O(N^\frac{1}{4})$로 알려져있다. 이는 $N \le 10^{18}$인 경우에도 해결할 수 있을 정도로 충분히 빠르다.&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;주의할 점으로 소수를 대상으로 Pollard rho를 수행하게 되면 무한루프에 빠지므로 각 step 시작하기 전에 소수인지 판별해야한다. 여기에서 $O(\sqrt{N})$ 소수판별법을 사용하면 병목이 있으므로 충분히 빠른 &lt;a href=&quot;https://riroan.tistory.com/113&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이런 방법&lt;/a&gt;이나 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%B0%80%EB%9F%AC-%EB%9D%BC%EB%B9%88_%EC%86%8C%EC%88%98%ED%8C%90%EB%B3%84%EB%B2%95&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pollard rho 연습문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/4149&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/4149&lt;/a&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;Reference&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000000930174&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #000000; text-align: center;&quot;&gt;기초정수론(6TH EDITION) - DAVID M BURTON&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>프로그래밍/알고리즘</category>
      <category>birthday paradox</category>
      <category>pollard rho</category>
      <category>소인수분해</category>
      <category>알고리즘</category>
      <category>정수론</category>
      <category>토끼와 거북이</category>
      <author>riroan</author>
      <guid isPermaLink="true">https://riroan.tistory.com/181</guid>
      <comments>https://riroan.tistory.com/181#entry181comment</comments>
      <pubDate>Sat, 28 Sep 2024 00:10:34 +0900</pubDate>
    </item>
    <item>
      <title>[암호학] 23. Zero Knowledge Proof</title>
      <link>https://riroan.tistory.com/180</link>
      <description>&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;Zero Knowledge Proof&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 문제를 해결하려는 사람 $P$(Prover)와 $P$가 제출한 데이터가 유효한지 검증하는 $V$(Verifier)가 있다고 하자. $P$는 $V$에게 어떤 데이터 $X$를 알고있다는 사실을 알려주고 싶다. 여기에서 $X$에 대한 정보가 오가지 않으면서 $P$가 $X$를 알고있다는 것을 $V$가 납득할 수 있도록 증명하는 과정을 &lt;b&gt;Zero Knowledge Proof&lt;/b&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;$P$와 $V$는 서로를 속일 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$X$를 모르는 $P$가 $X$를 아는 것처럼 속일 수 있다. 로그인 예제에서 우연히 아무 패스워드나 입력했는데 낮은 확률이지만 성공할 수 있다. 여기에서 $P$는 접근해서는 안되는 정보에 접근할 수 있기 때문에 문제가 생긴다. 이것이 $P$가 $V$를 속이는 경우이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$V$는 $P$가 $X$를 알고 있다는 것을 증명하는 과정에서 데이터를 조금씩 수집해 $X$에 대한 정보를 알아내는 것을 $V$가 $P$를 속이는 경우이다. $V$는 $X$를 알아내면 $P$인척 할 수 있기 때문에 문제가 생긴다.&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;다시말해 목표는 $P$는 $V$에게 $X$를 안다는 사실을 알려주고 싶은데 다음 조건을 만족해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$V$는 $P$가 아는 척을 할 때 넘어가면 안된다.&lt;/li&gt;
&lt;li&gt;$V$는 $X$에 대해 알아내면 안된다.&lt;/li&gt;
&lt;li&gt;$X$는 전달되면 안된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$V$는 $P$에게 어떤 문제를 낸다. 그 문제의 답은 $X$이고 $P$가 해당 문제를 해결해서 $X$를 알고있다는 것을 $V$에게 보이면 된다. 여기서 $V$도 그 문제를 해결하는 법을 모르고 $X$도 뭔지 모른다. 단지 문제만 낼 뿐이다. 그리고 정답을 아는 $P$외의 사람($V$를 포함하여)이 해결하기 힘들도록 $NP$인 문제를 출제한다.&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;Graph Isomorphism&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$V$가 문제로 사용할 수 있는 예시인 &lt;a href=&quot;https://en.wikipedia.org/wiki/Graph_isomorphism&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;Graph Isomorphism&lt;/b&gt;&lt;/a&gt;을 사용해보자. $G_1 = (V_1, E_1), G_2=(V_2, E_2)$가 존재하여 $V_1$과 $V_2$에 어떤 mapping이 존재한다면 $G_1$과 $G_2$는 isomorphic하다고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WI4MD/btsJD80ENpP/KebzhFb4z7OmVYdvB3ubv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WI4MD/btsJD80ENpP/KebzhFb4z7OmVYdvB3ubv1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;302&quot; data-filename=&quot;스크린샷 2024-09-14 22-36-23.png&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WI4MD/btsJD80ENpP/KebzhFb4z7OmVYdvB3ubv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWI4MD%2FbtsJD80ENpP%2FKebzhFb4z7OmVYdvB3ubv1%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;389&quot; height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btm6Ao/btsJDqumwRi/NuLK9S7gENbTmQKptdIPTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btm6Ao/btsJDqumwRi/NuLK9S7gENbTmQKptdIPTK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;389&quot; data-origin-height=&quot;302&quot; data-filename=&quot;스크린샷 2024-09-14 22-37-26.png&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btm6Ao/btsJDqumwRi/NuLK9S7gENbTmQKptdIPTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbtm6Ao%2FbtsJDqumwRi%2FNuLK9S7gENbTmQKptdIPTK%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;389&quot; height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;G1, G2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두 그래프는 달라보이지만 $0 \rightarrow 1, 1\rightarrow 2, 2 \rightarrow 3, 3 \rightarrow 4, 4 \rightarrow 0$으로 mapping하면 같은 그래프가 된다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;/li&gt;
&lt;li&gt;두 그래프가 동형인지 판단하기 위한 mapping을 구하는 것은 어렵다.&lt;/li&gt;
&lt;li&gt;mapping이 주어진다면 동형인지 판단하는 것은 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동형인 그래프를 만드는 것은 단지 현재 노드 집합의 아무 permutation을 하나 구해서 mapping시키면 만들 수 있으므로 매우 쉽다. mapping을 구하는 것은 어렵기 때문에 $P$가 두 그래프의 mapping을 알고 있다면 문제를 해결했다고 보면 된다. 여기에서 $G_1 \leftrightarrow G_2$간의 mapping이 $X$가 된다. 물론 mapping을 직접 제공하면 안되기 때문에 다음 과정을 거친다고 하자.&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;$G_1 \leftrightarrow G_2$의 mapping을 알고 있는 $P$가 $G_1$과 동형인 그래프 $G_{temp}$를 만든다. ($G_2$로부터 $G_{temp}$를 만들어도 된다.) 여기에서 $P$는 $G_1 \leftrightarrow G_{temp}, G_1 \leftrightarrow G_2, G_{temp} \leftrightarrow G_{2}$ 모두의 mapping을 알 수 있다. (잘 생각해보면 $G_1\leftrightarrow G_2$의 mapping을 알고있을 때 $G_2 \leftrightarrow G_{temp}$의 mapping을 아는 것도 쉽다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$P$는 만든 $G_{temp}$를 $V$에게 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 $V$는 $G_{temp}$를 받고 두 가지 선택지가 생긴다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;$P$에게 $G_1 \leftrightarrow G_{temp}$의 mapping을 묻는다.&lt;/li&gt;
&lt;li&gt;$P$에게 $G_2 \leftrightarrow G_{temp}$의 mapping을 묻는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$G_1 \leftrightarrow G_2$의 mapping을 &lt;b&gt;아는&lt;/b&gt; $P$라면 두 질문 중 어떤 것이 들어오더라도 정확히 답할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$G_1 \leftrightarrow G_2$의 mapping을 &lt;b&gt;모르는&lt;/b&gt; $P$라면 1번 질문이 들어오면 정확히 답할 수 있지만 2번 질문이 들어오면 정확히 답할 수 없을 것이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;$P$가 $G_1$에서 $G_{temp}$를 만들었고 $V$는 $G_1 \leftrightarrow G_{temp}$의 mapping을 묻는다.&lt;/li&gt;
&lt;li&gt;$P$가 $G_1$에서 $G_{temp}$를 만들었고 $V$는 $G_2 \leftrightarrow G_{temp}$의 mapping을 묻는다.&lt;/li&gt;
&lt;li&gt;$P$가 $G_2$에서 $G_{temp}$를 만들었고 $V$는 $G_1 \leftrightarrow G_{temp}$의 mapping을 묻는다.&lt;/li&gt;
&lt;li&gt;$P$가 $G_2$에서 $G_{temp}$를 만들었고 $V$는 $G_2 \leftrightarrow G_{temp}$의 mapping을 묻는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$X$를 아는 $P$는 모든 문제를 해결할 수 있지만 $X$를 모르는 $P$는 1, 4번 케이스만 해결할 수 있다. 따라서 이 과정을 한 번 진행하면 $V$는 $\frac{1}{2}$확률로 $P$가 $X$를 아는지 확인할 수 있다. 이 과정을 $k$번 반복하면 $1-\frac{1}{2^k}$의 확률로 $P$가 아는지 검증할 수 있게 된다. 이 과정에서 $G_1 \leftrightarrow G_2$에 대한 mapping정보는 전달되지 않았기 때문에 $V$는 이 mapping을 여전히 모른다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 $k$가 너무 커지게 된다면 확실한 검증이 가능하겠지만 검증 과정이 길어지게 돼서 UX관점에서 사용자가 서비스를 사용하지 않을 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 $P$는 주의할 것이 있다. $G_{temp, i}$를 $i$번째 전달 과정에서 만든 $G_{temp}$라고 하자. 만약 다음 과정에서 $G_{temp, i+1} = G_{temp, i}$로 만들게 된다면 안된다. $V$가 $i$번째 과정에서 $G_{temp, i} \leftrightarrow G_1$의 mapping을 물어보고 $G_{temp, i+1} \leftrightarrow G_2$의 mapping을 물어보면 $V$가 $G_1 \leftrightarrow G_2$에 대한 mapping, 즉 $X$를 알게되는 것이기 때문이다. $P$는 이전에 만든 $G_{temp}$를 다음 과정에서 제공하면 안된다. (유사해도 안된다.)&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;Why zero knowledge?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 저 위의 과정을 정말 많이 하면 $V$는 $X$를 알아낼 수 있다. $G_1$의 노드 개수가 $N$개라고 하자. $P$는 $V$를 permutation하여 동형인 $G_{temp}$를 만들 것이기 때문에 만들 수 있는 동형 그래프의 개수는 $N!$개가 된다. 그렇다면 $V$가 검증할 때 위 과정을 $N! + 1$번을 하게 된다면? 비둘기집 원리에 의해 적어도 동일한 $G_{temp}$가 한 번 이상 나오게 되고 $V$는 $X$를 알게 된다. 그래서 검증을 진행할 때마다 $V$는 $X$를 알아낼 수 있는 아주 적은 양의 정보가 전달된다. 하지만 여전히 $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;그렇다면 $P$가 $G_{temp}$를 만들지 않고 $V$ 혼자서 위 과정을 진행하여 $X$를 알아낼 수 있는 것 아닌가? 이것도 맞다. $V$가 $N!$번 혼자 $G_{temp}$를 만들어서 $X$를 알아낼 수도 있다. 즉 실제 $P$가 없어도 가능한 과정이다. 이 말은 $P$와 $V$사이에 어떠한 정보가 전달되지 않았다는 의미이기 때문에 &lt;b&gt;Zero Knowledge Proof&lt;/b&gt;가 된다.&lt;/p&gt;</description>
      <category>기타/암호학</category>
      <category>zero knowledge</category>
      <category>ZK</category>
      <category>암호학</category>
      <author>riroan</author>
      <guid isPermaLink="true">https://riroan.tistory.com/180</guid>
      <comments>https://riroan.tistory.com/180#entry180comment</comments>
      <pubDate>Sat, 14 Sep 2024 23:07:37 +0900</pubDate>
    </item>
  </channel>
</rss>