힙 (Heap)
힙 (Heap) : 최대값과 최소값을 바르게 찾기 위해 고안된 완전 이진 트리 (Complete Binary Tree)
힙 (Heap) 사용 이유
- 배열로 최소/최대를 찾으려면 O(n) 으로 걸림
- 힙 (Heap) 으로 최소/최대를 찾으면 O(logn) 으로 걸림
- 우선순위 큐와 같이 최소/최대값을 빠르게 찾아야하는 자료구조 및 알고리즘 구현에 활용됨
구조
- 최소값을 구하는 힙 (최소 힙, Min Heap) 과 최대값을 구하는 힙 (최대 힙, Max Heap) 으로 분류
- 힙은 아래 조건을 가지고 있는 자료구조
- 최대 힙 경우 각 노드의 값이 자식 노드가 가진 값보다 크거나 같음
- 최소 힙 경우 각 노드의 값이 자식 노드가 가진 값보다 크거나 작음
- 완전 이진 트리 형태
힙 데이터 삽입 - 기본
힙은 완전 이진 트리이므로, 삽입할 노드는 기본적으로 왼쪽 하단부 노드부터 채워지는 형태로 삽입
힙 데이터 삽입 - 삽입할 데이터가 힙의 데이터보다 클 경우 (Max Heap)
먼저 삽입된 데이터는 완전 이진 트리 구조에 맞춰서 최하단부 왼쪽 노드부터 채워짐
부모 노드보다 값이 큰 경우 부모 노드와 위치를 바꿔주는 작업을 반복 (swap)
힙 데이터 삽입 - 데이터 삭제 (Max Heap)
보통은 최상단 노드를 삭제하는 것이 일반적
- 힙의 용도는 최소/최대를 root에 넣고 바로 꺼내 쓸 수 있도록 하는 것
상단 데이터 삭제 시, 가장 최하단부 왼족에 위치한 노드 (일반적으로는 가장 마지막에 추가한 노드)를 root 노드로 이동
root 노드값이 자식노드보다 작을 경우, root노드의 자식노드 중 가장 큰 값을 가진 노드와 root 노드 위치를 바꿔주는 작업을 반복 (swap)
힙 구현
일반적으로 힙 구현 시 배열을 활용함
배열은 0번부터 시작이지만, root 노드 인덱스 번호를 1로 지정하면 구현이 조금 더 수월함
- 부모 노드 인덱스 번호 = 자식 노드 인덱스 번호 / 2
- 왼쪽 자식 노드 인덱스 번호 = 부모 노드 인덱스 번호 * 2
- 오른쪽 자식 노드 인덱스 번호 = 부모 노드 인덱스 번호 * 2 + 1
힙 insert - 1
// ArrayList 활용
import java.util.ArrayList;
public class Heap {
public ArrayList<Integer> heapList = null;
public Heap (Integer data) {
heapList = new ArrayList<>();
heapList.add(null); // 인덱스 0은 null
heapList.add(data); // 인덱스 1은 data
}
}
힙 insert - 2
import java.util.ArrayList;
public class Heap {
public ArrayList<Integer> heapList = null;
public Heap (Integer data) {
heapList = new ArrayList<>();
heapList.add(null);
heapList.add(data);
}
/*
* insert
* 인덱스 번호는 1번부터
*/
public boolean insert(Integer data) {
if(heapList == null) {
heapList = new ArrayList<>();
heapList.add(null);
heapList.add(data);
} else {
heapList.add(data);
}
return true;
}
}
힙 insert - 3
- data > parent node = data, parent node swap
- 삽입한 노드가 루트 노드가 되거나, 부모 노드보다 값이 작거나 같을 때까지 반복
Collections.swap()
swap (스왑) 이란, 두 데이터의 위치를 맞바꾸는 것을 의미함
하나의 배열 안에 있는 두 데이터의 위치를 서로 맞바꾸고 싶을 때 사용 가능
swap 함수를 별도로 구현할 수도 있지만, JAVA 에서는 Collections 패키지에서 swap() 메서드를 제공해주고 있음
Collections.swap(List list, int a, int b)
- list : 스왑할 데이터들이 들어 있는 배열 변수
- a : 스왑할 데이터 인덱스 번호
- b : 스왑할 데이터 인덱스 번호
import java.util.ArrayList;
public class Heap {
public ArrayList<Integer> heapList = null;
public Heap (Integer data) {
heapList = new ArrayList<>();
heapList.add(null);
heapList.add(data);
}
/*
* move
*/
public boolean move_up(Integer insert_idx) {
if(insert_idx <= 1) {
return false;
}
Integer parent_idx = insert_idx / 2;
if(this.heapList.get(insert_idx) > this.heapList.get(parent_idx)) {
return true;
} else {
return false;
}
}
/*
* insert
* 인덱스 번호는 1번부터
*/
public boolean insert(Integer data) {
if(heapList == null) {
heapList = new ArrayList<>();
heapList.add(null);
heapList.add(data);
return true;
}
this.heapList.add(data);
inserted_idx = this.heapList.size() - 1;
while(this.move_up(inserted_idx)) {
parent_idx = inserted_idx / 2;
// swap
Collection.swap(this.heapList, inserted_idx, parent_idx);
inserted_idx = parent_idx;
}
return true;
}
}
Heap heap = new Heap(15);
heap.insert(10);
heap.insert(8);
heap.insert(5);
heap.insert(4);
heap.insert(20);
System.out.println(heapTest.heapArray); // null, 20, 10, 15, 5, 4, 8
힙 delete - 1
보통 root 노드를 삭제하는게 일반적
import java.util.ArrayList;
public class Heap {
public ArrayList<Integer> heapList = null;
public Heap (Integer data) {
heapList = new ArrayList<>();
heapList.add(null);
heapList.add(data);
}
/*
* move
*/
public boolean move_up(Integer insert_idx) {
if(insert_idx <= 1) {
return false;
}
Integer parent_idx = insert_idx / 2;
if(this.heapList.get(insert_idx) > this.heapList.get(parent_idx)) {
return true;
} else {
return false;
}
}
/*
* insert
* 인덱스 번호는 1번부터
*/
public boolean insert(Integer data) {
if(heapList == null) {
heapList = new ArrayList<>();
heapList.add(null);
heapList.add(data);
return true;
}
this.heapList.add(data);
inserted_idx = this.heapList.size() - 1;
while(this.move_up(inserted_idx)) {
parent_idx = inserted_idx / 2;
// swap
Collection.swap(this.heapList, inserted_idx, parent_idx);
inserted_idx = parent_idx;
}
return true;
}
/*
* pop
*/
public Integer pop() {
if(this.heapList == null) {
return null;
}
return this.heapList.get(1);
}
}
힙 delete - 2
상단 데이터 삭제 시, 가장 마지막에 추가한 노드를 root 노드로 이동
root 노드의 값이 자식 노드보다 작을 경우, root 노드의 자식 노드 중 가장 큰 값을 가진 노드와 root 노드 위치를 바꿔주는 작업을 반복 (swap)
import java.util.ArrayList;
public class Heap {
public ArrayList<Integer> heapList = null;
public Heap (Integer data) {
heapList = new ArrayList<>();
heapList.add(null);
heapList.add(data);
}
/*
* move
*/
public boolean move_up(Integer insert_idx) {
if(insert_idx <= 1) {
return false;
}
Integer parent_idx = insert_idx / 2;
if(this.heapList.get(insert_idx) > this.heapList.get(parent_idx)) {
return true;
} else {
return false;
}
}
/*
* insert
* 인덱스 번호는 1번부터
*/
public boolean insert(Integer data) {
if(heapList == null) {
heapList = new ArrayList<>();
heapList.add(null);
heapList.add(data);
return true;
}
this.heapList.add(data);
inserted_idx = this.heapList.size() - 1;
while(this.move_up(inserted_idx)) {
parent_idx = inserted_idx / 2;
// swap
Collection.swap(this.heapList, inserted_idx, parent_idx);
inserted_idx = parent_idx;
}
return true;
}
/*
* pop
*/
public Integer pop() {
Integer return_data, pop_idx, left_child_pop_idx, right_child_pop_idx;
if(this.heapList.size() <= 1) {
return null;
}
return_data = this.heapList.get(1);
this.heapList.set(1, this.heapList.get(this.heapList.size() - 1));
this.heapList.remove(this.heapList.size() - 1);
pop_idx = 1;
while(this.move_down(pop_idx)) {
left_child_pop_idx = pop_idx * 2;
right_child_pop_idx = pop_idx * 2 + 1;
if(right_child_pop_idx >= this.heapList.size()) {
if(this.heapList.get(pop_idx) < this.heapList.get(left_child_pop_idx)) {
Collections.swap(this.heapList, pop_idx, left_child_pop_idx);
pop_idx = left_child_pop_idx;
}
} else {
if(this.heapList.get(pop_idx) < this.heapList.get(right_child_pop_idx)) {
Collections.swap(this.heapList, pop_idx, right_child_pop_idx);
pop_idx = right_child_pop_idx;
}
}
}
return returned_data;
}
/*
* move_down
*/
public boolean move_down(Integer pop_idx) {
Integer left_child_pop_idx, right_child_pop_idx;
left_child_pop_idx = pop_idx * 2;
right_child_pop_idx = pop_idx * 2 + 1;
// 자식 노드가 하나도 없을 경우
if(left_child_pop_idx >= this.heapList.size()) {
return false;
// 오른쪽 자식 노드만 없을 경우
} else if(right_child_pop_idx >= this.heapList.size()) {
if(this.heapList.get(popped_idx) < this.heapList.get(left_child_pop_idx)) {
return true;
} else {
return false;
}
// 왼쪽/오른쪽 자식 노드가 모두 있을 경우
} else {
if(this.heapList.get(left_child_pop_idx) > this.heapList.get(right_child_pop_idx)) {
if(this.heapList.get(popped_idx) < this.heapList.get(left_child_pop_idx)) {
return true;
} else {
return false;
}
} else {
if(this.heapList.get(popped_idx) < this.heapList.get(right_child_pop_idx)) {
return true;
} else {
return false;
}
}
}
}
}
feat.한 번에 끝내는 코딩테스트369 Java편 - 패스트캠퍼스
'코테 > 자료구조' 카테고리의 다른 글
트리와 이진 탐색 트리 - 2 (0) | 2023.08.18 |
---|---|
트리와 이진 탐색 트리 - 1 (0) | 2023.08.16 |
해쉬 테이블 (HashTable) (0) | 2023.08.15 |
링크드 리스트 (Linked List) - 2 (0) | 2023.08.10 |
링크드 리스트 (Linked List) - 1 (0) | 2023.08.10 |