완전 이진 트리의 일종으로 반정렬 상태를 유지하여 최대 혹은 최소의 값을 빠르게 반환할 수 있는 자료구조이다. 주로 우선순위 큐를 구현하기 위해 이용된다.
루트 노드를 첫번째 인덱스에 저장하고 부모 노드의 인덱스를 기준으로 2i에 왼쪽 자식 2i+1에 오른쪽 자식을 저장한다.
삽입은 맨 마지막 자리에 요소를 삽입 후 힙의 속성에 맞을 때 까지 부모와 자식간의 교환을 한다.
루트노드와 맨 마지막 요소를 스왑한 후 마지막 요소를 반환한다. 그 후 힙의 속성에 맞을 때까지 부모와 자식간의 교환을 한다.
이진탐색트리에서 편향이 발생하려면 완전 정렬된 상태로 삽입이 이루어졌을 시에 발생한다. 하지만 이때 힙은 최대 혹은 최소 요소를 루트 노드로 하는 완전 이진 트리를 유지하도록 삽입 및 삭제를 하기 때문에 편향이 발생하지 않는다.
힙 정렬의 시간복잡도는 힙을 구성하고 나서 삭제연산을 반복하면 되므로 O(nlogN)이다. 그리고 unstable하다.
이진탐색트리의 단점을 보완한 트리로 편향트리가 발생하지 않도록 삽입 혹은 삭제할때 높이의 균형을 유지하는 트리이다. 그 종류로는 AVL, red-black tree, B트리, B+ 트리 등이 있다.
리프노드에서 루트노드까지 가는 경로에서 만나는 검은색 노드의 개수가 같다.
Restructuring
1. 새로운 노드 부모 노드 조상노드를 오름차순으로 정렬
2. 셋 중 중간값을 부모로 만들고 나머지 둘을 자식으로 만듬
3. 새로 부모가 된 노드를 검은색으로 만들고 나머지 자식들을 빨간색으로 변경
Recoloring
1. 새로운 노드의 부모와 삼촌을 검은색으로 바꾸고 조상을 빨간색으로 바꿈.
1-1. 조상이 루트노드라면 검은색으로 바꿈
1-2. 조상을 빨간색으로 바꿨을 때 다시 문제가 발생하면 문제가 발생하지 않을 때까지 반복
아래의 예는 이중 흑색 노드가 왼쪽에서 발생할 시 이다. 따라서 오른쪽에서 발생할 시도 고려를 해야한다.
Default
case change
case a
case b
case c
1) Red Black Tree
2) AVL
BF(K) = K의 왼쪽 서브트리의 높이 - 오른쪽 서브 트리의 높이
위 그림처럼 BF가 벗어난 노드를 기준으로 우회전을 적용한다.
위 그림처럼 BF가 벗어난 노드를 기준으로 좌회전을 적용한다.
위 같은 케이스일 시 BF에 이상이 있는 노드의 왼쪽 자식 노드를 기준으로 좌회전한다. 그 후 BF에 이상이 있는 노드를 기준으로 우회전을 진행한다.
위 같은 케이스일 시 BF에 이상이 있는 노드의 오른쪽 자식 기준으로 우회전한 후, BF에 이상이 있는 노드를 기준으로 좌회전한다.
3) 2-3-4 트리
정렬 알고리즘은 말 그대로 어떠한 자료구조를 정렬하는 방법으로 대표적으로 quick sort, merge sort, radix sort, counting sort, bubble sort, insertion sort, selection sort 등이 있다.
quick sort는 pivot을 기준으로 나눠 정렬하는 기법이고, merge sort는 자료구조를 분할 후에 합치면서 정렬하는 방법이다. merge sort는 최악, 평균, 최선일 경우 모두 O(nlogn)의 소요되는 반면에 quick sort는 역순으로 정렬되어 있을 시 pivot의 효과를 볼 수 없어 최악의 경우 O(n^2)의 시간이 소요된다.
오름차순 혹은 내림차순으로 정렬되어 파티션이 나뉘지 않는 경우이다. 이를 개선하기 위해서는 중간값과 맨 앞값을 서로 스왑해주면 어느정도 개선이 가능하다.
stable sort란 중복된 값이 있을 시 이 순서가 변경되지 않는 정렬을 의미한다. insertion sort, merge sort, bubble sort, counting sort가 있다.
큐를 이용하여 구현할 수 있다.
struct Queue<T>{...}
func merge(_ lhs: [Int], _ rhs: [Int]) -> [Int]{...}
func mergeSortWithOutRecursion(arr: [Int]) -> [Int]{
var queue = Queue<[Int]>()
for i in arr{
queue.push([i])
}
while queue.queue.count > 1{
guard let arr1 = queue.pop() else{
return
}
guard let arr2 = queue.pop() else{
return
}
queue.push(merge(arr1, arr2))
}
return queue.pop()!
}
Radix sort는 자릿수를 이용해 정렬을 하는 정렬 기법이다. 0~9에 해당하는 버킷을 만들고, 낮은 자릿수 부터 진행하며, 순서대로 해당하는 자릿수의 버킷에 요소를 저장하고 정렬시키는 과정을 반복하여 정렬을 하는 기법입니다. 시간복잡도는 O(dn)으로 여기서 d는 자릿수를 의미합니다.
시간복잡도는 모두 O(n^2)
Selection sort는 정렬되어있을 시 비교연산만 하기 때문 O(n)의 시간복잡도를 가진다.
timsort 사용, 다양한 종류의 실제 데이터에서 잘 수행되도록 설계된 merge sort 및 insertion sort에서 파생된 안정적인 하이브리드 정렬방법. 최선의 경우 O(n) 최악의 경우 O(nlogn)
그래프란 노드와 edge로 구성되어있는 자료구조이다. 이를 구현하기 위해서는 연결 리스트와 배열을 이용하여 구현할 수 있다.
배열의 경우 인덱스를 이용해 접근하기 때문에 O(1) 만큼 걸리지만 연결리스트는 순회를 필요로 하기 때문에 최악의 경우 O(V)만큼 걸리게 된다.
배열의 경우 모든 노드를 수노히해야하기 떄문에 O(V)만큼 걸리지만 연결리스트는 O(E)만큼 걸린다.
이차원 배열은 O(V^2)의 공간복잡도를 가지나 연결리스트는 O(V+E)의 공간복잡도를 가진다.
간선의 개수가 많기 때문에 배열이 효율적이다.
아니요, 방향성이 없는 그래프는 트리가 아닙니다.
그래프의 종류에 따라 BFS(가중치 없는경우), 다익스트라(가중치가 다 양수일 경우), Bellman-ford 알고리즘 등이 있다.
LCA를 이용하면 가능하다.
O(N^2)으로 변화한다.
g(n)은 시작노드부터 현재 노드까지의 비용
h(n)은 휴리스틱 함수라 하여 주로 맨해튼거리나 유클리드 거리를 사용한다.
음수 간선이 있을시 floyd나 bellman을 사용하면 된다. 음수 사이클이 있으시에는 어떠한 알고리즘도 작동하지 않는다.
참고자료