전체 보기
🚀

트리, 트라이

작성일자
2024/02/06
태그
SUB PAGE
프로젝트
30일 알고리즘
책 종류
1 more property

트리

정의

무방향이면서 사이클이 없는 연결 그래프
노드와 에지로 연결된 그래프의 특수한 형태

특징

임의의 에지를 추가하면 사이클이 생김
임의의 두 에지를 제거하면 연결 그래프가 아니게 됨
임의의 노드를 루트 노드로 만들 수 있음
트리가 마치 구슬과 실로 연결되어 있는 모양이라고 생각할 때, 아무 구슬 하나를 잡고 위로 올린 상황
루트가 정해진 상태에선 부모를 가지지 않는 1개의 루트 노드가 존재하고, 루트 노드를 제외한 노드들은 단 1개의 부모 노드를 가짐
임의의 두 노드를 이어주는 경로가 유일함. 이때 경로는 정점이 중복해서 나오지 않는 경로를 의미함
V개의 노드를 가지고 V-1개의 에지를 가짐
노드가 1개이고 에지가 없는 그래프도 트리임
트리의 부분 트리 역시 트리의 모든 특징을 따름

용어

에지: 노드와 노드의 연결 관계 나타내는 선
노드: 데이터의 index와 value를 표현하느 요소
루트 노드: 트리에서 가장 상위에 존재하는 노드
부모 노드: 두 노드 사이의 관계에서 상위 노드에 해당하는 노드
자식 노드: 두 노드 사이의 관계에서 하위 노드에 해당하는 노드
리프 노드: 트리에서 가장 하위에 존재하는 노드 (자식 노드가 없는 노드)
서브 트리: 전체 트리에 속한 작은 트리 (부분 트리)
노드의 차수: 연결된 자식의 수→ A의 차수: 2
노드의 크기: 자신을 포함한 자손의 수 (자식 x) → A의 크기: 5
노드의 깊이: 루트와의 거리 → B의 깊이: 1
트리의 차수: 트리의 최대 차수 → 차수: 2
트리의 깊이: 가장 깊숙이 있는 노드의 깊이. 높이라고도 부름 → 높이: 2

코딩테스트에서 등장하는 트리 문제 유형
1.
그래프로 푸는 트리
dfs, bfs : 트리를 인접리스트로 표현해서 품
2.
트리만을 위한 문제
이진트리, 세그먼트 트리(인덱스 트리), LCA(최소공통조상) : 트리를 1차원 배열로 표현해서 품
부모 노드로 이동 : index / 2
자식 노드로 이동 : index * 2 ( + 1)

과정 (BFS/DFS)

특징) 트리의 어떤 노드와 연결된 노드들은 1개만 부모이고 나머진 전부 자식이란 성질 이용해서 visited 배열 대신 부모 배열 이용해 처리 가능
BFS
1.
초기화하기
트리(그래프)를 인접리스트로 표현하기
부모 배열(부모 정보 저장) 만들기 (전부 0으로 초기화 → 루트의 부모도 0)
임의의 잡은 시작점을 루트로 정해서 큐에 넣기
2.
큐에서 빼온 노드(현재 노드)에 대해 출력하기
3.
현재 노드의 인접 노드들 중 부모 노드를 제외한 나머지 노드들을 큐에 넣고, 해당 노드들의 부모 정보를 현재 노드로 저장하기
4.
큐가 빌 때까지 단계 2부터 다시 반복
DFS (BFS에서 큐만 스택으로 바뀜)
1.
초기화하기
트리(그래프)를 인접리스트로 표현하기
부모 배열(부모 정보 저장) 만들기 (전부 0으로 초기화 → 루트의 부모도 0)
임의로 잡은 시작점을 루트로 정해서 스택에 넣기
2.
스택에서 빼온 노드(현재 노드)에 대해 출력하기
3.
현재 노드의 인접 노드들 중 부모 노드를 제외한 나머지 노드들을 스택에 넣고, 해당 노드들의 부모 정보를 현재 노드로 갱신하기
4.
스택이 빌 때까지 단계 4부터 다시 반복

예시 (BFS)

코드 (BFS/DFS) (with 백준 11725)

private static LinkedList<Integer>[] tree; private static int[] parent; private static int[] solution(int n, int[][] numbers) { // 노드 수는 n, 에지 수는 n-1 // 1. 트리를 인접 리스트로 표현하고, 부모배열 생성(0으로 초기화)하기 tree = new LinkedList[n + 1]; for (int i = 1; i <= n; i++) { tree[i] = new LinkedList<>(); } for (int i = 1; i < n; i++) { int start = numbers[i][0]; int end = numbers[i][1]; tree[start].add(end); tree[end].add(start); } parent = new int[n + 1]; // 0은 비우고, 노드 개수만큼 생성 // 2. bfs 혹은 dfs (시작점은 1) // bfs(1); // dfs(1); dfsWithRecursion(1); return parent; } private static void bfs(int start) { // 1. 큐에 시작점(루트) 넣기 Queue<Integer> queue = new LinkedList<>(); queue.add(start); // 2. bfs (큐가 빌 때까지 반복) while (!queue.isEmpty()) { // 2-1. 큐에서 뺀 노드(현재 노드)를 출력하기 int current = queue.remove(); System.out.println(current); // 2-2. 현재 노드의 인접 노드들 중 부모 노드 제외하고 나머지를 큐에 넣고, 해당 노드들의 부모로 현재 노드를 저장하기 for (int next : tree[current]) { if (next == parent[current]) { // current의 인접 노드 중 current의 부모 노드는 제외 continue; } parent[next] = current; // current의 자식 노드의 부모로 current를 저장 queue.add(next); } } } private static void dfs(int start) { // 1. 스택에 시작점(루트) 넣기 Stack<Integer> stack = new Stack<>(); stack.push(start); // 2. dfs (스택이 빌 때까지 반복) while (!stack.isEmpty()) { // 2-1.스택에서 뺀 노드(현재 노드)를 출력하기 int current = stack.pop(); System.out.println(current); // 2-2. 현재 노드의 인접 노드들 중 부모 노드 제외하고 나머지를 스택에 넣고, 해당 노드들의 부모로 현재 노드를 저장하기 for (int next : tree[current]) { if (next == parent[current]) { // current의 인접 노드 중 current의 부모 노드는 제외 continue; } parent[next] = current; // current의 자식 노드의 부모로 current를 저장 stack.push(next); } } } private static void dfsWithRecursion(int current) { // 2-1.현재 노드를 출력하기 System.out.println(current); // 2-2. 현재 노드의 인접 노드들 중 부모 노드 제외하고 나머지 노드들의 부모로 현재 노드를 저장하고, 해당 노드들 탐색하기 for (int next : tree[current]) { if (next == parent[current]) { continue; } parent[next] = current; dfsWithRecursion(next); // 2. dfs (모든 노드의 자식 노드 다 탐색할 때까지 반복) } }
Java
복사
현재 노드의 인접 노드들 중 부모 노드 제외한 나머지 노드들 == 현재 노드의 자식 노드들

트라이

정의

문자열을 효율적으로 처리하기 위한 트리 자료구조

특징

시간복잡도: 단어 S를 삽입/탐색/삭제 시 O(|S|)
공간복잡도: 문자열을 그냥 배열에 저장하는 것보다 최대 (4*글자의 종류)배 만큼 더 사용
트라이 삭제하더라도 이전에 삽입한 정점들은 계속 메모리에 남아있게 되어 메모리 측면에서 비효율적이고 삭제가 계속 발생하는 환경에선 트라이 적합하지 않음

과정

삽입(Insert)
루트부터 바라보고 원하는 단어의 첫 알파벳부터 순서대로 삽입함
현재 바라보는 노드의 자식에 알파벳이 없으면 해당 노드의 자식으로 추가 후, 새로 추가한 노드를 바라봄
원하는 단어의 모든 알파벳 다 넣으면 마지막 글자엔 별도의 표시 해둠
검색(Fetch)
루트부터 바라보고 원하는 단어의 첫 알파벳부터 검색함
원하는 단어의 마지막 알파벳까지 다 찾으면 마지막 노드에 마지막 문자라는 표시가 있는지 확인함
제거(Erase)
루트부터 바라보고 원하는 단어의 첫 알파벳부터 검색함
원하는 단어의 마지막 단어까지 다 찾으면 마지막 노드에 있는 마지막 문자라는 표시를 제거함
노드 자체를 제거하지 않음
참고