본문으로 바로가기
제품데스크톱 도구TASTECAD도면 확인TASTEFILES파일 관리TASTEFTP파일 전송TASTESSH원격 작업공간TASTEZIP압축과 해제개발 도구TASTECODERust 통합 개발 환경TASTESTUDIO통합 개발·테스트 환경TASTEETL데이터 통합(ETL)서버TASTEREMOTE원격 데스크톱 서버TASTEGEO공간정보 서버TASTEMAIL메일 작업공간시작하기다운로드문서구매모든 제품
마켓플레이스
소식
개발소식오늘의 개발 소식개발 문서프로그래밍 언어RustTypeScriptJavaScriptPythonGoC#JavaCC++KotlinDart프런트엔드 프레임워크Next.jsReactVue.js · NuxtSvelteKitAngular서버 프레임워크Spring BootDjangoFastAPIASP.NET CoreNestJS · Express앱 · 데스크톱 프레임워크TauriFlutterReact NativeElectron알고리즘알고리즘 기초알고리즘 핵심알고리즘 심화실무 알고리즘
커뮤니티프로젝트개발 로그프롬프트AI 도구 이슈쇼케이스커뮤니티 안내
협업기술 전수홈페이지 구축데스크톱 앱 구축릴리스·결제 파이프라인판매 파트너문의
서비스원격 데스크톱설치 패키지 커스텀
TASTEDEV
제품

데스크톱 도구

TASTECAD복잡한 도면을 가볍게 열다TASTEFILES필요한 파일만, 가장 빠르게TASTEFTP파일이 오가는 가장 단정한 길TASTESSH서버와 나 사이, 더 단단한 연결TASTEZIP더 빠르게 묶고, 더 안전하게 풀다

개발 도구

TASTECODERust 앱을 편집 · 빌드 · 디버깅 · 테스트까지 한 창에서TASTESTUDIO편집부터 원격 테스트와 승인 기반 AI 수정까지TASTEETL값을 보며 데이터를 옮깁니다

서버

TASTEREMOTE우리 회사 서버로 운영하는 원격 데스크톱TASTEGEOGeoServer 호환 공간정보 서버를 Rust로 다시 세웁니다TASTEMAIL메일의 주도권을 다시 내 손에

시작하기

다운로드모든 제품 최신 판문서제품 사용법구매구독 · 영구 라이선스모든 제품모든 제품 한눈에
모든 제품
마켓플레이스소식
개발

소식

오늘의 개발 소식언어 · 프레임워크 · GitHub · AI 공식 발표개발 문서언어 · 프레임워크 · 알고리즘 안내서

프로그래밍 언어

RustTypeScriptJavaScriptPythonGoC#JavaCC++KotlinDart

프레임워크

프런트엔드 프레임워크Next.jsReactVue.js · NuxtSvelteKitAngular
서버 프레임워크Spring BootDjangoFastAPIASP.NET CoreNestJS · Express
앱 · 데스크톱 프레임워크TauriFlutterReact NativeElectron

알고리즘

알고리즘 기초복잡도 분석 · 정렬 · 탐색 · 기본 자료구조 · 트리와 힙알고리즘 핵심그래프 탐색 · 최단 경로와 최소 신장 트리 · 동적 계획법 · 탐욕 알고리즘 · 분할 정복 · 백트래킹알고리즘 심화문자열 알고리즘 · 수학과 정수론 · 비트 연산 · 구간 트리 · 유니온 파인드실무 알고리즘코딩 면접 풀이 전략 · LRU 캐시 · 레이트 리미터 · 일관된 해시
개발 문서 전체 보기
커뮤니티
프로젝트빌더와 작업 발견개발 로그결정과 시행착오프롬프트재사용 가능한 지시AI 도구 이슈Claude · Codex · Gemini 문제와 우회법쇼케이스완성된 결과커뮤니티 안내공개하고 함께 만드는 방식
협업
기술 전수Rust·egui 네이티브 앱 만들기홈페이지 구축10개 언어, 계정, 문서·다운로드데스크톱 앱 구축Rust·egui, WebView 없이릴리스·결제 파이프라인서명 · MSI·deb·rpm·AppImage · 해시판매 파트너소개하고 결제액의 30% 받기문의구축 · 제휴 · 투자 문의
서비스
원격 데스크톱브라우저로 내 컴퓨터에 접속설치 패키지 커스텀필요한 기능만, 설정은 미리
구매
KO
한국어EnglishDeutschEspañolFrançaisItalianoPortuguês (Brasil)日本語简体中文繁體中文
로그인
로그인
TASTEDEV

작은 도구부터, 더 좋은 개발까지.

support@tastedev.netTASTEDEV 후원하기
제품제품군구매문서다운로드마켓플레이스고객 지원
커뮤니티프로젝트개발 로그프롬프트AI 도구 이슈쇼케이스
정책개인정보처리방침이용약관구독 약관환불 정책커뮤니티 운영 정책
계정로그인가입하기
방문자
오늘
012345678901234567890123456789012345678901234567890123456789
어제
012345678901234567890123456789012345678901234567890123456789
누적 조회
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
상호
지엑스소프트
대표자
최영구
사업자등록번호
853-44-00368
주소
인천광역시 미추홀구 경인로 351 9층 V24
전화
+82-10-4404-1523
이메일
whitecalvin@tastedev.net
개인정보 보호책임자
최영구

© 2026 TASTEDEV제품 정보는 현재 검증된 개발 소스를 기준으로 합니다.출시·고도화 중

구간 트리 문서
구간 트리
개념동작 원리구현복잡도연습 문제실무 활용
토론

구간 트리 안내서 · 3/6

구현

이 장에서는 가장 널리 쓰는 반복문(아래에서 위로) 구간 트리를 Python으로 작성하고 한 줄씩 설명합니다. 이어서 같은 루틴을 C++, Java, TypeScript로 옮기고, 합 전용의 가벼운 대안인 펜윅 트리도 구현합니다. 구간은 모두 반열린 구간 [left, right)입니다.

Python 구간 트리

class SegmentTree:
    def __init__(self, values):
        self.n = len(values)
        self.tree = [0] * self.n + list(values)
        for i in range(self.n - 1, 0, -1):
            self.tree[i] = self.tree[2 * i] + self.tree[2 * i + 1]

    def update(self, index, value):
        i = index + self.n
        self.tree[i] = value
        while i > 1:
            i //= 2
            self.tree[i] = self.tree[2 * i] + self.tree[2 * i + 1]

    def query(self, left, right):
        total = 0
        lo, hi = left + self.n, right + self.n
        while lo < hi:
            if lo & 1:
                total += self.tree[lo]
                lo += 1
            if hi & 1:
                hi -= 1
                total += self.tree[hi]
            lo //= 2
            hi //= 2
        return total


st = SegmentTree([5, 3, 7, 9, 6, 4, 1, 2])
print(st.query(2, 6))  # 26
st.update(3, 0)
print(st.query(2, 6))  # 17

한 줄씩 보면 다음과 같습니다.

  • self.tree = [0] * self.n + list(values): 크기 2n 배열을 만들고 뒤쪽 절반 tree[n..2n)에 원소를 놓습니다. 인덱스 0은 쓰지 않습니다.
  • 생성자의 for 문: n-1부터 1까지 거꾸로 돌며 두 자식 2i, 2i+1을 합칩니다. 자식이 먼저 계산되므로 O(n)에 끝납니다.
  • update: 리프 index + n을 바꾼 뒤 i //= 2로 부모로 올라가며 다시 계산합니다. 높이만큼, 곧 O(log n)번 돕니다.
  • lo, hi: 질의 구간의 양 끝을 리프 위치로 옮긴 값입니다.
  • lo & 1: lo가 오른쪽 자식(홀수)이면 그 부모는 구간 밖까지 덮으므로, 이 노드만 결과에 넣고 lo를 한 칸 오른쪽으로 옮깁니다.
  • hi & 1: 반열린 오른쪽 끝이 홀수이면 바로 왼쪽 노드 hi - 1이 구간 안에 있으므로 그 노드를 넣습니다.
  • lo //= 2, hi //= 2: 한 층 위로 올라갑니다. 두 끝이 만나면 멈춥니다.

최솟값 트리로 바꾸려면 +를 min으로, 초깃값 0을 float("inf")로 바꾸면 됩니다. 순서가 중요한 연산이라면 왼쪽과 오른쪽에서 모은 값을 따로 두었다가 마지막에 순서대로 합칩니다.

C++

합이 int 범위를 넘기 쉬우므로 long long을 씁니다.

#include <vector>

struct SegmentTree {
    int n;
    std::vector<long long> tree;

    explicit SegmentTree(const std::vector<long long>& values)
        : n(static_cast<int>(values.size())), tree(2 * values.size()) {
        for (int i = 0; i < n; ++i) tree[n + i] = values[i];
        for (int i = n - 1; i > 0; --i) tree[i] = tree[2 * i] + tree[2 * i + 1];
    }

    void update(int index, long long value) {
        int i = index + n;
        tree[i] = value;
        while (i > 1) { i /= 2; tree[i] = tree[2 * i] + tree[2 * i + 1]; }
    }

    long long query(int left, int right) const {  // [left, right)
        long long total = 0;
        for (int lo = left + n, hi = right + n; lo < hi; lo /= 2, hi /= 2) {
            if (lo & 1) total += tree[lo++];
            if (hi & 1) total += tree[--hi];
        }
        return total;
    }
};

Java

public final class SegmentTree {
    private final int n;
    private final long[] tree;

    public SegmentTree(long[] values) {
        n = values.length;
        tree = new long[2 * n];
        System.arraycopy(values, 0, tree, n, n);
        for (int i = n - 1; i > 0; i--) tree[i] = tree[2 * i] + tree[2 * i + 1];
    }

    public void update(int index, long value) {
        int i = index + n;
        tree[i] = value;
        while (i > 1) { i /= 2; tree[i] = tree[2 * i] + tree[2 * i + 1]; }
    }

    public long query(int left, int right) { // [left, right)
        long total = 0;
        for (int lo = left + n, hi = right + n; lo < hi; lo /= 2, hi /= 2) {
            if ((lo & 1) == 1) total += tree[lo++];
            if ((hi & 1) == 1) total += tree[--hi];
        }
        return total;
    }
}

TypeScript

class SegmentTree {
  private readonly n: number;
  private readonly tree: number[];

  constructor(values: number[]) {
    this.n = values.length;
    this.tree = new Array<number>(this.n).fill(0).concat(values);
    for (let i = this.n - 1; i > 0; i--) this.tree[i] = this.tree[2 * i] + this.tree[2 * i + 1];
  }

  update(index: number, value: number): void {
    let i = index + this.n;
    this.tree[i] = value;
    while (i > 1) { i >>= 1; this.tree[i] = this.tree[2 * i] + this.tree[2 * i + 1]; }
  }

  query(left: number, right: number): number {
    let total = 0;
    for (let lo = left + this.n, hi = right + this.n; lo < hi; lo >>= 1, hi >>= 1) {
      if (lo & 1) total += this.tree[lo++];
      if (hi & 1) total += this.tree[--hi];
    }
    return total;
  }
}

Python 펜윅 트리

펜윅 트리(BIT)는 i & -i, 곧 i의 가장 낮은 1비트만큼씩 이동하며 누적 합을 나누어 저장합니다. 인덱스를 1부터 쓰는 것이 핵심입니다.

class FenwickTree:
    def __init__(self, n):
        self.n = n
        self.tree = [0] * (n + 1)  # tree[0]은 쓰지 않는다

    def add(self, index, delta):  # a[index] += delta
        i = index + 1
        while i <= self.n:
            self.tree[i] += delta
            i += i & -i

    def prefix_sum(self, count):  # a[0] + ... + a[count-1]
        total = 0
        while count > 0:
            total += self.tree[count]
            count -= count & -count
        return total

    def range_sum(self, left, right):  # [left, right)
        return self.prefix_sum(right) - self.prefix_sum(left)


bit = FenwickTree(8)
for i, v in enumerate([5, 3, 7, 9, 6, 4, 1, 2]):
    bit.add(i, v)
print(bit.range_sum(2, 6))   # 26
bit.add(3, -9)               # a[3] = 0 과 같다
print(bit.range_sum(2, 6))   # 17

펜윅 트리는 값을 "더하는" 연산만 받으므로, 값을 덮어쓰려면 차이(새 값 - 옛 값)를 더합니다. 구간 합이 뺄셈으로 구해지므로 최솟값처럼 되돌릴 수 없는 연산에는 그대로 쓸 수 없습니다.

정리

반복문 구간 트리는 크기 2n 배열, 생성 반복문 하나, 갱신과 질의 반복문 각각 하나로 끝나며 언어가 바뀌어도 구조가 같습니다. 합에는 더 짧은 펜윅 트리를 쓸 수 있고, 최솟값 · 최댓값처럼 빼기가 안 되는 연산에는 구간 트리를 씁니다.

이 장에 대해 질문이 있나요?
이전 장동작 원리다음 장복잡도

댓글 0개

인기 댓글순최신순

로그인 · 로그인하면 댓글을 남길 수 있습니다.