https://www.acmicpc.net/problem/11286
11286번: 절댓값 힙
첫째 줄에 연산의 개수 N(1≤N≤100,000)이 주어진다. 다음 N개의 줄에는 연산에 대한 정보를 나타내는 정수 x가 주어진다. 만약 x가 0이 아니라면 배열에 x라는 값을 넣는(추가하는) 연산이고, x가 0
www.acmicpc.net
이해
- 우선순위 큐를 이용하여 풀 수 있는 문제
- 파이썬 내장함수인 Heapq를 사용하면 간단하게 풀 수 있지만, 직접 구현해봄
구현
- 내장함수 사용
- import heapq 를 사용하여 절댓값과 실값을 같이 저장하여 비교하여 사용
- 직접 구현
- class를 통해 실 값을 저장하면 절댓값을 기준으로 비교하여 트리를 구성
코드
# 11286 절댓값 힙
import sys
input = sys.stdin.readline
class AbsoluteHeap:
def __init__(self, N:int) -> None:
# 전체 길이를 미리 선정하여 heap list를 생성
# 길이를 미리 저장
self.tree = [0] * (2*N+2)
self.length = 0
# 힙 소트(시작 노드를 입력받음)
# 시작 노드 0 -> root에서 아래로 내려감
# 시작 노드 0이 아닌 다른 인덱스 -> 인덱스에서 위로 올라감
def heap_sort(self, node:int) -> None:
# 시작노드가 root 노드 일 때 -> 아래로 내려감
if node == 0:
while node < self.length:
left, right = 2*node+1, 2*node+2
# 길이를 넘어가면 트리에서 벗어남(leaf 노드가 0임)
if node >= self.length:
break
if left >= self.length and right >= self.length:
node += 1
# leaf 노드에서 하나만 넘어가는 경우(leaf 노드가 없는 경우)
elif left >= self.length: # 왼쪽 leaf 노드 0
# 절댓값이 작은 것 기준, 같을 땐 값이 작은 친구를 root 노드와 교환
if abs(self.tree[right]) < abs(self.tree[node]):
self.tree[right], self.tree[node] = self.tree[node], self.tree[right]
elif abs(self.tree[right]) == abs(self.tree[node]):
if self.tree[right] < self.tree[node]:
self.tree[right], self.tree[node] = self.tree[node], self.tree[right]
node = right # 다음 탐색 확인
elif right >= self.length: # 오른쪽 leaf 노드 0
if abs(self.tree[left]) < abs(self.tree[node]):
self.tree[left], self.tree[node] = self.tree[node], self.tree[left]
elif abs(self.tree[left]) == abs(self.tree[node]):
if self.tree[left] < self.tree[node]:
self.tree[left], self.tree[node] = self.tree[node], self.tree[left]
node = left
else:
# 모두 범위 내의 경우(leaf 노드가 둘 다 있는 경우)
# 왼쪽 leaf 노드와 오른쪽 leaf 노드를 비교
# 더 작은 쪽 leaf 노드와 root 노드 비교 -> root 노드가 더 크다면 교환
if abs(self.tree[left]) > abs(self.tree[right]):
if abs(self.tree[node]) > abs(self.tree[right]) or (abs(self.tree[node]) == abs(self.tree[right]) and self.tree[node] > self.tree[right]):
self.tree[node], self.tree[right] = self.tree[right], self.tree[node]
node = right
elif abs(self.tree[left]) == abs(self.tree[right]):
if self.tree[left] > self.tree[right]:
if abs(self.tree[node]) > abs(self.tree[right]) or (abs(self.tree[node]) == abs(self.tree[right]) and self.tree[node] > self.tree[right]):
self.tree[node], self.tree[right] = self.tree[right], self.tree[node]
node = right
else:
if abs(self.tree[node]) > abs(self.tree[left]) or (abs(self.tree[node]) == abs(self.tree[left]) and self.tree[node] > self.tree[left]):
self.tree[node], self.tree[left] = self.tree[left], self.tree[node]
node = left
else:
if abs(self.tree[node]) > abs(self.tree[left]) or (abs(self.tree[node]) == abs(self.tree[left]) and self.tree[node] > self.tree[left]):
self.tree[node], self.tree[left] = self.tree[left], self.tree[node]
node = left
# 다음 노드를 탐색하지 더 이상 않아도 되는 경우 break
if node != left and node != right:
break
else: # 시작 노드가 0이 아닌 경우 -> leaf 노드에서 root노드 쪽으로 탐색
while node > 0:
# 인덱스가 leaf 노드 기준 왼쪽(홀수), 오른쪽(짝수)
# root node(N) -> left leaf node (2*N+1) / right leaf node (2*N + 2)
if node % 2: # left leaf node (2*N+1) -> (2*N+1)//2 = N
root = node // 2
else: # right leaf node (2*N+2) -> (2*N+2) //2 -1 = N
root = node // 2 - 1
# root 노드와 비교하여 교환할 여지가 있으면 교환하여 탐색
if abs(self.tree[root]) > abs(self.tree[node]) or (abs(self.tree[root]) == abs(self.tree[node]) and self.tree[root] > self.tree[node]):
self.tree[root], self.tree[node] = self.tree[node], self.tree[root]
node = root
else: # 교환할 필요 없으면 더 이상 탐색 X
break
return
# 힙 푸시
def push_num(self, num:int) -> None:
# 마지막 노드에 추가하여 힙 소트(leaf -> root)
self.tree[self.length] = num
self.heap_sort(self.length)
self.length += 1
return
# 힙 팝
def pop_num(self) -> int:
# root 노드를 제거한 후, 마지막 leaf 노드를 root 노드에 넣어 힙 소트(root -> leaf)
if self.length == 0:
return 0
root = self.tree[0]
self.tree[0], self.tree[self.length-1] = self.tree[self.length-1], 0
self.length -= 1
self.heap_sort(0)
return root
def solution():
N = int(input())
# class 를 이용하여 절댓값 힙 구성
AH = AbsoluteHeap(N)
for _ in range(N):
x = int(input())
if x == 0:
print(AH.pop_num())
else:
AH.push_num(x)
return
if __name__ == "__main__":
solution()
느낀점
- 평소에 heapq를 자주 사용하였음에도 불구하고 꽤 힘들게 구현했습니다.
- 우선순위 큐의 구조와 로직을 다시 돌아보는 계기가 되었습니다.
- 다른 알고리즘들도 한 번씩 구현해봐야겠다는 생각이 들었습니다...
반응형
'Algorithm > BaekJoon Review' 카테고리의 다른 글
10986. 나머지 합 (0) | 2024.04.02 |
---|---|
13549. 숨바꼭질 3 (2) | 2024.01.31 |
12851. 숨바꼭질 2 (2) | 2024.01.31 |
1697. 숨바꼭질 (2) | 2024.01.29 |
9935. 문자열 폭발 (0) | 2024.01.21 |