<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>begin-play 님의 블로그</title>
    <link>https://begin-play.tistory.com/</link>
    <description>begin-play 님의 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Mon, 5 Oct 2026 02:11:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>begin-play</managingEditor>
    <item>
      <title>2026/10/01  언리얼 엔진 캐릭터 기초 기능 구현-1</title>
      <link>https://begin-play.tistory.com/100</link>
      <description>&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h1&gt;TIL &amp;mdash; 언리얼 엔진 C++ 캐릭터 기능 구현&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;학습 주제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 캐릭터 이동과 입력 처리를 제외하고, AMyCharacter에 구현한 &lt;span&gt;클라이밍, 점프 상태 제어, 활 조준, 카메라 전환, 화살 발사 시스템&lt;/span&gt;을 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 정리에서는 단순히 기능의 사용법을 나열하기보다, 각 기능을 구현하기 위해 어떤 변수를 사용했고, 어떤 방식으로 상태를 관리했는지에 중점을 둔다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 점프 상태 관리 및 재점프 방지&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현 목적&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진의 기본 Jump()만으로는 애니메이션 상태와 점프 입력을 세밀하게 동기화하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 보완하기 위해 점프 요청, 실제 상승 상태, 착지 상태를 구분하여 관리했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 변수&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;bool bJumpRequested = false;
bool bIsJumping = false;
bool bJumpLocked = false;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bJumpRequested: 점프 입력이 발생했음을 기록한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bIsJumping: 실제로 점프하여 상승 중인지 나타낸다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bJumpLocked: 착지 직후 일정 시간 동안 재점프를 방지한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 코드&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;void AMyCharacter::Jump()
{
    if (!GetCharacterMovement()-&amp;gt;IsMovingOnGround())
    {
        return;
    }

    bJumpRequested = true;
    Super::Jump();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점프 입력이 발생했다고 즉시 bIsJumping을 활성화하지 않고, 실제 상승이 확인되었을 때 상태를 변경한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;if (bJumpRequested &amp;amp;&amp;amp;
    GetCharacterMovement()-&amp;gt;IsFalling() &amp;amp;&amp;amp;
    GetCharacterMovement()-&amp;gt;Velocity.Z &amp;gt; 0.0f)
{
    bIsJumping = true;
    bJumpRequested = false;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;착지 시에는 점프 상태를 초기화하고 짧은 재점프 방지 시간을 설정한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;void AMyCharacter::Landed(const FHitResult&amp;amp; Hit)
{
    Super::Landed(Hit);

    bIsJumping = false;
    bJumpRequested = false;
    bJumpLocked = true;

    GetWorldTimerManager().SetTimer(
        JumpLockTimerHandle,
        this,
        &amp;amp;AMyCharacter::UnlockJump,
        0.1f,
        false
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;학습 내용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 요청과 실제 캐릭터 상태를 분리하여 관리할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IsFalling()과 Velocity.Z를 조합해 실제 상승 여부를 판별할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Landed()와 타이머를 활용해 착지 직후의 입력을 제어할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 상태 변수는 AnimBP에서 점프 및 착지 애니메이션 전환 조건으로 활용할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 클라이밍 시스템 구현&lt;/h2&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;p data-ke-size=&quot;size16&quot;&gt;클라이밍 가능한 위치를 별도 액터인 AClimbPoint로 관리하여 캐릭터와 등반 지점의 역할을 분리했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주요 변수&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;bool bCanClimb = false;
bool bIsClimbing = false;

TObjectPtr&amp;lt;AClimbPoint&amp;gt; CurrentClimbPoint = nullptr;

TObjectPtr&amp;lt;UAnimMontage&amp;gt; ClimbMontage;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bCanClimb: 클라이밍 가능한 지점에 접근했는지 나타낸다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bIsClimbing: 현재 클라이밍 동작 중인지 나타낸다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CurrentClimbPoint: 현재 상호작용할 클라이밍 지점을 참조한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ClimbMontage: 클라이밍 애니메이션 몽타주를 참조한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클라이밍 시작 조건&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;if (bCanClimb &amp;amp;&amp;amp; CurrentClimbPoint)
{
    if (CurrentClimbPoint-&amp;gt;IsPlayerFacingStartPoint(
        GetActorLocation(),
        GetActorForwardVector()))
    {
        StartClimb();
        return;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이밍 가능 여부뿐만 아니라 캐릭터가 올바른 방향을 바라보고 있는지도 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건을 만족하면 일반 점프를 실행하지 않고 StartClimb()을 호출한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클라이밍 실행&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;void AMyCharacter::StartClimb()
{
    if (!CurrentClimbPoint || !ClimbMontage)
    {
        return;
    }

    bIsClimbing = true;
    GetCharacterMovement()-&amp;gt;SetMovementMode(MOVE_Flying);

    SetActorLocation(
        CurrentClimbPoint-&amp;gt;GetStartLocation(GetActorLocation())
    );

    SetActorRotation(
        CurrentClimbPoint-&amp;gt;GetStartRotation(GetActorLocation())
    );

    PlayAnimMontage(ClimbMontage);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심은 MOVE_Flying으로 이동 모드를 변경하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 중력 기반 이동의 영향을 받지 않도록 이동 모드를 변경하고, 클라이밍 지점에서 계산한 위치와 회전을 적용한 뒤 몽타주를 재생한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;애니메이션 노티파이를 이용한 종료 처리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이밍 몽타주에 ClimbComplete 노티파이를 배치하고, 해당 시점에 종료 함수를 호출한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void AMyCharacter::OnClimbMontageNotify(
    FName NotifyName,
    const FBranchingPointNotifyPayload&amp;amp; BranchingPointPayload)
{
    if (NotifyName == TEXT(&quot;ClimbComplete&quot;))
    {
        OnClimbComplete();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;void AMyCharacter::OnClimbComplete()
{
    bIsClimbing = false;
    bIsJumping = false;
    bJumpRequested = false;

    GetCharacterMovement()-&amp;gt;SetMovementMode(MOVE_Walking);

    ResetJumpState();

    StopAnimMontage(ClimbMontage);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;학습 내용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별도 액터를 활용해 상호작용 지점과 캐릭터 로직을 분리할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SetMovementMode()를 이용해 상황에 따라 캐릭터 이동 방식을 변경할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애니메이션 노티파이를 이용하면 애니메이션의 특정 시점과 C++ 로직을 연결할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몽타주 종료 시점에도 이동 모드를 복구하도록 처리하여 상태가 남는 문제를 방지할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 활 조준 시스템 및 캐릭터 회전 제어&lt;/h2&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;p data-ke-size=&quot;size16&quot;&gt;또한 조준 상태에 따라 이동 속도와 상체 기울기 값을 변경한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조준 상태 변경&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;void AMyCharacter::StartAim()
{
    if (bIsClimbing || GetCharacterMovement()-&amp;gt;IsFalling())
    {
        return;
    }

    bIsAiming = true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void AMyCharacter::StopAim()
{
    bIsAiming = false;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조준 여부를 bIsAiming 하나로 관리하고, 이후 Tick에서 관련 기능을 처리한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조준 중 캐릭터 회전&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;if (bIsAiming)
{
    const FRotator TargetRotation(
        0.0f,
        GetControlRotation().Yaw + AimYawOffset,
        0.0f
    );

    SetActorRotation(FMath::RInterpTo(
        GetActorRotation(),
        TargetRotation,
        DeltaTime,
        AimRotationInterpSpeed
    ));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GetControlRotation()에서 Yaw를 가져와 캐릭터가 바라볼 목표 회전을 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RInterpTo()를 사용하여 목표 방향으로 부드럽게 회전시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 일반 이동 중에는 이동 방향으로 회전하지만, 조준 중에는 해당 회전 처리를 생략하여 스트레이프 이동이 가능하도록 했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조준 상체 Pitch 계산&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;const float TargetAimPitch = bIsAiming
    ? FMath::Clamp(
        FRotator::NormalizeAxis(GetControlRotation().Pitch),
        -AimPitchLimit,
        AimPitchLimit)
    : 0.0f;

AimPitch = FMath::FInterpTo(
    AimPitch,
    TargetAimPitch,
    DeltaTime,
    AimPitchInterpSpeed
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라의 상하 각도를 AimPitch에 저장한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NormalizeAxis() : 회전 각도를 정규화한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Clamp() : 상하 회전 범위를 제한한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FInterpTo() : 목표 각도까지 부드럽게 보간한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값은 AnimBP에서 상체나 척추 본의 회전에 활용할 수 있도록 BlueprintReadOnly로 선언했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;학습 내용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 상태 변수로 여러 기능의 동작을 제어할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RInterpTo()와 FInterpTo()를 활용하면 회전과 수치 변화를 부드럽게 처리할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조준 중 캐릭터 회전과 이동 방향 회전을 분리하면 TPS 방식의 조준 이동을 구현할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서 계산한 값을 AnimBP에 전달하여 캐릭터의 상체 애니메이션을 제어할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 조준 상태에 따른 카메라 및 이동 속도 변경&lt;/h2&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;p data-ke-size=&quot;size16&quot;&gt;조준 해제 시에는 기존 카메라 위치와 이동 속도로 복귀한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본값 저장&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;void AMyCharacter::BeginPlay()
{
    Super::BeginPlay();

    DefaultMaxWalkSpeed =
        GetCharacterMovement()-&amp;gt;MaxWalkSpeed;

    DefaultArmLength =
        SpringArm-&amp;gt;TargetArmLength;

    DefaultSocketOffset =
        SpringArm-&amp;gt;SocketOffset;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 시작 시 기본 이동 속도와 카메라 설정을 저장한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조준 상태에 따른 속도 변경&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;GetCharacterMovement()-&amp;gt;MaxWalkSpeed = FMath::FInterpTo(
    GetCharacterMovement()-&amp;gt;MaxWalkSpeed,
    bIsAiming ? AimMaxWalkSpeed : DefaultMaxWalkSpeed,
    DeltaTime,
    AimSpeedInterpSpeed
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;카메라 거리 변경&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;SpringArm-&amp;gt;TargetArmLength = FMath::FInterpTo(
    SpringArm-&amp;gt;TargetArmLength,
    bIsAiming ? AimArmLength : DefaultArmLength,
    DeltaTime,
    AimCameraInterpSpeed
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;카메라 오프셋 변경&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;SpringArm-&amp;gt;SocketOffset = FMath::VInterpTo(
    SpringArm-&amp;gt;SocketOffset,
    bIsAiming ? AimSocketOffset : DefaultSocketOffset,
    DeltaTime,
    AimCameraInterpSpeed
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;학습 내용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태에 따라 서로 다른 목표값을 설정하고 보간할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 설정값을 저장하면 기능 종료 후 원래 상태로 복구할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FInterpTo()는 실수형 값, VInterpTo()는 FVector 값의 보간에 활용된다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라와 이동 속도를 동일한 조준 상태에 연결하여 일관된 조작감을 구현할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 카메라 기반 조준 판정 및 화살 발사&lt;/h2&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;p data-ke-size=&quot;size16&quot;&gt;이를 위해 Line Trace로 조준 목표 지점을 계산한 다음, 화살 생성 위치에서 해당 지점을 향하는 방향을 구한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조준 목표 지점 계산&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;FVector AMyCharacter::GetAimTargetLocation() const
{
    const FVector Start = Camera-&amp;gt;GetComponentLocation();

    const FVector End =
        Start + Camera-&amp;gt;GetForwardVector() * AimTraceDistance;

    FHitResult Hit;

    FCollisionQueryParams Params(
        SCENE_QUERY_STAT(AimTrace),
        false,
        this
    );

    if (GetWorld()-&amp;gt;LineTraceSingleByChannel(
        Hit, Start, End, ECC_Visibility, Params))
    {
        return Hit.ImpactPoint;
    }

    return End;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라 위치에서 정면으로 Line Trace를 발사한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌한 물체가 있다면 ImpactPoint를 반환한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌하지 않았다면 최대 거리의 끝 지점을 반환한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Params의 세 번째 인자에 this를 전달하여 자기 자신을 무시하도록 설정한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;화살 발사 방향 계산&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;FVector Direction =
    (GetAimTargetLocation() - SpawnLocation).GetSafeNormal();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화살 생성 위치에서 조준 목표 지점까지의 방향을 구하고 정규화한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;if (Direction.IsNearlyZero() ||
    FVector::DotProduct(Direction, CameraForward) &amp;lt;= 0.0f)
{
    Direction = CameraForward;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조준 목표 지점이 너무 가까워 화살 방향이 카메라의 정면과 반대가 되는 상황을 방지한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;화살 생성&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;FActorSpawnParameters Params;

Params.Owner = this;
Params.Instigator = this;

Params.SpawnCollisionHandlingOverride =
    ESpawnActorCollisionHandlingMethod::AlwaysSpawn;

GetWorld()-&amp;gt;SpawnActor&amp;lt;AArrowProjectile&amp;gt;(
    ArrowClass,
    SpawnLocation,
    Direction.Rotation(),
    Params
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpawnActor()를 이용해 화살 액터를 생성한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Owner: 화살의 소유자를 캐릭터로 지정한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Instigator: 화살을 발생시킨 캐릭터를 지정한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Direction.Rotation(): 계산한 발사 방향을 회전값으로 변환한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;학습 내용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라 기반 Line Trace를 이용해 TPS 조준 시스템을 구현할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라의 조준 지점과 실제 투사체 생성 위치를 분리하여 계산할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GetSafeNormal()로 방향 벡터를 정규화할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpawnActor()의 Owner와 Instigator를 활용해 생성 액터와 발사 주체의 관계를 설정할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 애니메이션 노티파이와 타이머를 활용한 발사 제어&lt;/h2&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;h3 data-ke-size=&quot;size23&quot;&gt;발사 가능 여부 확인&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;if (!bIsAiming || !bCanFire || bIsClimbing)
{
    return;
}

bCanFire = false;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조준 상태, 발사 가능 여부, 클라이밍 상태를 검사한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;발사 쿨다운&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;GetWorldTimerManager().SetTimer(
    FireCooldownTimerHandle,
    this,
    &amp;amp;AMyCharacter::UnlockFire,
    FireCooldown,
    false
);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지정된 시간이 지나면 UnlockFire()를 호출한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void AMyCharacter::UnlockFire()
{
    bCanFire = true;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;애니메이션 노티파이를 통한 화살 생성&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;else if (NotifyName == TEXT(&quot;FireArrow&quot;))
{
    if (bArrowPending)
    {
        SpawnArrow();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발사 몽타주에 FireArrow 노티파이를 배치하고 해당 시점에 화살을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몽타주가 끝날 때까지 화살이 생성되지 않았다면 종료 콜백에서 예외적으로 처리한다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;void AMyCharacter::OnFireMontageEnded(
    UAnimMontage* Montage,
    bool bInterrupted)
{
    if (bArrowPending &amp;amp;&amp;amp; !bInterrupted)
    {
        SpawnArrow();
    }

    bArrowPending = false;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;학습 내용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애니메이션 노티파이를 이용하면 애니메이션 재생과 게임 로직을 동기화할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타이머를 이용해 쿨다운과 일정 시간 후 실행할 함수를 구현할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bArrowPending을 이용해 발사 요청과 실제 화살 생성을 구분할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몽타주 종료 콜백을 이용해 노티파이 누락 상황에 대한 예외 처리를 구현할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 조준 중 크로스헤어 UI 제어&lt;/h2&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;div&gt;
&lt;div&gt;
&lt;pre class=&quot;erlang-repl&quot;&gt;&lt;code&gt;if (!CrosshairWidget)
{
    APlayerController* PC =
        Cast&amp;lt;APlayerController&amp;gt;(GetController());

    if (!PC || !PC-&amp;gt;IsLocalController())
    {
        return;
    }

    CrosshairWidget =
        CreateWidget&amp;lt;UUserWidget&amp;gt;(PC, CrosshairWidgetClass);

    if (!CrosshairWidget)
    {
        return;
    }

    CrosshairWidget-&amp;gt;AddToViewport();
    CrosshairWidget-&amp;gt;SetVisibility(
        ESlateVisibility::Collapsed
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;div&gt;
&lt;div&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;const ESlateVisibility DesiredVisibility =
    bIsAiming
    ? ESlateVisibility::HitTestInvisible
    : ESlateVisibility::Collapsed;

if (CrosshairWidget-&amp;gt;GetVisibility() != DesiredVisibility)
{
    CrosshairWidget-&amp;gt;SetVisibility(DesiredVisibility);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;학습 내용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서 CreateWidget()으로 UMG 위젯을 생성할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AddToViewport()로 화면에 위젯을 추가할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Collapsed는 위젯을 숨기면서 레이아웃 공간도 차지하지 않도록 한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HitTestInvisible은 위젯을 표시하면서 마우스 및 입력 판정을 통과시킨다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IsLocalController()를 이용해 로컬 플레이어에게만 UI를 표시하도록 제한할 수 있다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&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;div&gt;핵심 기술&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;활용 기능
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;상태 변수 기반 제어&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;점프, 클라이밍, 조준, 발사&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Movement Mode 변경&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;클라이밍 중 이동 제어&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;AnimMontage 및 Notify&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;클라이밍 완료, 화살 발사 시점&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;TimerManager&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;점프 잠금, 발사 쿨다운&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;FInterpTo / VInterpTo / RInterpTo&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;조준 카메라, 이동 속도, 캐릭터 회전&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Line Trace&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;카메라 기반 조준 위치 계산&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;SpawnActor&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;화살 투사체 생성&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;UMG 위젯 제어&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;조준 크로스헤어 표시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;기본값 저장 및 복구&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;조준 해제 시 카메라와 이동 속도 복원&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;핵심적으로 배운 점:&lt;/span&gt; 캐릭터의 기능은 각각 독립적으로 동작하는 것이 아니라, 상태 변수와 애니메이션 이벤트를 중심으로 연결된다. 따라서 새로운 기능을 추가할 때는 기능 자체의 구현뿐 아니라 기존 점프, 클라이밍, 조준 상태와 충돌하지 않도록 상태 전환과 복구 로직을 함께 설계해야 한다.&lt;/p&gt;
&lt;/div&gt;</description>
      <category>언리얼 Project_A 게임개발</category>
      <author>begin-play</author>
      <guid isPermaLink="true">https://begin-play.tistory.com/100</guid>
      <comments>https://begin-play.tistory.com/100#entry100comment</comments>
      <pubDate>Thu, 1 Oct 2026 20:39:41 +0900</pubDate>
    </item>
    <item>
      <title>힙(Heap) / 우선순위 큐(Priority Queue)</title>
      <link>https://begin-play.tistory.com/99</link>
      <description>&lt;h1&gt;힙(Heap) / 우선순위 큐(Priority Queue)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 힙(Heap)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 트리 자료구조에서 자연스럽게 이어지는 &lt;b&gt;힙(Heap)&lt;/b&gt;과 &lt;b&gt;우선순위 큐(Priority Queue)&lt;/b&gt;에 대해 학습했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙은 &lt;b&gt;완전 이진 트리(Complete Binary Tree)&lt;/b&gt;의 형태를 가지면서, 부모 노드와 자식 노드 사이에 특정한 우선순위 관계를 유지하는 자료구조이다.&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;Min Heap&lt;/li&gt;
&lt;li&gt;Max Heap&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 힙을 단순히 &lt;b&gt;값이 정렬되어 있는 트리&lt;/b&gt;라고 생각하면 안 된다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Max Heap은 다음과 같은 형태를 가질 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
     80     70
    /  \   /  \
   50  60 40  30
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Heap에서는 부모가 항상 자식보다 크거나 같아야 한다.&lt;/p&gt;
&lt;pre class=&quot;fix&quot;&gt;&lt;code&gt;부모 &amp;gt;= 자식
&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;b&gt;부모와 자식 사이의 우선순위 관계를 유지하는 자료구조&lt;/b&gt;라고 이해하는 것이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 완전 이진 트리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙을 이해하기 위해서는 먼저 &lt;b&gt;완전 이진 트리(Complete Binary Tree)&lt;/b&gt;의 개념을 이해해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전 이진 트리는 마지막 레벨을 제외한 모든 레벨이 가득 차 있고, 마지막 레벨은 왼쪽부터 차례대로 채워지는 형태의 이진 트리이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 트리는 완전 이진 트리이다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \  /
    D  E F
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 다음과 같은 형태는 완전 이진 트리가 아니다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
       \
        E
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙은 이러한 완전 이진 트리 구조를 사용하기 때문에 배열로 효율적으로 표현할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 힙을 배열로 표현하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙에서 중요한 특징 중 하나는 트리 형태로 보이지만 실제 구현에서는 &lt;b&gt;배열 하나만으로 표현할 수 있다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 Max Heap이 있다고 생각해 보았다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      80    70
     / \   / \
    50 60 40 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 배열로 표현하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[100, 80, 70, 50, 60, 40, 30]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0부터 시작하는 배열의 인덱스를 기준으로 현재 노드의 인덱스를 i라고 하면 부모와 자식의 위치를 계산할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;부모 = (i - 1) / 2

왼쪽 자식 = 2 * i + 1

오른쪽 자식 = 2 * i + 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 80은 인덱스 1에 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모는 다음과 같이 계산할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;(1 - 1) / 2
= 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 부모는 인덱스 0의 100이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 자식은:&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;2 * 1 + 1
= 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로 50이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 자식은:&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;2 * 1 + 2
= 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로 60이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적으로 보면 다음과 같은 관계가 된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100 [0]
       /        \
   80 [1]      70 [2]
   /   \        /   \
50[3] 60[4] 40[5] 30[6]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 힙은 트리의 구조를 가지지만, 완전 이진 트리의 특성 때문에 배열의 인덱스 계산만으로 부모와 자식의 위치를 알 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. Min Heap&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Min Heap&lt;/b&gt;에서는 부모 노드의 값이 자식 노드보다 작거나 같아야 한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        10
       /  \
     20    30
    / \   / \
   40 50 60 70
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 부모와 자식의 관계는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;10 &amp;lt;= 20
10 &amp;lt;= 30

20 &amp;lt;= 40
20 &amp;lt;= 50

30 &amp;lt;= 60
30 &amp;lt;= 70
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Min Heap에서는 &lt;b&gt;가장 작은 값이 항상 루트에 존재한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;Root = 최솟값
&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;따라서 가장 작은 값을 빠르게 확인하거나 꺼내야 하는 상황에서 Min Heap을 사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. Max Heap&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Max Heap&lt;/b&gt;에서는 부모 노드의 값이 자식 노드보다 크거나 같아야 한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      80    70
     / \   / \
    50 60 40 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 부모와 자식의 관계를 보면:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;100 &amp;gt;= 80
100 &amp;gt;= 70

80 &amp;gt;= 50
80 &amp;gt;= 60

70 &amp;gt;= 40
70 &amp;gt;= 30
&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;따라서 Max Heap에서는 &lt;b&gt;가장 큰 값이 항상 루트에 존재한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;Root = 최댓값
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. Min Heap과 Max Heap 비교&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종류부모와 자식의 관계Root&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Min Heap&lt;/td&gt;
&lt;td&gt;부모 &amp;le; 자식&lt;/td&gt;
&lt;td&gt;최솟값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Max Heap&lt;/td&gt;
&lt;td&gt;부모 &amp;ge; 자식&lt;/td&gt;
&lt;td&gt;최댓값&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;Min Heap
&amp;rarr; 가장 작은 값이 위로 올라감

Max Heap
&amp;rarr; 가장 큰 값이 위로 올라감
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. Heapify&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Heapify&lt;/b&gt;는 힙의 조건을 만족하도록 노드의 위치를 조정하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Max Heap에서 다음과 같은 상태가 있다고 생각해 보았다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        50
       /  \
      80   70
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Heap에서는 부모가 자식보다 커야 하는데:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;50 &amp;lt; 80
&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;따라서 50과 80의 위치를 바꾼다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        80
       /  \
      50   70
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 부모인 80이 자식보다 크기 때문에 Max Heap 조건을 만족한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &lt;b&gt;힙의 조건을 다시 만족하도록 노드들을 조정하는 과정&lt;/b&gt;을 Heapify라고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. Heapify의 기본 과정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Heap을 기준으로 생각하면 Heapify는 다음과 같은 방식으로 진행된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;현재 노드를 확인한다.&lt;/li&gt;
&lt;li&gt;왼쪽 자식을 확인한다.&lt;/li&gt;
&lt;li&gt;오른쪽 자식을 확인한다.&lt;/li&gt;
&lt;li&gt;현재 노드와 두 자식 중 가장 큰 값을 찾는다.&lt;/li&gt;
&lt;li&gt;자식이 현재 노드보다 크다면 두 값을 교환한다.&lt;/li&gt;
&lt;li&gt;교환한 위치에서도 다시 Heapify를 진행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        50
       /  \
      80   70
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서 가장 큰 값은 80이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        80
       /  \
      50   70
&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 data-ke-size=&quot;size16&quot;&gt;Heapify는 트리의 높이만큼 내려가면서 확인할 수 있기 때문에 일반적으로:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 시간 복잡도를 가진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. Insert&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙에 새로운 값을 추가하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Heap에 90이라는 값을 추가한다고 생각해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 힙은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      80    70
     / \   / \
    50 60 40 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 값은 완전 이진 트리의 구조를 유지하기 위해 가장 마지막 위치에 추가한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      80    70
     / \   / \
    50 60 40 30
   /
  90
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 90은 부모인 50보다 크다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;90 &amp;gt; 50
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Max Heap의 조건을 만족하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모와 위치를 바꾼다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      80    70
     / \   / \
    90 60 40 30
   /
  50
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 90이 부모인 80보다 크다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;90 &amp;gt; 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다시 교환한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      90    70
     / \   / \
    80 60 40 30
   /
  50
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;100 &amp;gt;= 90
90 &amp;gt;= 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로 Max Heap 조건을 만족한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 새로운 값을 마지막에 삽입한 후 부모와 비교하면서 위쪽으로 이동시키는 과정을 &lt;b&gt;Sift Up&lt;/b&gt;, &lt;b&gt;Bubble Up&lt;/b&gt;, &lt;b&gt;Up-Heap&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Insert의 시간 복잡도는:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. Delete&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙에서 Delete를 할 때 중요한 점은 일반적으로 &lt;b&gt;루트 노드를 제거한다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Heap에서는 최댓값을 제거하고, Min Heap에서는 최솟값을 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Max Heap에서 다음과 같은 구조가 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      80    70
     / \   / \
    50 60 40 30
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트인 100을 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 단순히 100만 삭제하면 완전 이진 트리의 구조가 깨질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 일반적으로 가장 마지막에 있는 노드 30을 루트로 이동시킨다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        30
       /  \
      80   70
     / \   /
    50 60 40
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;30 &amp;lt; 80
30 &amp;lt; 70
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로 Max Heap 조건을 만족하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 30을 더 큰 자식인 80과 교환한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        80
       /  \
      30   70
     / \   /
    50 60 40
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 확인하면:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;30 &amp;lt; 50
30 &amp;lt; 60
&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;이번에는 더 큰 자식인 60과 교환한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        80
       /  \
      60   70
     / \   /
    50 30 40
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Max Heap 조건을 만족한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 루트를 제거하고 마지막 노드를 위로 이동시킨 다음, 아래쪽으로 내려가면서 힙을 다시 정리하는 과정을 &lt;b&gt;Sift Down&lt;/b&gt;, &lt;b&gt;Bubble Down&lt;/b&gt;, &lt;b&gt;Down-Heap&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Delete의 시간 복잡도 역시:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. Insert와 Delete 비교&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Insert와 Delete는 이동 방향이 반대라는 점을 기억하면 이해하기 쉽다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Insert&lt;/h3&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;새로운 값 삽입
&amp;darr;
마지막 위치에 추가
&amp;darr;
부모와 비교
&amp;darr;
필요하면 위로 이동
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;Insert
&amp;rarr; Sift Up
&amp;rarr; O(log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Delete&lt;/h3&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Root 제거
&amp;darr;
마지막 노드를 Root로 이동
&amp;darr;
자식과 비교
&amp;darr;
필요하면 아래로 이동
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;Delete
&amp;rarr; Sift Down
&amp;rarr; O(log N)
&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;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Insert &amp;rarr; 위로

Delete &amp;rarr; 아래로
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 정리할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. 힙의 시간 복잡도&lt;/h1&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;최솟값/최댓값 확인&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Insert&lt;/td&gt;
&lt;td&gt;O(log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root Delete&lt;/td&gt;
&lt;td&gt;O(log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heapify&lt;/td&gt;
&lt;td&gt;O(log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전체 Heap 구성&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 기억해야 할 부분은:&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Root 확인 &amp;rarr; O(1)
Insert &amp;rarr; O(log N)
Root Delete &amp;rarr; O(log N)
&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;힙의 높이가 O(log N)이기 때문에 Insert와 Delete가 O(log N)으로 처리될 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;13. 힙은 일반적인 탐색에는 적합하지 않다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙은 모든 데이터가 정렬되어 있는 구조가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Max Heap이:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      80    70
     / \   / \
    50 60 40 30
&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;여기서 60이라는 값을 찾는다고 하면, 힙에서는 특정 값의 위치를 빠르게 알 수 있는 규칙이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙이 보장하는 것은:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;부모와 자식의 우선순위 관계
&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;b&gt;최솟값이나 최댓값처럼 우선순위가 높은 데이터를 빠르게 처리하는 용도&lt;/b&gt;에 적합하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;14. 우선순위 큐(Priority Queue)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Queue는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;FIFO
First In First Out
&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 data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A &amp;rarr; B &amp;rarr; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서로 들어왔다면:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A
B
C
&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;b&gt;Priority Queue&lt;/b&gt;는 들어온 순서가 아니라 &lt;b&gt;우선순위가 높은 데이터를 먼저 처리한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;A : 우선순위 3
B : 우선순위 10
C : 우선순위 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라면 우선순위가 높은 순서대로:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;B
C
A
&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;즉 Priority Queue의 핵심은:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;먼저 들어온 데이터
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 아니라:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;우선순위가 높은 데이터
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 먼저 처리한다는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;15. Heap과 Priority Queue의 관계&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 학습에서 가장 중요하게 이해해야 할 부분 중 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Priority Queue는 우선순위가 높은 데이터를 먼저 처리하는 추상적인 자료구조 개념&lt;/b&gt;이고, &lt;b&gt;Heap은 Priority Queue를 효율적으로 구현할 수 있는 대표적인 자료구조&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관계를 정리하면:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Priority Queue
      &amp;uarr;
   구현 방법
      &amp;uarr;
     Heap
&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;Max Heap을 사용하면:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;가장 큰 값
&amp;rarr; 가장 높은 우선순위
&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;Min Heap을 사용하면:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;가장 작은 값
&amp;rarr; 가장 높은 우선순위
&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;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Max Heap
&amp;rarr; 최댓값 우선 Priority Queue

Min Heap
&amp;rarr; 최솟값 우선 Priority Queue
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 연결해서 이해할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;16. C++의 Priority Queue&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 STL을 통해 Priority Queue를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::priority_queue
&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;기본적으로 std::priority_queue는 &lt;b&gt;Max Heap 방식&lt;/b&gt;으로 동작한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::priority_queue&amp;lt;int&amp;gt; pq;

pq.push(10);
pq.push(30);
pq.push(20);

std::cout &amp;lt;&amp;lt; pq.top();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;30
&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 data-ke-size=&quot;size16&quot;&gt;현재 구조를 개념적으로 보면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;30
20
10
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과 같은 우선순위로 처리된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;17. Min Priority Queue&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 작은 값을 먼저 처리하고 싶다면 Min Heap을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 다음과 같은 형태를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::priority_queue&amp;lt;int, std::vector&amp;lt;int&amp;gt;, std::greater&amp;lt;int&amp;gt;&amp;gt;
&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10
20
30
&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;따라서 C++에서는:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::priority_queue
&amp;rarr; 기본적으로 Max Heap

std::priority_queue + std::greater
&amp;rarr; Min Heap
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라고 이해할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;18. Priority Queue의 주요 연산&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Priority Queue에서 자주 사용하는 연산은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;push()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 삽입한다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;top()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 가장 높은 우선순위의 데이터를 확인한다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;pop()
&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;pre class=&quot;isbl&quot;&gt;&lt;code&gt;push &amp;rarr; O(log N)
top  &amp;rarr; O(1)
pop  &amp;rarr; O(log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 가장 높은 우선순위의 데이터를 확인하는 것은 매우 빠르고, 데이터를 삽입하거나 제거할 때는 힙의 구조를 다시 정리하기 때문에 O(log N)이 걸린다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;19. Priority Queue의 활용&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Priority Queue는 실제 프로그램에서 다양한 상황에 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 게임에서 여러 적을 관리한다고 생각해 보았다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;적 A : 거리 100
적 B : 거리 20
적 C : 거리 50
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 가까운 적을 먼저 처리하고 싶다면 Min Heap 기반 Priority Queue를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에도:&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;퀘스트 우선순위
이벤트 우선순위
AI 행동 우선순위
작업 처리 순서
작업 스케줄링
경로 탐색
&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;b&gt;Dijkstra 최단 경로 알고리즘&lt;/b&gt; 등을 학습하게 되면 Priority Queue가 중요한 역할을 한다는 것을 확인할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;20. Heap Sort&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 Priority Queue뿐만 아니라 &lt;b&gt;Heap Sort&lt;/b&gt;와도 연결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 이번 단계에서는 다음 두 가지 방향으로 연결해서 이해할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Heap
 &amp;darr;
Priority Queue
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고:&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Heap
 &amp;darr;
Heap Sort
&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;Heap Sort는 &lt;b&gt;Heap의 특성을 이용해서 데이터를 정렬하는 알고리즘&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;21. Max Heap을 이용한 Heap Sort&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 배열을 오름차순으로 정렬한다고 생각해 보았다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[4, 10, 3, 5, 1]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 배열을 Max Heap으로 만든다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        10
       /  \
      5    3
     / \
    4   1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열로 나타내면:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[10, 5, 3, 4, 1]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Max Heap의 루트에는 가장 큰 값인 10이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 10을 배열의 마지막 위치로 이동한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 5, 3, 4, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 10은 정렬이 끝난 영역으로 취급하고, 나머지 영역을 다시 Heapify한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[5, 4, 3, 1, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 루트에 있는 가장 큰 값 5를 뒤쪽으로 이동한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 4, 3, 5, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다시 Heapify한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[4, 1, 3, 5, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 반복하면 최종적으로:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 3, 4, 5, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라는 오름차순 배열이 만들어진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;22. Heap Sort의 전체 과정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap Sort의 기본적인 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;배열
 &amp;darr;
Heap 생성
 &amp;darr;
Root의 최댓값 확인
 &amp;darr;
최댓값을 배열의 뒤쪽으로 이동
 &amp;darr;
남은 영역 Heapify
 &amp;darr;
다시 최댓값을 뒤쪽으로 이동
 &amp;darr;
반복
 &amp;darr;
정렬 완료
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Max Heap에서는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;현재 가장 큰 값
&amp;rarr; Root
&amp;rarr; 배열의 마지막으로 이동
&amp;rarr; 남은 영역을 다시 Heapify
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하는 과정을 반복한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;23. Heap Sort의 시간 복잡도&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap Sort의 시간 복잡도는:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;O(N log N)
&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;Heap을 구성하고, 이후 반복적으로 Heapify를 수행하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Heap Sort에서 중요한 특징은 &lt;b&gt;최악의 경우에도 O(N log N)을 보장한다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퀵 정렬의 경우 평균적으로 O(N log N)이지만 피벗 선택 등에 따라 최악의 경우 O(N&amp;sup2;)이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Heap Sort는 일반적인 Heap Sort 구현에서:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;최선 &amp;rarr; O(N log N)
평균 &amp;rarr; O(N log N)
최악 &amp;rarr; O(N log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으로 볼 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;24. Heap Sort의 공간 복잡도&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap Sort는 배열 내부에서 데이터를 이동시키면서 정렬할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 별도의 큰 배열을 추가로 만들 필요가 없는 &lt;b&gt;In-place 정렬 알고리즘&lt;/b&gt;으로 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 In-place Heap Sort에서는 추가 공간을:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수준으로 사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;25. Heap Sort와 다른 정렬 알고리즘 비교&lt;/h1&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;버블 정렬&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;구현이 단순하지만 느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;선택 정렬&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;구현이 단순함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;삽입 정렬&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;거의 정렬된 데이터에서 유리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;병합 정렬&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;안정적이며 추가 공간 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;퀵 정렬&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;평균적으로 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;힙 정렬&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;최악에도 일정하며 In-place 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap Sort는 &lt;b&gt;최악의 경우에도 O(N log N)을 보장하고, In-place 방식으로 구현할 수 있다는 점&lt;/b&gt;이 중요한 특징이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;26. Heap과 Binary Search Tree의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 트리와 관련되어 있기 때문에 헷갈릴 수 있지만 목적이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Heap은:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      80    70
     / \
    50 60
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 부모와 자식 사이의 관계를 보장한다.&lt;/p&gt;
&lt;pre class=&quot;fix&quot;&gt;&lt;code&gt;부모 &amp;gt;= 자식
&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;반면 Binary Search Tree는:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        50
       /  \
     30    70
    / \    / \
   20 40  60 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;왼쪽 &amp;lt; 부모 &amp;lt; 오른쪽
&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;pre class=&quot;coq&quot;&gt;&lt;code&gt;Heap
&amp;rarr; 우선순위 처리에 적합

Binary Search Tree
&amp;rarr; 탐색에 적합
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하다고 이해할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;27. Heap에서 배운 핵심 개념&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 단계에서 배운 개념을 정리하면 다음과 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Heap&lt;/h3&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;완전 이진 트리
+
부모와 자식 사이의 우선순위 관계
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Min Heap&lt;/h3&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;부모 &amp;lt;= 자식
&amp;rarr; Root = 최솟값
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Max Heap&lt;/h3&gt;
&lt;pre class=&quot;fix&quot;&gt;&lt;code&gt;부모 &amp;gt;= 자식
&amp;rarr; Root = 최댓값
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Heapify&lt;/h3&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;힙의 조건을 만족하도록 노드의 위치를 조정하는 과정
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Insert&lt;/h3&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;마지막 위치에 삽입
&amp;rarr; Sift Up
&amp;rarr; O(log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Delete&lt;/h3&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Root 제거
&amp;rarr; 마지막 노드를 Root로 이동
&amp;rarr; Sift Down
&amp;rarr; O(log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Priority Queue&lt;/h3&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;우선순위가 높은 데이터를 먼저 처리하는 자료구조
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 구현 방법으로 Heap을 사용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Heap Sort&lt;/h3&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;Heap 생성
&amp;rarr; Root의 최댓값 또는 최솟값을 정렬 영역으로 이동
&amp;rarr; Heapify
&amp;rarr; 반복
&amp;rarr; O(N log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;28. 전체 개념 연결&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 단계의 전체적인 연결 관계는 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;트리
 &amp;darr;
완전 이진 트리
 &amp;darr;
Heap
 ├── Min Heap
 │     └── Root = 최솟값
 │
 └── Max Heap
       └── Root = 최댓값
 &amp;darr;
Heapify
 &amp;darr;
Insert / Delete
 &amp;darr;
Priority Queue
 &amp;darr;
우선순위가 높은 데이터부터 처리
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Heap은 정렬 알고리즘에도 연결된다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;Heap
 &amp;darr;
Heap Sort
 &amp;darr;
O(N log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이번 단계에서는 단순히 Heap이라는 자료구조 하나를 배우는 것이 아니라,&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Tree
 &amp;darr;
Complete Binary Tree
 &amp;darr;
Heap
 &amp;darr;
Priority Queue
 &amp;darr;
Heap Sort
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 이어지는 하나의 흐름을 이해하는 것이 중요했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;29. 자료구조 전체 흐름에서 Heap의 위치&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 학습한 자료구조를 연결해 보면 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Array
 &amp;darr;
인덱스를 이용한 빠른 접근

Linked List
 &amp;darr;
노드 간 연결을 이용한 데이터 관리

Stack
 &amp;darr;
LIFO
 &amp;darr;
마지막에 들어온 데이터부터 처리

Queue
 &amp;darr;
FIFO
 &amp;darr;
먼저 들어온 데이터부터 처리

Tree
 &amp;darr;
계층적인 데이터 구조

Heap
 &amp;darr;
우선순위가 높은 데이터를 빠르게 확인하고 처리
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 트리의 개념을 기반으로 하지만, 일반적인 트리 탐색보다는 &lt;b&gt;우선순위 관리&lt;/b&gt;에 특화된 자료구조라고 이해할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;30. C++에서의 Heap&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++ STL에서는 Priority Queue뿐만 아니라 Heap을 직접 다룰 수 있는 함수도 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 다음과 같은 함수들이 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::make_heap
std::push_heap
std::pop_heap
std::sort_heap
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 C++에서 Heap을 공부할 때는 단순히 std::priority_queue의 사용법만 외우는 것이 아니라, 내부적으로 Heap이 어떤 원리로 동작하는지 이해하는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적으로:&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Heap의 원리 이해
 &amp;darr;
std::priority_queue 이해
 &amp;darr;
make_heap / push_heap / pop_heap 이해
 &amp;darr;
Heap Sort 이해
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 흐름으로 연결할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;31. 이번 학습에서 정리한 핵심&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 10단계에서 가장 중요하게 이해한 부분은 Heap을 단순한 트리 자료구조로 보는 것이 아니라 &lt;b&gt;우선순위 처리와 정렬 알고리즘으로 연결해서 이해하는 것&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 완전 이진 트리의 구조를 사용하고, 부모와 자식 사이에 우선순위 관계를 유지한다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Min Heap
&amp;rarr; 최솟값이 Root

Max Heap
&amp;rarr; 최댓값이 Root
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 값을 넣을 때는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;마지막 위치에 삽입
&amp;rarr; Sift Up
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Root를 제거할 때는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;마지막 노드를 Root로 이동
&amp;rarr; Sift Down
&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;그리고 이러한 Heap의 특성을 이용하면 Priority Queue를 효율적으로 구현할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Heap
 &amp;darr;
Priority Queue
 &amp;darr;
우선순위가 높은 데이터부터 처리
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Heap을 이용해 Heap Sort를 구현할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;Heap
 &amp;darr;
Heap Sort
 &amp;darr;
O(N log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이번 단계의 핵심 흐름은 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Complete Binary Tree
        &amp;darr;
      Heap
     ↙    ↘
Min Heap  Max Heap
     &amp;darr;       &amp;darr;
최솟값 우선  최댓값 우선
     ↘       ↙
   Priority Queue
        &amp;darr;
우선순위 처리
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 별도로:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;Heap
 &amp;darr;
Heap Sort
 &amp;darr;
O(N log N)
&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;b&gt;Heap = 완전 이진 트리 + 부모/자식 간 우선순위 관계&lt;/b&gt;, &lt;b&gt;Priority Queue = 우선순위에 따라 데이터를 처리하는 구조&lt;/b&gt;, &lt;b&gt;Heap Sort = Heap의 특성을 이용한 정렬 알고리즘&lt;/b&gt;이라는 관계를 중심으로 이해하는 것이 핵심이었다.&lt;/p&gt;</description>
      <category>자료구조와 알고리즘</category>
      <author>begin-play</author>
      <guid isPermaLink="true">https://begin-play.tistory.com/99</guid>
      <comments>https://begin-play.tistory.com/99#entry99comment</comments>
      <pubDate>Fri, 4 Sep 2026 23:03:29 +0900</pubDate>
    </item>
    <item>
      <title>Unreal Engine의 Foliage와 HISM, 그리고 자료구조&amp;middot;알고리즘의 연관성</title>
      <link>https://begin-play.tistory.com/98</link>
      <description>&lt;h1&gt;Unreal Engine의 Foliage와 HISM, 그리고 자료구조&amp;middot;알고리즘의 연관성&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 오늘 학습한 내용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 언리얼 엔진에서 많은 나무, 풀, 바위 등의 오브젝트를 배치할 때 사용하는 &lt;b&gt;Foliage&lt;/b&gt;와 &lt;b&gt;HISM(Hierarchical Instanced Static Mesh)&lt;/b&gt;에 대해 학습했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 Foliage와 HISM을 단순히 여러 개의 Static Mesh를 효율적으로 배치하기 위한 언리얼 엔진의 기능이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 내용을 학습하면서 Foliage와 HISM이 자료구조와 알고리즘에서 배운 개념들과 연결된다는 것을 알게 되었다.&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;배열(Array)&lt;/li&gt;
&lt;li&gt;인스턴스(Instance)&lt;/li&gt;
&lt;li&gt;공간 분할(Spatial Partitioning)&lt;/li&gt;
&lt;li&gt;계층 구조(Hierarchy)&lt;/li&gt;
&lt;li&gt;트리(Tree)&lt;/li&gt;
&lt;li&gt;탐색(Search)&lt;/li&gt;
&lt;li&gt;정렬 및 그룹화&lt;/li&gt;
&lt;li&gt;시간 복잡도&lt;/li&gt;
&lt;li&gt;메모리와 CPU/GPU 작업량 최적화&lt;/li&gt;
&lt;li&gt;컬링(Culling)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 자료구조와 알고리즘은 단순히 코딩 테스트를 위한 개념이 아니라 실제 게임 엔진에서 대량의 데이터를 효율적으로 처리하고 성능을 최적화하는 데에도 사용되는 중요한 개념이라는 것을 알게 되었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. Foliage란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진의 Foliage는 나무, 풀, 바위 등의 오브젝트를 넓은 공간에 대량으로 배치하기 위한 시스템이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 일반적인 방법으로 나무 1,000개를 배치한다고 생각해보면 각각의 나무를 서로 독립적인 Actor로 만들어 배치할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Tree Actor 1
Tree Actor 2
Tree Actor 3
...
Tree Actor 1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 화면에 나무가 1,000개 존재하는 것뿐만 아니라 엔진에서도 많은 오브젝트를 각각 관리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 같은 Static Mesh를 대량으로 배치해야 하는 상황에서는 각각을 독립적인 Actor로 관리하는 것보다 &lt;b&gt;Instancing&lt;/b&gt;이라는 개념을 사용하는 것이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. Instance란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Instance는 같은 Static Mesh를 여러 번 사용하면서 각각의 위치, 회전, 크기 등의 정보만 다르게 저장하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 하나의 나무 Mesh가 있다고 가정한다.&lt;/p&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;Tree Mesh
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Mesh를 1,000번 복제해서 각각 별도의 Mesh 데이터를 만드는 것이 아니라 하나의 Mesh를 공유하고 각각의 Instance에 Transform 등의 정보를 저장할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Tree Mesh
 ├─ Instance 0 &amp;rarr; 위치 A
 ├─ Instance 1 &amp;rarr; 위치 B
 ├─ Instance 2 &amp;rarr; 위치 C
 ├─ ...
 └─ Instance 999 &amp;rarr; 위치 Z
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조 관점에서 보면 하나의 Mesh 데이터를 공유하면서 여러 Instance의 데이터를 관리하는 구조라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 여러 Instance의 정보를 저장하고 관리하기 위해 배열과 같은 자료구조의 개념이 연결된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. HISM이란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HISM은 &lt;b&gt;Hierarchical Instanced Static Mesh&lt;/b&gt;의 약자이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 Instanced Static Mesh의 개념에 &lt;b&gt;Hierarchy&lt;/b&gt;, 즉 계층 구조를 추가한 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 Instance가 존재할 때 모든 Instance를 하나씩 검사하는 것은 비효율적일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 나무가 10,000개 있다고 가정한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Tree 1
Tree 2
Tree 3
...
Tree 10000
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라에서 실제로 보이는 나무를 찾기 위해 매번 10,000개의 Instance를 전부 검사한다면 상당히 많은 작업이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HISM은 Instance들을 공간적으로 묶고 계층적으로 관리하여 필요한 영역을 효율적으로 검사할 수 있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 &lt;b&gt;Hierarchy와 Tree 자료구조&lt;/b&gt;의 개념이 연결된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. HISM과 Tree 자료구조&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조에서 계층 구조를 표현하는 대표적인 자료구조가 &lt;b&gt;Tree&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 공간을 계층적으로 나눈다면 다음과 같은 구조로 생각할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;                 전체 공간
                /        \
             영역 A       영역 B
            /    \       /    \
          A1      A2    B1     B2
         / \      / \   / \    / \
        ...      ...  ...     ...
&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 data-ke-size=&quot;size16&quot;&gt;예를 들어 카메라가 A 영역에 있다면 다음과 같이 탐색 범위를 줄일 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;전체 공간
   &amp;darr;
영역 A
   &amp;darr;
A1
   &amp;darr;
필요한 Instance
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;계층 구조를 이용해서 탐색해야 할 범위를 줄이는 것&lt;/b&gt;이 핵심이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. HISM과 탐색 알고리즘&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분에서 자료구조와 알고리즘에서 공부했던 &lt;b&gt;탐색(Search)&lt;/b&gt;과 연결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 단순한 탐색 방법 중 하나는 선형 탐색이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;for (int i = 0; i &amp;lt; 10000; i++)
{
    // 각각의 Instance 검사
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Instance가 N개라면 최악의 경우 모든 Instance를 검사해야 하기 때문에 작업량은 O(N)이 된다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;N = 10,000

&amp;rarr; 최대 10,000개의 Instance 검사
&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;pre class=&quot;routeros&quot;&gt;&lt;code&gt;전체 Instance
      &amp;darr;
공간 분할
      &amp;darr;
관련 없는 영역 제거
      &amp;darr;
관심 영역 탐색
      &amp;darr;
필요한 Instance 검사
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 단순하게 모든 Instance를 순차적으로 검사하는 것보다 실제로 검사해야 하는 데이터의 양을 크게 줄일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 실제 HISM의 내부 동작을 단순하게 &lt;b&gt;O(log N)&lt;/b&gt;이라고 단정해서는 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 것은 HISM이 트리 기반의 계층적 공간 분할을 통해 &lt;b&gt;탐색 범위를 줄이고 불필요한 작업을 제거한다는 알고리즘적인 사고방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. Culling과 알고리즘&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임에서 중요한 개념 중 하나가 &lt;b&gt;Culling&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Culling은 현재 카메라에서 볼 필요가 없는 오브젝트를 렌더링 대상에서 제외하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 나무가 10,000개 존재하지만 현재 카메라에서 실제로 필요한 나무가 500개라고 가정한다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;전체 Instance = 10,000

화면에 필요한 Instance = 500
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10,000개의 나무를 전부 렌더링할 필요가 없다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10,000개
   &amp;darr;
Culling
   &amp;darr;
필요한 500개
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Culling을 효율적으로 수행하기 위해서는 어떤 오브젝트가 현재 화면에 필요한지 빠르게 판단할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 공간 정보를 활용하면 카메라와 관계없는 영역을 빠르게 제외할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 Culling 역시 단순히 렌더링 기능으로만 볼 것이 아니라,&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;자료구조
   +
공간 분할
   +
탐색 알고리즘
   &amp;darr;
필요한 데이터만 처리
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 관점에서 이해할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. 공간 분할(Spatial Partitioning)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임에서 오브젝트의 수가 많아지면 모든 오브젝트를 한꺼번에 관리하는 것은 비효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 공간을 여러 영역으로 나누어 관리하는 &lt;b&gt;Spatial Partitioning&lt;/b&gt;이라는 개념을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 맵을 다음과 같이 나눌 수 있다.&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;+---------+---------+
| Zone A  | Zone B  |
|         |         |
+---------+---------+
| Zone C  | Zone D  |
|         |         |
+---------+---------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라가 Zone A에 있다면 Zone D에 있는 나무까지 동일한 방식으로 검사할 필요가 없을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;공간 분할
   &amp;darr;
탐색 범위 감소
   &amp;darr;
불필요한 검사 감소
   &amp;darr;
렌더링 및 CPU 작업 감소
&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;b&gt;탐색 범위를 줄이는 것&lt;/b&gt;과 같은 최적화 사고방식이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. Foliage와 HISM의 관계&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Foliage와 HISM은 완전히 동일한 개념은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Foliage는 언리얼 엔진에서 많은 식생 오브젝트를 배치하고 관리하기 위한 시스템이고, HISM은 동일한 Static Mesh의 많은 Instance를 효율적으로 렌더링하고 관리하기 위한 인스턴싱 컴포넌트의 한 종류이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로 보면 다음과 같이 연결할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Foliage
   &amp;darr;
대량의 식생 배치 및 관리
   &amp;darr;
Instance 기반 처리
   &amp;darr;
HISM 등의 인스턴싱 방식 활용
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Foliage를 학습하면서 HISM을 함께 이해하면 &lt;b&gt;왜 수많은 나무와 풀을 배치해도 일반 Actor를 수천 개 배치하는 것과 다른 방식으로 처리할 수 있는지&lt;/b&gt; 이해하는 데 도움이 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. 일반 Actor와 Instance 방식의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 나무 1,000개를 배치한다고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Actor 방식에서는 다음처럼 각각의 Actor가 존재한다.&lt;/p&gt;
&lt;pre class=&quot;dos&quot;&gt;&lt;code&gt;Actor
 └─ Tree

Actor
 └─ Tree

Actor
 └─ Tree

...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Instance 방식에서는 다음과 같이 하나의 Tree Mesh를 공유하면서 여러 Instance의 Transform 등을 관리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;하나의 Tree Mesh
       +
Instance Transform 배열
       &amp;darr;
여러 개의 나무
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서 자료구조의 &lt;b&gt;Array&lt;/b&gt; 개념이 연결된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로는 다음과 같이 생각할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;Instances[0]
Instances[1]
Instances[2]
...
Instances[N]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Instance의 위치, 회전, 크기 등의 데이터를 관리하는 구조이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. 자료구조를 공부하는 이유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조를 공부하면서 배운 내용들은 처음에는 게임 개발과 크게 관련이 없어 보일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제 게임 엔진에서는 다양한 자료구조를 사용하여 수많은 데이터를 관리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어,&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Array
 &amp;darr;
Instance 데이터 관리

Tree
 &amp;darr;
계층 구조 관리

Spatial Partitioning
 &amp;darr;
공간을 나누어 관리

Search
 &amp;darr;
필요한 오브젝트 탐색

Algorithm
 &amp;darr;
탐색 및 컬링을 효율적으로 수행
&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;b&gt;어떤 자료구조를 사용하고 어떤 알고리즘으로 데이터를 처리하는지&lt;/b&gt;가 성능에 큰 영향을 줄 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. 시간 복잡도 관점에서 생각하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 게임 월드에 Instance가 100개 있다고 가정한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;N = 100
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Instance를 확인하는 방식이라면 작업량은 O(N)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Instance가 10,000개가 되면 확인해야 할 데이터도 크게 증가한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;100
 &amp;darr;
1,000
 &amp;darr;
10,000
 &amp;darr;
100,000
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 데이터가 증가할수록 &lt;b&gt;어떻게 탐색 범위를 줄일 것인가&lt;/b&gt;가 중요해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Tree나 Spatial Partitioning 등의 구조를 사용하면 모든 데이터를 직접 검사하지 않고 필요한 부분을 중심으로 탐색할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 자료구조와 알고리즘을 공부할 때 시간 복잡도를 중요하게 배우는 이유와 연결된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;13. 이것이 게임 최적화와 연결되는 이유&lt;/h1&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;pre class=&quot;properties&quot;&gt;&lt;code&gt;오브젝트 관리
      &amp;darr;
위치 확인
      &amp;darr;
카메라와의 관계 확인
      &amp;darr;
컬링
      &amp;darr;
렌더링 준비
      &amp;darr;
GPU 작업
&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;pre class=&quot;&quot;&gt;&lt;code&gt;많은 데이터
   &amp;darr;
효율적인 자료구조
   &amp;darr;
효율적인 탐색 알고리즘
   &amp;darr;
불필요한 작업 제거
   &amp;darr;
게임 성능 향상
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라는 흐름으로 이해할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;14. 실제 게임 상황에 적용해보기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 게임 맵에 나무가 20,000개 있다고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비효율적인 방식은 단순하게 모든 나무를 대상으로 작업하는 것이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;나무 20,000개
     &amp;darr;
전부 검사
     &amp;darr;
전부 처리
     &amp;darr;
전부 렌더링
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 인스턴싱과 공간 분할, 컬링 등을 이용하면 다음과 같은 방향으로 처리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;나무 20,000개
       &amp;darr;
공간/계층 구조로 관리
       &amp;darr;
카메라와 관계없는 영역 제거
       &amp;darr;
필요한 Instance만 처리
       &amp;darr;
필요한 것만 렌더링
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 데이터가 많아질수록 단순히 컴퓨터의 성능에 의존하는 것이 아니라 &lt;b&gt;데이터를 어떻게 구성하고 필요한 데이터만 어떻게 찾아낼 것인지&lt;/b&gt;가 중요해진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;15. 지금까지 공부한 자료구조와의 연결&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 공부했던 자료구조와 알고리즘도 언리얼 엔진의 여러 시스템과 연결해서 생각할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Array&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Instance 데이터나 Actor 목록처럼 순차적으로 데이터를 관리하는 데 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;Instances[0]
Instances[1]
Instances[2]
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Hash&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 ID나 키를 이용해서 데이터를 빠르게 찾는 데 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;ID &amp;rarr; Object
이름 &amp;rarr; Data
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Tree&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층 구조를 표현할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;전체
 ├─ 영역 A
 │   ├─ A1
 │   └─ A2
 └─ 영역 B
     ├─ B1
     └─ B2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HISM에서 학습한 계층 구조와 연결해서 생각할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Graph&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임에서는 NavMesh나 경로 탐색처럼 여러 노드와 연결 관계를 표현하는 문제와 연결된다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;A ─ B ─ C
│   │
D ─ E
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Heap / Priority Queue&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선순위가 필요한 작업이나 경로 탐색 알고리즘 등과 연결해서 생각할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Search Algorithm&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 오브젝트나 데이터를 빠르게 찾기 위한 탐색 문제와 연결된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;16. 이번 학습에서 알게 된 가장 중요한 점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 학습을 통해 자료구조와 알고리즘이 실제 게임 개발과 밀접하게 연결되어 있다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Foliage와 HISM을 단순히 언리얼 엔진의 기능으로만 보면&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;&quot;나무를 많이 배치할 때 사용하는 기능&quot;
&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;pre class=&quot;routeros&quot;&gt;&lt;code&gt;많은 Instance
      &amp;darr;
효율적인 데이터 관리
      &amp;darr;
공간 분할
      &amp;darr;
계층 구조
      &amp;darr;
탐색 범위 감소
      &amp;darr;
Culling
      &amp;darr;
불필요한 작업 제거
      &amp;darr;
성능 최적화
&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;b&gt;데이터가 많아질수록 모든 데이터를 무조건 처리하는 것이 아니라 필요한 데이터만 효율적으로 찾아내는 것이 중요하다&lt;/b&gt;는 점이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;17. 최종 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 학습한 내용을 정리하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Foliage&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 나무, 풀, 바위 등의 식생을 대량으로 배치하고 관리하기 위한 언리얼 엔진의 시스템&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Instance&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 동일한 Static Mesh를 공유하면서 여러 개의 Transform 등을 이용해 많은 오브젝트를 표현하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HISM&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 많은 Static Mesh Instance를 계층적으로 관리하여 효율적인 컬링과 렌더링을 지원하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Hierarchy&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 데이터를 계층적으로 구성하여 필요한 부분을 효율적으로 찾고 불필요한 부분을 제외하기 위한 구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tree&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 계층 구조를 표현하는 대표적인 자료구조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Spatial Partitioning&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 공간을 여러 영역으로 나누어 탐색해야 할 범위를 줄이는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Culling&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 현재 렌더링할 필요가 없는 오브젝트를 렌더링 대상에서 제외하는 과정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Search&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 필요한 데이터를 효율적으로 찾기 위한 알고리즘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 오늘 학습의 핵심은 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;게임 엔진은 매우 많은 데이터를 처리해야 하기 때문에 효율적인 자료구조와 알고리즘이 필요하다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Foliage와 HISM은 대량의 오브젝트를 효율적으로 관리하고 렌더링하기 위해 이러한 최적화 개념이 실제 게임 엔진에서 활용되는 사례라고 볼 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조와 알고리즘을 공부하면서 배운 &lt;b&gt;Array, Tree, Search, 시간 복잡도 등의 개념이 단순한 이론이 아니라 실제 언리얼 엔진의 성능과 연결될 수 있다는 점&lt;/b&gt;을 확인할 수 있었다.&lt;/p&gt;</description>
      <category>내일배움캠프_언리얼9기</category>
      <author>begin-play</author>
      <guid isPermaLink="true">https://begin-play.tistory.com/98</guid>
      <comments>https://begin-play.tistory.com/98#entry98comment</comments>
      <pubDate>Thu, 3 Sep 2026 23:37:05 +0900</pubDate>
    </item>
    <item>
      <title>Unreal Engine 네트워크 Replication 시행착오</title>
      <link>https://begin-play.tistory.com/97</link>
      <description>&lt;h1&gt;Unreal Engine 네트워크 Replication 시행착오&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘 학습한 내용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 LevelManager를 멀티플레이 환경에서 서버와 클라이언트가 동일하게 사용하도록 만드는 과정에서 &lt;b&gt;Replication과 관련된 문제를 직접 확인하고 해결했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 완성된 구조를 만든 것이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버에서는 정상적으로 작동하는데 클라이언트에서는 왜 안 되는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 로그를 하나씩 확인하면서 원인을 찾아갔다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 서버와 클라이언트에서 Zone 배치가 달라지는 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 게임에서는 여러 개의 Middle Zone을 가지고 있고, 게임이 시작될 때 서버에서 Zone의 순서를 랜덤하게 섞도록 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Middle Zone이:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;0 1 2 3 4 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라면 서버에서 Shuffle한 결과가:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;0 5 1 4 3 2
&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;서버에서는 이 순서를 기준으로 Zone을 배치하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 멀티플레이로 실행해 보니 &lt;b&gt;서버와 클라이언트의 Zone 배치가 서로 달라지는 문제가 발생했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 단순히 Zone 배치 코드 자체의 문제일 수도 있다고 생각했지만, 실제로는 &lt;b&gt;서버가 결정한 Shuffle 결과를 클라이언트가 제대로 전달받지 못하고 있는 문제&lt;/b&gt;였다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 클라이언트에서 랜덤을 다시 실행하면 안 된다는 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 문제를 해결하는 과정에서 중요한 부분을 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버와 클라이언트가 각각 랜덤을 실행하면 안 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 서버에서:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Server
0 5 1 4 3 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 나왔다고 해서 클라이언트에서도 똑같이 랜덤을 실행하면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Client
2 4 0 5 1 3
&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 data-ke-size=&quot;size16&quot;&gt;따라서 구조를:&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;서버
&amp;darr;
Zone 순서 결정
&amp;darr;
결과를 저장
&amp;darr;
클라이언트에게 전달
&amp;darr;
클라이언트는 전달받은 결과를 사용
&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;b&gt;멀티플레이에서는 랜덤 자체보다 랜덤의 결과를 누가 결정하고 공유하느냐가 중요하다&lt;/b&gt;는 것을 알게 되었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. MiddleZoneOrder를 Replication하도록 변경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 결정한 Zone 순서를 클라이언트에게 전달하기 위해 MiddleZoneOrder를 Replicated Property로 만들었다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UPROPERTY(ReplicatedUsing = OnRep_MiddleZoneOrder)
TArray&amp;lt;int32&amp;gt; MiddleZoneOrder;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 GetLifetimeReplicatedProps()에도 등록했다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;DOREPLIFETIME(ALevelManager, MiddleZoneOrder);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 서버가 만든:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;0 5 1 4 3 2
&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;pre class=&quot;stylus&quot;&gt;&lt;code&gt;OnRep_MiddleZoneOrder()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 실행되고 ArrangePlacedZones()를 호출하도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 최종적으로:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Server
MiddleZoneOrder 생성
       &amp;darr;
Replication
       &amp;darr;
Client
MiddleZoneOrder 수신
       &amp;darr;
OnRep_MiddleZoneOrder()
       &amp;darr;
ArrangePlacedZones()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 만들었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 그런데 Replication을 설정했는데도 클라이언트에서 OnRep가 호출되지 않았다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 또 문제가 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 로그를 확인했을 때는:&lt;/p&gt;
&lt;pre class=&quot;inform7&quot;&gt;&lt;code&gt;[Server] LevelManager BeginPlay
[Server] MiddleZoneOrder Num: 6
[Server] MiddleZoneOrder[0] = 0
[Server] MiddleZoneOrder[1] = 5
[Server] MiddleZoneOrder[2] = 1
[Server] MiddleZoneOrder[3] = 4
[Server] MiddleZoneOrder[4] = 3
[Server] MiddleZoneOrder[5] = 2
&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;즉 서버에서는 Shuffle도 정상적으로 이루어지고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 클라이언트에서는:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;[Client] LevelManager BeginPlay
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만 출력되고:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;[Client] OnRep_MiddleZoneOrder
&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;b&gt;MiddleZoneOrder 자체가 클라이언트까지 제대로 전달되지 않고 있다는 것&lt;/b&gt;을 확인했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 처음에는 ForceNetUpdate()를 의심했다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 해결하는 과정에서 서버가 MiddleZoneOrder를 변경한 직후 네트워크 업데이트를 강제로 요청하도록:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;ForceNetUpdate();
&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 data-ke-size=&quot;size16&quot;&gt;결국 중요한 것은 &lt;b&gt;해당 LevelManager Actor 자체가 클라이언트에게 제대로 Relevant한 상태인지&lt;/b&gt;였다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 최종적으로 Always Relevant가 문제의 원인이라는 것을 확인했다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LevelManager에:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;bReplicates = true;
bAlwaysRelevant = true;
&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;그리고 다시 실행하자 클라이언트에서도 서버가 만든 MiddleZoneOrder를 정상적으로 전달받기 시작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 이번 문제는 단순히:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;변수를 Replicated로 만들지 않았다
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 아니라,&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;변수는 Replicated로 만들어져 있었지만
그 변수를 가지고 있는 LevelManager Actor가
클라이언트에게 항상 필요한 Actor로 제대로 취급되지 않는 문제
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;였던 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 이번 문제를 통해 Relevancy를 이해하게 되었다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 일을 겪기 전에는 bReplicates = true이면 그냥 서버의 Actor가 클라이언트에게 전부 전달되는 것이라고 생각하기 쉬웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 실제로는 &lt;b&gt;Replication과 Relevancy가 함께 고려된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;bReplicates = true
&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;Unreal Engine은 네트워크 최적화를 위해 &lt;b&gt;해당 클라이언트에게 Actor가 필요한지&lt;/b&gt; 판단한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 LevelManager는 플레이어 주변에 있는 특정 오브젝트가 아니라 &lt;b&gt;게임 전체의 Zone을 관리하는 관리자 Actor&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이 Actor는 특정 위치에 있느냐와 관계없이 클라이언트에게 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;bAlwaysRelevant = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 사용하는 것이 현재 LevelManager의 역할에 맞았다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 가장 중요한 시행착오&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 가장 큰 시행착오는 다음과 같다.&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;서버와 클라이언트의 Zone 배치가 다름.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버의 Shuffle 결과는 정상.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 발견&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 서버의 Shuffle 결과를 받지 못함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MiddleZoneOrder를 Replication하도록 변경.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그런데도 문제 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OnRep_MiddleZoneOrder()가 클라이언트에서 호출되지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LevelManager Actor 자체의 네트워크 관련 설정을 확인.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최종 해결&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;bReplicates = true;
bAlwaysRelevant = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 MiddleZoneOrder를 정상적으로 전달받고 OnRep_MiddleZoneOrder()를 실행.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;darr;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최종적으로&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 결정한 Zone 순서를 클라이언트가 동일하게 사용하여 Zone을 배치할 수 있게 됨.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 오늘 가장 크게 배운 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 단순히 Replicated나 OnRep의 문법을 배운 것보다 &lt;b&gt;네트워크 문제가 발생했을 때 어디부터 확인해야 하는지&lt;/b&gt;를 경험한 것이 가장 중요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;서버에서 잘 되는데 클라이언트에서 안 된다.&quot;&lt;/p&gt;
&lt;/blockquote&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;pre class=&quot;yaml&quot;&gt;&lt;code&gt;서버에서 Shuffle이 제대로 되는가?
        &amp;darr;
Yes

MiddleZoneOrder가 제대로 생성되는가?
        &amp;darr;
Yes

클라이언트에서 OnRep가 호출되는가?
        &amp;darr;
No

그렇다면 데이터가 전달되지 않는 이유는?
        &amp;darr;
LevelManager의 네트워크 관련 설정 확인

Always Relevant 활성화
        &amp;darr;
해결
&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;b&gt;서버 &amp;rarr; 복제 &amp;rarr; 클라이언트의 각 단계에서 어디까지 정상적으로 진행되고 있는지 로그로 확인하는 것이 중요하다&lt;/b&gt;는 것을 배웠다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;오늘의 핵심 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제를 한 문장으로 정리하면:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버에서 결정한 Zone Shuffle 결과를 클라이언트에게 복제하려 했지만 LevelManager의 Relevancy 문제로 제대로 전달되지 않았고, Always Relevant를 적용하여 해결했다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 앞으로 비슷한 문제가 발생했을 때는 다음 순서로 확인해야겠다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. 서버에서 값이 제대로 만들어지는가?
        &amp;darr;
2. 해당 Actor가 Replicates 상태인가?
        &amp;darr;
3. Replicated Property로 등록되어 있는가?
        &amp;darr;
4. GetLifetimeReplicatedProps()에 등록되어 있는가?
        &amp;darr;
5. 클라이언트에서 OnRep가 호출되는가?
        &amp;darr;
6. Actor의 Relevancy에 문제가 없는가?
        &amp;darr;
7. 필요한 Actor라면 Always Relevant가 적절한가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 &lt;b&gt;&quot;서버에서 정상적으로 동작한다&quot;와 &quot;클라이언트에서도 정상적으로 동작한다&quot;는 완전히 다른 문제&lt;/b&gt;라는 것을 직접 경험했다.&lt;/p&gt;</description>
      <category>내일배움캠프_언리얼9기</category>
      <author>begin-play</author>
      <guid isPermaLink="true">https://begin-play.tistory.com/97</guid>
      <comments>https://begin-play.tistory.com/97#entry97comment</comments>
      <pubDate>Wed, 2 Sep 2026 23:33:28 +0900</pubDate>
    </item>
    <item>
      <title>트리(Tree)</title>
      <link>https://begin-play.tistory.com/96</link>
      <description>&lt;h1&gt;트리(Tree)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Tree란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 학습에서는 &lt;b&gt;Tree(트리)&lt;/b&gt; 자료구조에 대해 공부하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 학습했던 배열, 연결 리스트, 스택, 큐 등의 자료구조는 데이터를 일렬로 표현하는 형태가 많았다. 반면 Tree는 데이터를 &lt;b&gt;계층적인 구조&lt;/b&gt;로 표현할 수 있다는 특징이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 컴퓨터의 폴더 구조를 생각할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Root
├── Folder A
│   ├── File A
│   └── File B
└── Folder B
    ├── File C
    └── File D&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Root 아래에 Folder가 있고, Folder 아래에 다시 File이 존재하는 것처럼 &lt;b&gt;상위 데이터와 하위 데이터 사이에 부모-자식 관계가 존재하는 구조&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Tree는 다음과 같은 계층 구조를 표현하는 데 적합하다.&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;li&gt;게임 오브젝트의 계층 구조&lt;/li&gt;
&lt;li&gt;탐색 구조&lt;/li&gt;
&lt;li&gt;우선순위 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. Tree의 기본 용어&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree를 이해하기 위해서는 먼저 기본적인 용어를 이해해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 Tree가 있다고 가정한다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \
    D   E&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Node&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree를 구성하는 각각의 데이터를 &lt;b&gt;Node(노드)&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 Tree에서는 다음이 각각 Node이다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A
B
C
D
E&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Root&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree에서 가장 위에 있는 Node를 &lt;b&gt;Root(루트)&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;        A
       / \
      B   C&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서는 A가 Root이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Tree에서는 Root가 하나 존재한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Parent&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 Node의 바로 아래에 Child를 가지고 있는 Node를 &lt;b&gt;Parent(부모)&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;        A
       / \
      B   C&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 A는 B와 C의 Parent이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Child&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Parent 바로 아래에 연결된 Node를 &lt;b&gt;Child(자식)&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;        A
       / \
      B   C&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서는 B, C가 A의 Child이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sibling&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 Parent를 가지고 있는 Node들을 &lt;b&gt;Sibling(형제)&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;        A
       / \
      B   C&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B와 C는 모두 A를 Parent로 가지고 있으므로 Sibling 관계이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Leaf&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식이 없는 Node를 &lt;b&gt;Leaf(리프)&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \
    D   E&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Tree에서는:&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;C
D
E&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 자식을 가지고 있지 않으므로 Leaf이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Edge&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node와 Node를 연결하는 선을 &lt;b&gt;Edge(간선)&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;A
|
B&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A와 B 사이의 연결이 하나의 Edge이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. Depth와 Height&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree에서는 Node가 얼마나 깊은 위치에 있는지를 나타내기 위해 &lt;b&gt;Depth&lt;/b&gt;라는 개념을 사용한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        A       Depth 0
       / \
      B   C     Depth 1
     /
    D           Depth 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Root인 A에서 시작한다고 하면:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;A = Depth 0
B = Depth 1
C = Depth 1
D = Depth 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Root에서 해당 Node까지 내려가는 Edge의 개수를 기준으로 Depth를 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 &lt;b&gt;Height&lt;/b&gt;는 해당 Node에서 가장 깊은 Leaf까지의 거리를 의미한다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;        A
       /
      B
     /
    C&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A에서 C까지 Edge가 2개이므로 A의 Height는 2이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 Height는 자료구조를 설명하는 방식에 따라 Node 개수를 기준으로 정의하는 경우도 있으므로, 실제 문제를 풀 때는 해당 문제에서 사용하는 정의를 확인할 필요가 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. Tree의 특징&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree는 일반적으로 다음과 같은 특징을 가진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;계층 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree는 Parent와 Child 관계를 이용해서 데이터를 계층적으로 표현한다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Parent
  &amp;darr;
Child
  &amp;darr;
Child&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 단순히 데이터를 순서대로 저장하는 것보다 &lt;b&gt;상하 관계가 중요한 데이터&lt;/b&gt;를 표현하는 데 적합하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;순환이 없음&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Tree에는 Cycle이 존재하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A &amp;rarr; B &amp;rarr; C&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같이 연결되어 있을 때 C에서 다시 A로 돌아가는 연결이 존재한다면 일반적인 Tree 구조라고 보기 어렵다.&lt;/p&gt;
&lt;pre class=&quot;dns&quot;&gt;&lt;code&gt;A
&amp;darr;
B
&amp;darr;
C
↘
 A&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 순환 구조는 Graph에서 중요한 개념이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. Binary Tree&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree에는 여러 종류가 있는데, 이번 학습에서는 특히 &lt;b&gt;Binary Tree(이진 트리)&lt;/b&gt;를 중요하게 다루었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Binary Tree는 하나의 Node가 가질 수 있는 Child의 개수가 &lt;b&gt;최대 2개인 Tree&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \
    D   E&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Node는 최대 두 개의 Child를 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 &lt;b&gt;Binary Tree라는 것만으로 값의 크기 관계가 정해지는 것은 아니라는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        10
       /  \
      50   2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조도 Binary Tree이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 Node가 최대 두 개의 Child를 가지고 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이것은 BST의 규칙에는 맞지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음을 구분해야 한다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Binary Tree
&amp;rarr; Child가 최대 2개

BST
&amp;rarr; Binary Tree의 조건
+ 값의 크기 규칙&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. Binary Tree의 형태&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Binary Tree에는 여러 형태가 존재한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Full Binary Tree&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Node가 Child를 정확히 0개 또는 2개 가지는 형태이다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \
    D   E&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A와 B는 각각 Child를 2개 가지고 있고, C/D/E는 Child가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Full Binary Tree 조건을 만족한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Complete Binary Tree&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Complete Binary Tree는 위에서부터 Level을 채워나가며, 마지막 Level을 제외하면 모든 Level이 가득 차 있고 마지막 Level도 &lt;b&gt;왼쪽부터 빈틈없이 채워지는 형태&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \  /
    D  E F&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 Level에 D, E, F가 왼쪽부터 채워져 있기 때문에 Complete Binary Tree이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 개념은 이후 학습하는 &lt;b&gt;Heap에서 매우 중요하게 사용된다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Perfect Binary Tree&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Level이 완전히 채워져 있는 Binary Tree를 Perfect Binary Tree라고 한다.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;          A
        /   \
       B     C
      / \   / \
     D  E  F   G&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Leaf가 같은 Depth에 존재하고 모든 내부 Node가 Child를 2개씩 가지고 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. Binary Search Tree&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 &lt;b&gt;Binary Search Tree, 즉 BST&lt;/b&gt;를 학습하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BST는 Binary Tree의 한 종류이지만 단순히 Child가 최대 2개라는 것에서 끝나지 않고 &lt;b&gt;값의 크기에 대한 규칙&lt;/b&gt;을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 BST의 구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        50
       /  \
     30    70
    / \    / \
   20 40  60 80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 핵심 규칙은:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;왼쪽 Subtree &amp;lt; 현재 Node &amp;lt; 오른쪽 Subtree&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;따라서 50을 기준으로 보면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;왼쪽
20, 30, 40
&amp;rarr; 50보다 작음

오른쪽
60, 70, 80
&amp;rarr; 50보다 큼&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;30을 기준으로 보더라도:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;20 &amp;lt; 30 &amp;lt; 40&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라는 관계가 성립한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. BST가 검색에 유리한 이유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BST가 중요한 이유는 &lt;b&gt;값의 크기를 이용하여 탐색 범위를 줄일 수 있기 때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 BST에서 60을 찾는다고 가정한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        50
       /  \
     30    70
    / \    / \
   20 40  60 80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Root인 50과 60을 비교한다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;60 &amp;gt; 50&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 50보다 작은 값들이 존재하는 왼쪽 Subtree는 탐색할 필요가 없다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        50
          \
           70&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;70과 비교한다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;60 &amp;lt; 70&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 오른쪽이 아니라 왼쪽으로 이동한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        70
       /
      60&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;60을 찾았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 BST는 다음과 같은 방식으로 탐색한다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;찾는 값 &amp;lt; 현재 Node
&amp;rarr; 왼쪽으로 이동

찾는 값 &amp;gt; 현재 Node
&amp;rarr; 오른쪽으로 이동

찾는 값 == 현재 Node
&amp;rarr; 탐색 성공&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 값의 크기를 이용해 탐색 범위를 계속 줄일 수 있다는 것이 BST의 핵심이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. BST의 시간 복잡도&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BST가 균형 있게 구성되어 있다면 Tree의 높이가 대략 log N 수준이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 평균적인 경우:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;Search
O(log N)&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;하지만 BST가 항상 균형을 유지하는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 데이터가 다음과 같은 순서로 들어간다고 생각해보았다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10
  \
   20
     \
      30
        \
         40
           \
            50&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 한쪽으로만 치우치게 되면 Tree가 사실상 Linked List와 비슷한 형태가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 탐색할 때 최대 N개의 Node를 확인해야 할 수 있기 때문에:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Worst Case
O(N)&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;따라서 일반적인 BST의 탐색 성능은:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Average
O(log N)

Worst
O(N)&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;이러한 문제를 해결하기 위해 AVL Tree나 Red-Black Tree처럼 Tree의 균형을 유지하는 자료구조도 존재한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. Tree Traversal&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree의 데이터를 실제로 사용하려면 Tree 안의 Node들을 순서대로 방문해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 &lt;b&gt;Traversal(순회)&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 Tree Traversal은 다음 네 가지이다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Preorder
Inorder
Postorder
Level Order&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 네 가지는 단순히 이름을 외우기보다는 &lt;b&gt;Node를 어떤 순서로 방문하는지&lt;/b&gt;를 이해하는 것이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. Preorder Traversal&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preorder는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Root
&amp;rarr; Left
&amp;rarr; Right&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;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;현재 Node
&amp;rarr; 왼쪽 Subtree
&amp;rarr; 오른쪽 Subtree&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;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \
    D   E&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preorder로 순회하면:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A &amp;rarr; B &amp;rarr; D &amp;rarr; E &amp;rarr; C&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;A를 먼저 방문하고, B로 이동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B에서도 B를 먼저 방문한 후 왼쪽 D, 오른쪽 E를 방문한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 C를 방문한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. Inorder Traversal&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Inorder는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Left
&amp;rarr; Root
&amp;rarr; Right&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;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;왼쪽 Subtree
&amp;rarr; 현재 Node
&amp;rarr; 오른쪽 Subtree&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;같은 Tree를 사용하면:&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \
    D   E&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Inorder 결과는:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;D &amp;rarr; B &amp;rarr; E &amp;rarr; A &amp;rarr; C&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;13. BST에서 Inorder가 중요한 이유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 Tree 학습에서 가장 중요하게 이해한 부분 중 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BST에서 Inorder Traversal을 수행하면 값이 오름차순으로 정렬된 결과를 얻을 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 BST를 살펴보면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        50
       /  \
     30    70
    / \    / \
   20 40  60 80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Inorder는:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;왼쪽
&amp;rarr; 현재
&amp;rarr; 오른쪽&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;20
&amp;rarr; 30
&amp;rarr; 40
&amp;rarr; 50
&amp;rarr; 60
&amp;rarr; 70
&amp;rarr; 80&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 data-ke-size=&quot;size16&quot;&gt;이것은 우연이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BST 자체가:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;왼쪽 &amp;lt; 현재 &amp;lt; 오른쪽&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;그리고 Inorder가:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;왼쪽
&amp;rarr; 현재
&amp;rarr; 오른쪽&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;pre class=&quot;armasm&quot;&gt;&lt;code&gt;BST
+
Inorder Traversal
&amp;darr;
오름차순 정렬된 순서&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 단계에서 단순 암기해야 하는 공식이라기보다 &lt;b&gt;BST의 구조와 Inorder의 방문 순서가 만나서 만들어지는 결과&lt;/b&gt;라는 점을 이해하는 것이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;14. Postorder Traversal&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Postorder는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Left
&amp;rarr; Right
&amp;rarr; Root&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;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;왼쪽 Subtree
&amp;rarr; 오른쪽 Subtree
&amp;rarr; 현재 Node&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \
    D   E&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Postorder 결과는:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;D &amp;rarr; E &amp;rarr; B &amp;rarr; C &amp;rarr; A&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;b&gt;부모 Node를 마지막에 처리한다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 하위 Node들을 먼저 처리한 후 부모 Node를 처리해야 하는 작업에서 활용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;15. Level Order Traversal&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Level Order는 DFS 방식과 조금 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree를 깊게 들어가는 것이 아니라 &lt;b&gt;Level 단위로 순회&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        A        Level 0
       / \
      B   C      Level 1
     / \
    D   E        Level 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Level Order 결과는:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A &amp;rarr; B &amp;rarr; C &amp;rarr; D &amp;rarr; E&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Level 0
&amp;rarr; Level 1
&amp;rarr; Level 2
&amp;rarr; ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순으로 방문한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;16. Level Order와 Queue&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Level Order를 이해할 때 &lt;b&gt;Queue와 연결하면 매우 쉽게 이해할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue는 이전에 학습했던 것처럼:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;FIFO
First In First Out&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;다음 Tree가 있다고 하자.&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \
    D   E&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 A를 Queue에 넣는다.&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;Queue
[A]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A를 꺼내서 처리하고 A의 Child인 B와 C를 Queue에 넣는다.&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;Queue
[B, C]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B를 꺼낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B의 Child인 D와 E를 Queue 뒤에 넣는다.&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;Queue
[C, D, E]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C를 꺼낸다.&lt;/p&gt;
&lt;pre class=&quot;apache&quot;&gt;&lt;code&gt;Queue
[D, E]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 D, E 순서로 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A &amp;rarr; B &amp;rarr; C &amp;rarr; D &amp;rarr; E&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;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Level Order
&amp;darr;
BFS
&amp;darr;
Queue&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;17. DFS와 Tree Traversal&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preorder, Inorder, Postorder는 모두 &lt;b&gt;DFS(Depth First Search)&lt;/b&gt; 방식으로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS는:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깊이를 우선하여 탐색하는 방식&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree에서는 한쪽 방향으로 깊게 내려간 다음 다시 올라와 다른 경로를 탐색한다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;DFS
├── Preorder
├── Inorder
└── Postorder&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Level Order는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;BFS
└── Level Order&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 연결할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;18. Tree와 Recursion의 관계&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree Traversal을 공부하면서 이전에 배운 &lt;b&gt;재귀(Recursion)&lt;/b&gt;와도 연결할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Inorder Traversal은 다음과 같은 형태로 구현할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;void Inorder(Node* Node)
{
    if (Node == nullptr)
        return;

    Inorder(Node-&amp;gt;Left);
    Visit(Node);
    Inorder(Node-&amp;gt;Right);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드의 구조 자체가 Inorder의 정의와 동일하다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Inorder(Node-&amp;gt;Left)
&amp;rarr; Visit(Node)
&amp;rarr; Inorder(Node-&amp;gt;Right)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Tree Traversal을 공부하면서:&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;Tree
+
Recursion
+
DFS&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;Tree는 자기 자신과 비슷한 작은 Subtree로 계속 나눌 수 있기 때문에 재귀적인 사고방식과 잘 맞는다는 것도 이해할 수 있었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;19. 네 가지 Traversal 비교&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 Tree를 기준으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        1
       / \
      2   3
     / \
    4   5&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Preorder&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Root &amp;rarr; Left &amp;rarr; Right

1 &amp;rarr; 2 &amp;rarr; 4 &amp;rarr; 5 &amp;rarr; 3&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Inorder&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Left &amp;rarr; Root &amp;rarr; Right

4 &amp;rarr; 2 &amp;rarr; 5 &amp;rarr; 1 &amp;rarr; 3&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Postorder&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Left &amp;rarr; Right &amp;rarr; Root

4 &amp;rarr; 5 &amp;rarr; 2 &amp;rarr; 3 &amp;rarr; 1&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Level Order&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Level &amp;rarr; Level &amp;rarr; Level

1 &amp;rarr; 2 &amp;rarr; 3 &amp;rarr; 4 &amp;rarr; 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음 세 가지는 특히 구분해서 기억해야 한다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Preorder
&amp;rarr; Root Left Right

Inorder
&amp;rarr; Left Root Right

Postorder
&amp;rarr; Left Right Root&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Level Order는 Level을 기준으로 처리한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;20. Heap&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree에서 다음으로 학습한 것은 &lt;b&gt;Heap&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 일반적으로 &lt;b&gt;Complete Binary Tree 형태&lt;/b&gt;를 기반으로 하며, 추가적인 규칙인 &lt;b&gt;Heap Property&lt;/b&gt;를 유지하는 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 두 가지 종류가 있다.&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;Max Heap
Min Heap&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;21. Max Heap&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Heap에서는 &lt;b&gt;부모 Node가 자식 Node보다 크거나 같아야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      50    80
     / \   /
    20 30 70&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 부모와 자식을 비교하면:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;100 &amp;gt; 50
100 &amp;gt; 80

50 &amp;gt; 20
50 &amp;gt; 30

80 &amp;gt; 70&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;따라서 Max Heap에서는 항상 Root에 &lt;b&gt;가장 큰 값&lt;/b&gt;이 위치한다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Max Heap
&amp;rarr; Root = Maximum&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;22. Min Heap&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Min Heap은 반대이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 Node가 자식 Node보다 작거나 같아야 한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        10
       /  \
      30   20
     / \   /
    50 40 70&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Root에는 가장 작은 값이 위치한다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Min Heap
&amp;rarr; Root = Minimum&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;23. BST와 Heap의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BST와 Heap은 모두 Tree를 기반으로 하기 때문에 비슷해 보이지만 목적과 규칙이 다르다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BST&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BST의 핵심 규칙:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;왼쪽 &amp;lt; 현재 &amp;lt; 오른쪽&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;BST는 특정 값을 찾는 &lt;b&gt;검색&lt;/b&gt;에 유리하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Heap&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap의 핵심 규칙:&lt;/p&gt;
&lt;pre class=&quot;fix&quot;&gt;&lt;code&gt;Parent &amp;gt;= Child&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는&lt;/p&gt;
&lt;pre class=&quot;fix&quot;&gt;&lt;code&gt;Parent &amp;lt;= Child&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;Heap에서는 부모와 자식 사이의 관계가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Heap에서는 Root가 최대값이고, Min Heap에서는 Root가 최소값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Heap은 &lt;b&gt;가장 큰 값이나 가장 작은 값을 빠르게 꺼내는 작업&lt;/b&gt;에 적합하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;24. Heap은 왜 Complete Binary Tree를 사용하는가?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 일반적으로 Complete Binary Tree 형태를 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Complete Binary Tree는 구조가 빈틈없이 채워지기 때문에 배열로 표현하기 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \ /
    D  E F&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Tree를 배열로 표현하면:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[A, B, C, D, E, F]&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;따라서 Heap은 Node마다 별도의 포인터를 사용해 Tree를 구성하지 않고 &lt;b&gt;배열 기반으로 효율적으로 구현할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;25. Heap의 배열 Index&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0부터 Index를 사용한다고 할 때 다음 공식이 성립한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Parent(i) = (i - 1) / 2

Left Child = 2 * i + 1

Right Child = 2 * i + 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;          A(0)
        /     \
      B(1)    C(2)
      / \      /
    D(3) E(4) F(5)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B의 Index는 1이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B의 왼쪽 Child:&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;2 * 1 + 1
= 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 D의 Index가 3이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B의 오른쪽 Child:&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;2 * 1 + 2
= 4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 E의 Index가 4이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 공식이 성립하는 이유는 Complete Binary Tree의 구조가 빈틈없이 채워져 있기 때문이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;26. Heap의 Insert&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap에 데이터를 추가할 때는 단순히 아무 위치에 넣는 것이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Complete Binary Tree 구조를 유지하기 위해 &lt;b&gt;가장 마지막 위치에 새로운 값을 추가&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 Heap의 규칙을 만족할 때까지 부모와 비교하며 위로 이동시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Heapify Up
또는
Bubble Up&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;27. Heapify Up&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Heap에 새로운 값을 추가한다고 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Heap:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      50    80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 120을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Complete Binary Tree 구조를 유지하기 위해 마지막 위치에 넣는다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      50    80
     /
   120&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;120은 부모인 50보다 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 둘을 교환한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
     120    80
     /
    50&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 120은 다시 부모인 100보다 크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 교환한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        120
       /   \
     100    80
     /
    50&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Max Heap 조건을 만족한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 새로운 Node를 위쪽으로 올려가면서 Heap 조건을 만족시키는 것이 Heapify Up이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;28. Heap의 Remove&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Heap에서 가장 중요한 데이터는 Root에 있는 최대값이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        120
       /   \
     100    80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Root인 120을 제거하면 빈 공간이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 일반적으로 마지막 Node를 Root 위치로 가져온 후 Heap 조건을 다시 맞춘다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 과정에서 자식 Node와 비교하면서 아래쪽으로 이동시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을:&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Heapify Down&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;29. Heap의 시간 복잡도&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 Complete Binary Tree이므로 높이가 O(log N) 수준이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 대표적인 연산의 시간 복잡도는:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Insert
O(log N)

Remove
O(log N)

Peek
O(1)&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;Peek가 O(1)인 이유는 최댓값 또는 최솟값이 항상 Root에 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Root만 확인하면 되므로 다른 Node들을 탐색할 필요가 없다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;30. Priority Queue&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;b&gt;Priority Queue(우선순위 큐)&lt;/b&gt;를 학습하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Queue는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;FIFO
First In First Out&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 data-ke-size=&quot;size16&quot;&gt;하지만 Priority Queue는 이름에 Queue가 들어가더라도 단순한 FIFO 구조가 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우선순위가 높은 데이터가 먼저 처리되는 자료구조&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;A &amp;rarr; 우선순위 1
B &amp;rarr; 우선순위 10
C &amp;rarr; 우선순위 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 한다면 일반 Queue에서는 들어온 순서대로:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A &amp;rarr; B &amp;rarr; C&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;하지만 Priority Queue에서는 우선순위가 높은 순서대로:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;B &amp;rarr; C &amp;rarr; A&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 처리할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;31. Priority Queue와 Heap의 관계&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Priority Queue를 구현하는 대표적인 방법이 &lt;b&gt;Heap&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 최댓값 또는 최솟값을 Root에 유지할 수 있기 때문에 Priority Queue의 동작과 잘 맞는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Max Heap에서는:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        100
       /   \
      50    80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 가장 큰 값인 100이 Root에 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Priority Queue에서 가장 높은 우선순위의 데이터를 가져올 때 Root만 확인하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Heap
&amp;darr;
최댓값 / 최솟값을 Root에 유지
&amp;darr;
Priority Queue 구현에 활용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라는 관계가 성립한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;32. C++의 std::priority_queue&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 Priority Queue를 직접 Heap으로 구현하지 않고 다음 자료구조를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::priority_queue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 std::priority_queue는 &lt;b&gt;Max Heap 방식&lt;/b&gt;으로 동작한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;std::priority_queue&amp;lt;int&amp;gt; PQ;

PQ.push(30);
PQ.push(10);
PQ.push(50);
PQ.push(20);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 다음과 같이 들어가더라도:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;30
10
50
20&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;top()을 확인하면 가장 큰 값인:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;50&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;그 후 pop()을 하면 50이 제거되고 다음으로 큰 값이 Top에 위치한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;33. std::priority_queue의 주요 함수&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;push()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 추가한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;PQ.push(50);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap의 구조와 우선순위를 유지하면서 데이터를 추가한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;top()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 가장 우선순위가 높은 데이터를 확인한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;PQ.top();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 top()은 &lt;b&gt;데이터를 제거하지 않는다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 현재 가장 높은 우선순위의 값을 확인한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;pop()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Top에 있는 데이터를 제거한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;PQ.pop();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, pop() 자체가 제거되는 값을 반환하지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 값을 사용하면서 제거하려면:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;int Value = PQ.top();
PQ.pop();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과 같은 방식으로 사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;empty()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Priority Queue가 비어 있는지 확인한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;PQ.empty();&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;size()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 들어 있는 데이터의 개수를 확인한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;PQ.size();&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;34. Min Heap으로 사용하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::priority_queue는 기본적으로 Max Heap이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::priority_queue&amp;lt;int&amp;gt; PQ;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;큰 값
&amp;darr;
작은 값&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;반대로 작은 값을 먼저 꺼내는 Min Heap 형태로 사용하고 싶다면 비교 기준을 변경할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::priority_queue&amp;lt;int, std::vector&amp;lt;int&amp;gt;, std::greater&amp;lt;int&amp;gt;&amp;gt; PQ;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;작은 값
&amp;darr;
큰 값&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순으로 처리된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;35. 이번 학습에서 다른 자료구조와 연결한 내용&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 Tree 학습은 지금까지 공부했던 자료구조들과도 많이 연결되었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tree + Recursion&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree Traversal은 재귀적인 구조로 구현하기 좋다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Tree
&amp;darr;
Subtree
&amp;darr;
Subtree
&amp;darr;
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이전에 공부했던 Recursion과 연결된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Level Order + Queue&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Level Order는 같은 Level을 순서대로 처리하기 때문에 Queue의 FIFO 특성을 활용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Level Order
&amp;darr;
BFS
&amp;darr;
Queue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 관계로 연결된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Heap + Priority Queue&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 최대값 또는 최소값을 빠르게 확인할 수 있기 때문에 Priority Queue를 구현하는 데 활용된다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Heap
&amp;darr;
최댓값 / 최솟값 관리
&amp;darr;
Priority Queue&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BST + Inorder&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BST의 값의 크기 규칙과 Inorder의 순회 순서가 결합되면 정렬된 결과를 얻을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;BST
&amp;darr;
Left &amp;lt; Root &amp;lt; Right
&amp;darr;
Inorder
&amp;darr;
오름차순&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 관계가 이번 학습에서 특히 중요했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;36. BST와 Heap 비교&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BST와 Heap은 모두 Tree를 기반으로 하지만 목적이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분BSTHeap&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;기본 구조&lt;/td&gt;
&lt;td&gt;Binary Tree&lt;/td&gt;
&lt;td&gt;Complete Binary Tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;핵심 규칙&lt;/td&gt;
&lt;td&gt;Left &amp;lt; Root &amp;lt; Right&lt;/td&gt;
&lt;td&gt;Parent와 Child의 크기 관계&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root의 의미&lt;/td&gt;
&lt;td&gt;특정 값이 보장되지 않음&lt;/td&gt;
&lt;td&gt;Max 또는 Min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주요 목적&lt;/td&gt;
&lt;td&gt;검색&lt;/td&gt;
&lt;td&gt;우선순위 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검색&lt;/td&gt;
&lt;td&gt;유리&lt;/td&gt;
&lt;td&gt;일반적인 검색에는 불리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최댓값/최솟값&lt;/td&gt;
&lt;td&gt;구조에 따라 탐색&lt;/td&gt;
&lt;td&gt;Root에서 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inorder&lt;/td&gt;
&lt;td&gt;BST라면 오름차순&lt;/td&gt;
&lt;td&gt;정렬을 보장하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대표 활용&lt;/td&gt;
&lt;td&gt;탐색 구조&lt;/td&gt;
&lt;td&gt;Priority Queue&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;BST
&amp;rarr; 값을 찾는 데 유리

Heap
&amp;rarr; 가장 큰 값 또는 가장 작은 값을 빠르게 꺼내는 데 유리&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 구분할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;37. 시간 복잡도 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 단계에서 학습한 대표적인 시간 복잡도를 정리하면 다음과 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BST&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;균형이 잡힌 경우:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;Search &amp;rarr; O(log N)
Insert &amp;rarr; O(log N)
Delete &amp;rarr; O(log N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최악의 경우:&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Search &amp;rarr; O(N)
Insert &amp;rarr; O(N)
Delete &amp;rarr; O(N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Heap&lt;/h2&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Peek   &amp;rarr; O(1)
Insert &amp;rarr; O(log N)
Remove &amp;rarr; O(log N)&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;Heap의 높이가 O(log N)이기 때문에 Insert와 Remove에서 위아래로 이동하는 비용도 O(log N)이 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;38. 이번 단계에서 배운 내용을 전체적으로 연결&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 학습을 통해 Tree라는 하나의 자료구조에서 여러 가지 형태와 활용 방법이 파생되는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Tree
│
├── Binary Tree
│   │
│   └── BST
│       │
│       └── Inorder
│           &amp;darr;
│        오름차순
│
└── Heap
    │
    ├── Max Heap
    │
    └── Min Heap
         &amp;darr;
    Priority Queue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Traversal은 별도로:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Tree Traversal
│
├── Preorder
│   └── Root &amp;rarr; Left &amp;rarr; Right
│
├── Inorder
│   └── Left &amp;rarr; Root &amp;rarr; Right
│
├── Postorder
│   └── Left &amp;rarr; Right &amp;rarr; Root
│
└── Level Order
    └── Level 순서
        &amp;darr;
       BFS
        &amp;darr;
      Queue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 연결된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;39. 이번 단계에서 가장 중요했던 내용&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 학습에서 단순히 Tree의 종류를 외우는 것보다 다음의 관계를 이해하는 것이 중요했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① Tree&lt;/h3&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;계층적인 데이터 구조&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② Binary Tree&lt;/h3&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;하나의 Node가 가질 수 있는 Child
&amp;rarr; 최대 2개&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ BST&lt;/h3&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Left &amp;lt; Root &amp;lt; Right&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값의 크기를 이용해서 탐색 범위를 줄일 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;④ BST + Inorder&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;BST
+
Inorder
&amp;darr;
오름차순 정렬&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BST의 구조적 특징과 Inorder의 방문 순서가 결합해서 발생하는 결과이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⑤ Level Order&lt;/h3&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Level Order
&amp;darr;
BFS
&amp;darr;
Queue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue의 FIFO 특성을 이용해서 같은 Level부터 순서대로 처리할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⑥ Heap&lt;/h3&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Complete Binary Tree
+
Heap Property&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Max Heap에서는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;가장 큰 값 &amp;rarr; Root&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Min Heap에서는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;가장 작은 값 &amp;rarr; Root&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;⑦ Heap + Priority Queue&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Heap
&amp;darr;
최댓값 / 최솟값을 빠르게 확인
&amp;darr;
Priority Queue 구현&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::priority_queue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 이용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;40. 최종 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 9단계에서는 &lt;b&gt;Tree 자료구조의 기본 개념부터 Binary Tree, BST, Tree Traversal, Heap, Priority Queue까지 학습하였다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree는 배열이나 연결 리스트처럼 데이터를 단순히 일렬로 저장하는 것이 아니라 &lt;b&gt;Parent와 Child 관계를 통해 계층적으로 데이터를 표현하는 자료구조&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Binary Tree는 하나의 Node가 최대 2개의 Child를 가질 수 있는 Tree이고, BST는 여기에:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Left &amp;lt; Root &amp;lt; Right&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;BST는 값의 크기를 비교하면서 왼쪽 또는 오른쪽 Subtree만 선택하여 탐색할 수 있기 때문에 균형이 잘 잡혀 있다면 O(log N)의 탐색 성능을 기대할 수 있다. 하지만 한쪽으로 치우친다면 최악의 경우 O(N)까지 성능이 떨어질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree Traversal에는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Preorder
Inorder
Postorder
Level Order&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 있으며, Preorder/Inorder/Postorder는 DFS 방식으로 생각할 수 있고 Level Order는 BFS 방식으로 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;BST에서 Inorder Traversal을 수행하면 오름차순으로 정렬된 결과가 나온다는 관계가 매우 중요하다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;BST
&amp;rarr; Left &amp;lt; Root &amp;lt; Right

Inorder
&amp;rarr; Left &amp;rarr; Root &amp;rarr; Right

따라서
&amp;rarr; 작은 값부터 큰 값 순서&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Level Order는 Queue의 FIFO 특성을 활용하여 구현할 수 있기 때문에:&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Level Order
&amp;rarr; BFS
&amp;rarr; Queue&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;Heap은 Complete Binary Tree를 기반으로 하며, Max Heap에서는 부모가 자식보다 크거나 같고 Min Heap에서는 부모가 자식보다 작거나 같다. 이를 통해 Max Heap은 최댓값을, Min Heap은 최솟값을 Root에서 O(1)에 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Heap은 &lt;b&gt;Priority Queue를 구현하는 대표적인 방법&lt;/b&gt;으로 사용된다. C++에서는 std::priority_queue를 통해 이러한 우선순위 기반 처리를 쉽게 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이번 단계에서 배운 내용을 하나의 흐름으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Tree
│
├── Binary Tree
│   │
│   └── BST
│       │
│       └── Inorder
│           &amp;darr;
│        오름차순
│
├── Traversal
│   ├── Preorder
│   ├── Inorder
│   ├── Postorder
│   └── Level Order
│                &amp;darr;
│               BFS
│                &amp;darr;
│              Queue
│
└── Heap
    ├── Max Heap
    └── Min Heap
          &amp;darr;
    Priority Queue
          &amp;darr;
std::priority_queue&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 학습을 통해 Tree는 단순히 하나의 자료구조가 아니라 &lt;b&gt;탐색을 위한 BST와 우선순위 처리를 위한 Heap 등 여러 목적의 자료구조로 확장되는 기본 구조&lt;/b&gt;라는 것을 이해하였다.&lt;/p&gt;</description>
      <category>자료구조와 알고리즘</category>
      <author>begin-play</author>
      <guid isPermaLink="true">https://begin-play.tistory.com/96</guid>
      <comments>https://begin-play.tistory.com/96#entry96comment</comments>
      <pubDate>Tue, 1 Sep 2026 21:14:11 +0900</pubDate>
    </item>
    <item>
      <title>해시</title>
      <link>https://begin-play.tistory.com/95</link>
      <description>&lt;h1&gt;해시(Hash)&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Hash란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 &lt;b&gt;Hash(해시)&lt;/b&gt;에 대해 학습했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시는 데이터를 빠르게 저장하고 찾기 위한 방법이며, 특히 &lt;b&gt;특정 데이터를 빠르게 탐색하는 상황에서 실전 활용도가 높은 자료구조&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시(Hash)는 쉽게 말하면 어떤 데이터를 일정한 규칙을 통해 다른 값으로 변환하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 문자열 &quot;Apple&quot;이라는 데이터를 어떤 계산을 통해 1234라는 숫자로 변환할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;&quot;Apple&quot; &amp;rarr; 1234
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 데이터를 다른 값으로 변환하는 과정을 &lt;b&gt;해싱(Hashing)&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해싱의 목적은 단순히 데이터를 다른 값으로 바꾸는 것이 아니라, 변환된 값을 이용해서 데이터를 &lt;b&gt;빠르게 저장하고 찾는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. Hash Function&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시를 만들기 위해서는 &lt;b&gt;Hash Function(해시 함수)&lt;/b&gt;이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 함수는 데이터를 입력받아서 &lt;b&gt;Hash Value(해시 값)&lt;/b&gt;를 만들어내는 함수이다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;입력 데이터
    &amp;darr;
Hash Function
    &amp;darr;
Hash Value
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 방식으로 동작할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;&quot;Apple&quot;
   &amp;darr;
Hash Function
   &amp;darr;
1234
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 데이터는 다른 해시 값으로 변환될 수 있다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;&quot;Banana&quot;
   &amp;darr;
Hash Function
   &amp;darr;
5678
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 해시 함수는 입력 데이터를 가능한 한 &lt;b&gt;고르게 분산&lt;/b&gt;시키는 것이 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 해시 테이블의 크기가 10이라면 다음과 같이 여러 위치에 데이터가 골고루 분산되는 것이 좋다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Apple  &amp;rarr; 2
Banana &amp;rarr; 7
Cat    &amp;rarr; 4
Dog    &amp;rarr; 9
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 다음처럼 특정 위치에 데이터가 몰리게 되면 좋지 않다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Apple  &amp;rarr; 2
Banana &amp;rarr; 2
Cat    &amp;rarr; 2
Dog    &amp;rarr; 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 &lt;b&gt;Collision(충돌)&lt;/b&gt;이 많이 발생하기 때문이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. Hash Table&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Hash Table(해시 테이블)&lt;/b&gt;은 해시를 이용해서 데이터를 저장하고 탐색하는 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 동작 과정은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;데이터
 &amp;darr;
Hash Function
 &amp;darr;
Hash Value
 &amp;darr;
Index
 &amp;darr;
Hash Table에 저장
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &quot;Apple&quot;이라는 문자열을 해시 함수에 넣었더니 1234라는 값이 나왔다고 가정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 테이블의 크기가 10이라면 다음과 같이 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;1234 % 10 = 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 4번 위치에 &quot;Apple&quot;을 저장할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Index

0
1
2
3
4 &amp;rarr; Apple
5
6
7
8
9
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 &quot;Apple&quot;을 찾을 때도 같은 과정을 거친다.&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;&quot;Apple&quot;
   &amp;darr;
Hash Function
   &amp;darr;
1234
   &amp;darr;
1234 % 10
   &amp;darr;
4
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 4번 위치로 바로 접근해서 데이터를 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 해시 테이블이 빠른 이유이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. Hash Table이 빠른 이유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 배열에서 특정 값을 찾는다고 가정한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[10, 25, 33, 41, 57, 62, 78]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;57을 찾기 위해 처음부터 하나씩 확인한다면 다음과 같은 과정이 필요하다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;10 &amp;rarr; X
25 &amp;rarr; X
33 &amp;rarr; X
41 &amp;rarr; X
57 &amp;rarr; 발견
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식은 &lt;b&gt;선형 탐색&lt;/b&gt;이며 시간 복잡도는 O(N)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 해시 테이블에서는 데이터 자체를 이용해서 저장 위치를 계산할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;57
 &amp;darr;
Hash Function
 &amp;darr;
Index 3
&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;b&gt;평균적으로 O(1)&lt;/b&gt;의 시간 복잡도를 가진다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;일반적인 선형 탐색

데이터
 &amp;darr;
처음부터 하나씩 확인
 &amp;darr;
O(N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 해시 테이블은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Hash Table

데이터
 &amp;darr;
Hash Function
 &amp;darr;
위치 계산
 &amp;darr;
바로 접근
 &amp;darr;
평균 O(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. 해시 테이블이 항상 O(1)은 아니다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 테이블의 탐색이 O(1)이라고 할 때는 정확하게는 &lt;b&gt;평균적인 경우&lt;/b&gt;를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 O(1)인 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 이유가 바로 &lt;b&gt;Collision(충돌)&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. Collision&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Collision(충돌)&lt;/b&gt;은 서로 다른 데이터가 같은 해시 테이블의 위치를 사용하게 되는 상황이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 결과가 나왔다고 가정한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Apple  &amp;rarr; 4
Banana &amp;rarr; 7
Cat    &amp;rarr; 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apple과 Cat이 모두 4번 위치를 사용하려고 한다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;4 &amp;rarr; Apple
4 &amp;rarr; Cat
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 Collision이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 함수가 아무리 좋아도 Collision을 완전히 없애기는 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 종류는 매우 많을 수 있지만 해시 테이블의 저장 공간에는 한계가 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 해시 테이블에서는 &lt;b&gt;Collision이 발생했을 때 어떻게 처리할 것인지&lt;/b&gt;가 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 방법으로 다음 두 가지가 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1. Chaining
2. Open Addressing
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. Chaining&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Chaining&lt;/b&gt;은 충돌이 발생했을 때 같은 위치에 여러 데이터를 연결해서 저장하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 상황을 생각할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Apple &amp;rarr; 4
Cat   &amp;rarr; 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 데이터가 모두 4번 위치를 사용하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chaining에서는 이를 연결해서 관리한다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;0
1
2
3
4 &amp;rarr; Apple &amp;rarr; Cat
5
6
7
&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;예를 들어 4번 위치에 데이터가 다음과 같이 연결되어 있을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;4 &amp;rarr; Apple &amp;rarr; Cat &amp;rarr; Dog
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cat을 찾는 경우에는 먼저 Cat의 해시 값을 계산해서 4번 위치를 찾는다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Cat
 &amp;darr;
Hash Function
 &amp;darr;
4
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 4번 위치에 연결되어 있는 데이터를 확인한다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;4 &amp;rarr; Apple &amp;rarr; Cat &amp;rarr; Dog
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. Chaining의 장점과 단점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chaining의 장점은 충돌이 발생했을 때 같은 위치에 데이터를 연결하면 되기 때문에 개념적으로 비교적 직관적이라는 점이다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;같은 위치에 들어오면
        &amp;darr;
연결해서 저장
&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;pre class=&quot;basic&quot;&gt;&lt;code&gt;4 &amp;rarr; A &amp;rarr; B &amp;rarr; C &amp;rarr; D &amp;rarr; E &amp;rarr; F
&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;극단적으로 모든 데이터가 하나의 위치에 몰린다면 탐색 성능이 O(N)까지 떨어질 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. Open Addressing&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Open Addressing&lt;/b&gt;은 충돌이 발생했을 때 다른 빈 공간을 찾아서 데이터를 저장하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 상황이 있다고 가정한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Apple &amp;rarr; 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 4번 위치에 &quot;Apple&quot;이 저장되어 있는데 &quot;Cat&quot;도 4번 위치를 사용해야 한다면 충돌이 발생한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Cat &amp;rarr; 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4번 위치가 이미 사용 중이기 때문에 다른 위치를 찾는다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;4 &amp;rarr; 사용 중
5 &amp;rarr; 빈 공간
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 5번 위치에 &quot;Cat&quot;을 저장한다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;0
1
2
3
4 &amp;rarr; Apple
5 &amp;rarr; Cat
6
7
8
9
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chaining처럼 같은 위치에 연결하는 것이 아니라 &lt;b&gt;해시 테이블 내부의 다른 빈 공간을 찾아서 저장&lt;/b&gt;하는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. Open Addressing의 대표적인 방법&lt;/h1&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Linear Probing&lt;/li&gt;
&lt;li&gt;Quadratic Probing&lt;/li&gt;
&lt;li&gt;Double Hashing&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중 가장 단순한 방법이 &lt;b&gt;Linear Probing&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. Linear Probing&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Linear Probing은 충돌이 발생하면 다음 칸을 순서대로 확인하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 4번 위치에 저장하려고 했는데 이미 사용 중이라면 다음과 같이 확인한다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;4 &amp;rarr; 사용 중
5 &amp;rarr; 사용 중
6 &amp;rarr; 빈 공간
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 6번에 데이터를 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;index + 1
index + 2
index + 3
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서로 빈 공간을 찾는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. Chaining과 Open Addressing 비교&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 방식 모두 Collision을 해결하기 위한 방법이지만 동작 방식이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분ChainingOpen Addressing&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;충돌 처리&lt;/td&gt;
&lt;td&gt;같은 위치에 연결&lt;/td&gt;
&lt;td&gt;다른 빈 위치 탐색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;저장 방식&lt;/td&gt;
&lt;td&gt;별도의 연결 구조 활용&lt;/td&gt;
&lt;td&gt;테이블 내부에 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;구현 개념&lt;/td&gt;
&lt;td&gt;비교적 직관적&lt;/td&gt;
&lt;td&gt;탐사 방식 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빈 공간 활용&lt;/td&gt;
&lt;td&gt;상대적으로 유연&lt;/td&gt;
&lt;td&gt;테이블 공간이 중요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대표적인 방법&lt;/td&gt;
&lt;td&gt;Linked List 등&lt;/td&gt;
&lt;td&gt;Linear Probing 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심적으로 기억할 내용은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Chaining
충돌
 &amp;darr;
같은 위치에 여러 데이터 연결

Open Addressing
충돌
 &amp;darr;
다른 빈 위치 탐색
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;13. Hash Table의 시간 복잡도&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 테이블에서 중요한 연산은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;삽입
탐색
삭제
&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;삽입&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;탐색&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;삭제&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 해시 테이블은 &lt;b&gt;평균적으로 매우 빠른 탐색&lt;/b&gt;이 필요한 상황에서 강력한 자료구조이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;14. C++의 std::unordered_map&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 해시 테이블을 직접 구현하지 않아도 해시 기반 자료구조를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::unordered_map
&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;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;unordered_map&amp;gt;
#include &amp;lt;string&amp;gt;

std::unordered_map&amp;lt;std::string, int&amp;gt; Scores;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 std::string을 Key로 사용하고 int를 Value로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 다음과 같은 형태로 데이터를 저장할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Key       Value

Player1 &amp;rarr; 100
Player2 &amp;rarr; 200
Player3 &amp;rarr; 150
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;15. unordered_map의 구조&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 데이터를 저장할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Scores[&quot;Player1&quot;] = 100;
Scores[&quot;Player2&quot;] = 200;
Scores[&quot;Player3&quot;] = 150;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로 보면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Player1 &amp;rarr; 100
Player2 &amp;rarr; 200
Player3 &amp;rarr; 150
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Key가 데이터의 식별자 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 게임에서 다음과 같은 구조를 만들 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;PlayerID &amp;rarr; Score
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Player001 &amp;rarr; 100
Player002 &amp;rarr; 250
Player003 &amp;rarr; 180
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 PlayerID를 이용해서 해당 플레이어의 점수를 빠르게 찾을 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;16. unordered_map의 주요 기능&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;삽입&lt;/h2&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Scores[&quot;Player1&quot;] = 100;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Player1이라는 Key에 100이라는 Value를 저장한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;조회&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;int Score = Scores[&quot;Player1&quot;];
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Player1이라는 Key를 이용해서 값을 가져온다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;존재 여부 확인&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;if (Scores.contains(&quot;Player1&quot;))
{
    // 존재함
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;contains()를 이용해서 해당 Key가 존재하는지 확인할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;삭제&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Scores.erase(&quot;Player1&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;erase()를 이용해서 Player1을 삭제한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;17. std::unordered_set&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::unordered_set도 해시 테이블을 기반으로 하는 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 unordered_map처럼 Key와 Value를 따로 저장하지 않는다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::unordered_set&amp;lt;int&amp;gt; Numbers;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 값을 저장한다.&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;Numbers.insert(10);
Numbers.insert(20);
Numbers.insert(30);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10
20
30
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 특징은 &lt;b&gt;중복된 값을 저장하지 않는다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;Numbers.insert(10);
Numbers.insert(10);
Numbers.insert(10);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 실행해도 10은 하나만 존재한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;18. unordered_set을 사용하는 상황&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;unordered_set은 특히&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특정 값이 존재하는가?&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&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;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;Sword
Shield
Potion
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 아이템을 가지고 있는지 확인할 때 다음과 같이 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;if (Items.contains(&quot;Sword&quot;))
{
    // Sword를 가지고 있음
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 &lt;b&gt;존재 여부를 빠르게 확인하는 용도&lt;/b&gt;로 사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;19. unordered_map과 unordered_set의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 Hash Table 기반이라는 공통점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 저장하는 데이터의 형태가 다르다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;unordered_map&lt;/h3&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Key &amp;rarr; Value
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;PlayerID &amp;rarr; Score
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;unordered_set&lt;/h3&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Value
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;ItemID
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;unordered_map
&amp;rarr; Key와 Value를 연결해서 저장

unordered_set
&amp;rarr; 값 자체만 저장하며 중복을 허용하지 않음
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;20. Tree 기반 자료구조&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Hash 기반 자료구조와 비교하기 위해 &lt;b&gt;Tree 기반 자료구조&lt;/b&gt;를 살펴봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++의 대표적인 Tree 기반 자료구조가 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::map
std::set
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 균형 이진 탐색 트리 계열을 기반으로 구현되며, 대표적으로 &lt;b&gt;Red-Black Tree&lt;/b&gt;가 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리의 구조를 단순하게 표현하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        50
       /  \
     30    70
    / \    / \
   20 40  60 80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 데이터가 정렬된 형태로 구성된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;21. std::map&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::map은 Key &amp;rarr; Value 형태로 데이터를 저장하는 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::map&amp;lt;int, std::string&amp;gt; Players;

Players[1] = &quot;Alice&quot;;
Players[2] = &quot;Bob&quot;;
Players[3] = &quot;Charlie&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 형태로 저장된다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;1 &amp;rarr; Alice
2 &amp;rarr; Bob
3 &amp;rarr; Charlie
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::map의 중요한 특징은 &lt;b&gt;Key가 정렬된 상태로 관리된다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;22. std::set&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::set은 값을 하나씩 저장하면서 &lt;b&gt;중복을 허용하지 않고 정렬된 상태로 관리하는 자료구조&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;std::set&amp;lt;int&amp;gt; Numbers;

Numbers.insert(30);
Numbers.insert(10);
Numbers.insert(20);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력 순서는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;30
10
20
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Tree 기반으로 정렬된 상태를 유지하기 때문에 개념적으로 다음과 같이 관리된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10
20
30
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;23. map/set과 unordered_map/unordered_set 비교&lt;/h1&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;unordered_map&lt;/td&gt;
&lt;td&gt;Hash Table&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unordered_set&lt;/td&gt;
&lt;td&gt;Hash Table&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;map&lt;/td&gt;
&lt;td&gt;Tree&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O(log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;set&lt;/td&gt;
&lt;td&gt;Tree&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O(log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;unordered_map
unordered_set
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은 &lt;b&gt;Hash Table 기반&lt;/b&gt;이고,&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;map
set
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;은 &lt;b&gt;Tree 기반&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;24. Tree가 O(log N)인 이유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree가 균형 있게 구성되어 있다고 가정한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;          50
        /    \
      30      70
     /  \    /  \
   20   40  60   80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;80을 찾는다고 하면 다음과 같은 과정을 거친다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;50
 &amp;darr;
80 &amp;gt; 50

70
 &amp;darr;
80 &amp;gt; 70

80
 &amp;darr;
발견
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 데이터를 처음부터 확인하는 것이 아니라 &lt;b&gt;트리의 높이를 따라 내려가면서 탐색&lt;/b&gt;하기 때문에 균형 잡힌 Tree에서는 O(log N)의 시간 복잡도를 가진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;25. Hash Table과 Tree의 가장 큰 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hash Table과 Tree 모두 데이터를 빠르게 찾을 수 있지만, &lt;b&gt;데이터를 찾는 방식 자체가 다르다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Hash Table&lt;/h2&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Key
 &amp;darr;
Hash Function
 &amp;darr;
위치 계산
 &amp;darr;
접근
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균적인 탐색 시간 복잡도:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(1)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tree&lt;/h2&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Key
 &amp;darr;
Root
 &amp;darr;
작으면 왼쪽
크면 오른쪽
 &amp;darr;
반복
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;균형 잡힌 Tree의 탐색 시간 복잡도:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;26. Tree 기반 자료구조의 장점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hash Table과 달리 Tree 기반 자료구조는 데이터를 &lt;b&gt;정렬된 상태로 유지&lt;/b&gt;할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hash Table은 일반적으로 데이터가 정렬된 상태로 저장된다는 보장이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::unordered_set&amp;lt;int&amp;gt; Numbers;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에 다음과 같이 데이터를 넣었다고 하더라도:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;50
10
30
20
40
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과가 반드시 다음처럼 정렬되어 나오는 것은 아니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10
20
30
40
50
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::set&amp;lt;int&amp;gt; Numbers;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;는 정렬된 상태를 유지한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10
20
30
40
50
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;27. 범위 검색에서는 Tree가 유리하다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree 기반 자료구조가 가지는 중요한 장점 중 하나가 &lt;b&gt;범위 검색&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 데이터를 가지고 있다고 가정한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10
20
30
40
50
60
70
80
90
100
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;40 이상 80 이하
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인 데이터를 찾고 싶다면 정렬된 Tree 구조를 활용하기 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Hash Table은 데이터가 정렬되어 있지 않기 때문에&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;100 ~ 200 사이의 데이터
&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;Hash Table은 기본적으로 &lt;b&gt;특정 Key를 빠르게 찾는 것&lt;/b&gt;에 강하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;28. Hash와 Tree를 언제 사용하는가?&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Hash를 사용하기 좋은 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 Key를 이용해서 데이터를 빠르게 찾고 싶을 때 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;PlayerID &amp;rarr; PlayerData
ItemID &amp;rarr; ItemData
MonsterID &amp;rarr; MonsterData
&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;C++에서는 다음과 같은 자료구조를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::unordered_map
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tree를 사용하기 좋은 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 정렬된 상태로 유지해야 하거나 범위 검색이 필요한 경우 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;점수 순으로 정렬
ID 범위 검색
최솟값 / 최댓값 탐색
순서 기반 탐색
&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;C++에서는 다음과 같은 자료구조를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::map
std::set
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;29. 상황별 비교&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상황 A&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;PlayerID가 1234인 플레이어를 찾아라.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 Hash Table이 적합하다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;1234
 &amp;darr;
Hash Function
 &amp;darr;
위치
 &amp;darr;
Player Data
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 Key를 빠르게 찾아야 하기 때문이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상황 B&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;PlayerID가 1000~2000 사이인 플레이어를 찾아라.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우에는 Tree 기반 자료구조가 유리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Key가 정렬되어 있기 때문에 특정 범위에 해당하는 데이터를 찾기 쉽기 때문이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;30. Hash의 단점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hash가 항상 좋은 자료구조인 것은 아니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Collision&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 데이터가 같은 위치를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;A &amp;rarr; 3
B &amp;rarr; 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Collision을 처리하는 방법이 필요하다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Chaining
Open Addressing
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등을 사용한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 정렬되지 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hash Table은 일반적으로 데이터를 정렬된 상태로 유지하지 않는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 범위 검색에 불리하다&lt;/h2&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;100 ~ 200
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과 같은 범위 검색에는 적합하지 않다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 최악의 경우 O(N)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;충돌이 심하게 발생하면 탐색 성능이 O(N)까지 떨어질 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;31. Tree의 단점&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree도 장점만 있는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hash Table의 평균 탐색이 O(1)인 것에 비해 균형 잡힌 Tree는 O(log N)이다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Hash &amp;rarr; 평균 O(1)
Tree &amp;rarr; O(log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 단순하게 &lt;b&gt;특정 값을 빠르게 찾는 것&lt;/b&gt;이 목적이라면 Hash Table이 더 유리한 경우가 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Tree는 다음과 같은 기능에서 강점을 가진다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;정렬
범위 검색
최솟값 / 최댓값
순서 기반 탐색
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;32. 전체 구조 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 학습한 내용을 하나의 흐름으로 연결하면 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Hash
 │
 └─ 데이터를 특정 값으로 변환하는 개념
      │
      &amp;darr;
Hash Function
 │
 └─ 데이터를 Hash Value로 변환
      │
      &amp;darr;
Hash Table
 │
 ├─ 데이터를 빠르게 저장
 └─ 데이터를 빠르게 탐색
      │
      └─ Collision 발생 가능
            │
            ├─ Chaining
            │
            └─ Open Addressing
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 C++에서는 다음과 같이 연결된다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;Hash Table
 │
 ├─ unordered_map
 └─ unordered_set
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tree 기반 자료구조는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;Tree
 │
 ├─ map
 └─ set
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;33. 최종 비교표&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;unordered_map&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; unordered_set&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;map&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; set&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;기반&lt;/td&gt;
&lt;td&gt;Hash Table&lt;/td&gt;
&lt;td&gt;Hash Table&lt;/td&gt;
&lt;td&gt;Tree&lt;/td&gt;
&lt;td&gt;Tree&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key-Value&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;중복 Key/값&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정렬&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평균 탐색&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(log N)&lt;/td&gt;
&lt;td&gt;O(log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;범위 검색&lt;/td&gt;
&lt;td&gt;불리함&lt;/td&gt;
&lt;td&gt;불리함&lt;/td&gt;
&lt;td&gt;유리함&lt;/td&gt;
&lt;td&gt;유리함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;특정 값 빠른 검색&lt;/td&gt;
&lt;td&gt;매우 유리&lt;/td&gt;
&lt;td&gt;매우 유리&lt;/td&gt;
&lt;td&gt;유리&lt;/td&gt;
&lt;td&gt;유리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;34. 이번 학습에서 기억해야 할 핵심&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 단계에서는 단순히&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;unordered_map = O(1)
map = O(log N)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라고 외우는 것보다 &lt;b&gt;왜 그런 차이가 발생하는지&lt;/b&gt; 이해하는 것이 중요하다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해시 테이블의 핵심 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;데이터
 &amp;darr;
Hash Function
 &amp;darr;
Hash Value
 &amp;darr;
Hash Table의 위치
 &amp;darr;
저장 / 탐색
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 서로 다른 데이터가 같은 위치에 들어가려고 하면 Collision이 발생한다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;서로 다른 데이터
       &amp;darr;
같은 위치
       &amp;darr;
Collision
       &amp;darr;
Chaining / Open Addressing
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;unordered_map
&amp;rarr; Hash Table + Key / Value

unordered_set
&amp;rarr; Hash Table + 값만 저장 + 중복 허용 X

map
&amp;rarr; Tree + Key / Value + 정렬

set
&amp;rarr; Tree + 값만 저장 + 정렬
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 비교는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Hash Table
&amp;rarr; 평균 O(1)
&amp;rarr; 특정 값의 빠른 탐색에 강함
&amp;rarr; 정렬되지 않음
&amp;rarr; 범위 검색에 불리함

Tree
&amp;rarr; O(log N)
&amp;rarr; 정렬된 상태 유지
&amp;rarr; 범위 검색에 유리함
&amp;rarr; 순서 기반 탐색에 유리함
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 자료구조를 선택할 때는 단순히 &lt;b&gt;&quot;O(1)이니까 Hash가 더 좋다&quot;&lt;/b&gt;라고 생각하는 것이 아니라, 내가 필요한 연산이 무엇인지에 따라 자료구조를 선택해야 한다는 것을 학습했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 &lt;b&gt;특정 ID를 이용해 데이터를 빠르게 찾는 것이 목적이라면 Hash&lt;/b&gt;, &lt;b&gt;데이터를 정렬된 상태로 유지하거나 범위 검색이 필요하다면 Tree&lt;/b&gt;를 사용하는 식으로 판단할 수 있다.&lt;/p&gt;</description>
      <category>자료구조와 알고리즘</category>
      <author>begin-play</author>
      <guid isPermaLink="true">https://begin-play.tistory.com/95</guid>
      <comments>https://begin-play.tistory.com/95#entry95comment</comments>
      <pubDate>Mon, 31 Aug 2026 21:28:17 +0900</pubDate>
    </item>
    <item>
      <title>정렬 알고리즘</title>
      <link>https://begin-play.tistory.com/94</link>
      <description>&lt;h1&gt;정렬 알고리즘&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 정렬 알고리즘이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬 알고리즘은 여러 개의 데이터를 &lt;b&gt;특정한 기준에 따라 순서대로 배치하는 알고리즘&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 데이터가 있다고 하자.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[5, 2, 8, 1, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오름차순으로 정렬하면 다음과 같이 된다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 2, 3, 5, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬은 단순히 숫자를 순서대로 배치하는 것뿐만 아니라, &lt;b&gt;탐색, 데이터 처리, 우선순위 관리 등 다양한 알고리즘의 기초&lt;/b&gt;가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 정렬 알고리즘을 난이도 순으로 학습했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Bubble Sort&lt;/li&gt;
&lt;li&gt;Selection Sort&lt;/li&gt;
&lt;li&gt;Insertion Sort&lt;/li&gt;
&lt;li&gt;Merge Sort&lt;/li&gt;
&lt;li&gt;Quick Sort&lt;/li&gt;
&lt;li&gt;Heap Sort&lt;/li&gt;
&lt;/ol&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;li&gt;Stable 여부&lt;/li&gt;
&lt;li&gt;언제 사용하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. Stable Sort란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬 알고리즘을 공부하면서 &lt;b&gt;Stable(안정 정렬)&lt;/b&gt;이라는 개념을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stable Sort는 &lt;b&gt;값이 같은 데이터들의 원래 순서를 유지하는 정렬&lt;/b&gt;을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 데이터가 있다고 하자.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;이름   점수
A      80
B      70
C      80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점수를 기준으로 정렬했을 때 Stable Sort라면 다음과 같이 된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;B  70
A  80
C  80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A와 C는 점수가 모두 80점이므로 값은 같지만, 정렬하기 전에도 A &amp;rarr; C 순서였기 때문에 정렬 후에도 A &amp;rarr; C 순서가 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 Unstable Sort에서는 다음처럼 순서가 바뀔 수도 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;B  70
C  80
A  80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Stable 여부는 단순히 정렬 알고리즘의 성능만을 비교하는 것이 아니라, &lt;b&gt;같은 값을 가진 데이터의 기존 순서를 유지해야 하는 상황에서 중요한 요소&lt;/b&gt;라고 이해했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. Bubble Sort&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bubble Sort는 &lt;b&gt;서로 인접한 두 값을 비교하고 순서가 잘못되어 있다면 서로 교환하는 방식&lt;/b&gt;으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[5, 3, 8, 1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 5와 3을 비교한다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;5 &amp;gt; 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 두 값을 교환한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[3, 5, 8, 1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 5와 8을 비교한다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;5 &amp;lt; 8&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;다음으로 8과 1을 비교한다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;8 &amp;gt; 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 교환한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[3, 5, 1, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 한 번 끝까지 탐색하면 &lt;b&gt;가장 큰 값이 배열의 가장 오른쪽으로 이동&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 앞에서부터 같은 과정을 반복하면 최종적으로 정렬된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[5, 3, 8, 1]
&amp;darr;
[3, 5, 1, 8]
&amp;darr;
[3, 1, 5, 8]
&amp;darr;
[1, 3, 5, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bubble Sort는 한 번의 탐색이 끝날 때마다 &lt;b&gt;가장 큰 값이 뒤쪽으로 밀려나는 것&lt;/b&gt;이 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 큰 값이 뒤쪽으로 이동하는 모습이 마치 거품이 위로 올라가는 것과 비슷하기 때문에 Bubble Sort라는 이름이 붙었다.&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;최선&lt;/td&gt;
&lt;td&gt;O(N)&amp;sup1;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평균&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최악&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;sup1; 이미 정렬되어 있는지 확인하는 조기 종료 최적화를 적용한 경우&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공간 복잡도&lt;/h2&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 자체에서 값을 교환하면서 정렬할 수 있기 때문에 별도의 큰 배열이 필요하지 않다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stable 여부&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Stable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Bubble Sort 구현은 같은 값의 순서를 유지할 수 있다.&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;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;h3 data-ke-size=&quot;size23&quot;&gt;핵심 정리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bubble Sort는 &lt;b&gt;인접한 값끼리 비교하고 교환하면서 큰 값을 뒤로 보내는 정렬&lt;/b&gt;이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. Selection Sort&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Selection Sort는 Bubble Sort처럼 인접한 값을 계속 교환하는 방식이 아니라, &lt;b&gt;현재 정렬되지 않은 영역에서 최솟값을 찾아서 앞으로 가져오는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[5, 3, 8, 1, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 데이터에서 가장 작은 값을 찾는다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;최솟값 = 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾은 1을 첫 번째 위치의 5와 교환한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 3, 8, 5, 4]&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[1, 3, 8, 5, 4]
    &amp;uarr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남아 있는 데이터에서 최솟값을 찾는다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;3&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[1, 3, 8, 5, 4]
       &amp;uarr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남은 데이터에서 최솟값인 4를 찾는다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 3, 4, 5, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 반복해서 전체 배열을 정렬한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Selection Sort는 배열을 다음과 같이 생각하면 이해하기 쉽다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;정렬된 영역 | 정렬되지 않은 영역&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[1, 3] | [8, 5, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬되지 않은 영역에서 최솟값을 찾고 정렬된 영역의 바로 다음 위치에 배치한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[1, 3, 4] | [5, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 반복하면서 정렬된 영역을 하나씩 늘려간다.&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;최선&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평균&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최악&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 데이터가 정렬되어 있어도 남아 있는 데이터에서 최솟값을 찾기 위한 탐색을 계속 수행하기 때문에 일반적인 구현에서는 최선의 경우에도 O(N&amp;sup2;)이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공간 복잡도&lt;/h2&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가 배열을 만들지 않고 원본 배열에서 교환할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stable 여부&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Unstable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Selection Sort 구현에서는 같은 값을 가진 데이터의 순서가 바뀔 수 있다.&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;p data-ke-size=&quot;size16&quot;&gt;다만 Selection Sort는 정렬 과정에서 &lt;b&gt;교환 횟수를 비교적 적게 가져갈 수 있다는 특징&lt;/b&gt;이 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 정리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Selection Sort는 &lt;b&gt;정렬되지 않은 영역에서 최솟값을 찾아 현재 위치로 가져오는 정렬&lt;/b&gt;이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. Insertion Sort&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Insertion Sort는 &lt;b&gt;이미 정렬된 영역을 유지하면서 새로운 값을 적절한 위치에 삽입하는 방식&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 카드 게임에서 카드를 손에 들고 정렬하는 과정을 생각하면 이해하기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[5, 3, 8, 1, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 값인 5는 이미 정렬된 것으로 생각한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 값 3을 가져온다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[5] 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3은 5보다 작기 때문에 5를 오른쪽으로 이동시키고 3을 앞에 넣는다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[3, 5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 값 8을 가져온다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[3, 5] 8&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8은 가장 크기 때문에 그대로 뒤에 놓는다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[3, 5, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 값 1을 가져온다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[3, 5, 8] 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1보다 큰 값들을 오른쪽으로 이동시킨다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[3, 5, 8]
 &amp;uarr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 3, 5, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 4도 적절한 위치에 삽입한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 3, 4, 5, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Insertion Sort 역시 배열을 다음과 같이 나누어 생각하면 이해하기 쉽다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;정렬된 영역 | 정렬되지 않은 영역&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬되지 않은 영역에서 하나의 값을 가져온 뒤, 정렬된 영역에서 &lt;b&gt;자신이 들어갈 위치를 찾아 삽입&lt;/b&gt;한다.&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;최선&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평균&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최악&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 데이터가 이미 정렬되어 있거나 거의 정렬되어 있다면 매우 효율적으로 동작할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공간 복잡도&lt;/h2&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가 배열 없이 원본 배열에서 값을 이동시키면서 정렬할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stable 여부&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Stable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 값의 기존 순서를 유지할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Insertion Sort는 다음과 같은 상황에서 유용하다.&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;li&gt;고급 정렬 알고리즘에서 작은 구간을 처리할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 정리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Insertion Sort는 &lt;b&gt;정렬된 영역을 유지하면서 새로운 값을 알맞은 위치에 끼워 넣는 정렬&lt;/b&gt;이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. Merge Sort&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기부터는 기본 정렬보다 한 단계 높은 정렬 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge Sort는 &lt;b&gt;분할 정복(Divide and Conquer)&lt;/b&gt;을 사용하는 대표적인 정렬 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;계속 반으로 나눈다.
&amp;darr;
더 이상 나눌 수 없을 때까지 나눈다.
&amp;darr;
작은 배열부터 정렬하면서 합친다.&lt;/code&gt;&lt;/pre&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;pre class=&quot;json&quot;&gt;&lt;code&gt;[8, 3, 5, 4, 7, 6, 1, 2]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 반으로 나눈다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[8, 3, 5, 4] [7, 6, 1, 2]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 나눈다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[8, 3] [5, 4] [7, 6] [1, 2]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 나눈다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[8] [3] [5] [4] [7] [6] [1] [2]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 더 이상 나눌 수 없으므로 다시 합친다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[8] + [3]
&amp;rarr; [3, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[5] + [4]
&amp;rarr; [4, 5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[3, 8] + [4, 5]
&amp;rarr; [3, 4, 5, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대쪽도:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[7] + [6]
&amp;rarr; [6, 7]

[1] + [2]
&amp;rarr; [1, 2]

[6, 7] + [1, 2]
&amp;rarr; [1, 2, 6, 7]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[3, 4, 5, 8]
+
[1, 2, 6, 7]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 배열을 비교하면서 작은 값을 순서대로 가져오면:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 2, 3, 4, 5, 6, 7, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 O(N log N)인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge Sort는 데이터를 계속 절반으로 나눈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 N개라면 절반으로 나누는 과정은 대략:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;log N&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 data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;N &amp;times; log N&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 되어 시간 복잡도는:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;O(N log N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 된다.&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;최선&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평균&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최악&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge Sort의 중요한 특징은 &lt;b&gt;최악의 경우에도 O(N log N)을 보장한다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공간 복잡도&lt;/h2&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 배열을 합치는 과정에서 추가적인 공간이 필요하기 때문이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stable 여부&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Stable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Merge Sort 구현은 Stable하게 만들 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 상황&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안정적인 O(N log N) 성능이 필요할 때&lt;/li&gt;
&lt;li&gt;Stable Sort가 필요할 때&lt;/li&gt;
&lt;li&gt;데이터의 크기가 클 때&lt;/li&gt;
&lt;li&gt;외부 정렬(External Sort)이 필요한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 정리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge Sort는 &lt;b&gt;데이터를 계속 반으로 나눈 다음 정렬하면서 다시 합치는 분할 정복 알고리즘&lt;/b&gt;이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. Quick Sort&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Quick Sort 역시 &lt;b&gt;분할 정복(Divide and Conquer)&lt;/b&gt;을 사용하는 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Merge Sort와는 분할하는 방식이 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Quick Sort의 핵심 개념은 &lt;b&gt;Pivot(피벗)&lt;/b&gt;이다.&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;pre class=&quot;json&quot;&gt;&lt;code&gt;[6, 3, 8, 5, 2, 7, 4, 1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 5를 Pivot으로 선택했다고 하자.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;Pivot = 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pivot보다 작은 값과 큰 값을 나눈다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[3, 2, 4] 5 [6, 8, 7]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 왼쪽 영역과 오른쪽 영역에 다시 Quick Sort를 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[3, 2, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pivot을 3으로 선택하면:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[2] 3 [4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[6, 8, 7]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pivot을 7로 선택하면:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[6] 7 [8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[2, 3, 4, 5, 6, 7, 8]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Merge Sort와의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 분할 정복을 사용하지만 핵심이 다르다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Merge Sort&lt;/h3&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;데이터를 분할
&amp;darr;
작은 단위까지 나눔
&amp;darr;
병합하는 과정에서 정렬&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Quick Sort&lt;/h3&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;Pivot 선택
&amp;darr;
Pivot보다 작은 값 / 큰 값으로 분할
&amp;darr;
각 영역을 다시 정렬&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음과 같이 기억할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge Sort는 &lt;b&gt;합치는 과정에서 정렬&lt;/b&gt;하고, Quick Sort는 &lt;b&gt;Pivot을 기준으로 나누는 과정에서 정렬의 구조를 만든다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. Quick Sort의 시간 복잡도&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Quick Sort는 Merge Sort와 달리 최악의 경우 시간 복잡도가 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경우시간 복잡도&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;최선&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평균&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최악&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균적으로 Pivot이 적절하게 선택되어 데이터가 균형 있게 나뉘면:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;N
&amp;darr;
N/2 + N/2
&amp;darr;
N/4 + N/4 + N/4 + N/4
&amp;darr;
...&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;따라서 평균적으로 O(N log N)이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Pivot이 계속 최솟값이나 최댓값으로 선택되면 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 이미 정렬된 데이터:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 2, 3, 4, 5, 6, 7]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서 계속 가장 작은 값을 Pivot으로 선택하면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[1] | [2, 3, 4, 5, 6, 7]

[2] | [3, 4, 5, 6, 7]

[3] | [4, 5, 6, 7]&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;이렇게 되면 균형 잡힌 분할이 이루어지지 않아서 최악의 경우 O(N&amp;sup2;)가 된다.&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;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(log N)&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;다만 분할이 계속 한쪽으로 치우치는 최악의 경우에는 재귀 깊이가 N까지 증가하여 O(N)이 될 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stable 여부&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Unstable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Quick Sort 구현은 같은 값의 순서를 보장하지 않는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 상황&lt;/h2&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;h3 data-ke-size=&quot;size23&quot;&gt;핵심 정리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Quick Sort는 &lt;b&gt;Pivot을 기준으로 데이터를 나누고, 각각의 영역을 다시 정렬하는 분할 정복 알고리즘&lt;/b&gt;이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균적으로 O(N log N)이지만 Pivot 선택이 좋지 않으면 최악의 경우 O(N&amp;sup2;)가 될 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. Heap Sort&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap Sort는 &lt;b&gt;Heap 자료구조를 이용하는 정렬 알고리즘&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap은 완전 이진 트리 형태를 기반으로 하는 자료구조이며, Max Heap에서는 다음 조건을 만족한다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;부모 노드 &amp;ge; 자식 노드&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;        9
      /   \
     7     8
    / \   / \
   3   5 6   2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에서는 가장 큰 값인 9가 Root에 위치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Max Heap을 사용하면 &lt;b&gt;가장 큰 값을 빠르게 확인할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Heap Sort의 기본 과정&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;배열을 Max Heap으로 만든다.&lt;/li&gt;
&lt;li&gt;Root에 있는 최댓값을 꺼낸다.&lt;/li&gt;
&lt;li&gt;꺼낸 값을 배열의 뒤쪽에 배치한다.&lt;/li&gt;
&lt;li&gt;남은 데이터로 Heap을 다시 구성한다.&lt;/li&gt;
&lt;li&gt;이 과정을 반복한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[4, 10, 3, 5, 1]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 Max Heap으로 만들면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;       10
      /  \
     5    3
    / \
   4   1&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;가장 큰 값인 10을 뒤쪽으로 이동시킨다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[4, 5, 3, 1, 10]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남은 영역을 다시 Heap으로 정리한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[5, 4, 3, 1, 10]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 최댓값인 5를 뒤로 이동시킨다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[4, 1, 3, 5, 10]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 반복하면:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 3, 4, 5, 10]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 된다.&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;최선&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;평균&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;최악&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap Sort의 중요한 장점은 &lt;b&gt;최악의 경우에도 O(N log N)을 보장한다는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공간 복잡도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 배열 기반 In-place 구현에서는:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(1)&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;추가적인 배열을 만들지 않고 원본 배열 내부에서 Heap을 구성할 수 있기 때문이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stable 여부&lt;/h2&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Unstable&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 Heap Sort에서는 같은 값을 가진 데이터의 순서를 보장하지 않는다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 상황&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최악의 경우에도 O(N log N) 성능이 필요한 경우&lt;/li&gt;
&lt;li&gt;추가 메모리 사용을 최소화해야 하는 경우&lt;/li&gt;
&lt;li&gt;Heap 자료구조의 특성을 활용해야 하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 정리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap Sort는 &lt;b&gt;Heap을 구성한 뒤 최댓값 또는 최솟값을 반복적으로 꺼내 정렬하는 알고리즘&lt;/b&gt;이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. 6가지 정렬 알고리즘 비교&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘핵심&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 동작&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;최선&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 평균&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;최악 추가 공간&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Stable&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bubble Sort&lt;/td&gt;
&lt;td&gt;인접 값 비교 및 교환&lt;/td&gt;
&lt;td&gt;O(N)&amp;sup1;&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Selection Sort&lt;/td&gt;
&lt;td&gt;최솟값을 찾아 배치&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Insertion Sort&lt;/td&gt;
&lt;td&gt;정렬 영역에 삽입&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merge Sort&lt;/td&gt;
&lt;td&gt;분할 후 병합&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quick Sort&lt;/td&gt;
&lt;td&gt;Pivot 기준 분할&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;O(N&amp;sup2;)&lt;/td&gt;
&lt;td&gt;O(log N)&amp;sup2;&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap Sort&lt;/td&gt;
&lt;td&gt;Heap을 이용한 추출&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;O(N log N)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;sup1; 조기 종료 최적화를 적용한 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;sup2; 평균적인 재귀 호출 스택 기준이며, 최악의 경우 O(N)이 될 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. O(N&amp;sup2;)와 O(N log N)의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 정렬 알고리즘을 공부하면서 &lt;b&gt;데이터의 크기가 커질수록 시간 복잡도가 얼마나 중요한지&lt;/b&gt; 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 N = 1,000,000이라고 하면:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;O(N&amp;sup2;)
= 1,000,000&amp;sup2;
= 1,000,000,000,000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;O(N log N)
&amp;asymp; 1,000,000 &amp;times; 20
&amp;asymp; 20,000,000&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;Big-O가 실제 실행 시간을 정확하게 나타내는 것은 아니지만, &lt;b&gt;데이터의 크기가 증가할 때 알고리즘의 성능이 어떤 형태로 증가하는지 비교하는 데 매우 중요하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 기본 정렬과 고급 정렬 사이에는 다음과 같은 차이가 있다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Bubble / Selection / Insertion
            &amp;darr;
          O(N&amp;sup2;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면:&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;Merge / Quick / Heap
            &amp;darr;
       O(N log N) 중심&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. 기본 정렬 3개의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지 기본 정렬은 모두 비교를 기반으로 하지만 동작 방식이 서로 다르다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Bubble Sort&lt;/h3&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;인접한 값 비교
&amp;darr;
필요하면 교환
&amp;darr;
큰 값을 뒤로 이동&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;비교와 교환&lt;/b&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Selection Sort&lt;/h3&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;정렬되지 않은 영역 탐색
&amp;darr;
최솟값 탐색
&amp;darr;
현재 위치와 교환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;선택&lt;/b&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Insertion Sort&lt;/h3&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;정렬된 영역 유지
&amp;darr;
새로운 값 선택
&amp;darr;
적절한 위치에 삽입&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;삽입&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;13. 고급 정렬 3개의 차이&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Merge Sort&lt;/h3&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;분할
&amp;darr;
분할
&amp;darr;
분할
&amp;darr;
작은 단위
&amp;darr;
병합하면서 정렬&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;분할과 병합&lt;/b&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Quick Sort&lt;/h3&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;Pivot 선택
&amp;darr;
작은 값 / 큰 값으로 분할
&amp;darr;
각 영역에 다시 Quick Sort&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;Pivot&lt;/b&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Heap Sort&lt;/h3&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Heap 구성
&amp;darr;
최댓값 또는 최솟값 추출
&amp;darr;
Heap 재구성
&amp;darr;
반복&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;Heap&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;14. 어떤 정렬을 사용해야 하는가?&lt;/h1&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;h2 data-ke-size=&quot;size26&quot;&gt;데이터가 거의 정렬되어 있는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Insertion Sort가 유리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[1, 2, 3, 5, 4, 6, 7]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 이미 대부분 정렬되어 있고 일부 데이터만 위치가 잘못되어 있다면 Insertion Sort는 적은 작업으로 정렬할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stable Sort가 필요한 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge Sort를 고려할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 값을 가진 데이터들의 기존 순서를 유지해야 하는 상황에서 Stable Sort가 중요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;평균적인 빠른 정렬이 필요한 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Quick Sort 계열이 많이 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 배열 기반 데이터에서 효율적인 구현이 가능하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최악의 경우에도 O(N log N)이 필요한 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Merge Sort 또는 Heap Sort를 고려할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 알고리즘 모두 최악의 경우에도:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;O(N log N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 보장한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가 메모리를 최소화해야 하는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 In-place Heap Sort는 추가 공간을:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;까지 줄일 수 있다는 장점이 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;15. 정렬 알고리즘을 한 문장으로 정리하기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 알고리즘의 원리를 다음과 같이 기억할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Bubble Sort
&amp;rarr; 옆에 있는 값끼리 비교해서 큰 값을 뒤로 보낸다.

Selection Sort
&amp;rarr; 남은 값 중 최솟값을 찾아 앞으로 가져온다.

Insertion Sort
&amp;rarr; 정렬된 영역에 새로운 값을 알맞은 위치에 끼워 넣는다.

Merge Sort
&amp;rarr; 데이터를 계속 나눈 뒤 정렬하면서 다시 합친다.

Quick Sort
&amp;rarr; Pivot을 기준으로 작은 값과 큰 값을 나누고 다시 정렬한다.

Heap Sort
&amp;rarr; Heap에서 최댓값 또는 최솟값을 꺼내면서 정렬한다.&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;16. 이번 학습에서 중요하게 기억할 내용&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 단계에서는 단순히 알고리즘의 이름과 시간 복잡도를 암기하는 것보다 &lt;b&gt;각 알고리즘이 어떤 방식으로 데이터를 움직이는지 이해하는 것&lt;/b&gt;이 중요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 다음 내용을 기억해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Bubble Sort&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인접한 값들을 비교하고 교환하면서 큰 값을 뒤로 이동시킨다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(N&amp;sup2;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Selection Sort&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬되지 않은 영역에서 최솟값을 찾아 현재 위치에 배치한다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(N&amp;sup2;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Insertion Sort&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬된 영역에 새로운 값을 적절한 위치로 삽입한다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;최선 O(N)
평균/최악 O(N&amp;sup2;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Merge Sort&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 계속 반으로 나누고 병합하면서 정렬한다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;최선/평균/최악 O(N log N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stable Sort가 가능하지만 추가 공간 O(N)이 필요하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Quick Sort&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pivot을 기준으로 데이터를 분할하고 각 영역을 다시 정렬한다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;평균 O(N log N)
최악 O(N&amp;sup2;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pivot을 어떻게 선택하느냐가 성능에 큰 영향을 준다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Heap Sort&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap의 구조를 이용하여 최댓값 또는 최솟값을 반복적으로 꺼내 정렬한다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;최선/평균/최악 O(N log N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 In-place 구현에서는 추가 공간 O(1)을 사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;17. 최종 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 단계에서 가장 중요한 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;기본 정렬
│
├─ Bubble Sort
│   └─ 인접한 값 비교/교환
│
├─ Selection Sort
│   └─ 최솟값 선택
│
└─ Insertion Sort
    └─ 정렬된 영역에 삽입

        &amp;darr;

고급 정렬
│
├─ Merge Sort
│   └─ 분할 &amp;rarr; 병합
│
├─ Quick Sort
│   └─ Pivot &amp;rarr; 분할
│
└─ Heap Sort
    └─ Heap &amp;rarr; 최댓값/최솟값 추출&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 시간 복잡도를 기준으로 보면:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Bubble / Selection / Insertion
            &amp;darr;
          O(N&amp;sup2;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;Merge / Quick / Heap
            &amp;darr;
       O(N log N) 중심&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;pre class=&quot;sql&quot;&gt;&lt;code&gt;Merge Sort
&amp;rarr; &quot;나누고 합친다&quot;

Quick Sort
&amp;rarr; &quot;Pivot으로 나눈다&quot;

Heap Sort
&amp;rarr; &quot;Heap에서 꺼낸다&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 정렬 알고리즘 학습을 통해 &lt;b&gt;같은 문제인 &quot;데이터를 정렬한다&quot;를 해결하더라도 알고리즘의 구조에 따라 시간 복잡도와 메모리 사용량, 안정성, 실제 활용 상황이 크게 달라진다는 점&lt;/b&gt;을 이해했다.&lt;/p&gt;</description>
      <category>자료구조와 알고리즘</category>
      <author>begin-play</author>
      <guid isPermaLink="true">https://begin-play.tistory.com/94</guid>
      <comments>https://begin-play.tistory.com/94#entry94comment</comments>
      <pubDate>Fri, 28 Aug 2026 16:19:10 +0900</pubDate>
    </item>
    <item>
      <title>탐색 알고리즘</title>
      <link>https://begin-play.tistory.com/93</link>
      <description>&lt;h1&gt;탐색 알고리즘&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 탐색 알고리즘이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탐색(Search)은 &lt;b&gt;주어진 데이터에서 원하는 값을 찾는 과정&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 배열이 있다고 하겠습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int arr[] = {10, 30, 20, 50, 40};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 50을 찾는 것이 탐색입니다.&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;b&gt;선형 탐색(Linear Search)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이진 탐색(Binary Search)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 알고리즘의 가장 큰 차이는 &lt;b&gt;데이터를 어떤 방식으로 확인하느냐&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. 선형 탐색(Linear Search)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선형 탐색은 가장 단순한 탐색 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;처음부터 끝까지 데이터를 하나씩 확인합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 배열에서 50을 찾는다고 하겠습니다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[10, 30, 20, 50, 40]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 하나씩 비교합니다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;10 &amp;rarr; 30 &amp;rarr; 20 &amp;rarr; 50&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;50을 발견하면 탐색을 종료합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본적인 형태&lt;/h3&gt;
&lt;pre class=&quot;matlab&quot;&gt;&lt;code&gt;for (int i = 0; i &amp;lt; N; i++)
{
    if (arr[i] == target)
    {
        return i;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;i를 이용하여 배열의 첫 번째 요소부터 마지막 요소까지 순서대로 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선형 탐색은 데이터가 정렬되어 있을 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[50, 10, 90, 20, 30]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 정렬되지 않은 데이터에서도 사용할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. 선형 탐색의 시간 복잡도&lt;/h1&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[10, 20, 30, 40, 50, 60, 70, 80]
 &amp;uarr;
 &amp;uarr;
 &amp;uarr;
 &amp;uarr;
 &amp;uarr;
 &amp;uarr;
 &amp;uarr;
 &amp;uarr;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾는 값이 마지막에 있다면 8개를 전부 확인해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 N개라면 최대 N번 정도 확인하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 시간 복잡도는:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;O(N)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 데이터의 크기가 커질수록 탐색에 필요한 작업도 데이터의 크기에 비례해서 증가합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. 이진 탐색(Binary Search)&lt;/h1&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;탐색할 범위를 절반씩 줄여나간다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&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;h2 data-ke-size=&quot;size26&quot;&gt;이진 탐색은 정렬된 데이터에서 사용해야 합니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[10, 20, 30, 40, 50, 60, 70, 80]&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 data-ke-size=&quot;size16&quot;&gt;이진 탐색은 가운데 값을 확인한 다음, &lt;b&gt;찾는 값이 가운데 값보다 작은지 큰지&lt;/b&gt;를 이용하여 왼쪽 또는 오른쪽에 있는지를 판단하기 때문입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. 이진 탐색의 핵심 개념&lt;/h1&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;left&lt;/li&gt;
&lt;li&gt;right&lt;/li&gt;
&lt;li&gt;mid&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음 배열이 있다고 하겠습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[10, 20, 30, 40, 50, 60, 70, 80]
  0   1   2   3   4   5   6   7&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;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 0;
right = 7;&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;여기서 left와 right는 &lt;b&gt;값이 아니라 인덱스&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. left란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;left는 현재 탐색 범위에서 &lt;b&gt;가장 왼쪽에 있는 인덱스&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[10, 20, 30, 40, 50, 60, 70, 80]
  &amp;uarr;
 left&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 배열 전체를 탐색하므로:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 0;&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;탐색 범위가 줄어들면 left도 이동할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. right란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;right는 현재 탐색 범위에서 &lt;b&gt;가장 오른쪽에 있는 인덱스&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 배열 전체를 탐색하므로:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;right = 7;&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[10, 20, 30, 40, 50, 60, 70, 80]
  &amp;uarr;                       &amp;uarr;
 left                    right&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 left ~ right 사이가 우리가 탐색해야 하는 범위입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;left와 right는 현재 탐색 범위를 나타낸다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 이해하면 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. mid란?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mid는 현재 탐색 범위의 &lt;b&gt;가운데 인덱스&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 계산 방법은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;int mid = (left + right) / 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 0
right = 7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;mid = (0 + 7) / 2
    = 3&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[10, 20, 30, 40, 50, 60, 70, 80]
  0   1   2   3   4   5   6   7
              &amp;uarr;
             mid&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arr[3]인 40을 확인하게 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. mid를 안전하게 계산하는 방법&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 다음과 같이 작성하는 것도 좋습니다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;int mid = left + (right - left) / 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 상황에서는:&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;(left + right) / 2&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;하지만 left + right가 매우 큰 경우 정수 오버플로가 발생할 가능성이 있기 때문에 다음 방식이 더 안전합니다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;int mid = left + (right - left) / 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘 문제에서는 이 형태를 자주 볼 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. 이진 탐색 과정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾고 싶은 값이 70이라고 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[10, 20, 30, 40, 50, 60, 70, 80]
  0   1   2   3   4   5   6   7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 0
right = 7
mid = 3&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;pre class=&quot;ini&quot;&gt;&lt;code&gt;arr[mid] = 40
target = 70&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 비교합니다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;40 &amp;lt; 70&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;그러면 70은 어디에 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 정렬되어 있기 때문에 40보다 작은 왼쪽 영역에는 70이 있을 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 왼쪽 영역을 버립니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[10, 20, 30, 40] [50, 60, 70, 80]
                  &amp;uarr;           &amp;uarr;
                 left        right&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = mid + 1;&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;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 4
right = 7&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. 다시 mid를 계산&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 탐색 범위는:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 4
right = 7&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;pre class=&quot;excel&quot;&gt;&lt;code&gt;mid = left + (right - left) / 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 계산하면:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;mid = 5&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[10, 20, 30, 40, 50, 60, 70, 80]
                  &amp;uarr;
                 mid&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arr[5] = 60입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;60 &amp;lt; 70&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로 70은 오른쪽에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = mid + 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 6
right = 7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. 다시 mid를 계산&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 6
right = 7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;mid = left + (right - left) / 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 계산하면:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;mid = 6&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;arr[6] = 70&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이고:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;arr[mid] == target&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로 값을 찾았습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;13. 이진 탐색의 전체 과정&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이진 탐색은 다음 과정을 반복합니다.&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;1. 탐색 범위를 설정한다.
       &amp;darr;
2. left와 right로 범위를 표현한다.
       &amp;darr;
3. mid를 계산한다.
       &amp;darr;
4. arr[mid]를 확인한다.
       &amp;darr;
5. target과 비교한다.
       &amp;darr;
6. target이 왼쪽에 있으면 right를 이동한다.
   target이 오른쪽에 있으면 left를 이동한다.
       &amp;darr;
7. 탐색 범위를 절반으로 줄인다.
       &amp;darr;
8. 다시 mid를 계산한다.
       &amp;darr;
9. 값을 찾을 때까지 반복한다.&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;14. target이 오른쪽에 있는 경우&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 상황을 생각해 봅니다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;arr[mid] &amp;lt; target&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 가운데 값보다 target이 더 큽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 오름차순으로 정렬되어 있기 때문에 target은 가운데보다 오른쪽에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = mid + 1;&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;pre class=&quot;excel&quot;&gt;&lt;code&gt;왼쪽 부분 &amp;rarr; 버림
mid       &amp;rarr; 이미 확인했으므로 버림
오른쪽 부분 &amp;rarr; 탐색&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;15. target이 왼쪽에 있는 경우&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는:&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;arr[mid] &amp;gt; target&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;현재 가운데 값보다 target이 더 작습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬되어 있기 때문에 target은 가운데보다 왼쪽에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;right = mid - 1;&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;pre class=&quot;excel&quot;&gt;&lt;code&gt;왼쪽 부분 &amp;rarr; 탐색
mid       &amp;rarr; 이미 확인했으므로 버림
오른쪽 부분 &amp;rarr; 버림&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;16. 왜 mid + 1, mid - 1인가?&lt;/h1&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;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 0
mid = 3
right = 7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이고:&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;arr[mid] &amp;lt; target&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라면 mid에 있는 값은 이미 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 target이 아니기 때문에 mid를 다시 탐색할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = mid + 1;&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;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;arr[mid] &amp;gt; target&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이면 mid는 이미 확인했으므로:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;right = mid - 1;&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;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = mid + 1;
right = mid - 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중요한 형태가 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;17. 이진 탐색 기본 코드&lt;/h1&gt;
&lt;pre class=&quot;vbscript&quot;&gt;&lt;code&gt;int BinarySearch(const vector&amp;lt;int&amp;gt;&amp;amp; arr, int target)
{
    int left = 0;
    int right = arr.size() - 1;

    while (left &amp;lt;= right)
    {
        int mid = left + (right - left) / 2;

        if (arr[mid] == target)
        {
            return mid;
        }
        else if (arr[mid] &amp;lt; target)
        {
            left = mid + 1;
        }
        else
        {
            right = mid - 1;
        }
    }

    return -1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;18. 코드 한 줄씩 이해하기&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수 선언&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;int BinarySearch(const vector&amp;lt;int&amp;gt;&amp;amp; arr, int target)&lt;/code&gt;&lt;/pre&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;arr &amp;rarr; 탐색할 배열&lt;/li&gt;
&lt;li&gt;target &amp;rarr; 찾고 싶은 값&lt;/li&gt;
&lt;li&gt;int &amp;rarr; 찾은 값의 인덱스를 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const vector&amp;lt;int&amp;gt;&amp;amp;는 배열의 데이터를 복사하지 않고 참조하면서, 함수 내부에서 배열을 변경하지 않겠다는 의미입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;탐색 시작점&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int left = 0;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 첫 번째 인덱스에서 시작합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;탐색 끝점&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int right = arr.size() - 1;&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;예를 들어 데이터가 8개라면 인덱스는:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;0 1 2 3 4 5 6 7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;right = 7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;탐색 범위가 존재하는 동안 반복&lt;/h3&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;while (left &amp;lt;= right)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;left가 right보다 작거나 같은 동안 탐색합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 3
right = 5&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;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 6
right = 5&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가운데 위치 계산&lt;/h3&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;int mid = left + (right - left) / 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 탐색 범위의 가운데 인덱스를 계산합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;값을 찾았는지 확인&lt;/h3&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;if (arr[mid] == target)
{
    return mid;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가운데 값이 target과 같다면 값을 찾은 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 해당 값의 인덱스를 반환합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오른쪽을 탐색&lt;/h3&gt;
&lt;pre class=&quot;vbscript&quot;&gt;&lt;code&gt;else if (arr[mid] &amp;lt; target)
{
    left = mid + 1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가운데 값이 target보다 작다면 target은 오른쪽에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 탐색 범위를:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;mid + 1 ~ right&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 변경합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왼쪽을 탐색&lt;/h3&gt;
&lt;pre class=&quot;vbscript&quot;&gt;&lt;code&gt;else
{
    right = mid - 1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가운데 값이 target보다 크다면 target은 왼쪽에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 탐색 범위를:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;left ~ mid - 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 변경합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;값을 찾지 못한 경우&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;return -1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탐색 범위가 모두 사라질 때까지 target을 찾지 못했다면 일반적으로 -1을 반환하여 &lt;b&gt;값이 존재하지 않는다&lt;/b&gt;는 것을 나타냅니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;19. 왜 이진 탐색은 O(log N)인가?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진 탐색의 핵심은 &lt;b&gt;매번 탐색 범위를 절반으로 줄이는 것&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 8개라면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;8
&amp;darr;
4
&amp;darr;
2
&amp;darr;
1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 16개라면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;16
&amp;darr;
8
&amp;darr;
4
&amp;darr;
2
&amp;darr;
1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 32개라면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;32
&amp;darr;
16
&amp;darr;
8
&amp;darr;
4
&amp;darr;
2
&amp;darr;
1&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 data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;O(log N)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;20. O(N)과 O(log N)의 차이&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 알고리즘의 차이를 비교하면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 크기선형 탐색 O(N)이진 탐색 O(log N)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;최대 약 10번&lt;/td&gt;
&lt;td&gt;약 4번&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;최대 약 100번&lt;/td&gt;
&lt;td&gt;약 7번&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;최대 약 1,000번&lt;/td&gt;
&lt;td&gt;약 10번&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;최대 약 10,000번&lt;/td&gt;
&lt;td&gt;약 14번&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;최대 약 1,000,000번&lt;/td&gt;
&lt;td&gt;약 20번&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 비교 횟수는 구현 방식과 상황에 따라 조금 달라질 수 있지만 중요한 것은 &lt;b&gt;증가하는 속도&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선형 탐색은 데이터가 10배가 되면 탐색량도 대략 10배 증가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 이진 탐색은 데이터가 2배가 되어도 탐색 단계가 약 1단계 증가하는 정도입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;21. 이진 탐색은 무조건 좋은가?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지는 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진 탐색은 &lt;b&gt;정렬된 데이터가 필요하다&lt;/b&gt;는 조건이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[50, 10, 80, 20, 40]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 데이터에서는 가운데 값을 확인하더라도 target이 왼쪽에 있는지 오른쪽에 있는지 판단할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 정렬되지 않은 데이터에서는 일반적인 이진 탐색을 사용할 수 없습니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;정렬되지 않은 데이터
        &amp;darr;
선형 탐색&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;정렬된 데이터
        &amp;darr;
이진 탐색 사용 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;22. 정렬 비용도 고려해야 한다&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬되지 않은 데이터를 이진 탐색하고 싶다면 먼저 정렬해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;정렬되지 않은 데이터
        &amp;darr;
       정렬
        &amp;darr;
정렬된 데이터
        &amp;darr;
    이진 탐색&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;pre class=&quot;erlang&quot;&gt;&lt;code&gt;정렬
 &amp;darr;
탐색
탐색
탐색
탐색
탐색
...&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;데이터가 정렬되어 있는가?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;23. 선형 탐색 vs 이진 탐색&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분선형 탐색이진 탐색&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;영어&lt;/td&gt;
&lt;td&gt;Linear Search&lt;/td&gt;
&lt;td&gt;Binary Search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;탐색 방식&lt;/td&gt;
&lt;td&gt;처음부터 하나씩 확인&lt;/td&gt;
&lt;td&gt;가운데를 확인하고 범위를 절반으로 줄임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;정렬 필요&lt;/td&gt;
&lt;td&gt;필요 없음&lt;/td&gt;
&lt;td&gt;필요함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;탐색 범위&lt;/td&gt;
&lt;td&gt;한 칸씩 이동&lt;/td&gt;
&lt;td&gt;절반씩 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시간 복잡도&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;O(log N)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;구현 난이도&lt;/td&gt;
&lt;td&gt;쉬움&lt;/td&gt;
&lt;td&gt;상대적으로 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;핵심&lt;/td&gt;
&lt;td&gt;순서대로 확인&lt;/td&gt;
&lt;td&gt;left, right, mid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;어떤 데이터에서도 사용 가능&lt;/td&gt;
&lt;td&gt;정렬된 데이터에서 매우 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;td&gt;데이터가 많으면 느려짐&lt;/td&gt;
&lt;td&gt;정렬된 데이터가 필요함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;24. 이진 탐색과 탐색 범위&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진 탐색을 이해할 때 가장 중요한 개념 중 하나가 &lt;b&gt;탐색 범위&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[10, 20, 30, 40, 50, 60, 70, 80]
 &amp;uarr;                       &amp;uarr;
left                    right&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[10, 20, 30, 40] [50, 60, 70, 80]
                  &amp;uarr;           &amp;uarr;
                 left        right&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[50, 60] [70, 80]
 &amp;uarr;          &amp;uarr;
left       right&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;pre class=&quot;erlang&quot;&gt;&lt;code&gt;전체
 &amp;darr;
절반
 &amp;darr;
절반
 &amp;darr;
절반
 &amp;darr;
...&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;따라서 이진 탐색을 볼 때는 단순히 mid만 보는 것이 아니라:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;현재 탐색 범위가 어디부터 어디까지인가?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 항상 생각해야 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;25. 재귀와 이진 탐색의 연결&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 배운 &lt;b&gt;재귀&lt;/b&gt;와 이진 탐색도 연결됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진 탐색은 매번 문제의 크기를 절반으로 줄입니다.&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;전체 범위
   &amp;darr;
절반
   &amp;darr;
절반
   &amp;darr;
절반
   &amp;darr;
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방식은 &lt;b&gt;분할 정복(Divide and Conquer)&lt;/b&gt; 사고방식과 연결됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀적인 형태로 생각하면:&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;현재 범위에서 mid 확인
        &amp;darr;
target이 있는 쪽만 선택
        &amp;darr;
선택한 범위에서 다시 이진 탐색
        &amp;darr;
반복&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;다만 실제 알고리즘 문제에서는 이진 탐색을 while 반복문으로 구현하는 경우도 매우 많습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;26. 이진 탐색에서 자주 하는 실수&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;① 정렬되지 않은 데이터에 사용&lt;/h2&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[50, 10, 30, 20, 40]&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;② left와 right를 값으로 생각&lt;/h2&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 0;
right = 7;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 0, 7은 데이터의 값이 아니라 &lt;b&gt;인덱스&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;③ mid를 잘못 계산&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 방법:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;int mid = (left + right) / 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안전한 방법:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;int mid = left + (right - left) / 2;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = mid;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;right = mid;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 확인한 mid를 다시 탐색하게 될 수 있기 때문에 같은 위치를 반복해서 확인하는 상황이 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 정확한 값 찾기에서는:&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = mid + 1;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;right = mid - 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태를 사용합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;pre class=&quot;swift&quot;&gt;&lt;code&gt;while (left &amp;lt;= right)&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;left &amp;gt; right가 되었다는 것은 현재 탐색 범위가 완전히 사라졌다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;left &amp;gt; right
    &amp;darr;
탐색할 데이터가 없음
    &amp;darr;
탐색 실패&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;27. 알고리즘 문제에서의 사고 순서&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탐색 문제가 나왔을 때 다음 순서로 생각하면 좋습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 무엇을 찾는가?&lt;/h3&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;target&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾고 싶은 값이 무엇인지 확인합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 데이터가 정렬되어 있는가?&lt;/h3&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;정렬되지 않음
    &amp;darr;
선형 탐색 고려

정렬되어 있음
    &amp;darr;
이진 탐색 고려&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 이진 탐색이라면 탐색 범위를 잡는다.&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = 시작 인덱스
right = 마지막 인덱스&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 가운데를 구한다.&lt;/h3&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;mid = left + (right - left) / 2&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 가운데 값을 확인한다.&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;arr[mid]&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. target과 비교한다.&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;arr[mid] == target&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이면 찾은 것입니다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;arr[mid] &amp;lt; target&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이면 target은 오른쪽에 있습니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;left = mid + 1;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;arr[mid] &amp;gt; target&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이면 target은 왼쪽에 있습니다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;right = mid - 1;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 범위가 없어질 때까지 반복한다.&lt;/h3&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;while (left &amp;lt;= right)&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;28. 이번 단계 핵심 정리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;선형 탐색&lt;/h2&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;처음부터 끝까지 하나씩 확인
        &amp;darr;
정렬 필요 없음
        &amp;darr;
O(N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선형 탐색은 구현이 쉽고 정렬되지 않은 데이터에서도 사용할 수 있지만, 데이터가 많아질수록 탐색해야 하는 데이터가 많아집니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이진 탐색&lt;/h2&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;정렬된 데이터
        &amp;darr;
탐색 범위 설정
        &amp;darr;
left / right
        &amp;darr;
mid 계산
        &amp;darr;
target과 비교
        &amp;darr;
왼쪽 또는 오른쪽 절반 제거
        &amp;darr;
남은 범위에서 반복
        &amp;darr;
O(log N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이진 탐색은 &lt;b&gt;정렬된 데이터&lt;/b&gt;라는 조건이 필요하지만, 탐색 범위를 매번 절반으로 줄이기 때문에 매우 빠릅니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;29. 반드시 연결해서 기억할 것&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 6단계에서 가장 중요한 연결 관계는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;정렬되지 않은 데이터
        &amp;darr;
선형 탐색
        &amp;darr;
하나씩 확인
        &amp;darr;
O(N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고:&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;정렬된 데이터
        &amp;darr;
이진 탐색
        &amp;darr;
탐색 범위 설정
        &amp;darr;
left / right
        &amp;darr;
mid
        &amp;darr;
target 비교
        &amp;darr;
절반 제거
        &amp;darr;
O(log N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 다음 문장은 확실하게 기억해두는 것이 좋습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이진 탐색은 정렬된 데이터에서 left ~ right라는 탐색 범위를 정하고, mid를 기준으로 target과 비교하여 탐색 범위를 절반씩 줄여나가는 알고리즘이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;30. 앞에서 배운 내용과의 연결&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 배운 내용을 연결하면 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;배열
 &amp;darr;
인덱스를 이용하여 데이터에 접근
 &amp;darr;
선형 탐색
 &amp;darr;
정렬된 배열
 &amp;darr;
탐색 범위
 &amp;darr;
left / right
 &amp;darr;
mid
 &amp;darr;
이진 탐색
 &amp;darr;
O(log N)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이전에 배운 재귀와도 연결됩니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;재귀
 &amp;darr;
문제를 작은 문제로 나눔
 &amp;darr;
이진 탐색
 &amp;darr;
탐색 범위를 절반으로 줄임
 &amp;darr;
분할 정복 사고방식&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이번 단계에서 단순히 &lt;b&gt;&quot;이진 탐색은 O(log N)&quot;&lt;/b&gt;만 외우는 것보다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정렬된 데이터 &amp;rarr; 탐색 범위 &amp;rarr; left와 right &amp;rarr; mid &amp;rarr; 비교 &amp;rarr; 절반 제거 &amp;rarr; O(log N)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라는 흐름을 하나의 과정으로 이해하는 것이 중요&lt;/p&gt;</description>
      <category>자료구조와 알고리즘</category>
      <author>begin-play</author>
      <guid isPermaLink="true">https://begin-play.tistory.com/93</guid>
      <comments>https://begin-play.tistory.com/93#entry93comment</comments>
      <pubDate>Thu, 27 Aug 2026 12:31:06 +0900</pubDate>
    </item>
    <item>
      <title>재귀</title>
      <link>https://begin-play.tistory.com/92</link>
      <description>&lt;h1&gt;재귀(Recursion) &amp;mdash; TIL 정리&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 재귀란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;재귀(Recursion)&lt;/b&gt;란 함수가 &lt;b&gt;자기 자신을 다시 호출하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 형태는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;void Function()
{
    Function();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Function()이 실행되면 다시 Function()을 호출하고, 그 함수가 다시 Function()을 호출한다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;Function()
 └─ Function()
     └─ Function()
         └─ Function()
             └─ ...&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;b&gt;Stack Overflow&lt;/b&gt;가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 정상적인 재귀 함수에는 반드시 &lt;b&gt;종료 조건&lt;/b&gt;이 필요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. 재귀 함수의 두 가지 핵심&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀 함수를 이해할 때 가장 중요한 것은 &lt;b&gt;Base Case&lt;/b&gt;와 &lt;b&gt;Recursive Case&lt;/b&gt;이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-1. Base Case&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;재귀를 멈추는 조건&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;if (n == 0)
{
    return;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n == 0이 되면 더 이상 자기 자신을 호출하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Base Case
&amp;rarr; 재귀를 언제 멈출 것인가?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 결정한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-2. Recursive Case&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자기 자신을 다시 호출하는 부분&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;Function(n - 1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 재귀 함수는 기본적으로 다음과 같은 구조를 가진다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;void Function(int n)
{
    if (n == 0)
    {
        return;
    }

    Function(n - 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면:&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;재귀 함수
├── Base Case
│   └── 재귀 종료
│
└── Recursive Case
    └── 자기 자신을 다시 호출&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀 문제를 풀 때는 항상 먼저 &lt;b&gt;Base Case가 무엇인지&lt;/b&gt; 찾고, 그다음 &lt;b&gt;문제를 어떻게 더 작은 문제로 만들 것인지&lt;/b&gt; 생각해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. 호출 스택(Call Stack)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀에서 가장 중요한 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 호출하면 프로그램은 해당 함수의 실행 정보 등을 &lt;b&gt;호출 스택(Call Stack)&lt;/b&gt;에 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 코드가 있다고 하자.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;void A()
{
    B();
}

void B()
{
    C();
}

void C()
{
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A()를 호출하면:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;A()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A()가 B()를 호출하면:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;B()
A()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B()가 C()를 호출하면:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;C()
B()
A()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 C()가 끝나면:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;B()
A()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B()가 끝나면:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;A()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A()가 끝나면:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;비어 있음&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;가장 마지막에 들어간 함수가 가장 먼저 빠진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 바로 &lt;b&gt;LIFO(Last In, First Out)&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 배운 &lt;b&gt;스택 자료구조가 실제 함수 호출에도 사용되는 것&lt;/b&gt;이라고 생각할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. 재귀에서 호출 스택이 어떻게 움직이는가&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 보자.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void CountDown(int n)
{
    if (n == 0)
    {
        return;
    }

    cout &amp;lt;&amp;lt; n &amp;lt;&amp;lt; endl;
    CountDown(n - 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 호출한다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;CountDown(3);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;① CountDown(3)&lt;/h2&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;CountDown(3)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n == 0이 아니므로 3을 출력하고 CountDown(2)를 호출한다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;CountDown(3)
CountDown(2)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;② CountDown(2)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 CountDown(1)을 호출한다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;CountDown(3)
CountDown(2)
CountDown(1)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;③ CountDown(1)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 CountDown(0)을 호출한다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;CountDown(3)
CountDown(2)
CountDown(1)
CountDown(0)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;④ CountDown(0)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Base Case에 도달한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;if (n == 0)
{
    return;
}&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;b&gt;재귀가 풀리기 시작한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;CountDown(0) 종료
&amp;darr;
CountDown(1) 종료
&amp;darr;
CountDown(2) 종료
&amp;darr;
CountDown(3) 종료&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 Base Case를 만난 후 &lt;b&gt;쌓여 있던 함수 호출이 역순으로 빠져나오는 과정&lt;/b&gt;을 흔히 &lt;b&gt;Unwinding&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. 재귀에서 가장 헷갈리는 부분&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 보자.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void Test(int n)
{
    if (n == 0)
    {
        return;
    }

    cout &amp;lt;&amp;lt; &quot;들어감 : &quot; &amp;lt;&amp;lt; n &amp;lt;&amp;lt; endl;

    Test(n - 1);

    cout &amp;lt;&amp;lt; &quot;나옴 : &quot; &amp;lt;&amp;lt; n &amp;lt;&amp;lt; endl;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 호출한다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Test(3);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 결과는:&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;들어감 : 3
들어감 : 2
들어감 : 1
나옴 : 1
나옴 : 2
나옴 : 3&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;왜냐하면 Test(3)이 바로 &quot;나옴 : 3&quot;을 출력하는 것이 아니기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Test(2);&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;그다음 Test(2)도:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Test(1);&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;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Test(3)
Test(2)
Test(1)
Test(0)&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;그리고 Test(0)이 종료된 후:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Test(0) 제거
Test(1) 제거 &amp;rarr; &quot;나옴 : 1&quot;
Test(2) 제거 &amp;rarr; &quot;나옴 : 2&quot;
Test(3) 제거 &amp;rarr; &quot;나옴 : 3&quot;&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;b&gt;재귀 호출 아래에 어떤 코드가 있느냐&lt;/b&gt;도 매우 중요하다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Test(n - 1);

cout &amp;lt;&amp;lt; &quot;나옴&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 Test(n - 1)이 모두 끝난 후 &quot;나옴&quot;이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;재귀는 내려갈 때와 올라올 때 실행 순서가 다를 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. Factorial(팩토리얼)&lt;/h1&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;5! = 5 &amp;times; 4 &amp;times; 3 &amp;times; 2 &amp;times; 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0! = 1&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;pre class=&quot;excel&quot;&gt;&lt;code&gt;n! = n &amp;times; (n - 1)!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 관계를 코드로 그대로 옮길 수 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int Factorial(int n)
{
    if (n == 0)
    {
        return 1;
    }

    return n * Factorial(n - 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Factorial(5)를 호출하면:&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;Factorial(5)
= 5 &amp;times; Factorial(4)
= 5 &amp;times; 4 &amp;times; Factorial(3)
= 5 &amp;times; 4 &amp;times; 3 &amp;times; Factorial(2)
= 5 &amp;times; 4 &amp;times; 3 &amp;times; 2 &amp;times; Factorial(1)
= 5 &amp;times; 4 &amp;times; 3 &amp;times; 2 &amp;times; 1 &amp;times; Factorial(0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Factorial(0)에서 Base Case를 만나:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;return 1;&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;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Factorial(0) &amp;rarr; 1
Factorial(1) &amp;rarr; 1
Factorial(2) &amp;rarr; 2
Factorial(3) &amp;rarr; 6
Factorial(4) &amp;rarr; 24
Factorial(5) &amp;rarr; 120&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Factorial은 재귀 호출이 먼저 스택에 쌓이고, Base Case에 도달한 뒤 결과가 역순으로 반환되는 과정을 이해하기 좋은 예제이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. Fibonacci(피보나치)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;피보나치 수열은:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0, 1, 1, 2, 3, 5, 8, 13, 21...&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;pre class=&quot;lisp&quot;&gt;&lt;code&gt;F(n) = F(n - 1) + F(n - 2)&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;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;int Fibonacci(int n)
{
    if (n &amp;lt;= 1)
    {
        return n;
    }

    return Fibonacci(n - 1) + Fibonacci(n - 2);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;Fibonacci(5);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 호출하면 다음과 같이 분리된다.&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;                F(5)
              /     \
           F(4)     F(3)
          /   \     /   \
       F(3) F(2) F(2) F(1)&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;F(3)이나 F(2)처럼 &lt;b&gt;같은 값에 대한 계산이 여러 번 반복된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 단순 재귀 방식의 Fibonacci는 매우 비효율적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 복잡도는 대략 &lt;b&gt;O(2ⁿ)&lt;/b&gt; 수준으로 증가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 통해 알 수 있는 것은:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;재귀라고 해서 항상 효율적인 것은 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀 호출이 한 번 발생하는지, 여러 갈래로 발생하는지를 확인해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. 재귀의 시간 복잡도&lt;/h1&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;pre class=&quot;fortran&quot;&gt;&lt;code&gt;void Function(int n)
{
    if (n == 0)
        return;

    Function(n - 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출 흐름은:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;n
&amp;darr;
n - 1
&amp;darr;
n - 2
&amp;darr;
...
&amp;darr;
0&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;한 번씩 호출되므로 전체 호출 횟수는 n에 비례한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 시간 복잡도는:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(n)&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;하지만 Fibonacci처럼:&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;Fibonacci(n - 1);
Fibonacci(n - 2);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 재귀 호출이 계속 발생하면 호출 수가 급격하게 증가한다.&lt;/p&gt;
&lt;pre class=&quot;r&quot;&gt;&lt;code&gt;        F(n)
       /    \
    F(n-1)  F(n-2)
    /  \     /  \
   ... ...  ... ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 재귀에서는 단순히:&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;재귀 &amp;rarr; O(n)&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;b&gt;재귀 호출이 어떤 형태로 발생하는지&lt;/b&gt;를 확인해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. 재귀의 공간 복잡도&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀에서는 시간 복잡도뿐만 아니라 &lt;b&gt;호출 스택이 얼마나 깊게 쌓이는지&lt;/b&gt;도 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;Function(n);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 호출했을 때 재귀 깊이가 n이라면 호출 스택에도 최대 n개의 함수 호출이 쌓일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;Function(int n)
{
    Function(n - 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 함수의 공간 복잡도는:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;O(n)&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;pre class=&quot;markdown&quot;&gt;&lt;code&gt;시간 복잡도
+
호출 스택에 필요한 공간&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. 재귀 &amp;rarr; 반복문 변환&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 재귀 함수는 반복문으로 바꿀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Factorial:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int Factorial(int n)
{
    if (n == 0)
        return 1;

    return n * Factorial(n - 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 반복문으로 바꾸면:&lt;/p&gt;
&lt;pre class=&quot;nimrod&quot;&gt;&lt;code&gt;int Factorial(int n)
{
    int result = 1;

    for (int i = 1; i &amp;lt;= n; i++)
    {
        result *= i;
    }

    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 코드의 결과는 같다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;재귀
5 &amp;rarr; 4 &amp;rarr; 3 &amp;rarr; 2 &amp;rarr; 1 &amp;rarr; 0

반복문
1 &amp;rarr; 2 &amp;rarr; 3 &amp;rarr; 4 &amp;rarr; 5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문은 일반적으로 &lt;b&gt;함수 호출에 따른 호출 스택을 사용하지 않기 때문에&lt;/b&gt; 공간 측면에서 유리할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. 그런데 왜 재귀를 사용하는가?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 모든 재귀를 반복문으로 바꾸면 되는 것처럼 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 재귀가 훨씬 자연스러운 문제들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로:&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;DFS&lt;/li&gt;
&lt;li&gt;Divide and Conquer&lt;/li&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;p data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;트리&lt;/b&gt;는 재귀와 매우 잘 맞는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \
    D   E&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;B 밑에 자식이 있고, 그 자식 밑에도 또 자식이 있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;노드를 처리한다
&amp;rarr; 자식 노드를 처리한다
&amp;rarr; 그 자식의 자식 노드를 처리한다
&amp;rarr; ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 구조를 재귀로 표현하기 좋다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. Tree Traversal&lt;/h1&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;pre class=&quot;livescript&quot;&gt;&lt;code&gt;        A
       / \
      B   C
     / \
    D   E&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 트리가 있다고 하자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Preorder&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Root
Left
Right&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;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A B D E C&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;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;void Preorder(Node* node)
{
    if (node == nullptr)
        return;

    cout &amp;lt;&amp;lt; node-&amp;gt;value;
    Preorder(node-&amp;gt;left);
    Preorder(node-&amp;gt;right);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서:&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;Preorder(node-&amp;gt;left);
Preorder(node-&amp;gt;right);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 재귀 호출이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Inorder&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Left
Root
Right&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;pre class=&quot;armasm&quot;&gt;&lt;code&gt;D B E A C&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Postorder&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서는:&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Left
Right
Root&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;pre class=&quot;armasm&quot;&gt;&lt;code&gt;D E B C A&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;재귀 호출을 어떤 위치에 배치하느냐에 따라 트리 순회 순서가 달라진다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;13. DFS와 재귀&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS는 &lt;b&gt;Depth First Search&lt;/b&gt;, 즉 &lt;b&gt;깊이 우선 탐색&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A
├── B
│   ├── D
│   └── E
└── C&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS는 한 방향으로 최대한 깊게 들어가는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀로 작성하면 개념적으로:&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;void DFS(Node* node)
{
    if (node == nullptr)
        return;

    방문(node);

    DFS(node-&amp;gt;left);
    DFS(node-&amp;gt;right);
}&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DFS 자체가 재귀라는 의미는 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS는 &lt;b&gt;재귀 방식으로 구현할 수도 있고, 반복문 + Stack으로 구현할 수도 있다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;재귀 DFS
&amp;rarr; 시스템의 Call Stack 사용

반복문 DFS
&amp;rarr; 직접 만든 Stack 사용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 둘은 비슷한 원리를 사용한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;14. Divide and Conquer&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Divide and Conquer(분할 정복)&lt;/b&gt;는 하나의 큰 문제를 여러 개의 작은 문제로 나눈 다음 각각 해결하고 결과를 합치는 방법이다.&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;Merge Sort&lt;/li&gt;
&lt;li&gt;Quick Sort&lt;/li&gt;
&lt;li&gt;Binary Search&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;예를 들어 Binary Search는:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[1 2 3 4 5 6 7 8 9]&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;pre class=&quot;&quot;&gt;&lt;code&gt;왼쪽만 탐색&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾는 값이 가운데 값보다 크면:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;오른쪽만 탐색&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;9개
&amp;darr;
4개
&amp;darr;
2개
&amp;darr;
1개&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;그래서 Binary Search의 시간 복잡도는:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;O(log n)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;15. 재귀 문제를 푸는 사고방식&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀 문제를 처음 봤을 때 바로 코드를 작성하기보다는 다음 순서로 생각하는 것이 좋다.&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;b&gt;Base Case&lt;/b&gt;를 찾는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;if (n == 0)
    return 1;&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;② 현재 문제를 더 작은 문제로 어떻게 만들 것인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Factorial은:&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;Factorial(n)
&amp;rarr; n &amp;times; Factorial(n - 1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;으로 만들 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Factorial(n - 1)&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;return n * Factorial(n - 1);&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;내부의 Factorial(n - 1)이 &lt;b&gt;어떻게 계산되는지 하나하나 직접 작성할 필요가 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 자기 자신이 더 작은 문제를 해결하도록 맡기는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Factorial(5)
Factorial(4)
Factorial(3)
Factorial(2)
Factorial(1)
Factorial(0)&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;그리고 Base Case를 만난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 다시:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;0 &amp;rarr; 1 &amp;rarr; 2 &amp;rarr; 6 &amp;rarr; 24 &amp;rarr; 120&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;b&gt;내려가는 과정과 올라오는 과정을 둘 다 생각해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;16. 재귀에서 자주 하는 실수&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Base Case가 없는 경우&lt;/h2&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;void Function(int n)
{
    Function(n - 1);
}&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;결국 Stack Overflow가 발생한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Base Case에 도달하지 못하는 경우&lt;/h2&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;void Function(int n)
{
    if (n == 0)
        return;

    Function(n + 1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 n이 증가하기 때문에 0에 도달하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 재귀가 끝나지 않는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제의 크기가 줄어들지 않는 경우&lt;/h2&gt;
&lt;pre class=&quot;delphi&quot;&gt;&lt;code&gt;Function(n);&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;pre class=&quot;ada&quot;&gt;&lt;code&gt;현재 문제
&amp;darr;
더 작은 문제
&amp;darr;
더 작은 문제
&amp;darr;
Base Case&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 흐름이 만들어져야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;17. 재귀의 장점과 단점&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;장점&lt;/h2&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;DFS 구현이 간단하다.&lt;/li&gt;
&lt;li&gt;Divide and Conquer 알고리즘을 표현하기 좋다.&lt;/li&gt;
&lt;li&gt;문제의 수학적 정의를 코드로 옮기기 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단점&lt;/h2&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;재귀 깊이가 너무 깊으면 Stack Overflow가 발생할 수 있다.&lt;/li&gt;
&lt;li&gt;같은 계산을 반복하면 매우 느려질 수 있다.&lt;/li&gt;
&lt;li&gt;반복문보다 실행 구조를 이해하기 어려울 수 있다.&lt;/li&gt;
&lt;li&gt;호출 스택 때문에 추가적인 메모리를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;18. 재귀에서 반드시 기억해야 할 핵심&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀는 단순히 &lt;b&gt;&quot;함수가 자기 자신을 호출한다&quot;&lt;/b&gt;라고만 이해하면 부족하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진짜 핵심은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;재귀
│
├─ Base Case
│    └─ 재귀 종료
│
└─ Recursive Case
     └─ 자기 자신 호출&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 실제 실행 과정은:&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;              함수 호출
                  &amp;darr;
              현재 함수
                  &amp;darr;
          더 작은 문제 호출
                  &amp;darr;
          더 작은 문제 호출
                  &amp;darr;
          더 작은 문제 호출
                  &amp;darr;
              Base Case
                  &amp;darr;
                  &amp;uarr;
             결과 반환
                  &amp;uarr;
             결과 반환
                  &amp;uarr;
             결과 반환
                  &amp;uarr;
              최종 결과&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;함수가 호출될 때 호출 스택에 쌓이고, Base Case에 도달하면 하나씩 빠져나오면서 이전 함수가 이어서 실행된다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 재귀의 가장 중요한 원리이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;19. 5단계 핵심 키워드 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념의미&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Recursion&lt;/td&gt;
&lt;td&gt;함수가 자기 자신을 호출하는 것&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Base Case&lt;/td&gt;
&lt;td&gt;재귀를 종료하는 조건&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recursive Case&lt;/td&gt;
&lt;td&gt;자기 자신을 다시 호출하는 부분&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Call Stack&lt;/td&gt;
&lt;td&gt;함수 호출 정보를 저장하는 스택&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stack Overflow&lt;/td&gt;
&lt;td&gt;호출 스택이 지나치게 커진 상태&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unwinding&lt;/td&gt;
&lt;td&gt;Base Case 이후 재귀 호출이 역순으로 종료되는 과정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Factorial&lt;/td&gt;
&lt;td&gt;재귀를 이해하는 대표적인 예&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fibonacci&lt;/td&gt;
&lt;td&gt;재귀 호출이 여러 갈래로 발생하는 대표적인 예&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tree Traversal&lt;/td&gt;
&lt;td&gt;트리 구조를 탐색하는 방법&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DFS&lt;/td&gt;
&lt;td&gt;깊이 우선 탐색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Divide and Conquer&lt;/td&gt;
&lt;td&gt;문제를 작은 문제로 나누어 해결하는 방법&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time Complexity&lt;/td&gt;
&lt;td&gt;재귀 호출 횟수를 고려한 시간 복잡도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Space Complexity&lt;/td&gt;
&lt;td&gt;호출 스택의 최대 깊이를 고려한 공간 복잡도&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;20. 지금까지 배운 자료구조와 재귀의 연결&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스택&lt;/b&gt;과 직접 연결된다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;Stack
&amp;darr;
LIFO
&amp;darr;
가장 나중에 들어온 것이 가장 먼저 나옴
&amp;darr;
Recursion
&amp;darr;
함수 호출이 Call Stack에 쌓임
&amp;darr;
Base Case 도달
&amp;darr;
가장 마지막에 호출된 함수부터 종료&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;스택 자료구조를 배운 이유 중 하나가 재귀를 이해하는 데에도 연결된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 배우게 될 자료구조와 알고리즘에서도 계속 연결된다.&lt;/p&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Stack
  &amp;darr;
Recursion
  &amp;darr;
Tree
  &amp;darr;
DFS
  &amp;darr;
Graph&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;21. 5단계에서 꼭 기억할 것&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① 재귀에는 Base Case가 필요하다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀를 언제 끝낼 것인지 정해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② Recursive Case에서는 문제의 크기가 점점 작아져야 한다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 문제를 더 작은 문제로 만들어야 Base Case에 도달할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ 재귀 호출은 Call Stack에 쌓인다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 호출될 때마다 해당 함수의 실행 정보가 스택에 쌓인다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;④ Base Case에 도달하면 쌓였던 함수들이 역순으로 빠져나온다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LIFO 구조이기 때문에 가장 마지막에 호출된 함수가 먼저 종료된다.&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;h3 data-ke-size=&quot;size23&quot;&gt;⑥ 재귀는 반복문으로 바꿀 수 있는 경우가 많다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 트리, DFS, Divide and Conquer처럼 재귀로 표현하는 것이 자연스러운 문제도 많다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한 문장으로 정리&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;재귀는 자기 자신을 호출하면서 문제를 더 작은 문제로 나누고, Base Case에 도달한 뒤 Call Stack에 쌓였던 함수들이 역순으로 실행되면서 문제를 해결하는 방법이다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>자료구조와 알고리즘</category>
      <author>begin-play</author>
      <guid isPermaLink="true">https://begin-play.tistory.com/92</guid>
      <comments>https://begin-play.tistory.com/92#entry92comment</comments>
      <pubDate>Wed, 26 Aug 2026 11:22:12 +0900</pubDate>
    </item>
    <item>
      <title>스택과 큐</title>
      <link>https://begin-play.tistory.com/91</link>
      <description>&lt;h1&gt;스택과 큐&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 스택과 큐란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택(Stack)과 큐(Queue)는 여러 데이터를 저장하고 관리하는 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘의 가장 큰 차이점은 &lt;b&gt;데이터를 어떤 순서로 꺼내는가&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stack &amp;rarr; LIFO&lt;/li&gt;
&lt;li&gt;Queue &amp;rarr; FIFO&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 데이터를 저장하는 방식보다 &lt;b&gt;데이터를 꺼내는 순서&lt;/b&gt;가 핵심이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;2. Stack&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-1. Stack이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack은 &lt;b&gt;LIFO(Last In, First Out)&lt;/b&gt; 방식의 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LIFO는 &lt;b&gt;후입선출&lt;/b&gt;이라는 의미로, 가장 나중에 들어온 데이터가 가장 먼저 나간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 위로 계속 쌓는 상황을 생각하면 이해하기 쉽다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;      [ C ] &amp;larr; 가장 먼저 꺼냄
      [ B ]
      [ A ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A &amp;rarr; B &amp;rarr; C 순서로 데이터를 넣었다면:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Push(A)
Push(B)
Push(C)

Pop() &amp;rarr; C
Pop() &amp;rarr; B
Pop() &amp;rarr; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;입력: A &amp;rarr; B &amp;rarr; C
출력: C &amp;rarr; B &amp;rarr; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. Stack의 주요 연산&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack에서 가장 중요한 연산은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Push&lt;/li&gt;
&lt;li&gt;Pop&lt;/li&gt;
&lt;li&gt;Top&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 Empty와 Size도 자주 사용한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-1. Push&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Push는 Stack에 데이터를 추가하는 연산이다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;stack.push(10);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 20을 넣으면:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;stack.push(20);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scss&quot;&gt;&lt;code&gt;[20] &amp;larr; Top
[10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;30을 넣으면:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;stack.push(30);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;scss&quot;&gt;&lt;code&gt;[30] &amp;larr; Top
[20]
[10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Push는 &lt;b&gt;Stack의 위쪽에 데이터를 추가하는 연산&lt;/b&gt;이라고 이해하면 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-2. Pop&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pop은 Stack의 가장 위에 있는 데이터를 제거하는 연산이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Stack이 다음과 같다고 하자.&lt;/p&gt;
&lt;pre class=&quot;scss&quot;&gt;&lt;code&gt;[30] &amp;larr; Top
[20]
[10]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;stack.pop();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 실행하면:&lt;/p&gt;
&lt;pre class=&quot;scss&quot;&gt;&lt;code&gt;[20] &amp;larr; Top
[10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;30이 제거된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은 C++의 pop()은 &lt;b&gt;제거한 값을 반환하지 않는다&lt;/b&gt;는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음과 같이 사용할 수 없다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int value = stack.pop();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 데이터를 확인한 후 제거해야 한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int value = stack.top();
stack.pop();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;top() &amp;rarr; 현재 데이터를 확인
pop() &amp;rarr; 데이터를 제거
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 역할을 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-3. Top&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Top은 Stack의 가장 위에 있는 데이터를 확인하는 연산이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;scss&quot;&gt;&lt;code&gt;[30] &amp;larr; Top
[20]
[10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일 때:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;stack.top();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 실행하면 30을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Top은 데이터를 제거하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;top()
&amp;darr;
데이터 확인

pop()
&amp;darr;
데이터 제거
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로 구분해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-4. Empty&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack이 비어 있는지 확인하는 연산이다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;stack.empty();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비어 있다면 true, 데이터가 있다면 false를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;if (!stack.empty())
{
    int value = stack.top();
    stack.pop();
}
&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;빈 Stack에서 top()이나 pop()을 사용하는 것은 잘못된 사용이므로, 데이터를 꺼내기 전에 비어 있는지 확인하는 습관이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-5. Size&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Stack에 몇 개의 데이터가 들어 있는지 확인한다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;stack.size();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[30]
[20]
[10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라면:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;stack.size();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 결과는 3이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. C++의 std::stack&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 Stack을 직접 구현하지 않아도 STL에서 제공하는 std::stack을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;stack&amp;gt;

std::stack&amp;lt;int&amp;gt; stack;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::stack&amp;lt;int&amp;gt;에서 int는 Stack에 저장할 데이터의 타입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;stack.push(10);
stack.push(20);
stack.push(30);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 실행하면:&lt;/p&gt;
&lt;pre class=&quot;scss&quot;&gt;&lt;code&gt;[30] &amp;larr; Top
[20]
[10]
&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;pre class=&quot;cpp&quot;&gt;&lt;code&gt;while (!stack.empty())
{
    int value = stack.top();
    stack.pop();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 사용하면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;30
20
10
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서로 데이터를 처리하게 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;5. Queue&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5-1. Queue란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue는 &lt;b&gt;FIFO(First In, First Out)&lt;/b&gt; 방식의 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FIFO는 &lt;b&gt;선입선출&lt;/b&gt;이라는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 가장 먼저 들어온 데이터가 가장 먼저 나간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들이 줄을 서 있는 상황을 생각하면 이해하기 쉽다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;입구                         출구
 &amp;darr;                            &amp;darr;

[A] [B] [C] [D]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A가 가장 먼저 들어왔다면 A가 가장 먼저 나간다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;Enqueue(A)
Enqueue(B)
Enqueue(C)

Dequeue() &amp;rarr; A
Dequeue() &amp;rarr; B
Dequeue() &amp;rarr; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;입력: A &amp;rarr; B &amp;rarr; C
출력: A &amp;rarr; B &amp;rarr; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;6. Queue의 주요 연산&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue에서는 다음과 같은 개념을 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Enqueue&lt;/li&gt;
&lt;li&gt;Dequeue&lt;/li&gt;
&lt;li&gt;Front&lt;/li&gt;
&lt;li&gt;Back&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++의 std::queue에서는 Enqueue와 Dequeue라는 이름 대신 push()와 pop()을 사용한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6-1. Enqueue&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Enqueue는 Queue의 뒤쪽에 데이터를 추가하는 연산이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 20과 30을 추가하면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Front              Back
 &amp;darr;                   &amp;darr;
[10] [20] [30]
&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;C++의 std::queue에서는 다음과 같이 사용한다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;queue.push(10);
queue.push(20);
queue.push(30);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 push()는 Queue에서는 &lt;b&gt;뒤쪽에 데이터를 추가하는 역할&lt;/b&gt;을 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;7. Dequeue&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dequeue는 Queue의 가장 앞쪽에 있는 데이터를 제거하는 연산이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Queue가:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Front              Back
 &amp;darr;                   &amp;darr;
[10] [20] [30]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라면:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;queue.pop();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 실행했을 때 10이 제거된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Front        Back
 &amp;darr;             &amp;darr;
[20] [30]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack과 마찬가지로 queue.pop()도 &lt;b&gt;제거한 값을 반환하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음과 같이 사용해야 한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int value = queue.front();
queue.pop();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;front() &amp;rarr; 데이터 확인
pop()   &amp;rarr; 데이터 제거
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 순서이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;8. Front&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Front는 Queue의 가장 앞에 있는 데이터를 확인하는 연산이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Front              Back
 &amp;darr;                   &amp;darr;
[10] [20] [30]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;queue.front();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 실행하면 10을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 front()는 데이터를 제거하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int value = queue.front();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 실행해도 Queue는 그대로 유지된다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[10] [20] [30]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;9. Back&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Back은 Queue의 가장 뒤에 있는 데이터를 확인하는 연산이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Front              Back
 &amp;darr;                   &amp;darr;
[10] [20] [30]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;queue.back();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 실행하면 30을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Front와 Back 역시 &lt;b&gt;확인만 하는 연산&lt;/b&gt;이고 데이터를 제거하지 않는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;10. Queue의 Empty와 Size&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack과 마찬가지로 Queue에서도 empty()와 size()를 사용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Empty&lt;/h3&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;queue.empty();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue가 비어 있는지 확인한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Size&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;queue.size();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Queue에 저장된 데이터의 개수를 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[10] [20] [30]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이라면:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;queue.size();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 결과는 3이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;11. C++의 std::queue&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 STL에서 제공하는 std::queue를 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;#include &amp;lt;queue&amp;gt;

std::queue&amp;lt;int&amp;gt; queue;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 추가하면:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;queue.push(10);
queue.push(20);
queue.push(30);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 상태가 된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Front              Back
 &amp;darr;                   &amp;darr;
[10] [20] [30]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;while (!queue.empty())
{
    int value = queue.front();
    queue.pop();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 실행하면:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;10
20
30
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서로 데이터를 처리하게 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;12. Stack과 Queue 비교&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack과 Queue는 둘 다 데이터를 저장하지만 &lt;b&gt;데이터를 꺼내는 순서가 다르다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분StackQueue&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;방식&lt;/td&gt;
&lt;td&gt;LIFO&lt;/td&gt;
&lt;td&gt;FIFO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;의미&lt;/td&gt;
&lt;td&gt;후입선출&lt;/td&gt;
&lt;td&gt;선입선출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;먼저 나가는 데이터&lt;/td&gt;
&lt;td&gt;가장 나중에 들어온 데이터&lt;/td&gt;
&lt;td&gt;가장 먼저 들어온 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 추가&lt;/td&gt;
&lt;td&gt;Push&lt;/td&gt;
&lt;td&gt;Enqueue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 제거&lt;/td&gt;
&lt;td&gt;Pop&lt;/td&gt;
&lt;td&gt;Dequeue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 확인&lt;/td&gt;
&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;Front&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;뒤쪽 확인&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;Back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C++&lt;/td&gt;
&lt;td&gt;std::stack&lt;/td&gt;
&lt;td&gt;std::queue&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 기억하면:&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;Stack

넣기 &amp;darr;
[ C ] &amp;larr; 꺼내기
[ B ]
[ A ]

LIFO
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;Queue

넣기 &amp;rarr; [ A ] [ B ] [ C ] &amp;rarr; 꺼내기

        FIFO
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;13. std::deque&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack과 Queue를 공부하면 std::deque도 함께 알아둘 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deque는 &lt;b&gt;Double Ended Queue&lt;/b&gt;의 줄임말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 &lt;b&gt;양쪽 끝에서 데이터를 추가하거나 제거할 수 있는 자료구조&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre class=&quot;inform7&quot;&gt;&lt;code&gt;Front                    Back
 &amp;darr;                         &amp;darr;
[ A ] [ B ] [ C ] [ D ]
 &amp;uarr;                         &amp;uarr;
앞에서 추가/삭제       뒤에서 추가/삭제
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;deque&amp;gt;

std::deque&amp;lt;int&amp;gt; dq;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같이 사용할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13-1. push_front()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞쪽에 데이터를 추가한다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;dq.push_front(10);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13-2. push_back()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤쪽에 데이터를 추가한다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;dq.push_back(20);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과:&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;[10] [20]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13-3. pop_front()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞쪽의 데이터를 제거한다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;dq.pop_front();
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;13-4. pop_back()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뒤쪽의 데이터를 제거한다.&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;dq.pop_back();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 deque는 양쪽을 자유롭게 사용할 수 있다는 특징이 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;14. Stack, Queue, Deque의 관계&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack과 Queue가 완전히 별개의 저장 방식이라고 생각할 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack과 Queue는 &lt;b&gt;데이터를 사용하는 규칙이 다른 자료구조&lt;/b&gt;라고 이해하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로:&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Deque
 ├── 앞/뒤 모두 사용 가능
 │
 ├── Stack처럼 사용 가능
 │
 └── Queue처럼 사용 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deque는 양쪽에서 데이터를 추가하고 제거할 수 있기 때문에 Stack과 Queue를 구현하는 데 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 자료구조에서는 단순히 &quot;어떤 데이터를 저장하는가&quot;뿐만 아니라 &lt;b&gt;어떤 방식으로 데이터를 접근하고 제거하도록 제한하는가&lt;/b&gt;도 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;15. Stack과 이후 자료구조의 연결&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack은 이후에 공부할 내용과 연결되는 중요한 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 다음과 같은 관계가 있다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;Stack
  &amp;darr;
재귀
  &amp;darr;
DFS
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여기서 재귀와 DFS의 자세한 내용을 다루지는 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 단계에서는 &lt;b&gt;Stack이 이후 알고리즘 공부에서 중요한 역할을 한다는 것&lt;/b&gt;만 알아두면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 단계인 재귀에서 함수를 반복적으로 호출하고 종료하면서 이전 호출 위치로 돌아가는 과정 등을 공부하게 되면 Stack과의 관계를 자세하게 이해할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;16. Stack과 Queue를 실제 문제에 적용하면&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료구조는 단순히 자료를 저장하기 위한 문법이 아니라 &lt;b&gt;문제의 상황에 맞는 데이터 처리 순서를 선택하는 도구&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 작업을 처리한다고 생각해보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stack을 사용하는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 들어온 작업을 먼저 처리해야 한다면 Stack이 적합하다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;작업 A
작업 B
작업 C &amp;larr; 가장 최근에 들어옴

처리 순서:
C &amp;rarr; B &amp;rarr; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 LIFO이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Queue를 사용하는 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 들어온 작업을 먼저 처리해야 한다면 Queue가 적합하다.&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;작업 A &amp;rarr; 작업 B &amp;rarr; 작업 C

처리 순서:
A &amp;rarr; B &amp;rarr; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 FIFO이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 자료구조를 공부할 때는 단순히 함수 이름을 외우는 것보다:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 상황에서는 어떤 순서로 데이터를 꺼내야 하는가?&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 생각하는 것이 중요하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;17. Unreal Engine에서 생각해보기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unreal Engine에서도 C++ 자료구조를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 여러 작업을 순서대로 처리해야 하는 상황을 생각하면 Queue의 개념을 적용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;처리해야 할 작업

[이동] [공격] [재장전]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 들어온 작업부터 처리한다면:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;이동 &amp;rarr; 공격 &amp;rarr; 재장전
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이므로 Queue의 FIFO 방식과 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 가장 최근의 작업을 먼저 처리해야 하는 상황이라면:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;이동
공격
재장전 &amp;larr; 가장 최근 작업
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack의 LIFO 방식이 적합할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이것은 자료구조의 개념을 이해하기 위한 예시이며, Unreal Engine에서 모든 작업 시스템을 무조건 std::stack이나 std::queue로 구현한다는 의미는 아니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;18. 이번 단계에서 꼭 기억해야 할 핵심&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stack&lt;/h2&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;LIFO
Last In First Out
후입선출
&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;pre class=&quot;stylus&quot;&gt;&lt;code&gt;push()
pop()
top()
empty()
size()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int value = stack.top();
stack.pop();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;top()은 확인하고 pop()은 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pop()이 값을 반환한다고 생각하면 안 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Queue&lt;/h2&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;FIFO
First In First Out
선입선출
&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;pre class=&quot;stylus&quot;&gt;&lt;code&gt;push()
pop()
front()
back()
empty()
size()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int value = queue.front();
queue.pop();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;front()는 확인하고 pop()은 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue에서도 pop()이 값을 반환하지 않는다는 점을 기억해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Deque&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Double Ended Queue
&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;pre class=&quot;stylus&quot;&gt;&lt;code&gt;push_front()
push_back()
pop_front()
pop_back()
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;19. 최종 정리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 단계에서 가장 중요한 것은 다음 세 가지이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;① Stack = LIFO&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A &amp;rarr; B &amp;rarr; C

C &amp;rarr; B &amp;rarr; A
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마지막에 들어온 것이 먼저 나간다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;② Queue = FIFO&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;A &amp;rarr; B &amp;rarr; C

A &amp;rarr; B &amp;rarr; C
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;먼저 들어온 것이 먼저 나간다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;③ 데이터 확인과 제거는 별개이다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;stack.top();
stack.pop();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Queue:&lt;/p&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;queue.front();
queue.pop();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉:&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;확인 &amp;rarr; 제거
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 구조를 기억해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;20. C++ 자료구조 정리&lt;/h1&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;std::stack
    &amp;darr;
한쪽 끝에서 추가/제거
    &amp;darr;
LIFO

std::queue
    &amp;darr;
뒤에서 추가 / 앞에서 제거
    &amp;darr;
FIFO

std::deque
    &amp;darr;
앞과 뒤 모두 추가/제거 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 4단계의 핵심은 &lt;b&gt;Stack과 Queue의 동작 원리를 이해하고, 각각 언제 필요한지를 구분하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 다음 단계에서 재귀를 공부할 때 Stack과의 연결 관계가 등장하므로, 지금은 Stack = LIFO, Queue = FIFO라는 기본 구조를 확실하게 이해해 두는 것이 중요하다.&lt;/p&gt;</description>
      <category>자료구조와 알고리즘</category>
      <author>begin-play</author>
      <guid isPermaLink="true">https://begin-play.tistory.com/91</guid>
      <comments>https://begin-play.tistory.com/91#entry91comment</comments>
      <pubDate>Tue, 25 Aug 2026 17:21:00 +0900</pubDate>
    </item>
  </channel>
</rss>