Search

최소/최대 힙, 이진 탐색

힙(heap)이란?완전 이진 트리의 일종인 자료구조 - 완전 이진 트리: 마지막을 제외한 모든 노드의 자식들이 꽉 채워진 이진 트리 우선순위 큐를 위하여 만들어진 자료구조여러 개의 값들 중에서 최댓값/최솟값을 빠르게 찾아내도록 만들어짐 최대 힙(max heap): (부모 노드의 키 값 >= 자식 노드의 키 값)을 만족하는 완전 이진 트리 최소 힙(min heap): (부모 노드의 키 값 <= 자식 노드의 키 값)을 만족하는 완전 이진 트리 느슨한 정렬 상태(반정렬 상태) 유지이진 탐색 트리와 달리 중복 값 허용
배열로 힙 구현하기
배열로 힙 구현하기힙은 완전 이진 트리이기 때문에 빈 공간이 없어 배열로 구현하기에 용이하다. 구현을 쉽게 하기 위해 배열의 인덱스를 1부터 사용하면, 루트 노드를 1번으로 저장하게 된다. 이후 다른 노드들을 다음 그림과 같은 순서로 저장한다.
Plain Text
복사
힙 재구조화(heapify)힙에서 원소의 삽입이나 삭제가 일어나면 최대 힙의 조건이 깨질 수 있다. 이 경우 다시 최대 힙의 조건을 만족하도록 노드의 위치를 바꾸는 것을재구조화(heapify)라고 한다.
삽입과 삭제의 경우 연산 자체는 O(1)의 시간 복잡도로 작동하지만, heapify의 과정이 O(logN)이기 때문에 최종적으로O(logN)의 시간 복잡도를 가지게 된다.
<최대 힙 삽입 과정> 원소를 가장 말단 노드에 삽입한 다음, 가장 말단 노드부터 부모 노드와 값을 비교하면서 최대 힙 조건을 만족하는지 확인한다.
최악의 경우 가장 말단에 삽입한 원소가 루트 노드까지 올라가게 되고, 이때의 비교 횟수는 트리의 높이만큼이다. 따라서 삽입의 경우 heapify의 시간 복잡도는O(logN)이 된다.
<최대 힙 삭제 과정> 가장 큰 원소인 루트 노드를 삭제하고 나면, 가장 말단의 원소를 루트로 이동시킨다. 이후 루트 노드부터 자식 노드와 비교하여 최대 힙 조건을 만족하는지 확인한다.
삽입 과정과 유사하게, 최악의 경우 루트 노드에서 말단 노드까지 내려오게 되므로 비교 횟수는 트리의 높이만큼이다. 따라서 삭제 과정에서 heapify의 시간 복잡도는O(logN)이 된다.
힙 만들기(build heap) build heap은 힙이 아닌 배열을 힙으로 만드는 과정이다. 이때 heapify를 N번 진행하게 되므로 시간 복잡도는 O(NlogN)이다.    <build heap 과정> 가장 말단의 오른쪽에 있는 노드의 부모 노드부터 위에서 아래로 heapify를 진행한다.
힙 삽입삭제 예제
#include <iostream> #include <iostream> #include <queue> int heap[256] = {}; int hn = 0; void push(int value) { heap[++hn] = value; int now = hn; int parent = 0; while (true) { // 부모의 index = 자식의 나누기 2 parent = now / 2; // now가 root이면 부모가 없기 때문에 더이상 swap할필요가 없다. if (now == 1) break; // now가 부모보다 깊거나 클 경우 더이상 swap할필요가 없다. if (heap[parent] <= heap[now]) break; std::swap(heap[parent], heap[now]); now = parent; } } int pop() { int backup = heap[1]; heap[1] = heap[hn]; heap[hn--] = 0; int now = 1; int son = 0; while (true) { // 왼쪽 자식 son = now * 2; //왼쪽자식과 오른쪽 자식중에 더 작은 값을 선택 (둘째가 없으면 선택하지 않음) if (son + 1 <= hn && heap[son] > heap[son+1]) { son++; } // 자식이 없거나, 자식이 부모다 크거나 같은값을 가지고 있으면 더이상 swap할필요가 없다. if (son > hn || heap[son] >= heap[now]) break; std::swap(heap[son], heap[now]); now = son; } return backup; } int main() { //Heap 자료구조 //std::priority_queue<int> pq; push(3); push(5); push(2); push(4); push(1); push(6); std::cout << pop() << std::endl; std::cout << pop() << std::endl; std::cout << pop() << std::endl; std::cout << pop() << std::endl; std::cout << pop() << std::endl; std::cout << pop() << std::endl; return 0; }
JavaScript
복사

이진탐색이란?

이진 탐색이란 정렬돼 있는 데이터에서 특정한 값을 찾아내는 알고리즘이다.
이진탐색은 이분탐색이라고도 불린다. 이진탐색은 탐색 범위를 반으로 나누어 찾는 값을 포함하는 범위를 좁혀가는 방식으로 동작한다. 주로 배열의 인덱스를 기준으로 배열 내의 값을 탐색하는데 사용되지만, 리스트, 트리 등에서도 사용할 수 있다.
하지만 모든 리스트, 트리에서 사용할 수 있는 것이 아니라 반드시 원소들이 일정한 순서(예를 들면 오름차순)로 정렬된 구조에서만 사용할 수 있기 때문에 정렬된 리스트나 트리에서 사용이 가능하다.
이진탐색의 시간 복잡도는 O(log n)으로, 배열의 크기에 비례하여 실행 시간이 빨라진다.
따라서 대용량 데이터에서 특정 값의 위치를 찾는 데 용이하다.

이진탐색의 동작 방식

이진 탐색의 동작 방식은 우리가 생각하는 중간점 탐색소거 방식과 크게 다르지 않다.

배열에서의 동작방식

1.
정렬된 배열에서 중간 인덱스(mid)를 찾는다.
2.
찾으려는 값(target)과 중간 값(mid_val)을 비교한다.
3.
target이 mid_val보다 작으면 mid를 기준으로 왼쪽 부분 배열을 탐색한다.target이 mid_val보다 크면 mid를 기준으로 오른쪽 부분 배열을 탐색한다.
4.
탐색 범위를 반으로 줄인다.
5.
탐색 범위가 더 이상 없을 때까지 위 과정을 반복한다.
예제 코드
#include <iostream> #include <iostream> #include <queue> //binary search int vect[10] = { 1, 2, 3, 4, 5, 6, 7, 9 }; int target = 7; void runrecursive(int start, int end) { int mid = (start + end) / 2; if (vect[mid] == target) { std::cout << "found" << std::endl; return; } else if (vect[mid] < target) { runrecursive(mid + 1, end); } else { runrecursive(start, mid - 1); } } void run() { while (true) { int start = 0; int end = 7; int mid = (start + end) / 2; bool flag = false; while (start <= end) { mid = (start + end) / 2; if (vect[mid] == target) { flag = true; std::cout << "found" << std::endl; break; } else if (vect[mid] < target) { start = mid + 1; } else { end = mid - 1; } } if (flag) { break; } if (start > end) { std::cout << "not found" << std::endl; } } } int main() { run(); //runrecursive(0, 7); return 0; }
JavaScript
복사

연습문제

문제 1번
정렬 되어있는 데이터를 검색할 때, for문으로 찾는 것보다 Binary Search로 더 빨리 원하는 값을 찾을 수 있습니다.
(주의: Binary Search와 Binary Search Tree를 헷갈리지마세요!)
위 배열을 하드코딩 해 주세요.
숫자 하나를 입력받고, 배열 안에 존재하는 숫자인지 아닌지 찾는 프로그램을 작성 해 주세요.
존재한다면 O, 존재하지 않으면 X를 출력하면 됩니다.
(재귀호출을 이용해서 코딩을 해 주세요.)

입력 예제

20

출력 결과

O
문제 2번
북카페를 개업한 사장님은, 보유한 책 들을 미리 사전순으로 정렬 해 두고
Binary Search로 손님이 원하는 책을 빠르게 찾아 대여 해 주려고 합니다.
N개의 책 이름을 입력 받고, 아래 그림처럼 사전순 오름차순으로 정렬 해주세요.
(대문자 < 소문자)
사장님은 하나의 책 제목이 손님이 원하는 책 제목과 비교하는데 1 초가 걸립니다.
M명의 단골 고객이 원하는 책 제목과, 고객님이 준 시간(S) 안에 책을 찾아야 합니다.
단골 고객님이 준 시간 안에 책을 찾아줄 수 있으면 "pass",
책이 없거나, 손님이 준 시간안에 책을 찾아줄 수 없다면 "fail" 를 출력하세요.
1.
ex) 단골 고객이 사장님께 2 초 시간을 준다면, 사장님은 책을 2 번까지 탐색할 수 있습니다.
[입력]
책의 수 N와 N개의 책 제목들을 입력 받습니다. (1 <= N <= 100,000)
그리고 단골 고객의 수 M 을 입력 받습니다. (1 <= M <= 1,000)
그 다음줄 부터 M명의 고객들이 찾는 책 제목, 허용되는 검색 시간 S 를 입력 받아 주세요.
[출력]
M명의 고객들에 대해서
허용된 시간 안에 책을 찾아줄 수 있는지 pass 또는 fail을 각각 출력하면 됩니다.

입력 예제

6
Rabbit Moon Opening Alien Power Ai
3
Opening 5
Alien 2
Ai 2

출력 결과

pass
fail
pass
문제 3번
저장장치에 있는 데이터를 클라우드에 백업하려고 합니다.
데이터의 범위를 알아야 전송이 가능하기 때문에, 저장장치내에 어디까지 데이터가 적혀있는지 알아내야 합니다.
아래 그림처럼 N x N 의 데이터 저장장치가 있습니다.
데이터는 # 으로 표시되며, (0, 0) 에서 부터 차례로 쌓입니다.
데이터 백업을 위해, 저장장치내에 데이터가 어디까지 쓰여있는지 확인해주세요.
O(log N) 의 속도로 데이터를 검색해야 하며, 마지막 # 데이터의 좌표를 출력하면 됩니다.
[입력]
저장장치의 가로, 세로 N 이 입력 됩니다. (3 <= N <= 1,000)
그리고 데이터 # 가 적혀 있는 저장장치 내의 정보가 입력 됩니다.
[출력]
마지막 데이터가 적혀 있는 위치를 y x 좌표로 출력 해주세요.

입력 예제

8
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # # # # # #
# # # 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

출력 결과

5 2
문제 4번
약국에는 새로운 기계를 들여 놨습니다.
이 기계는 반창고 스캐너라고 불리는 기계로, 환자가 자신의 상처를 스캔하면
자동으로 상처 크기만큼 반창고를 잘라 공급해주는 스캐너 자판기입니다.
위 그림처럼 스캐너에 상처를 스캔하면, 상처의 크기를 측정합니다.
빠른 속도로 각 행마다 상처의 길이를 측정하여 반창고를 잘라 제공합니다.
많은 환자를 받기 위해 O(log N) 으로 연산을 처리하여 상처 길이를 측정해야 합니다.
[입력]
총 다섯 줄의 상태가 입력됩니다.
(3 <= 한 문자열의 길이 <= 1,000)
상처가 아닌 부분은 "_" 로 표시되고, 상처는 "*" 로 입력 됩니다.
[출력]
각 행마다 상처의 길이를 출력해주세요.

입력 예제

__*_______________
______***_______
__**********__________
****________
******

출력 결과

1
3
10
4
6
문제 5번
게임 프로그래머인 최 프로님은 3D 좌표를 계산할 때, 루트 값을 구해야하는 경우가 많습니다.
따라서 루트를 빠르게 계산하는 프로그램을 만들기로 했습니다.
입력 받은 값을 Binary Search 를 이용하여 루트 값의 정수부를 구해주세요.
만약 16 입력시 출력결과는 4 입니다.
만약 17 입력시 root 값은 4.xxx 이므로, 출력결과는 4 입니다.
만약 25 입력시 root 값은 5 입니다.
만약 35 입력시 root 값은 5.xxx 이므로, 출력결과는 5 입니다.
[입력]
숫자 값을 하나 입력 받습니다.
[출력]
입력 받은 값의 루트 값을 정수 부분만 출력 해주세요.

입력 예제

17

출력 결과

4
문제 6번
주유를 할 때, 기름이 어느정도 찼는지 알려주는 프로그램을 작성하려고 합니다.
for문으로 짜기에는 인식속도가 너무 느리기에 Binary Search를 이용하려고 합니다.
기름의 상태를 문자열로 입력받아주세요. (총 10글자)
Binary Search를 이용하여 10칸 중 총 몇 % 기름이 채워졌는지 출력 해 주세요.
(최소 한 칸은 항상 차 있습니다)

입력 예제

######____

출력 결과

60%
문제 7번
「Heap은 Data를 이진트리 형태로 저장해두고, 우선순위가 높은 값을 빠르게 찾아주는 자료구조 입니다. 」
띄어쓰기를 제외하고 대소문자, 특수문자가 섞인 한 문장을 입력받아주세요.
Max Heap에 문자열을 구성하는 알파벳들을 넣어두고, 아스키코드 순서대로 내림 차순으로 정렬하여 출력 해 주세요.
(최대 50글자)
[STL : Max Heap]
#include <queue>
priority_queue<int> t;
//priority_queue<int, vector<int>, less<int>> t;
을 간략하게 표현한 것이다.
C++
[STL : Min Heap]
#include <queue> priority_queue<int, vector<int>, greater<int>> t;
JavaScript
복사
[STL : 다중 조건 우선순위 큐]
#include <queue> struct Node { int a, b }; // 연산자 오버로딩, 첫 번째 인자값의 우선순위가 더 낮다면 True 리턴한다. (주의) bool operator<(Node t1, Node t2) { if (t1.a < t2.a) return true; if (t1.a > t2.a) return false; return t1.b < t2.b; } priority_queue<Node> t;
JavaScript
복사

입력 예제

DoYouWannaBuildASnowMan?

출력 결과

wuuooonnnnlidaaaYWSMDBA?
문제 8번
n개 도시의 여행경로 그래프(인접행렬)를 입력받아주세요.
이 중에 값 비싼 항로 Top 3개만 판매를 하려고 합니다.
간선의 길이와 정보들을 모두 Heap에 넣어두고 (구조체 Heap)
가장 비싼 간선 3개를 뽑아 출력 해 주세요.

입력 예제

4
0 0 30 20
10 0 5 40
10 0 0 5
0 50 0 0

출력 결과

D-B 50
B-D 40
A-C 30
문제 9번
도깨비의 보자기에는 n개 황금이 들어있습니다.
이 보자기는 한번 꺼낼 때 가장 가벼운 물건만 꺼내지는 마법의 보자기 입니다.
보자기에 있는 황금을 꺼내면 보자기의 무게는 점점 가벼워집니다.
도깨비에게 들키지 않도록, 황금을 2개씩 꺼낼 때 마다 무거운 짱돌 1개를 넣어두려고 합니다.
황금의 개수 n과
보자기에 들어있는 황금의 무게들을 입력 받습니다.
보자기에서 2 개의 내용물을 꺼냅니다.
그리고 마지막으로 꺼낸 황금의 2 배 무게를 가진 짱돌 1개 넣습니다.
꺼내어진 돌이 황금이 아닐 때까지 위 동작을 반복 합니다.
도깨비보자기에서 꺼내어진 황금의 개수를 출력 해 주세요.
[예시]
보자기에 1 3 3 4 9 무게의 황금이 보자기에 들어 있습니다.
1회 시도 : 보자기에서 1, 3 무게의 황금을 뺀 후, 3 x 2 = <6> 무게의 짱돌을 넣는다. -----> 3, 4, <6>, 9
2회 시도 : 보자기에서 3, 4 무게의 황금을 뺀 후, 4 x 2 = <8> 무게의 짱돌을 넣는다. -----> <6>, <8>, 9
3회 시도 : 보자기에서 꺼낸 돌이 짱돌 <6> 이기에 동작을 그만 둔다.
총 4개의 황금을 얻을 수 있기에, 출력결과는 4 입니다.
[세부사항]
1.
황금과 짱돌이 같은 무게일때는, 황금이 먼저 나와야 합니다.
2.
꺼낸 돌이 짱돌일 경우, 즉시 꺼내는 작업을 중단합니다.

입력 예제

5
1 3 3 4 9

출력 결과

4
문제 1번
아래 이진트리를 1차원 배열에 하드코딩 해 주세요
DFS를 돌리면서 탐색 순서대로 노드의 값을 출력하고자 합니다.
출력하는 타이밍은 아래와 같습니다.
왼쪽으로 진입했다가 돌아온 뒤 (왼쪽으로 재귀),
오른쪽으로 진입했다가 돌아왔을 때 (오른쪽으로 재귀) 출력합니다.
[입력]
입력 값은 없습니다.
[출력]
출력결과는 1 6 2 4 8 7 5 3 입니다

출력 결과

1 6 2 4 8 7 5 3
문제 2번
5개국 여행 계획을 세워주는 여행사에서 경로추천을 해주려합니다.
DFS를 활용하여 시작점부터 도착지점까지의 최소 비용과 경로를 출력해주세요.
아래 그래프를 인접행렬로 하드코딩해주세요.
[입력]
시작점고 도착지점을 입력 받으세요.
[출력]
최소 비용과 경로를 출력해주세요.
갈 수 있는 길이 없다면 "impossible" 을 출력 하세요.

입력 예제

G B

출력 결과

6:GTHB
문제 3번
입력받은 숫자 갯수로 10을 만들 수 있는 조합의 수를 출력하세요.
사용 되는 숫자의 범위는 1 ~ 9 입니다.
만일 입력 받은 N = 3 이라면, 3개의 숫자를 더하여 10이 되는 숫자 조합은 총 64개 입니다.
1 + 1 + 8 = 10
1 + 2 + 7 = 10
1 + 3 + 6 = 10
.
.
.
7 + 2 + 1 = 10
8 + 1 + 1 = 10
[입력]
1 ~ 9 의 숫자 중, 사용될 숫자의 개수 N을 입력 하세요.
[출력]
N 숫자의 개수를 조합하여 10이 되는 경우의 수의 총합을 출력하세요.

입력 예제

3

출력 결과

36
문제 4번
수족관에 일렬로 장어들이 지나가고 있습니다.
무료 시식권이 생겨 이 중 한마리를 시식하려고 할 때, 가장 긴 장어를 찾아 출력 해 주세요.
[입력]
최대길이 100인 문장이 입력 됩니다.
장어는 ~ 로 표시되고, 나머지 물체들은 #으로 표시 됩니다.
[출력]
가장 긴 장어를 출력 해주세요.

입력 예제

#~~~~~~~#~~~~#

출력 결과

~~~~~~~
문제 5번
문자 메세지에 다이어트에 도움이 안 되는 단어가 있으면, 모두 3개의 #으로 (###) 모자이크 처리하려고 합니다.
성공적인 다이어트를 위해, 문자메세지 필터기능을 완성시켜주세요.
모자이크 처리 할 5개의 위험 단어들은 다음과 같습니다.
대소문자 구분 없이 위험 단어가 있으면 모자이크 처리 해 주세요.
[입력]
문장의 길이가 최대 50 글자인 한 문장을 입력 받습니다.
[출력]
모자이크해야할 단어를 길이에 상관없이 "###" 로 바꿔 문장을 출력해주세요.

입력 예제

HeyBread!DoYouWannaBuildAChicken

출력 결과

Hey###!DoYouWannaBuildA###
문제 6번
숫자카드 4개를 입력 받고 숫자 카드를 재귀호출을 이용하여 조합해주세요.
조합한 숫자가 3000이 넘는 경우가 총 몇개인지 출력 해주세요.
[입력]
숫자 4개를 입력 하세요.
[출력]
경우의 수의 총합을 출력하세요.

입력 예제

2 5 1 6

출력 결과

12
문제 7번
2차원 배열 인형뽑기 기계에는 인형들이 불규칙하게 배치되어 있습니다.
그리고 인형에는 각각의 알파벳이 부여되어 있습니다.
인형 뽑기 집게의 좌표와 모양을 입력 받아 인형을 뽑았을때의 문자를 출력해주세요.
[Type 1 집게 모양]
위, 가운데, 왼쪽, 오른쪽, 아래 순서대로 인형의 알파벳이 출력됩니다.
[Type 2 집게 모양]
왼쪽 위, 가운데, 오른쪽 위, 오른쪽 아래, 왼쪽 아래 순서대로 인형의 알파벳이 출력됩니다.
[입력]
인형뽑기 기계의 가로, 세로 N이 입력 됩니다. (3 <= N <= 1,000)
그리고 집게 명령 개수 K 가 입력됩니다.
인형이 들어있는 인형 뽑기 기계 내의 정보가 입력 됩니다.
그 다음 줄 부터 K 개수의 집게 좌표 Y X 와 집게 형태 M 를 입력 받아 주세요.
[출력]
입력 받은 집게 내림 명령 만큼의 문장을 출력 해주세요.

입력 예제

5 2
A E K X K
Z O Q K P
C F W L X
C E I H B
N E M I O
1 1 1
3 1 2

출력 결과

EOZQF
CEWMN
문제 8번
2차원 배열의 그릴에 불을 놓을 수 있는 공간은 0, 불을 놓을 수 없는 공간은 1로 입력됩니다.
고기를 맛있게 굽기위해 불을 처음 놓을 좌표를 입력 받으세요.
불이 상, 하, 좌, 우로 한 칸씩 늘어날때마다 1초가 경과합니다.
불이 모든 자리에 붙었을때의 시간을 출력하세요.
2차원 배열에서 (0, 4)에 불을 놓는다면 2차원 배열안의 모든 0에 불이 퍼질때 까지 걸리는 시간은 8초입니다.
[입력]
그릴의 세로, 가로 길이 N 을 입력 받습니다. (3 <= n < 1,000)
그릴의 정보가 입력 됩니다.
그리고 불이 처음 놓일 좌표 Y X 가 입력됩니다.
[출력]
모든 '0' 자리에 불이 번지는데 걸리는 시간을 출력하세요.

입력 예제

5 5
1 0 0 0 0
1 0 1 0 1
0 0 1 0 0
0 0 0 0 1
0 1 0 0 0
0 4

출력 결과

8
문제 9번
수압이 좋지 않은 집을 수리하기 위해, 배관을 선택해 주세요.
전체 배관의 맵은 2차원 배열로 주어지고 각 행에서 0이 아닌 칸을 한 개씩 열어야 합니다.
총 수압은 각 행의 칸을 선택했을때, 선택한 칸들의 곱입니다.
위 그림처럼, 첫 번째 줄의 6, 두 번째 줄의 4, 세 번째 줄의 4, 네 번째 줄의 9 를 선택한다면
총 수압의 값은 864 로 수압의 값이 가장 큰 상태가 됩니다. 또한 최대 값이 중복될 수 있습니다.
모든 경우의 수에서 가장 큰 값을 출력해주세요.
[입력]
배관 사이즈의 세로, 가로 N 이 입력됩니다. (3 <= N <= 1,000)
배관 사이즈의 정보가 음수와 양수로 입력 됩니다.
[출력]
배관을 열어, 선택한 배관들의 곱이 가장 큰 숫자를 출력하세요.

입력 예제

4
3 4 -2 6
0 -4 4 1
4 4 0 -2
5 1 6 9

출력 결과

864
문제 10번
다빈치 코드게임을 벤치 마킹하여 다빈치 민코드라는 게임을 만들었습니다.
서로 M 개의 카드를 상대방의 카드패에서 뽑고, 카드들의 각각의 값들을 곱한 값으로 승패를 가립니다.
이길 확률을 높이려면 상대방이 내 카드에서 M 개를 뽑아 그 숫자들의 곱이 최소값이 되도록 유도해야합니다.
7개의 카드패가 주어 지고 M이 3이라면 카드패들의 곱이 가장 작은 값이 나와야합니다.
만일 M 이 3이라면 -2, 6, 7을 뽑도록 유도하여 최소값인 -84 가 나오도록 해야 이길 확률이 높아 집니다.
[입력]
숫자 카드의 개수 N 이 입력 됩니다. (3 <= N <= 1,000)
그 다음줄에, 음수와 양수의 값을 가진 카드들이 입력 됩니다.
그리고, 뽑아야할 카드 개수 M 이 입력 됩니다. (2 <= N < N)
[출력]
M 개를 선택하여 곱한 값이 최소값이 될때의 값을 출력하세요.

입력 예제

7 3
1 5 4 -2 6 7 -1

출력 결과

84