Home > libalgo > 最近点対

最近点対

概要

2 次元の点集合を入力とし,距離が最小の 2 点を出力する. 点集合を $x$ 座標でソートしたあと,右半分と左半分で分割統治する.

計算量

$O(n \log n)$

使い方

  • 両端 (半開区間) のポインタまたはイテレータを渡す.
  • 事前に $x$ 座標昇順にソートしておくこと!
  • 呼出し後は $y$ 座標昇順にソートされている.

実装

template <class iter>
std::tuple<ld, P, P> closest_pair(iter left, iter right) {
    int n = distance(left, right);

    if (n == 1) {
        return std::make_tuple(std::numeric_limits<ld>::max(), *left, *left);
    }

    if (n == 2) {
        if (left[0].imag() > left[1].imag()) swap(left[0], left[1]);
        return std::make_tuple(norm(left[0] - left[1]), left[0], left[1]);
    }

    iter middle = std::next(left, n / 2);
    ld x = middle->real();
    auto d = std::min(closest_pair(left, middle), closest_pair(middle, right));
    std::inplace_merge(left, middle, right,
                       [](const P &a, const P &b) { return a.imag() < b.imag(); });

    std::vector<iter> around;
    for (iter i = left; i != right; ++i) {
        ld dx = fabs(i->real() - x);
        ld &opt = std::get<0>(d);
        if (dx * dx >= opt) continue;
        for (auto j = around.rbegin(); j != around.rend(); ++j) {
            ld dx = i->real() - (**j).real();
            ld dy = i->imag() - (**j).imag();
            if (dy * dy >= opt) break;
            ld norm = dx * dx + dy * dy;
            if (opt > norm) {
                d = std::make_tuple(norm, *i, **j);
            }
        }
        around.push_back(i);
    }
    return d;
};

検証

参考文献

蟻本