본문 바로가기

Swift/자료구조

Swift 힙(Heap) 구현

Heap

  • Heap이란 완전이진트리의 일종으로 우선순위 큐를 위해 만들어진 자료구조이다.
  • 여러 가지의 값 중, 최댓값과 최솟값을 빠르게 찾아내기 위한 자료구조이다.
  • 중복값을 허용한다.

완전이진트리

- 마지막 레벨을 제외하고 모든 레벨에 노드가 꽉 차있다.

- 자식노드는 최대 2개만 가질 수 있고, 노드가 왼쪽부터 채워져야 한다.

- 배열을 사용한 표현이 가능하다.

 

 

 

Swift로 Heap 구현

  • 힙을 저장하는 자료구조는 배열이다.
  • 쉽게 구현하기 위해 배열의 첫 번째 인덱스인 0은 사용하지 않는다.
  • 코드로 보기 전에 Heap의 성질들을 본다.
  • 힙은 구조체로 구현한다.
struct Heap<T: Comparable> {
	// 힙의 원소를 넣을 배열 선언
    // 최대힙,최소힙 정렬함수 선언
    private var elements: [T] = []
    private let sortFunction: (T,T) -> Bool
    
    // 힙의 인덱스 0 은 사용하지 않는다
    // 인덱스 0의 값만 있으면 비어있다고 판단
    var isEmpty: Bool {
        return elements.isEmpty || elements.count == 1
    }
    
    // 루트노드의 원소 출력
    var peek: T? {
        if self.elements.isEmpty { return nil}
        return self.elements[1]
    }
    
    // elements[0]을 제외한 원소갯수
    var count: Int {
        if elements.isEmpty {
            return 0
        } else {
            return elements.count - 1
        }
    }
    
    // buildHeap() 함수는 힙의 재구성 함수
    // 인덱스0은 사용하지 않으므로 임의의 값 넣어서 칸 차지하기
    init(elements: [T] = [], sortFunction: @escaping (T,T) -> Bool) {
        if !elements.isEmpty {
            self.elements = [elements.first!] + elements
        } else {
            self.elements = elements
        }
        self.sortFunction = sortFunction
        if elements.count > 1 {
            self.buildHeap()
        }
    }
}
// 처음 힙이 선언되었을때 정렬함수를 통해 힙을 구성하는 함수이다.
// diveDown 함수는 입력된 index의 자식노드들과 정렬함수를 통해 값 비교 후 교환하는 함수이다.
// self.element.count / 2 는 노드중 자식노드를 가진 노드들을 전부 탐색한다.

	mutating func buildHeap() {
        for index in (1...(self.elements.count / 2)).reversed() {
            self.diveDown(from: index)
        }
    }

 

사진 출처: https://ko.wikipedia.org/wiki/%ED%9E%99_(%EC%9E%90%EB%A3%8C_%EA%B5%AC%EC%A1%B0) 

부모 노드와 자식 노드의 관계

왼쪽 자식 index = (부모 index) * 2
오른쪽 자식 index = (부모 index) * 2 + 1
부모 index = (자식 index) / 2
    // 왼쪽자식노드의 인덱스
    func leftChild(of index: Int) -> Int {
        return index * 2
    }
    // 오른쪽자식노드의 인덱스
    func rightChild(of index: Int) -> Int {
        return index * 2 + 1
    }
    // 부모노드의 인덱스
    func parent(of index: Int) -> Int {
        return index / 2
    }

 

힙의 삽입

1. 새로운 노드를 넣을 때, 배열의 마지막에 넣는다.

2. 새로운 노드를 부모노드와 비교하여 교환한다.

3. 부모노드는 새로운 노드의 index / 2 이므로 서로 swap 한다.

 

    // 힙이 비어있다면, 노드 추가 2번 (첫번째 인덱스를 1로 갖기 위해)
    // 힙이 비어있지 않다면 배열의 마지막에 노드 추가
    // 최대힙, 최소힙 정렬기준에따라 추가된 노드와 부모노드 비교 및 교환
    mutating func insert(element: T) {
        if self.elements.isEmpty {
            self.elements.append(element)
            self.elements.append(element)
            return
        }
        self.elements.append(element)
        self.swimUp(from: self.elements.endIndex - 1)
    }
// 힙의 정렬기준 sortFuction에 따라 부모노드와 자식노드 교환
	mutating func swimUp(from index: Int) {
        var index = index
        while index != 1 && self.sortFunction(self.elements[index], self.elements[self.parent(of: index)]) {
            self.elements.swapAt(index, self.parent(of: index))
            index = self.parent(of: index)
        }
    }

 

힙의 삭제

1. 힙의 루트노드를 마지막 원소와 바꾼다. swap

2. 마지막원소를 꺼내고 루트노드부터 자식노드와 교환한다.

3. 힙을 재구성한다.

 

 // 삭제할 루트노드 출력
 // 루트노드와 마지막노드를 바꾼후, 마지막 노드를 꺼낸다.
 // 루트노드를 자식노드들과 정렬기준으로 교환
 	mutating func remove() -> T? {
        if self.elements.isEmpty { return nil}
        self.elements.swapAt(1, self.elements.endIndex - 1)
        let deleted = self.elements.removeLast()
        self.diveDown(from: 1)

        return deleted
    }
// 자식노드들의 인덱스를 가져와 부모노드와 정렬함수를 통해 값 비교후 교환한다.
// 부모노드가 정렬기준으로 자리를 찾을 때 까지 계속 자식노드들과 비교후 교환한다.
	mutating func diveDown(from index: Int) {
        var higherPriority = index
        let leftIndex = self.leftChild(of: index)
        let rightIndex = self.rightChild(of: index)

        if leftIndex < self.elements.endIndex && self.sortFunction(self.elements[leftIndex], self.elements[higherPriority]) {
            higherPriority = leftIndex
        }
        if rightIndex < self.elements.endIndex && self.sortFunction(self.elements[rightIndex], self.elements[higherPriority]) {
            higherPriority = rightIndex
        }

        if higherPriority != index {
            self.elements.swapAt(index, higherPriority)
            self.diveDown(from: higherPriority)
        }
    }

 

힙의 시간복잡도 ( O(logn) )

- 배열의 최대 최소를 구하면 O(n)이 걸렸지만, 힙에 넣고 정렬한다면 더 빠르게 찾을 수 있다.

 

 

Heap 구현 코드

+ index 계산을 위해 첫 원소의 인덱스값을 1로 설정하므로 이에 IsEmpty와 insert부분 수정

import Foundation

struct Heap<T: Comparable> {
    var elements: [T] = []
    var sortFunction: (T,T) -> Bool
    var isEmpty: Bool {
        return elements.isEmpty || elements.count == 1
    }
    var peek: T? {
        if elements.isEmpty {
            return nil
        } else {
            return elements[1]
        }
    }
    var count: Int {
        if elements.isEmpty {
            return 0
        } else {
            return elements.count - 1
        }
    }
    init(elements: [T] = [], sortFunction: @escaping (T,T) -> Bool) {
        if !elements.isEmpty {
            self.elements = [elements.first!] + elements
        } else {
            self.elements = elements
        }
        self.sortFunction = sortFunction
        if elements.count > 1 {
            self.buildHeap()
        }
    }
    func leftChild(of index: Int) -> Int {
        return index * 2
    }
    func rightChild(of index: Int) -> Int {
        return index * 2 + 1
    }
    func parent(of index: Int) -> Int {
        return index / 2
    }

    mutating func swimUp(from index: Int) {
        var idx = index
        while idx != 1 && self.sortFunction(self.elements[idx], self.elements[parent(of: idx)]) {
            self.elements.swapAt(idx, self.parent(of: idx))
            idx = self.parent(of: idx)
        }
    }
    mutating func insert(element: T) {
        if self.elements.isEmpty {
            self.elements.append(element)
            self.elements.append(element)
            return
        }
        self.elements.append(element)
        self.swimUp(from: self.elements.endIndex - 1)
    }
    mutating func diveDown(from index: Int) {
        var higherPriority = index
        let leftIndex = self.leftChild(of: index)
        let rightIndex = self.rightChild(of: index)

        if leftIndex < self.elements.endIndex && self.sortFunction(self.elements[leftIndex], self.elements[higherPriority]) {
            higherPriority = leftIndex
        }
        if rightIndex < self.elements.endIndex && self.sortFunction(self.elements[rightIndex], self.elements[higherPriority]) {
            higherPriority = rightIndex
        }
        if higherPriority != index {
            self.elements.swapAt(index, higherPriority)
            self.diveDown(from: higherPriority)
        }
    }
    
    mutating func buildHeap() {
        for i in (1...(self.elements.count / 2)).reversed() {
            self.diveDown(from: i)
        }
    }
    mutating func remove() -> T? {
        if self.isEmpty {
            return nil
        }
        self.elements.swapAt(1, self.elements.endIndex - 1)
        var returnValue = self.elements.removeLast()
        self.diveDown(from: 1)
        
        return returnValue
    }
}

'Swift > 자료구조' 카테고리의 다른 글

분리집합과 Union-Find Swift  (1) 2024.04.28
Swift 덱(Deque) 구현  (0) 2024.04.03
Swift 큐(Queue) 구현  (0) 2024.04.03
Swift 우선순위 큐(Priority Queue) 구현  (0) 2023.05.17
Swift 스택(Stack) 구현  (0) 2023.04.17