0
0

More than 1 year has passed since last update.

Goで Quick Select を書いた

Posted at

日本語の資料が乏しかったので,ここにまとめます。
必要な前提知識も多く、この記事単体では完結していないです。
詳しくは、参考文献等をご参照ください。
また、書かれているコードは、以下の問題で検証済みですが、
不十分であるため、コピペされる方は自己責任でお願いいたします。

Quick Select とは

「ある配列の中で、k番目に小さい値はいくつ?」に答えるアルゴリズム。
クイックソートの要領で pivot を選択し、pivotを元に、配列を分割しながら答えを探す。
愚直に考えると、ソートしてk番目をとればいいが、その場合は、最悪計算量がソートがボトルネックになり$O(nlogn)$になる。
一方、このアルゴリズムは、平均計算量$O(n)$で解くことができる。

実装方針

pivot の選択方法には、以下の方法があるが、今回は中央値の中央値をpivotにすることにした。

  • 常に左の要素を採用する
  • ランダムに取得した要素を採用する
  • 中央値の中央値を採用する

また、pivotに対して、配列を並べ替える戦略が以下にあるが、効率がよい前者を採用した。

  • Hoare partition scheme
  • Lomuto partition scheme

コード

func insertionSort(a []int, left, right int) {
    for i := left; i <= right; i++ {
        j := i
        for (j > left) && (a[j-1] > a[j]) {
            a[j-1], a[j] = a[j], a[j-1]
            j--
        }
    }
}

func median(a []int, left, right int) int {
    insertionSort(a, left, right)
    return a[left+(right-left)/2]
}

func partition(a []int, left, right, pivot int) int {
    i := left - 1
    j := right + 1

    for {
        i, j = i+1, j-1
        for a[i] < pivot {
            i += 1
        }
        for a[j] > pivot {
            j -= 1
        }
        if i < j {
            a[i], a[j] = a[j], a[i]
        } else {
            return j
        }
    }
}

func selectPivot(a []int, left, right int) int {
    if right-left < 5 {
        return median(a, left, right)
    }

    for i := left; i+4 <= right; i += 5 {
        insertionSort(a, i, i+4)
        a[i+2], a[left+(i-left)/5] = a[left+(i-left)/5], a[i+2]
    }

    n := right - left + 1
    return innerSelect(a, left, left+n/5-1, left+n/10-1)
}

func innerSelect(a []int, left, right, kth int) int {
    if left == right {
        return a[left]
    }

    pivot := selectPivot(a, left, right)
    pivotIndex := partition(a, left, right, pivot)
    if kth <= pivotIndex {
        return innerSelect(a, left, pivotIndex, kth)
    } else {
        return innerSelect(a, pivotIndex+1, right, kth)
    }
}

func QuickSelect(a []int, left, right, kth int) int {
    return innerSelect(a, left, right, kth)
}

参考文献

クイックセレクトの考え方の元となるクイックソートの詳細

クイックセレクトの実装の概要

中央値の中央値のわかりやすい解説

実装がハマったときにみたサイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0