Tree - GitDeveloperKim/DreamEach GitHub Wiki

트리 (Tree)

  • 트리란 노드 N개와 간선 N-1개로 이루어진 사이클 없는 연결 그래프를 말한다
  • 루트 노드 : 부모가 없는 최상위 노드
  • 단말 노드 : 자식이 없는 노드
  • 크기 : 트리에 포함된 모든 노드의 개수
  • 깊이 : 루트 노드부터의 거리
  • 높이 : 깊이 중 최대값
  • 차수 : 각 노드의 간선 개수

이진트리 (Binary Search Tree)

  • 부모노드 : N
  • 자식 노드 : Nx2, Nx2+1
  • 자식 노드 -> 부모노드 : N/2
  • 필요한 노드의 개수 2^(h+1) +1 or 보통 4*N이면 커버 가능 (종만북)
  • 트리의 높이 logN
  • 문제풀이

트리의 지름

  • 트리의 지름은 두 노드 간 경로의 길이 중 최댓값이다.
  • 첫번째 방법, 임의의 노드를 루트로 지정한 다음 각 서브트리에 대해 따로따로 문제를 푼다
  • 두번째 방법, 깊이 우선 탐색을 두번 진행
  • TBD

LCA (lowest Common Acestor)

  • 최소 공통 조상 문제는 두 노드의 공통된 조상 중에서 가장 가까운 조상을 찾는 문제

필요 자료구조

  • ArrayList : 입력값 정점들의 정보
  • parent[] : 나의 부모 정점
  • depth[] : 나의 깊이
  • 루트노드(1)가 정해지면 각 노드의 Depth 를 기록할 수 있다 -> DFS 이용
  • 매 쿼리마다 부모 방향으로 거슬러 올라가기 위해 최악의 경우 O(N)의 시간 복잡도, 즉 모든 쿼리 처리 시간 복잡도 O(NM)
  • 백준 3584

static int solve(int a, int a_depth, int b, int b_depth){
        // 둘의 depth가 같아질 때까지 위로 올린다.
        if(a_depth > b_depth){
            while(a_depth != b_depth){
                a_depth--;
                a = parent[a];
            }
        }
        else if(a_depth < b_depth){
            while(a_depth != b_depth){
                b_depth--;
                b = parent[b];
            }
        }

        // 둘의 부모값이 같아질 때까지 위로 올린다
        while(a != b){
            a = parent[a];
            b = parent[b];
        }

        return a;
    }

Sparse table + LCA (성능 개선)

  • LCA 로직에서 부모를 바로위 하나씩 검색하지 않고 2^i 지수승으로 올라가면서 비교를 하면 속도가 좋아진다?
  • sparse table 확인 필요 click
  • Sparse Table 에서 점화식 암기: arr[i][j] = arr[ arr[i][j-1] ][j-1]
  • j 값은 총 log2(노드 수) 로 계산
  • 총 15칸 올라가야 한다면 (8칸 -> 4칸 -> 2칸 -> 1칸)
  • 메모리를 조금 더 사용하여 각 노드에 대하여 2^i 번 부모에 대한 정보를 기록 / 시간복잡도 O(MlogN)
  • 백준 11438 lca2
 //find_depth(1,1) 루트가 1번 노드, 깊이 1
	public static void find_depth (int from, int depth) {
		d[from] = depth;
		
		for (int i = 0; i < mat[from].size(); i++) {
			int to = mat[from].get(i);
			if (d[to] == 0) {
				parent[to][0] = from; // 기저 배열 생성
				find_depth(mat[from].get(i), depth+1);
			}
		}
	}

	public static void make_parent ( ) {
		for (int j = 1 ; j < k; j++) {  // j먼저
			for (int i = 1; i<= N; i++) {  // i번
				parent[i][j] = parent[ parent[i][j-1] ] [j-1];// 점화식 적용
			}
		}
	}

int find_lca(int a, int b) {
    if (depth[a] < depth[b]) { // a가 더 깊은 노드가 되게
        int temp = a;
        a = b;
        b = temp;
    }

    // a의 depth를 b에 맞춘다
    int diff = depth[a] - depth[b];
    int m = 0;

    // 예를 들어 13만큼 올라가야 한다면 이것을 이진수로 표현, 1101(2)이므로 
    // 1인 곳을 탐색할 때 마다 sparse 테이블의 높이 값을 증가 시키면 된다
    while (diff) {
        if (diff & 1) {
            a = parent[a][m];
        }
        m++;
        diff /= 2;
    }

    int lca;

    // a와 b가 같다면 그것이 lca
    if (a == b) {
        lca = a;
    } else {
        for (int k = 15; k >= 0; k--) {
            if (parent[a][k] != parent[b][k]) {
                a = parent[a][k];
                b = parent[b][k];
            }
        }
        lca = parent[a][0];
    }

    return lca;
}

출처 click

세그먼트 트리 (Segment Tree)

  • 다차원 세그먼트 트리
  • 세그먼트 트리 with lazy propagation
  • 세그먼트 트리 with dynamic
  • 세그먼트 트리 with Persistent segment tree
  • bottom up 구현
  • top down 구현
  • 백준 이론 설명 click

문제풀이 백준2042

세그먼트 트리 생성 수도코드

  • 트리의 크기를 구하는 원리 click

tree = new int [1 << (my_log(200_0000)+1)]; // 200_0000 리프노드의 최대갯수

static long init (int node, int start, int end) {
	if (start == end) {
		// leaf node
		return tree[node] = array[start];
	} else {
		// not leaf node
		return tree[node] = init (node*2, start, (start+end)/2) + init (node*2+1, (start+end)/2+1, end);	// init left child node and right child node 
	}
}

구간합 구하기 수도코드 left right 가 찾아야 하는 구간, start, end가 탐색 구간


static long sum (int node, int start, int end, int left, int right) {
	if (left > end || right < start) {
		return 0;
	} 
	if (left <= start && end <= right) {
		return tree[node];
	}
	return sum (node*2, start, (start+end)/2, left, right) + sum (node*2+1, (start+end)/2+1, end, left, right);		
}

세그먼트 트리 업데이트 수도코드


static void update (int node, int start, int end, int index, long diff) {
	if (index < start || index > end) 
		return;
	tree[node] += diff;	
	if (start != end) {
		update (node*2, start, (start+end)/2, index, diff);
		update (node*2+1, (start+end)/2+1, end, index, diff);
	}
}

  • k번째 수 찾기
    1. 전체 가질수 있는 수를 0으로 초기화하여 배열로 만듬
    2. 세그먼트 트리에 들어온 해당 숫자의 인덱스(리프)를 1 증가 시킴
    3. 구해야하는 k번째 수를 구간합을 이용해 구함
    4. 비교할때 왼쪽 자식과 비교한다, 왼쪽 자식보다 크면 그 구간을 포함하는 것, 왼쪽 구간합 값을 빼주고 오른쪽을 탐색하면 된다
    5. 우리가 구해야 하는것은 1~N 까지의 구간합이 K인데 그 숫자 N을 구하면 된다

public static int kth (int node, int start, int end, int k) {
	if (start == end) {
		return start; // 인덱스를 리턴
	} else {
		int mid = (start+end)/2;
		// 왼쪽 자식 노드보다 현재값이 크거나 같다면 우측 탐색해야함 
		if (k > tree[2*node]) {
                    // (k값은 왼쪽 구간합 + 오른쪽 구간합이므로) 오른쪽 구간을 탐색할때는 왼쪽 구간합을 빼줘야한다
		    return kth(2*node+1, mid+1, end, k-tree[node*2]); 
                }
		else // k<= tree[2*node] 
		{
        	     return kth(2*node, start, mid, k);
                }
	}
}

백준 12899 데이터구조

세그먼트 트리 (Bottom up 방식 + lazy propagation)

다차원 세그먼트 트리 (multidimensional segment tree)

click

세그먼트 트리 with lazy propagation

  • 일반 세그먼트와 달리 구간을 여러개 업데이트 해줘야 할때 적용
  • ex) 0-10 배열이 있을때 2-7 구간 1씩 증가, 3-10 구간 1씩 증가
    click

세그먼트 트리 with dynamic

click

세그먼트 트리 with Persistent segment tree

click

Fenwick Tree

백준 이론 설명 click

머지 소트 트리

click
reference

public ArrayListinit (int left, int right, int pos) {
    int (left == right) {
        tree[pos] = new ArrayList<>();
        tree[pos].add(arr[left]);
        return tree[pos];
    }
    int mid = (left + right) /2;
    ArrayList leftArr = init(left, mid, pos*2);
    ArrayList rightArr = init(mid+1, right, pos*2+1);
    return tree[pos] = merge(leftArr, rightArr);
}


public ArrayList merge(ArrayList leftArr, ArrayList rightArr) {
    ArrayList returnArr = new ArrayList<>();
    int i = 0;
    int j = 0;
    leftLen = leftArr.size();
    riightLen = rightArr.size();

    while(i < leftLen && j < rightLen) {
        if (leftArr.get(i) <= rightArr.get(i)) {
            returnArr.add(leftArr.get(i);
            i++;
        } else { 
            returnArr.add(rightArr.get(j);
            j++;
        }
    }
    while (i < leftLen) {
        returnArr.add(left.get(i);
        i++;
    }
    while (j < rightLen) {
        returnArr.add(right.get(j);
        j++;
    }
    return returnArr;
}

트리 dp

<개요> 그래프에선 어떤 정점 u에 대한 상태가 인접한 정점 v에 대한 상태로 정의되며 참조 투명하다면 dp를 적용할 수 있다.
좀 더 쉬운 말로, 정점u에 대한 상태가 인접 정점 v의 상태를 사용해서 점화식이 세워진다면, dp를 적용할 수 있다.
트리에선 좀 특수하게, 정점 u의 상태가 인접 정점 v의 상태나 루트의 상태로 정의 된다면 dp를 적용할 수 있다.
정점 u를 루트로 하는 서브트리에 대한 상태를 묻는 문제가 자주 나온다.
dp는 보통 선형으로 이루어진 공간에서 이루어졌지만, 트리는 비선형 구조입니다. 트리에서 dp를 하기 전에, 탐색 순서를 미리 정해주는 것이 일반적입니다.
탐색 순서를 정하는 것은 dfs를 돌면서 나오는 트리, 즉 dfs tree를 기준으로 합니다.
reference
reroot

  • 트리 구조(root)를 바꾸면서 백트래킹하여 정답 구하는 re-rooting
    click
    click
    click

  • 허프만 트리 click

⚠️ **GitHub.com Fallback** ⚠️