【发布时间】:2009-09-03 23:29:48
【问题描述】:
我有一种情况,我需要找到与我请求的键最接近的值。这有点像定义键之间距离的最近地图。
例如,如果我在地图中有键 {A, C, M, Z},则对 D 的请求将返回 C 的值。
有什么想法吗?
【问题讨论】:
我有一种情况,我需要找到与我请求的键最接近的值。这有点像定义键之间距离的最近地图。
例如,如果我在地图中有键 {A, C, M, Z},则对 D 的请求将返回 C 的值。
有什么想法吗?
【问题讨论】:
大多数树数据结构使用某种排序算法来存储和查找键。这样的许多实现可以找到您探测的键的关闭键(通常它是最接近的下方或最接近的上方)。例如,Java 的 TreeMap 实现了这样的数据结构,您可以告诉它获取查找键下方最接近的键,或查找键上方最接近的键(higherKey 和 lowerKey)。
如果您可以计算距离(它并不总是那么容易 - Java 的界面只要求您知道任何给定键是“低于”还是“高于”任何其他给定键),那么您可以要求最接近的上方和最接近的下方和然后自己计算哪个更接近。
【讨论】:
您的数据的维度是多少?如果它只是一维的,则排序数组会执行此操作 - 二进制搜索将找到完全匹配的位置和/或显示您的搜索关键字位于哪两个关键字之间 - 并且一个简单的测试会告诉您哪个更接近。
如果您不仅需要查找最近的键,还需要查找关联的值,请维护一个相同排序的值数组 - 键数组中检索到的键的索引就是值数组中的值的索引。
当然,有许多替代方法——使用哪一种取决于许多其他因素,例如内存消耗、是否需要插入值、是否控制插入顺序、删除,线程问题等......
【讨论】:
BK-trees 做你想做的事。这是一个关于实施它们的good article。
这是一个 Scala 实现:
class BKTree[T](computeDistance: (T, T) => Int, node: T) {
val subnodes = scala.collection.mutable.HashMap.empty[Int,BKTree[T]]
def query(what: T, distance: Int): List[T] = {
val currentDistance = computeDistance(node, what)
val minDistance = currentDistance - distance
val maxDistance = currentDistance + distance
val elegibleNodes = (
subnodes.keys.toList
filter (key => minDistance to maxDistance contains key)
map subnodes
)
val partialResult = elegibleNodes flatMap (_.query(what, distance))
if (currentDistance <= distance) node :: partialResult else partialResult
}
def insert(what: T): Boolean = if (node == what) false else (
subnodes.get(computeDistance(node, what))
map (_.insert(what))
getOrElse {
subnodes(computeDistance(node, what)) = new BKTree(computeDistance, what)
true
}
)
override def toString = node.toString+"("+subnodes.toString+")"
}
object Test {
def main(args: Array[String]) {
val root = new BKTree(distance, 'A')
root.insert('C')
root.insert('M')
root.insert('Z')
println(findClosest(root, 'D'))
}
def charDistance(a: Char, b: Char) = a - b abs
def findClosest[T](root: BKTree[T], what: T): List[T] = {
var distance = 0
var closest = root.query(what, distance)
while(closest.isEmpty) {
distance += 1
closest = root.query(what, distance)
}
closest
}
}
我承认它有些肮脏和丑陋,而且插入算法太聪明了。此外,它只适用于小距离,否则您将重复搜索树。这是一个更好的替代实现:
class BKTree[T](computeDistance: (T, T) => Int, node: T) {
val subnodes = scala.collection.mutable.HashMap.empty[Int,BKTree[T]]
def query(what: T, distance: Int): List[T] = {
val currentDistance = computeDistance(node, what)
val minDistance = currentDistance - distance
val maxDistance = currentDistance + distance
val elegibleNodes = (
subnodes.keys.toList
filter (key => minDistance to maxDistance contains key)
map subnodes
)
val partialResult = elegibleNodes flatMap (_.query(what, distance))
if (currentDistance <= distance) node :: partialResult else partialResult
}
private def find(what: T, bestDistance: Int): (Int,List[T]) = {
val currentDistance = computeDistance(node, what)
val presentSolution = if (currentDistance <= bestDistance) List(node) else Nil
val best = currentDistance min bestDistance
subnodes.keys.foldLeft((best, presentSolution))(
(acc, key) => {
val (currentBest, currentSolution) = acc
val (possibleBest, possibleSolution) =
if (key <= currentDistance + currentBest)
subnodes(key).find(what, currentBest)
else
(0, Nil)
(possibleBest, possibleSolution) match {
case (_, Nil) => acc
case (better, solution) if better < currentBest => (better, solution)
case (_, solution) => (currentBest, currentSolution ::: solution)
}
}
)
}
def findClosest(what: T): List[T] = find(what, computeDistance(node, what))._2
def insert(what: T): Boolean = if (node == what) false else (
subnodes.get(computeDistance(node, what))
map (_.insert(what))
getOrElse {
subnodes(computeDistance(node, what)) = new BKTree(computeDistance, what)
true
}
)
override def toString = node.toString+"("+subnodes.toString+")"
}
object Test {
def main(args: Array[String]) {
val root = new BKTree(distance, 'A')
root.insert('C')
root.insert('E')
root.insert('M')
root.insert('Z')
println(root.findClosest('D'))
}
def charDistance(a: Char, b: Char) = a - b abs
}
【讨论】:
对于 C++ 和 STL 容器 (std::map),您可以使用以下模板函数:
#include <iostream>
#include <map>
//!This function returns nearest by metric specified in "operator -" of type T
//!If two items in map are equidistant from item_to_find, the earlier occured by key will be returned
template <class T,class U> typename std::map<T,U>::iterator find_nearest(std::map<T,U> map_for_search,const T& item_to_find)
{
typename std::map<T,U>::iterator itlow,itprev;
itlow=map_for_search.lower_bound(item_to_find);
itprev=itlow;
itprev--;
//for cases when we have "item_to_find" element in our map
//or "item_to_find" occures before the first element of map
if ((itlow->first==item_to_find) || (itprev==map_for_search.begin()))
return itlow;
//if "item"to_find" is besides the last element of map
if (itlow==map_for_search.end())
return itprev;
return (itlow->first-item_to_find < item_to_find-itprev->first)?itlow:itprev; // C will be returned
//note that "operator -" is used here as a function for distance metric
}
int main ()
{
std::map<char,int> mymap;
std::map<char,int>::iterator nearest;
//fill map with some information
mymap['B']=20;
mymap['C']=40;
mymap['M']=60;
mymap['Z']=80;
char ch='D'; //C should be returned
nearest=find_nearest<char,int>(mymap,ch);
std::cout << nearest->first << " => " << nearest->second << '\n';
ch='Z'; //Z should be returned
nearest=find_nearest<char,int>(mymap,ch);
std::cout << nearest->first << " => " << nearest->second << '\n';
ch='A'; //B should be returned
nearest=find_nearest<char,int>(mymap,ch);
std::cout << nearest->first << " => " << nearest->second << '\n';
ch='H'; // equidistant to C and M -> C is returned
nearest=find_nearest<char,int>(mymap,ch);
std::cout << nearest->first << " => " << nearest->second << '\n';
return 0;
}
输出:
C => 40
Z => 80
B => 20
C => 40
假设operator - 用作评估距离的函数。如果class T 是您自己的类,您应该实现该运算符,其对象用作映射中的键。
您还可以更改代码以使用特殊的class T 静态成员函数(例如distance),而不是operator -,而是:
return (T::distance(itlow->first,item_to_find) < T::distance(item_to_find,itprev->first))?itlow:itprev;
distance 应该是什么。喜欢
static distance_type some_type::distance()(const some_type& first, const some_type& second){//...}
和distance_type应该支持operator <比较
【讨论】:
您可以将这样的东西实现为一棵树。一种简单的方法是为树中的每个节点分配一个位串。树的每一层都存储为一个位。所有父信息都编码在节点的位串中。然后,您可以轻松定位任意节点,并找到父节点和子节点。例如,Morton ordering 就是这样工作的。它还有一个额外的好处是可以通过简单的二进制减法计算节点之间的距离。
如果数据值之间有多个链接,那么您的数据结构是图而不是树。在这种情况下,您需要一个稍微复杂的索引系统。 Distributed hash tables 做这种事情。它们通常有一种计算索引空间中任意两个节点之间距离的方法。例如,Kademlia 算法(由 Bittorrent 使用)使用应用于位串 id 的 XOR 距离。这允许 Bittorrent 客户端在链中查找 id,收敛于未知的目标位置。您可以使用类似的方法来查找离您的目标节点最近的节点。
【讨论】:
如果你的键是字符串并且你的相似函数是Levenshtein distance,那么你可以使用finite-state machines:
您的地图是一个trie,构建为有限状态机(通过合并所有键/值对并确定)。然后,使用对 Levenshtein 距离进行编码的简单有限状态转换器组合您的输入查询,并使用您的 trie 组合它。然后,使用Viterbi algorithm 提取最短路径。
您可以通过使用finite-state toolkit 的几个函数调用来实现所有这些。
【讨论】:
在 scala 中,这是我用来查找与您正在寻找的键最近的 Int
val sMap = SortedMap(1 -> "A", 2 -> "B", 3 -> "C")
sMap.to(4).lastOption.get // Returns 3
sMap.to(-1) // Returns an empty Map
【讨论】: