• 포화 (Full), 완전 (Complete) 이진 트리와 와 구분하기 위해서 사용
완전이진트리에 가깝다는 가정이 있을 때만 배열로 관리 (메모리가 비어있으면 비효율적이기 때문에)
부모노드와 자식노드가 바로 배열 안에서의 인덱싱이 가능하다.
• 힙 순서 속성 (Heap Order Property)을 만족하는 완전 이진 트리
(Complete Binary Tree)
• 힙 순서 속성 : 트리 내의 모든 노드에 저장된 값은 자식 노드보다 작거나 같아야 한다
• Min Heap (최소힙), Max Heap (최대힙)
항상 완전이진트리이다.
"힙에서 가장 작은 데이터를 갖는 노드는 루트 노드이다.”를 보장
계산 복잡도: log2n
• 정렬된 값의 "삽입"과 "삭제"의 계산 복잡도 비교
배열 (Array) 기반, 연결 리스트 (Linked List) 기반, 힙 (Heap) 기반 리스트
삽입: 𝑶(𝑛) 𝑶(𝑛) 𝑶(log 𝑛)
삭제: 𝑶(1) 𝑶(1) 𝑶(log 𝑛)
힙을 주로 어디에 쓰는가?
삭제는 크지만 삽입은 더 복잡도가 작다
리스트와 관계 없음 트리 구조를 사용
• 우선순위 큐 (Priority Queue)
• 힙 정렬 (Heap Sort)
파이썬은 heaq가 있음(힙 자료구조)
heapq는 최소 힙의 구현이기 때문에 최대 힙을 사용하고 싶다면, Python 튜플 (Tuple)과 같은 묶음 자료구조를 사용해서 구현할 수 있다.
(정렬값, 사용값)
• 우선순위 큐 (Priority Queue)
일반적인 큐(Queue)는 먼저 집어넣은 데이터가 먼저 나오는 FIFO (First In First Out) 구조이지만, 우선순위 큐 (Priority Queue)는 들어온 순서와 상관 없이 우선 순위가 높은 데이터 (보통 더 높은 우선순위를 더 낮은 값을 표현) 가 먼저 나옴
import heapq
def heap_sort(nums):
heap = []
for num in nums:
heapq.heappush(heap, num)
sorted_nums = []
while heap:
sorted_nums.append(heapq.heappop(heap))
return sorted_nums
print(heap_sort([6, 8, 3, 9, 10, 1, 2, 4, 7, 5]))
# 실행결과 : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
계산 복잡도: 𝑶(𝑛log 𝑛)
best case: 𝑶(𝑛)
• 현존하는 컴퓨터 아키텍처상에서 비교 연산자를 이용하여 구현된 정렬 알고리즘 중 가장 고성능인 알고리즘이 퀵 정렬
• 단 데이터에 접근하는 시간이 오래 걸리는 외부 기억장소 (하드디스크 등)에서 직접 정렬을 수행할 경우에는 병합 정렬이 더 빠른 것으로 알려져 있음
퀵정렬이 왜 힙정렬보다 빠를까
: 캐시메모리에 이유가 있음 바로 옆에 인덱스를 처리
병합정렬 메모리적으로 효율성이 떨어짐
네트워크적으로 멀리 있는 정보를 활용할 때는 퀵 정렬이 좋을 수 있음 (pc 메모리 사용이 비효율적이라 많이 사용되지는 않음)
삽입 정렬: 점근적 / n이 충분히 작을 때는 더 효율적일 수도 있다.
• Tim Sort(파이썬에서 사용): 기본적으로 삽입 정렬
Tim Peters (2002)
for Python Sort (and > Java 7)
Hybrid Stable Sorting Algorithm • Merge + Insertion Sorts
• '검색’은 정렬된 상태에서 더 빠르게 원하는 것을 찾을 수 있음
코드 추가되어 짧아짐
그래봤자 반만 줄었다.(많이 줄지 않았다)
- 전체의 첫 데이터를 '시작'으로 지정하고 마지막 데이터를 '끝'으로 지정한 후 시작과 끝의 중앙에 있는 누나의 키를 (찾고 있는) 할머니의 키와 비교한다.
- 좌측을 버리고 끝은 그대로 두고 시작을 중앙(누나)의 바로 오른쪽으로 옮긴다. 중앙의 오른쪽 그룹에서 다시 시작과 끝의 반절 위치인 새 중앙에 있는 엄마의 키를 할머니의 키와 비교한다.
- 우측은 버리고 시작은 그대로 두고 끝을 중앙(엄마)의 바로 왼쪽(할머니)로 옮긴다. 중앙(엄마)의 왼쪽 그룹에서 다시 시작과 끝의 반절 위치인 새 중앙에 있는 이모의 키를 할머니의 키와 비교한다.
- 할머니보다는 키가 크고, 엄마보다는 키가 작은 삼촌을 찾는다면?
검색실패
▪ 이진 검색 알고리즘 정리
1) 중간 위치를 찾음 → (시작위치 + 끝위치) // 2
2) 찾는 값과 중간 위치 값을 비교
3) 같다면 원하는 값을 찾은 것이므로 위치 번호를 결과값으로 돌려줌
4) 찾는 값이 중간위치 값보다 크다면 중간 위치의 오른쪽을 대상으로 다시 탐색(1번 과정부터 반복) → 시작 위치를 중간 위치 바로 오른쪽으로
5) 찾는 값이 중간위치 값보다 작다면 중간위치의 왼쪽을 대상으로 다시 탐색(1번 과정부터 반복) → 끝 위치를 중간 위치 바로 왼쪽으로
▪ 자료의 중간부터 시작해 찾을 값이 더 크면 오른쪽으로, 작으면 왼쪽으로 점프하며 자료를 찾음
▪ 점프할 때마다 점프 거리는 반씩 줄어듬
아래를 참고하여 재귀 호출을 사용한 이분 탐색 알고리즘을 생각해 보세요.
종료 조건: while문의 반복조건 반대로 생각하면 쉽다.
종료 조건이 호출되었을 때는? (반복에서 빠져나갔을 때) → 검색에 실패 (-1 반환)
찾는 값과 주어진 탐색대상의 중간위치값을 비교
찾는 값과 중간 위치 값이 같다 → 검색에 성공 (위치 반환)
찾는값이 중간위치값보다 크다 → 중간위치의 오른쪽을 대상으로 재귀호출 찾는 값이 중간 위치 값보다 작다 → 중간 위치의 왼쪽을 대상으로 재귀 호출
• 이진 트리 중 활용도가 높은 트리로, 값 크기를 기준으로 일정 형태로 구성
각 노드가 다음 규칙을 따른다
• “왼쪽 자식 노드는 나보다 작고, 오른쪽 자식 노드는 나보다 크다.”
- 이진 검색 트리에서 값의 검색
• 매우 빠르게 (간단하게) 검색 가능
• 완전 이진 트리인 경우 → 𝐎(log2𝑛) =𝐎(log𝑛)
• 재귀의 방식 이용 가능
- 이진 검색 트리에서 값의 삽입
찾았을 때 True
None일 때 False
return node.data == data는 : True
중위 순회 검색 시 키값 오름차순
Python List를 통한 Queue의 구현
가까운 애들부터 검색됨
bst.breadthfirst(bst.root) 너비 우선 검색
레벨 순서 순회임
그래프는 간선의 개수가 정해져 있지 않고, 일반적인 상하구조가 없음 / 값 가중치 부여 가능(유향 그래프) / 순서 상관 없음
이진트리는 최대 두개이며 부모와 자식이라는 상하구조가 있음
좌측-우측 자식 순으로 Node 재귀 순회
내 노드(꺼)가 처리되는 순서에 따라
전위순회: bst.preorder
중위순회: bst.inorder
후위순회: bst.postorder
방법 1 : 자식 노드가 없는 경우
방법 2 : 자식 노드가 하나인 경우
방법 3 : 자식 노드가 두 개인 경우
- 예제
+추가나 삭제는 포인터 값의 변경이 필요하므로 재귀함수를 짤 때 차이가 있다.
None or A : 이렇게 하면 안됨 (합리적인 제약이 아니다.)