在clojure[script]
中,如何编写一个函数nearest
,它接收两个已排序的向量 a
,b
并返回{的每个元素{1}} a
的最近元素?
举个例子,
b
我希望解决方案兼具惯用性和良好的表现:(nearest [1 2 3 101 102 103] [0 100 1000]); [0 0 0 100 100 100]
是不可接受的!
答案 0 :(得分:4)
使用二进制搜索或排序集会产生O(n * log m)时间复杂度,其中n为(count a)
和m (count b)
。
然而,利用a和b排序的事实,时间复杂度可以是O(max(n,m))。
(defn nearest [a b]
(if-let [more-b (next b)]
(let [[x y] b
m (/ (+ x y) 2)
[<=m >m] (split-with #(<= % m) a)]
(lazy-cat (repeat (count <=m) x)
(nearest >m more-b)))
(repeat (count a) (first b))))
=> (nearest [1 2 3 101 102 103 501 601] [0 100 1000])
(0 0 0 100 100 100 100 1000)
答案 1 :(得分:3)
让n
为(count a)
,m
为(count b)
。然后,如果a
和b
是两个,那么这可以在我认为应该O(n log(log m))
时间完成,换句话说,非常接近在n
中线性化。
首先,让我们重新实现abs
和二进制搜索(改进here)独立于主机(利用本机,例如Java,版本应该明显更快)
(defn abs [x]
(if (neg? x) (- 0 x) x))
(defn binary-search-nearest-index [v x]
(if (> (count v) 1)
(loop [lo 0 hi (dec (count v))]
(if (== hi (inc lo))
(min-key #(abs (- x (v %))) lo hi)
(let [m (quot (+ hi lo) 2)]
(case (compare (v m) x)
1 (recur lo m)
-1 (recur m hi)
0 m))))
0))
如果b
已排序,则b
中的二进制搜索需要log m
步。因此,将此映射到a
是一个O(n log m)
解决方案,对于实用主义者而言可能已经足够了。
(defn nearest* [a b]
(map #(b (binary-search-nearest-index b %)) a))
但是,我们也可以使用a
排序以分割和征服a
的事实。
(defn nearest [a b]
(if (< (count a) 3)
(nearest* a b)
(let [m (quot (count a) 2)
i (binary-search-nearest-index b (a m))]
(lazy-cat
(nearest (subvec a 0 m) (subvec b 0 (inc i)))
[(b i)]
(nearest (subvec a (inc m)) (subvec b i))))))
我认为这应该是O(n log(log m))
。我们从a
的中位数开始,并在b
时间内找到距log m
最近的位置。然后我们以a
的分割部分递归b
的每一半。如果每次分割m
- 比例因子b,则有O(n log log m)。如果仅分离出一个常数部分,则在该部分上工作的a
的一半是线性时间。如果继续(对b
的常量大小部分的工作迭代一半),那么你有O(n)
。
答案 2 :(得分:1)
受@ amalloy的启发,我找到了这个interesting idea by Chouser并写了这个解决方案:
(defn abs[x]
(max x (- x)))
(defn nearest-of-ss [ss x]
(let [greater (first (subseq ss >= x))
smaller (first (rsubseq ss <= x))]
(apply min-key #(abs (- % x)) (remove nil? [greater smaller]))))
(defn nearest[a b]
(map (partial nearest-of-ss (apply sorted-set a)) b))
备注:创建sorted-set
一次非常重要,以避免性能下降!