已知组键最快的字符串键查找

考虑下面的签名,这需要返回一个给定的字符串键整数的查找功能:

int GetValue(string key) { ... }

考虑进一步的键 - 值映射,编号N,被称为提前被写入了功能的源代码时,例如:

// N=3 { "foo", 1 }, { "bar", 42 }, { "bazz", 314159 }

因此,对于为输入功能的有效(!但并不完美)执行上面会:

int GetValue(string key) { switch (key) { case "foo": return 1; case "bar": return 42; case "bazz": return 314159; } // Doesn't matter what we do here, control will never come to this point throw new Exception(); }

它也预先知道究竟有多少次(C> = 1)的功能将在运行时为每一个给定的键调用。 例如:

C["foo"] = 1; C["bar"] = 1; C["bazz"] = 2;

此类电话的顺序不但是闻名。 如上面的描述可以在运行时调用的顺序如下:

GetValue("foo"); GetValue("bazz"); GetValue("bar"); GetValue("bazz");

或任何其他序列,只要调用计数匹配。

也有一个限制男,在任何单位指定是最方便的,定义的约束,可以由使用任何查找表和其它辅助结构的上存储器GetValue (该结构被预先初始化;在初始化未对计数函数的复杂性)。 例如,M = 100个字符,或M = 256的sizeof(对象参考)。

现在的问题是,如何写体GetValue ,使得它尽可能快的-换句话说,所有的总时间GetValue调用(注意,我们知道总数,每高于一切)是最小的,用于指定N ,C和M?

该算法可能需要男,例如M> =一个合理的最小值char.MaxValue 。 它也可以要求在于M对准一些合理的边界 - 例如,它可能只为二的幂。 它也可以要求在于M必须是某一种N(例如一个功能,它可允许有效M = N或M = 2N,...;或有效M = N或M = N ^ 2, ...,等等)。

该算法可以以任何合适的语言或其他形式来表示。 有关生成的代码运行时的性能限制,假设为生成的代码GetValue将在C#,VB或Java(真的像字符串作为不可变的字符数组处理任何语言都行,只要-即O(1)长和O(1)的索引,以及预先计算的对他们没有其它数据)。 另外,为了简化此一点,这假定C = 1对于所有键被认为是有效的,尽管那些覆盖更一般的情况的答案是优选的答案。

在可能的方法的一些思索

最明显的一个答案上面用一个完美的哈希值,但通用的方法来找到一个似乎是完美的。 例如,人们可以很容易产生用于使用皮尔逊散列对于上述的样品数据最小完美哈希表,但随后的输入键将必须被散列为每次调用GetValue ,和Pearson散列一定扫描整个输入串。 但所有样品键实际上在其第三个字符不同,所以仅可以使用作为输入的散列而非整个字符串。 此外,如果M被要求为至少char.MaxValue ,则第三字符本身变成一个完备散列。

为一组不同的密钥,这可能不再是真实的,但它仍然有可能减少被认为可以给出精确的答案之前的字符的量。 此外,在一些情况下, 最小的完美散列将需要检查整个字符串,有可能到查找减少到一个子集,或以其它方式通过使散列非最小使其更快(例如,一个不复杂的散列函数?) (即M> N) - 有效地牺牲空间速度的原因。

它也可以是传统的散列是没有这样开始与一个好主意,它更容易构造的本体GetValue作为一系列条件句,布置为使得对于“最变量”字符(即改变所述一个所述第一检查在大多数键),以根据需要确定正确的答案进一步嵌套检查。 注意,“方差”在这里可以用的倍每个键是要的数目的影响,以进行查找(℃)。 此外,它并不总是容易显而易见分支的最佳结构应该是什么 - 它可以是,例如,“最变量”字符只让你区分10个键选自100,但对于其余90,一个额外的检查是不必要在它们之间进行区分,并在平均(考虑℃)有每个键更多的检查不是在不同的解决方案,它与“最变量”字符开始。 然后该目标是确定支票的完美序列。

--------------解决方案-------------

您可以使用博耶搜索,但我认为特里将是一个更加effiecent方法。 您可以修改特里为您做出命中计数的一个关键零,从而减少搜索次数,你就必须做下来你行越远崩溃的话。 你会得到最大的好处是,你正在做阵列查找的指标,这比比较快很多。

您谈到了一个内存限制 ,当涉及到预先计算-有还时间限制

我认为一个线索,而是一个在那里你并不一定与第一个字符开头。 相反,找到这将减少搜索空间最,并认为第一个索引。 因此,在您的样品的情况下(“富”,“酒吧”,“bazz”),你会采取第三个字符,这会立即告诉你,这是哪个字符串。 (如果我们知道我们将永远被赋予输入一个关键词,我们可以尽快,因为我们已经找到了一个独特的潜力比赛回来。)

现在,假设没有这将让你到一个独特的字符串中的一个指数,你需要确定字符看后。 从理论上讲,你预先计算特里制定出每个分支什么最佳字符来看看接下来就是(例如,“如果第三个角色是'A',我们需要看看接下来的第二个字符,如果是'O'我们需要看接下来的第一个字符),但可能需要更多的时间和空间。另一方面,它可以节省大量的时间-因为在经历下来一个字符,每个分支可能有一个索引来接这将唯一确定最终的字符串,但要一个不同的索引各一次。通过这种方法所需的空间量将取决于字符串的相似是,和可能很难提前预测,这将是很好能够动态地为你做这个可以将所有的线索节点,但是当你发现你正在运行的建筑面积,确定“在此节​​点下的一切”一个订单。(所以你最终不会存储“下一个字符指数“节点,只需单个序列之下的每个节点上。)让我知道这是不明确的,我可以尝试详细...

您如何代表该线索将取决于输入字符的范围。 如果他们都在'A' - 'Z',那么一个简单的数组将是令人难以置信的快速导航,合理高效地为那里有大多数的可用选项的可能性字典树节点。 稍后,当只有两个或三个可能的分支,即成为存储器浪费。 我建议多态特里节点类,这样你可以取决于有多少家支行有打造最合适类型的节点。

这一切都不执行任何扑杀-目前尚不清楚有多少可以通过快速灭杀来实现。 一种情况下可以看到它帮助时,是从一个字典树节点分支的数量下降到1(因为它的去除分行属于耗尽的),分支可以完全消除。 随着时间的推移,这可能有很大的不同,而不应过于很难计算。 基本上, 您打造您可以预测有多少次,每次分公司将要采取的线索,并为导航线索可以减去每个分支数之一,当你浏览它。

这就是我想出来的,到目前为止,它不完全是一个全面实施 - 但我希望它可以帮助...

是一个二进制搜索表真的这么可怕吗? 我会采取可能的字符串列表和“最小化”。其中,排序他们,终于做对他们的块中的二进制搜索。

通过最大限度地减少我的意思是他们减少所产生的一种自定义的,以他们需要的最低限度。

例如,如果你有字符串:“阿尔弗雷德”,“鲍勃”,“埋单”,“乔”,我会敲他们下调至“A”,“比”,“博”,“J”。

然后把那些到一个连续的内存块,例如:

char *table = "abiboj"; // last 0 is really redundant..but
char *keys[4];
keys[0] = table;
keys[1] = table + 2;
keys[2] = table + 5;
keys[3] = table + 8;

理想情况下,编译器会做这一切为你,如果你只是去:

keys[0] = "a";
keys[1] = "bi";
keys[2] = "bo";
keys[3] = "j";

但我不能说,如果这是真的还是假的。

现在可以bsearch该表,并且键是尽可能短。 如果你命中关键的最后,你匹配。 如果没有,那么按照标准bsearch算法。

我们的目标是让所有的数据并拢并保持代码itty片断,使这一切到CPU缓存适合。 可以处理来自程序直接,没有预先处理或添加任何东西的关键。

对于一个相当大的数字被分布合理的键,我认为这将是相当快。 这真的取决于所涉及弦数。 为更小的数字,计算散列值等的开销比搜索这样的多。 对于较大的值,它是值得的。 正是这些数字都依赖于算法等。

然而,这可能是在内存方面最小的解决方案,如果这是非常重要的。

这也有简单的好处。

附加物:

你不必对超越'串'输入任何规格。 但也没有你期望多少字符串使用,它们的长度,它们的共性或它们的使用频率的讨论。 这些可以或许一切从“源头”得到,而不是由算法设计者计划在。 你问对于这样造成东西的算法:

inline int GetValue(char *key) {
return 1234;
}

对于出现这种情况只用一个按键的时候,一路向上的东西,创造了数以百万计的字符串一个完美的哈希算法的小程序。 这是一个相当艰巨的任务。

任何设计“挤压性能的每一个位可能的”后会需要更多地了解投入比“任何和所有的字符串”。 如果你想让它以最快的速度对任何条件问题空间实在太大。

处理具有极长的前缀相同字符串的算法可能比一个工作在完全随机的字符串完全不同。 该算法可以说,“如果用钥匙启动”一“,跳过下100个字符,因为他们都是一个的”。

但是,如果这些字符串是由人类采购,他们使用相同的字母的长字符串,而不是要疯了试图保持这些数据,然后当他们抱怨说,算法执行得很厉害,你回答说“你做愚蠢的事情,不这样做。“ 但是,我们不知道这些字符串的来源无论是。

所以,你需要选择一个问题空间为目标的算法。 我们有各种各样的算法,表面上是做同样的事情,因为它们针对不同的约束条件,并在不同的情况下更好地工作。

散列是昂贵的,铺设了包含HashMap是昂贵的。 如果有没有涉及到足够的数据,还有比散列更好的技术。 如果您有大量内存的预算,你可以做一个巨大的国家机器,根据每个节点的N个状态(N为字符集的大小 - 你不指定 - ???BAUDOT 7位ASCII UTF-32) 。 将运行速度非常快,除非内存由各州消费量摔破CPU缓存或挤出其他的事情。

你可能产生这一切的代码,但是你可能运行到代码大小限制(你不说什么语言或者 - Java有例如64K方法字节码限制)。

但你不指定任何这些约束。 所以,这是一种很难获得您需要的最高效的解决方案。

你想要的是查找表的查表。 如果内存的成本是不是一个问题,你可以全力以赴。

const int POSSIBLE_CHARCODES = 256; //256 for ascii //65536 for unicode 16bit
struct LutMap {
int value;
LutMap[POSSIBLE_CHARCODES] next;
}
int GetValue(string key) {
LutMap root = Global.AlreadyCreatedLutMap;
for(int x=0; x<key.length; x++) {
int c = key.charCodeAt(x);
if(root.next[c] == null) {
return root.value;
}
root = root.next[c];
}
}

我认为,这是所有关于寻找合适的哈希函数。 只要你知道键 - 值关系是事先什么,你可以做一个分析,试图找到一个散列函数,以满足您requrements。 以您所提供的例子中,把输入的字符串作为二进制整数:

foo = 0x666F6F (hex value)
bar = 0x626172
bazz = 0x62617A7A

存在于所有他们的最后一列是在每一个不同。 进一步分析:

foo = 0xF = 1111
bar = 0x2 = 0010
bazz = 0xA = 1010

位右移两次,丢弃溢出,你会得到他们每个人的独特价值:

foo = 0011
bar = 0000
bazz = 0010

位右移再次两次,加入溢出的新缓冲区:富= 0010条= 0000 bazz = 0001

您可以使用这些查询静态3项查找表。 我估计这高度个人化的哈希函数将采取9非常基本的操作,以获得半字节(2),位移位(2),位移位和加法(4)和查询(1),而且很多这些操作都可以通过巧妙的装配使用进一步压缩。 这很可能是嘴皮子运行时信息来源考虑在内更快。

你有没有看着TCB。 也许使用那里的算法可用于检索你的价值观。 这听起来很像你正在试图解决的问题。 而从经验,我可以说TCB是最快的密钥存储查找我都用过之一。 它是一个恒定的查找时间,而不管存储的密钥的数目。

考虑使用克努特莫里斯普拉特算法。

预处理给予映射到一个大字符串像下面

String string = "{foo:1}{bar:42}{bazz:314159}";
int length = string.length();

据KMP预处理时间string将采取O(length) 。 对于任何单词/键,将搜索O(w)复杂性,其中w是字/密钥长度。

您将需要作出2修改KMP算法:

  • 重点应该出现下令加入了string
  • 而不是返回真/假应该解析的数量,并将其返回

希望它可以提供一个很好的提示。

这里有一个可行的方法来确定字符的最小的子集为目标的哈希例行:

让:
k为不同的字符数量在所有关键字
c是最大长度关键字
n为关键字的数量
在您的示例(填充较短的关键字W /空格):

"foo "
"bar "
"bazz"

K = 7(F,O,B,A,R,Z,),C = 4,N = 3

我们可以利用这个来计算一个下界为我们的搜索。 我们至少需要log_k(n)的字符来唯一标识一个关键字,如果log_k(N)> = C,那么你就需要使用整个关键字并没有理由继续进行。

接下来,杜绝一次一个列,检查是否还有n个不同的剩余价值。 使用每一列中的不同的字符作为启发式优化我们的搜索:

2 2 3 2
foo .
bar .
bazz

消除与最低的不同字符的第一列。 如果你有<= log_k(n)的列剩余就可以停止。 (可选)你可以随机位,消除第二个最低的不同山坳或者尝试在不到n个不同的字,如果消除COL结果恢复。 该算法大致为O(n!)这取决于你试图收回多少。 它不能保证找到一个最佳的解决方案,但它是一个很好的权衡。

一旦你有你的字符集,继续生成一个完美的哈希值通常套路。 结果应该是最佳的完备散列。

分类:哈希 时间:2015-03-15 人气:0
分享到:

相关文章

Copyright (C) 55228885.com, All Rights Reserved.

55228885 版权所有 京ICP备15002868号

processed in 0.791 (s). 10 q(s)