Search

Binary Search Tree(이진 탐색 트리), UnionFind(유니온 파인드)

이진 탐색 트리(Binary Search Tree)

이진 탐색 트리란?
이진 탐색의 속성이 이진 트리에 적용된 특별한 형태의 이진 트리

이진 탐색이란?

이진 탐색 알고리즘이란 정렬된 데이터 중에서 특정한 값을 찾기 위한 탐색 알고리즘 중 하나다.
이진 탐색 알고리즘은 오름차순으로 정렬된 정수의 배열을 같은 크기의 두 부분 배열로 나눈 후, 두 부분 중 탐색이 필요한 부분에서만 탐색하도록 탐색 범위를 제한하여 원하는 값을 찾는 알고리즘입니다.
1.
배열의 중간에 내가 찾고자 하는 값이 있는지 확인한다.
2.
중간값이 내가 찾고자 하는 값이 아닐 경우,
오름차순으로 정렬된 배열에서 중간값보다 큰 값인지 작은 값인지 판단한다.
3.
찾고자 하는 값이 중간값보다 작은 값일 경우,
배열의 맨 앞부터 중간값 전까지의 범위를 탐색 범위로 잡고 탐색을 반복 수행한다.
4.
찾고자 하는 값이 중간값보다 큰 값일 경우,
배열의 중간값의 다음 값부터 맨 마지막까지를 탐색 범위로 잡고 탐색을 반복 수행한다.
이진 탐색 트리는 이러한 이진 탐색의 알고리즘이 이진 트리에 적용된 형태로 트리의 루트노드는 이진 탐색에서 리스트의 중간값이 된다.
루트노드의 왼편 서브 트리의 값들은 이진 탐색 알고리즘에 기반하여 모두 루트노드의 값보다 작은 값들이 자리 잡고 있고 루트노드의 오른편 서브 트리의 값들은 루트노드의 값보다 큰 값들이 자리 잡고 있어야 한다.
Left < root < Right
정리하자면 다음과 같다.
각 노드에 중복되지 않는 키(Key)가 있다.루트노드의 왼쪽 서브 트리는 해당 노드의 키보다 작은 키를 갖는 노드들로 이루어져 있다.루트노드의 오른쪽 서브 트리는 해당 노드의 키보다 큰 키를 갖는 노드들로 이루어져 있다.좌우 서브 트리도 모두 이진 탐색 트리여야 한다.
즉 이진 탐색 트리(Binary Search Tree)는 모든 왼쪽 자식의 값이 루트나 부모보다 작고, 모든 오른쪽 자식의 값이 루트나 부모보다 큰 값을 가지는 특징이 있다.

이진탐색트리 insert(삽입) 코드 - while문 활용

#include <iostream> #include <vector> #include <list> #include <string> #include <algorithm> #include <map> int bst[256] = { 0, }; void insert(int data) { int idx = 1; while (1) { if (bst[idx] == 0) { bst[idx] = data; break; } else if (bst[idx] > data) { idx = idx * 2; } else { idx = idx * 2 + 1; } } } int main() { insert(10); insert(5); insert(15); //insert(8); //insert(3); //insert(7); //insert(12); return 0; }
C++
복사

이진탐색트리 insert(삽입) 코드 - 재귀함수 활용

#include <iostream> #include <vector> #include <list> #include <string> #include <algorithm> #include <map> int bst[256] = { 0, }; void insertRescursive(int data, int now) { if (bst[now] == 0) { bst[now] = data; return; } if (bst[now] > data) { insertRescursive(data, now * 2); } else { insertRescursive(data, now * 2 + 1); } } nt main() { insertRescursive(10, 1); insertRescursive(5, 1); insertRescursive(15, 1); insertRescursive(8, 1); return 0; }
C++
복사

이진탐색트리 Search(탐색) 코드 - while문, 재귀함수 활

#include <iostream> #include <vector> #include <list> #include <string> #include <algorithm> #include <map> int bst[256] = { 0, }; void insert(int data) { int idx = 1; while (1) { if (bst[idx] == 0) { bst[idx] = data; break; } else if (bst[idx] > data) { idx = idx * 2; } else { idx = idx * 2 + 1; } } } void insertRescursive(int data, int now) { if (bst[now] == 0) { bst[now] = data; return; } if (bst[now] > data) { insertRescursive(data, now * 2); } else { insertRescursive(data, now * 2 + 1); } } void search(int data) { int idx = 1; while (1) { if (bst[idx] == 0) { printf("Not Found\n"); break; } if (bst[idx] == data) { printf("Found\n"); break; } if (bst[idx] > data) { idx = idx * 2; } else { idx = idx * 2 + 1; } } } void searchRecursive(int data, int now) { if (bst[now] == 0) { printf("Not Found\n"); return; } if (bst[now] == data) { printf("Found\n"); return; } if (bst[now] > data) { searchRecursive(data, now * 2); } else { searchRecursive(data, now * 2 + 1); } } int main() { // Binary Search Tree // 데이터를 탐색하는데 있어서 효율성 높다. // 3가지 동작을 기본으로 둔다. // 데이터를 저장하는 Insert // 데이터를 찾는 Search // 데이터를 삭제하는 Delete // insert, serach 위조루 보겠다. // 사용하는 제일 큰 이유는 데이터를 더 빨리 찾기위해 사용하는 자료구조이다. //insert(10); //insert(5); //insert(15); //insert(8); //insert(3); //insert(7); //insert(12); insertRescursive(3, 1); insertRescursive(5, 1); insertRescursive(1, 1); insertRescursive(2, 1); insertRescursive(4, 1); insertRescursive(7, 1); return 0; }
C++
복사
유니온 파인드 알고리즘: 상호 배타적 집합, Disjoin-set(서로소 집합) 이라고도 부른다. 여러 노드가 존재할 때 어떤 두 개의 노드를 같은 집합으로 묶어 주고, 어떤 두 노드가 같은 집합에 있는지 확인하는 알고리즘이다.
왜 이름이 유니온 파인드인가?
유니온 파인드 알고리즘은 두가지의 연산을 진행한다. 이 연산 과정을 Union, Find라고 부른다.
Union: 서로 다른 두 개의 집합을 하나의 집합으로 병합하는 연산을 말한다. 이 자료구조에서는 상호 배타적 집합만을 다루므로 Union 연산은 합집한 연산과 같다.
Find: 하나읜 원소가 어떤 집합에 속해있는지를 판단한다.
크루스칼 알고리즘과 프림 알고리즘을 알기 위해선 유니온 파인드 알고리즘을 알아야 한다.
아래 그림은 각 데이터들을 총 3그룹으로 묶었다.

배열을 이용한 UnionFind Insert 구현해보기

#include <iostream> #include <vector> #include <list> #include <string> #include <algorithm> #include <map> int name[256] = {}; int n = 0; int group[256] = {}; int gCnt = 0; void insert(char a, char b) { if (group[a] == 0) name[n++] = a; if (group[b] == 0) name[n++] = b; // a는 그룹에 있는데, b는 그룹에 없다. if (group[a] != 0 && group[b] == 0) group[b] = group[a]; // a그룹은 없고, b 그룹은 있다. else if (group[a] == 0 && group[b] != 0) group[a] = group[b]; //둘다 그룹이 없는경우는 새로운그룹을 만든다. else if (group[a] == 0 && group[b] == 0) { gCnt++; group[a] = gCnt; group[b] = gCnt; } else { // 둘다 그룹이 있는 경우, 그룹을 a로 통합시킨다. int g = group[b]; for (int i = 0; i < n; i++) { if (group[name[i]] == g) group[name[i]] = group[a]; } } } int main() { // 그룹으로 관리하는것은 분류하거나 검색하는데 있어서 유용하다. // 그룹끼리 통합을 시키면 큰데이터를 연산하지 않고 작은 데이터로 연산할 수 있다. // 그룹 1 insert('A', 'B'); insert('A', 'C'); // 그룹2 insert('E', 'Q'); insert('E', 'F'); // 통합 insert('F', 'A'); return 0; }
C++
복사

재귀함수를 이용하여 UnionFind Insert구현해보기

#include <iostream> #include <vector> #include <list> #include <string> #include <algorithm> #include <map> char parnet[1000001]; char getParent(char x) { if (parnet[x] == 0) return x; int ret = getParent(parnet[x]); parnet[x] = ret; return ret; } void insert(char ch1, char ch2) { int a = getParent(ch1); int b = getParent(ch2); if (a != b) parnet[b] = a; } int main() { // 그룹으로 관리하는것은 분류하거나 검색하는데 있어서 유용하다. // 그룹끼리 통합을 시키면 큰데이터를 연산하지 않고 작은 데이터로 연산할 수 있다. insert('A', 'G'); insert('H', 'C'); insert('A', 'H'); insert('F', 'D'); insert('A', 'F'); return 0; }
C++
복사
문제 1번
노드 4개의 연결상태를 입력 받으세요.
Cycle이 있는 그래프인지 아닌지 판별하면 됩니다.
Cycle을 확인 후 발견 또는 미발견을 출력 해주세요.
만약, 다음과 같은 그래프를 입력 받았다면 Cycle이 있으므로 "발견"을 출력하면 됩니다.
[힌트]
union-find로 같은 그룹인지 아닌지를 알아내어
Cycle의 여부를 판단할 수 있습니다.

입력 예제

4
A C
A B
B D
C B

출력 결과

발견
문제 2번
원시족을 나타내는 각 문자들이 아래와 같이 부족을 형성하고 있습니다.
아래 그림은 부족의 초기 상태를 나타냅니다.
아래와 같이 같이 그룹이 지어진 상태에서 시작합니다.
위 그림과 같은 초기 상태에서 시작합니다.
따라서, 입력을 받기 전 남아있는 그룹의 수는 총 4개 입니다.
이제 그룹으로 합쳐질 n개의 문자 쌍을 입력 받아주세요.
n개의 문자 쌍을 모두 Groupping을 했을 때, 최종적으로 몇 개의 그룹이 남아있는지 출력 하세요.
[예시]
3
G I   // G와 I가 속한 그룹을 합침 (현재 그룹수 : 4 --> 3개)
D E  // D와 E가 속한 그룹을 합침, 하지만 이미 같은 그룹이라 그룹의 수는 변동 없음 (현재 그룹 수 : 3개)
H J  // D와 E가 속한 그룹을 합침, 하지만 이미 같은 그룹이라 그룹의 수는 변동 없음 (현재 그룹 수 : 3개)
출력결과 : 3개

입력 예제

3
G I
D E
H J

출력 결과

3개
문제 3번
숫자 5개를 5칸짜리 target 배열에 입력 받아주세요.
그리고 다음 Binary Search Tree를 1차원 배열에 하드코딩 해 주세요
재귀호출을 이용해서
target 숫자들이 각각 존재하는지 찾아서 출력 해 주세요
(찾는 find함수를 while로 구현하시면 됩니다)

입력 예제

8 16 9 25 30

출력 결과

8:3회만에찾음
16:4회만에찾음
9:없음
25:3회만에찾음
30:없음
문제 4번
BST 자료구조에 값을 넣을 n개의 숫자를 입력받으세요.
이제 1 ~ 6 까지 각 숫자들이 BST에 존재하는지 출력하면 됩니다.
(insert함수와 find함수는 모두 재귀호출로 구현 해 주세요.)
[참고]
BST는 Binary Search Tree 자료구조입니다.
Binary Search 알고리즘와는 다릅니다.
(인터뷰 볼때 혼용하지 마세요, Binary Search는 다음 시간에 배우게 됩니다)

입력 예제

4
1 3 4 7

출력 결과

1:O
2:X
3:O
4:O
5:X
6:X
문제 5번
노드의 개수와 인접행렬을 입력받고, cycle이 존재하는지 출력하세요.
[예시 1]
위 그래프는 cycle이 존재합니다. (C-D-E)
출력결과 : cycle 발견
[예시 2]
위 그래프는 cycle이 없습니다.
출력결과 : 미발견
[힌트]
간선을 발견할때마다, Grouping을 하면 됩니다.
만약 같은 그룹이 발견되면 Cycle 입니다.

입력 예제

5
0 0 0 1 0
0 0 1 0 0
0 0 0 1 1
1 0 1 0 1
0 0 1 1 0

출력 결과

cycle 발견
문제 6번
Minco 스테이크 레스토랑에서는 매일 고기 재료를 납품 받습니다.
1 ~ K번의 품목 번호를 가진 고기들은 A ~ F까지 각각의 등급을 가지고 있습니다.
직원들은 어떤 품목들이 들어왔는지 품목리스트에 적어 둡니다.
고기의 납품 이력이 적혀있는 품목 리스트의 내용들을 적절한 자료구조에 저장 해 주세요.
그리고 각 번호의 고기들이 각각 몇 등급인지 순차적으로 출력 해주는 프로그램을 제작 해 주세요.
예를들어, 품목 리스트에 다음과 같이 적혀있다고 가정합니다.
"3 2" // 3 번과 2 번 고기는 같은 등급입니다.
"B 4" // 4 번 고기는 B 등급 입니다.
"1 A" // 1 번 고기는 A 등급 입니다.
"3 4" // 3 번과 4 번 고기는 같은 등급 입니다.
이때 2, 3, 4번 고기 등급은 B 입니다.
[입력]
품목 리스트의 개수 N, 레스토랑에서 받은 고기 개수 K를 먼저 입력 받습니다.
그리고 N개의 품목 리스트를 입력 받습니다.
품목리스트는 숫자 2 개 or 1개 문자 + 1개 숫자로 구성되어 있습니다.
1 <= N <= 20
1 <= K <= 9
[출력]
1번부터 K번까지 각각 몇 등급인지 순서대로 출력하면 됩니다.
품목리스트를 확인했을 때, 무슨 등급인지 알 수 없는 입력데이터는 주어지지 않습니다.

입력 예제

6 4
3 2
B 4
1 A
3 4
3 B
A 1

출력 결과

ABBB
문제 7번
춘추 전국 시대에는 국가끼리 동맹을 맺고, 서로 침략하고, 전쟁을 일으키곤 했습니다.
이 시대에는 N개의 국가가 존재하며, 각 국가들은 인구수를 갖고 있습니다.
아래 그림은 총 7개의 나라를 나타낸 지도이며,
D와 B는 동맹을 맺고, A와 C와 F가 동맹을 맺었습니다.
이러한 상황에서, D와 F가 전쟁을 일으켰습니다.
만약 A+C+F 동맹의 총 인구수가 D+B 동맹의 총 인구수보다 더 크다면,
A+C+F동맹의 승리로 D 국가와 B 국가는 멸망하고 맙니다.
먼저 각 국가의 수와 각 국가의 인구수를 입력 받습니다.
그리고 동맹을 맺고 (alliance 명령어), 전쟁을 일으키는 (war 명령어) 상황을 입력받고,
살아남은 국가의 수를 출력 해 주세요.
[예시]
7   // 7개의 국가(N)
10 20 30 40 50 60 70   // 각 국가의 인구수
3   // 3개의 상황
alliance A C   // A와 C 동맹
alliance F C   // F와 C 동맹
alliance D B   // D와 B 동맹
alliance A F   // A와 F 동맹 but, 이미 동맹이 되어있으므로 무시
war D F   // D 동맹국들과 F 동맹국들의 전쟁
A + C + F 동맹국의 인구수가 더 많으므로, D와 B 국가는 멸망합니다.
따라서 총 5 개의 국가가 살아 남았으므로 출력결과는 5 입니다.
[세부조건]
1. 같은 동맹끼리 전쟁을 일으키지 않습니다.
2. 동맹을 파기하는 일은 없습니다.

입력 예제

7
10 20 30 40 50 60 70
5
alliance A C
alliance F C
alliance D B
alliance A F
war D F

출력 결과

5
문제 8번
5개의 숫자를 Binary Search Tree 자료구조에 저장 해 주세요.
작거나 같은 숫자는 왼쪽, 큰 숫자는 오른쪽에 저장하는 BST에서
DFS를 이용하면 쉽게 숫자를 정렬하여 출력할 수 있습니다.
BST를 자료구조를 만들고,
5개의 숫자를 입력받아 BST에 순서대로 insert 해 주세요.
만들어진 이진트리를 탐색하여, 정렬 결과를 출력해 주세요.

입력 예제

5 7 2 3 1

출력 결과

1 2 3 5 7