Algorithm
트리

List에서 Tree 연산

이진 트리

class Node :
  def __init__(self, value = 0, left = None, right = None) :
    self.value = value
    self.left = left
    self.right = right
 
class BinaryTree :
  def __init__(self) :
    self.root = None
 
bt = BinaryTree()
bt.root = Node(value=1)
bt.root.left = Node(value=2)
bt.root.right = Node(value=3)
bt.root.left.left = Node(value=4)
bt.root.left.right = Node(value=5)
bt.root.right.left = Node(value=6)
bt.root.right.right = Node(value=7)

일반적인 트리

class Node:
    def __init__(self, data):
        self.data = data
        self.children = []
 
    def add_child(self, node):
        self.children.append(node)
 
class Tree:
    def __init__(self, root):
        self.root = Node(root)
 
    def print_tree(self):
        self._print_tree(self.root)
 
    def _print_tree(self, node, level=0):
        print('  ' * level + str(node.data))
        for child in node.children:
            self._print_tree(child, level+1)
 
tree = Tree('root')
tree.root.add_child(Node('child_1'))
tree.root.add_child(Node('child_2'))
tree.root.children[0].add_child(Node('child_1_1'))
tree.root.children[0].add_child(Node('child_1_2'))
tree.print_tree()

BFS (Breadth-First Search)

너비 우선 탐색이라고 불리며, 상세 동작을 봤을 때 상단 > 자식 노드 > 자식노드의 자식노드 이런식으로 탐색하는 방식이다.

def bfs(root: Node) -> list :
  visited = []
  if root is None :
    return []
 
  q = deque()
  q.append(root)
 
  while q :
    cur_node = q.popleft()
    visited.append(cur_node.value)
 
    if cur_node.left :
      q.append(cur_node.left)
 
    if cur_node.right :
      q.append(cur_node.right)
 
  return visited
 
print(bfs(bt.root))

DFS (Depth-First Search)

깊이 우선 탐색, 특정 방향의 가장 깊은 곳 > 얕은 곳으로 짚고 넘오는 형식의 탐색 방법

def dfs(root: Node) :
  if root is None :
    return
 
  dfs(root.left)
  dfs(root.right)

전위순회(preorder)

자식 노드에 방문하기 전에 자기 자신부터 방문을 한다.

visited = []
def dfs(node: Node) :
  if node is None :
    return
 
  visited.append(node)
  dfs(node.left)
  dfs(node.right)

중위순회(inorder)

왼쪽에 있는 모든 노드를 확인하고 나서 자기 자신을 확인하는 방식이다.

visited = []
def dfs(node: Node) :
  if node is None :
    return
 
  dfs(node.left)
  visited.append(node)
  dfs(node.right)

후위순회(postorder)

자식을 다 방문한 다음에 자기 자신을 방문하는 방식이다.

visited = []
def dfs(node: Node) :
  if node is None :
    return
 
  dfs(node.left)
  dfs(node.right)
  visited.append(node)

세그먼트 트리(Segment Tree)

세그먼트 트리는 부모가 왼쪽 자식과 오른쪽 자식의 합을 가지는 구조의 완전 이진 트리(complete tree) 이다.

전체 코드

  • index는 1부터 시작된다.
  • init, sum, update 세 부분으로 나눠서 생각하면 좋을 것 같다.
class SegmentTree {
    long[] tree; // 각 원소가 담길 트리
    int treeSize; // 트리 크기
    long[] arr; // 누적합을 구할 기존 배열
 
    public SegmentTree(long[] arr) {
        this.arr = arr;
        // 트리 높이 구하기
        int h = (int) Math.ceil(Math.log(arr.length) / Math.log(2));
        this.treeSize = (int) Math.pow(2, h + 1);
        // 배열 생성
        tree = new long[treeSize];
    }
 
    // arr = 원소배열, node = 현재노드, start = 현재구간 배열 시작, end = 현재구간 배열 끝
    public long init(int node, int start, int end) {
        // 배열의 시작과 끝이 같다면 leaf 노드이므로
        // 원소 배열 값 그대로 담기
        if (start == end) {
            return tree[node] = arr[start];
        }
 
        // leaf 노드가 아니면, 자식노드 합 담기
        return tree[node] =
            init(node * 2, start, (start + end) / 2)
                + init(node * 2 + 1, (start + end) / 2 + 1, end);
    }
 
    // node: 현재노드 idx, start: 배열의 시작, end:배열의 끝
    // idx: 변경된 데이터의 idx, diff: 원래 데이터 값과 변경 데이터값의 차이
    public void update(int node, int start, int end, int idx, long diff) {
        // 만약 변경할 index 값이 범위 바깥이면 확인 불필요
        if (idx < start || end < idx) return;
 
        // 차를 저장
        tree[node] += diff;
 
        // 리프노드가 아니면 아래 자식들도 확인
        if (start != end) {
            update(node * 2, start, (start + end) / 2, idx, diff);
            update(node * 2 + 1, (start + end) / 2 + 1, end, idx, diff);
        }
    }
 
    // node: 현재 노드, start : 배열의 시작, end : 배열의 끝
    // left: 원하는 누적합의 시작, right: 원하는 누적합의 끝
    public 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);
    }
}

사용예시

        // 수 저장 배열
        long[] arr = new long[n+1];
        for(int i = 1; i <= n; i++){
            arr[i] = Long.parseLong(br.readLine());
        }
 
        SegmentTree stree = new SegmentTree(n);
 
        stree.init(arr,1,1,n);
 
        for(int i = 0; i < m+k; i++){
            st = new StringTokenizer(br.readLine());
 
            // 명령어
            int cmd = Integer.parseInt(st.nextToken());
            int a = Integer.parseInt(st.nextToken());
            // 원소의 범위는 2^63이므로 롱타입으로 받아야한다.
            long b = Long.parseLong(st.nextToken());
 
            // 데이터 변경 명령어
            if(cmd == 1){
                stree.update(1,1,n,a,b-arr[a]);
                arr[a] = b;
            // 구간합 명령어
            }else{
                bw.write(stree.sum(1,1,n,a,(int)b) +"\n");
            }
        }

Reference