ヒープの強化【競技プログラミング】

# はじめに
- 過去、swiftに存在しない構造体である[ヒープを自作](https://qiita.com/kei_tnk/items/47f952f3053301faa265#%E3%81%8A%E3%81%BE%E3%81%91)したことがあった。
- このヒープにremove関数を付けてなかったので、追加することとする。
- また、chgNodeっていう、1カ所のノードの値を更新した場合の関数をおまけで付けたけど、これって、下方向のみ対応していたから、上方向にも対応させたよ。

# ヒープへのremove関数追加のロジック
- この時のヒープは、remove関数を装備してなかったけど、今回追加するロジックは以下のとおり。
    1. 取り除きたい値の格納されている配列インデックスi(ノード番号を一般的な1スタートとすれば、ノード番号=インデックス+1)を得る。
    1. 当該ノードの値を、配列の最後尾の値vで更新する。⇒chgNode(v,i)：計算量(O(log N))
    1. 配列の最後尾を削除する。⇒removeLast()!：計算量(O(1))
- 配列を使って実装したため、もし、値指定でremoveを実装すれば、上記1.では、firstIndexでインデックスを得ることになるけど、下記の通り低速。
    - firstIndex(of:)メソッド:O(N)
        - 理由: firstIndex(of:)メソッドは配列の先頭から順に要素を比較していくため、最悪の場合、配列全体を1回走査する必要があります。このため、計算量はO(N)となります。

# ヒープのremove高速化のための手当
- 配列の検索関数であるfirstIndexは低速(O(N))なので、コレを高速化したい。
- どうすれば良いか？最近は面倒くさがりなので、すぐに生成AIに聞いちゃう。
    - 自分で調べる場合でもgoogle先生に聞くだけなので、AIに聞いて手間を省くことで失われるのは、「誰のサイトが上手くまとまっているか？」という知識程度。それくらいなら、生成AIに聞いちゃう方が良いよね。おそらく、外国語のサイトの知識も持ってるだろうしね。
- で、AIの答えは、「要素のインデックスをハッシュテーブルで管理する」でした。
- 具体的には、以下のとおり。
- 要素をHashableとすると同時に、辞書型(ハッシュテーブル)のindicesを導入する。
```swift
/// 元のコード
struct Heap<V: Comparable>: CustomStringConvertible {
    private var data: [V] = []
    ...
}

/// 改善後コード
struct Heap<V: Comparable & Hashable>: CustomStringConvertible { // Hashableを追加
    private var data: [V] = []
    private var indices: [V: Int] = [:] // インデックスを管理するハッシュテーブルを追加
    ...
}
```
- 辞書型の特徴として、ハッシュテーブルを作成して要素を管理している点が上げられる。
- 配列では、indexで管理しているため、indexを指定して、要素を取り出すのは高速に行えるものの、値を指定して要素の位置を見つけるのは遅い。
- これに対して、辞書型では、キーに対してハッシュ値を算出し、ハッシュテーブルでデータ管理しているため、キーを指定すれば、すぐに要素を見つけられる。
- このindicesがどのように作られるかは、build関数に追加されたコードを見れば分かる。
```swift
/// 改善後コード
mutating func build() {
    for n in (0...(data.count / 2)).reversed() {
        heapify(n)
    }
    // インデックスを構築するためのハッシュテーブルを初期化
    for (index, value) in data.enumerated() {
        indices[value] = index // インデックスをハッシュテーブルに保存
    }
}
```
- 上記のとおり、「配列の値」が辞書型のキー、「配列のインデックス」が辞書型の値となる。
- 次は、追加されたremove関数がどのように動作するか確認しよう。
```swift
mutating func remove(_ value: V) -> Bool {
    // 値に対応するindexを計算量O(1)で取得、値が存在しなければ、ここで終了
    guard let index = indices[value] else {return false}

    // indexが末尾のインデックスでないとき、data[index]とdata[lastIndex]の値を入替
    let lastIndex = data.count - 1
    if index != lastIndex {
        data.swapAt(index, lastIndex) // 計算量O(1)
        indices[data[index]] = index // 入れ替え後のインデックスを更新
    }

    // 末尾のデータを削除
    data.removeLast()
    indices.removeValue(forKey: value) // ハッシュテーブルから削除

    // 並び替え実行
    if index != lastIndex {
        chgNode(index, data[index]) // 当然、chgNodeでもハッシュテーブル対応あり
    }
    
    return true
}
```

# 既存関数の変更
- heapify
    - 本来的には、indicesへの対応だけで良かったはずだけど、swapAtを使ってないことや見苦しさを指摘されたのと、そもそも、下方向にしか対応していないから、名称も変更した。なので、①swapAt導入や名称変更等と、②indicesの対応の2つに分けた。
        - それにしても、「while let」って構文は初めて知ったよ
```swift
///元のコード
mutating func heapify(_ n: Int) {
    var index = n
    var value = data[index]
    while true {
        guard let s0index = s0Index(index) else { return } // 子ノードが無ければ終了
        let s0Value = data[s0index]
        guard let s1index = s1Index(index) else { // 子ノードがs0のみの時、s0とのみ比較する
            if gt(value, s0Value) { return } // 子ノードの方が小さければ終了
            data[index] = s0Value
            data[s0index] = value
            return // s1が無いとき、下層ないためここで終了
        }
        let s1Value = data[s1index]
        let (sValue, sindex) = gt(s0Value, s1Value) ? (s0Value, s0index) : (s1Value, s1index) // どちらの子と入れ替えるか判定
        if gt(value, sValue) { return } // 子ノードの方が小さければ終了
        data[index] = sValue
        data[sindex] = value
        index = sindex // 子を親に入れ替えて、更に下の階層へ
    }
}

///①swapAt導入や名称変更等
mutating func heapifyDown(_ index: Int) { // ←名称追加(Down)、引数名をnからindexへ
    var p_index = index
    while let s0index = s0Index(p_index) { // 子ノードが無ければ終了
        let s0Value = data[s0index]
        var s_index = s0index
        if let s1index = s1Index(p_index), gt(data[s1index], s0Value) { // どちらの子と入れ替えるか判定
            s_index = s1index
        }
        if gt(data[p_index], data[s_index]) { return } // 子ノードの方が小さければ終了
        data.swapAt(p_index, s_index) // swapAt関数導入
        p_index = s_index // 子を親に入れ替えて、更に下の階層へ
    }
}

///②indicesの対応
mutating func heapifyDown(_ index: Int) {
    var p_index = index
    while let s0index = s0Index(p_index) {
        let s0Value = data[s0index]
        var s_index = s0index
        if let s1index = s1Index(p_index), gt(data[s1index], s0Value) {
            s_index = s1index
        }
        if gt(data[p_index], data[s_index]) { return }
        data.swapAt(p_index, s_index)
        indices[data[p_index]] = p_index // ←ハッシュテーブル対応
        indices[data[s_index]] = s_index // ←ハッシュテーブル対応
        p_index = s_index
    }
}
```
- 上方向のheapifyを追加(chgNodeで上方向に入れ替えていくため)
```swift
private mutating func heapifyUp(_ index: Int) {
    var s_index = index
    let s_value = data[s_index]
    var p_index = pIndex(s_index)

    while s_index > 0 && gt(s_value, data[p_index]) {
        data[s_index] = data[p_index]
        indices[data[p_index]] = s_index // ←ハッシュテーブル対応
        s_index = p_index
        p_index = pIndex(s_index)
    }
    data[s_index] = s_value
    indices[data[s_index]] = s_index // ←ハッシュテーブル対応
}
```
- あらたに、upとdownを使い分けるheapifyを作った。入れ替え後の値の大小でupとdownを切り替えるよ。
```swift
private mutating func heapify(_ index: Int, _ oldValue: V, _ newValue: V) {
    if gt(newValue, oldValue) {
        heapifyUp(index)
    } else {
        heapifyDown(index)
    }
}
```
- 次に、heapifyを使うchgNodeの変更に触れるね。
```swift
///元のコード
mutating func chgNode(_ i: V, _ n: Int) {
    data[n] = i
    heapify(n)
}

///変更後
mutating func chgNode(_ index: Int, _ newValue: V) { // 引数の場所を入れ替え

    // heapifyを上下方向に対応させた
    let oldValue = data[index]
    data[index] = newValue
    heapify(index, oldValue, newValue)

    // ハッシュテーブル対応
    indices[newValue] = index
    indices.removeValue(forKey: oldValue)
    
}

```
- insertは凄くスッキリしたよ。考えてみたら、heapifyUpって、ここで実装してたんだね。
```swift
///元のコード
mutating func insert(_ i: V) {
    data.append(i)
    var index = data.count - 1
    var value = i
    while index > 0 {
        let pindex = pIndex(index)
        let pValue = data[pindex]
        if gt(pValue, value) { return }
        data[index] = pValue
        data[pindex] = value
        index = pindex
    }
}

///変更後
mutating func insert(_ value: V) {
    data.append(value)
    indices[value] = data.count - 1 // ←ハッシュテーブル対応
    heapifyUp(data.count - 1) // heapifyUpでスッキリ！
}
```
- お次は、chgRootとpopRootをまとめて説明するよ
```swift
/// 元のコード
mutating func chgRoot(_ i: V) {
    chgNode(i, 0)
}

///変更後
mutating func chgRoot(_ newValue: V) {
    guard !data.isEmpty else { return } // 念のため追加(popRootにあわせた)

    // ハッシュテーブル対応
    let oldValue = data[0]
    indices[newValue] = 0
    indices.removeValue(forKey: oldValue)

    // 下方向しかないので、chgNodeから、heapifyDownに変更
    data[0] = newValue    
    heapifyDown(0)
}


///元のコード
mutating func popRoot() -> V? {
    if data.isEmpty { return nil }
    let ans = data[0]
    let last = data.removeLast()
    if data.count > 0 {
        chgRoot(last)
    }
    return ans
}

mutating func popRoot() -> V? {
    guard !data.isEmpty else { return nil }
    let rootValue = data[0]
    data[0] = data.removeLast()    
    indices.removeValue(forKey: rootValue) // ←ハッシュテーブル対応
    if !data.isEmpty {
        indices[data[0]] = 0 // ←ハッシュテーブル対応
        heapifyDown(0) // heapifyDownでスッキリ
    }
    return rootValue
}

```

# コード全体
- 今更ながら、max値(またはmin値)を取得するコードを忘れてたのに気付いたから、rootって名前で取れることにするね。これまでは、popRootで取り去るパターンしか使ってなかったからね。
- あと、上記の関数別の比較で見落としていた部分も込みで、下記のコード全体では、修正しているよ。
```swift
struct Heap<V: Comparable & Hashable>: CustomStringConvertible {
    private var data: [V] = []
    private var indices: [V: Int] = [:]
    private var maxHeap: Bool

    var description: String { return (maxHeap ? ">" : "<") + data.description }

    var root: V? { data.isEmpty ? nil : data[0] }

    init(_ data: [V] = [], maxHeap: Bool = true) {
        self.data = data
        self.maxHeap = maxHeap
        for i in data.indices {
            indices[data[i]] = i
        }
        for i in stride(from: data.count / 2 - 1, through: 0, by: -1) {
            heapifyDown(i)
        }
    }

    private func pIndex(_ index: Int) -> Int {
        return (index + 1) / 2 - 1
    }

    private func s0Index(_ index: Int) -> Int? {
        let s0 = 2 * (index + 1) - 1
        return s0 < data.count ? s0 : nil
    }

    private func s1Index(_ index: Int) -> Int? {
        let s1 = 2 * (index + 1)
        return s1 < data.count ? s1 : nil
    }

    private func gt(_ l: V, _ r: V) -> Bool {
        return (maxHeap ? l > r : l < r)
    }

    private mutating func heapifyUp(_ index: Int) {
        var s_index = index
        let s_value = data[s_index]
        var p_index = pIndex(s_index)

        while s_index > 0 && gt(s_value, data[p_index]) {
            data[s_index] = data[p_index]
            indices[data[p_index]] = s_index
            s_index = p_index
            p_index = pIndex(s_index)
        }
        data[s_index] = s_value
        indices[data[s_index]] = s_index
    }

    private mutating func heapifyDown(_ index: Int) {
        var p_index = index
        while let s0index = s0Index(p_index) {
            let s0Value = data[s0index]
            var s_index = s0index
            if let s1index = s1Index(p_index), gt(data[s1index], s0Value) {
                s_index = s1index
            }
            if gt(data[p_index], data[s_index]) { return }
            data.swapAt(p_index, s_index)
            indices[data[p_index]] = p_index
            indices[data[s_index]] = s_index
            p_index = s_index
        }
    }

    private mutating func heapify(_ index: Int, _ oldValue: V, _ newValue: V) {
        if gt(newValue, oldValue) {
            heapifyUp(index)
        } else {
            heapifyDown(index)
        }
    }

    mutating func insert(_ value: V) {
        data.append(value)
        indices[value] = data.count - 1
        heapifyUp(data.count - 1)
    }

    mutating func popRoot() -> V? {
        guard !data.isEmpty else { return nil }
        let rootValue = data[0]
        indices.removeValue(forKey: rootValue)
        if data.count == 1 {
            return data.removeLast()
        } else {
            data[0] = data.removeLast()
            indices[data[0]] = 0
            heapifyDown(0)
            return rootValue
        }
    }

    mutating func chgNode(_ index: Int, _ newValue: V) {
        let oldValue = data[index]
        data[index] = newValue
        indices[newValue] = index
        indices.removeValue(forKey: oldValue)
        heapify(index, oldValue, newValue)
    }

    mutating func chgRoot(_ newValue: V) {
        guard !data.isEmpty else { return }
        let oldValue = data[0]
        data[0] = newValue
        indices[newValue] = 0
        indices.removeValue(forKey: oldValue)
        heapifyDown(0)
    }

    mutating func remove(_ value: V) -> Bool {
        guard let index = indices[value] else {
            return false
        }
        let lastIndex = data.count - 1
        let oldValue = data[index]
        if index != lastIndex {
            data.swapAt(index, lastIndex)
            indices[data[index]] = index
        }
        data.removeLast()
        indices.removeValue(forKey: value)
        if index != lastIndex {
            heapify(index, oldValue, data[index])
        }
        return true
    }
}

```
- 実行例(出力は、コメントを見て)
```swift
let vs0 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let vs1 = ["c", "b", "a", "C", "B", "A", "4", "3", "2", "1"] // 降順

var h0 = Heap(vs0)
print(h0) // >[10, 9, 7, 8, 5, 6, 3, 1, 4, 2]

var h1 = Heap(vs1, maxHeap: false)
print(h1) // <["1", "2", "4", "3", "B", "A", "a", "c", "C", "b"]

// 要素の追加 (insert)
h0.insert(11)
print(h0) // >[11, 10, 7, 8, 9, 6, 3, 1, 4, 2, 5]

h1.insert("b")
print(h1) // <["1", "2", "4", "3", "B", "A", "a", "c", "C", "b", "b"]

// 要素の削除 (remove)
h0.remove(7)
print(h0) // >[11, 10, 6, 8, 9, 5, 3, 1, 4, 2]
h1.remove("4")
print(h1) // <["1", "2", "A", "3", "B", "b", "a", "c", "C", "b"]

// 根の取り出し (popRoot)
if let root0 = h0.popRoot() {
    print("Root of h0: \(root0)") // Root of h0: 11
}
print(h0) // >[10, 9, 6, 8, 2, 5, 3, 1, 4]

if let root1 = h1.popRoot() {
    print("Root of h1: \(root1)") // Root of h1: 1
}
print(h1) // <["2", "3", "A", "C", "B", "b", "a", "c", "b"]

// ノードの値変更 (chgNode)
h0.chgNode(2, 20)
print(h0) // >[20, 9, 10, 8, 2, 5, 3, 1, 4]
h1.chgNode(2, "Z")
print(h1) // <["2", "3", "Z", "C", "B", "b", "a", "c", "b"]

// 根の値変更 (chgRoot)
h0.chgRoot(15)
print(h0) // >[15, 9, 10, 8, 2, 5, 3, 1, 4]
h1.chgRoot("X")
print(h1) // <["3", "B", "Z", "C", "X", "b", "a", "c", "b"]
```

# おわりに
- これで、ヒープを使った戦いの幅が広がったよ。

# 2つのheapを合体させるunion関数を作る
- ヒープは高速にmaxかminを出す目的で作るから、unionが活用される場面はないと思うけど、実験的な意味でやってみる。
- 2つのインスタンスのうち、片方のインスタンスのunionによって、新しいheapを作ることとして、2つのインスタンスに変更はないこととする。計算量は、O(n1+n2)みたいになるんだろうね。
```swift
let vs0 = [1, 2, 3, 4, 5]
let vs1 = [6, 7, 8, 9, 10]

var h0 = Heap(vs0) 
var h1 = Heap(vs1)

var hmix = h0.union(h1)
```
- やりたいのは、上記を可能とすること。追加したコードは、下記の通り。
```swift
    var getData : [V] {data}
    
    func union(_ other:Self) -> Self {
        Self(self.data + other.getData)
    }
```
- 実行結果は以下のとおり。
```swift
print(hmix) // >[10, 8, 9, 6, 7, 3, 5, 1, 4, 2]
```