从排序的字符串数组中找到第一个前缀匹配的最有效算法?
Posted
技术标签:
【中文标题】从排序的字符串数组中找到第一个前缀匹配的最有效算法?【英文标题】:The Most Efficient Algorithm to Find First Prefix-Match From a Sorted String Array? 【发布时间】:2010-10-02 05:05:24 【问题描述】:输入:
1) 一个巨大的字符串 SA 排序数组;
2) 前缀字符串 P;
输出:
匹配输入前缀的第一个字符串的索引(如果有)。 如果没有这样的匹配,那么输出将是-1。
例子:
SA = "ab", "abd", "abdf", "abz"
P = "abd"
输出应为 1(索引从 0 开始)。
做这种工作的最算法方法是什么?
【问题讨论】:
【参考方案1】:这是一个可能的解决方案(在 Python 中),它具有 O(k.log(n)) 时间复杂度和 O(1) 额外空间复杂度(考虑n 个字符串和 k 个前缀长度)。
执行仅考虑字符串的给定字符索引的二进制搜索背后的基本原理。如果这些存在,则继续下一个字符索引。如果在任何字符串中找不到任何前缀字符,则立即返回。
from typing import List
def first(items: List[str], prefix: str, i: int, c: str, left: int, right: int):
result = -1
while left <= right:
mid = left + ((right - left) // 2)
if ( i >= len(items[mid]) ):
left = mid + 1
elif (c < items[mid][i]):
right = mid - 1
elif (c > items[mid][i]):
left = mid + 1
else:
result = mid
right = mid - 1
return result
def last(items: List[str], prefix: str, i: int, c: str, left: int, right: int):
result = -1
while left <= right:
mid = left + ((right - left) // 2)
if ( i >= len(items[mid]) ):
left = mid + 1
elif (c < items[mid][i]):
right = mid - 1
elif (c > items[mid][i]):
left = mid + 1
else:
result = mid
left = mid + 1
return result
def is_prefix(items: List[str], prefix: str):
left = 0
right = len(items) - 1
for i in range(len(prefix)):
c = prefix[i]
left = first(items, prefix, i, c, left, right)
right = last(items, prefix, i, c, left, right)
if (left == -1 or right == -1):
return False
return True
# Test cases
a = ['ab', 'abjsiohjd', 'abikshdiu', 'ashdi','abcde Aasioudhf', 'abcdefgOAJ', 'aa', 'aaap', 'aas', 'asd', 'bbbbb', 'bsadiojh', 'iod', '0asdn', 'asdjd', 'bqw', 'ba']
a.sort()
print(a)
print(is_prefix(a, 'abcdf'))
print(is_prefix(a, 'abcde'))
print(is_prefix(a, 'abcdef'))
print(is_prefix(a, 'abcdefg'))
print(is_prefix(a, 'abcdefgh'))
print(is_prefix(a, 'abcde Aa'))
print(is_prefix(a, 'iod'))
print(is_prefix(a, 'ZZZZZZiod'))
此要点可在https://gist.github.com/lopespm/9790d60492aff25ea0960fe9ed389c0f 获得
【讨论】:
【参考方案2】:我的解决方案: 使用二分查找。
private static int search(String[] words, String searchPrefix)
if (words == null || words.length == 0)
return -1;
int low = 0;
int high = words.length - 1;
int searchPrefixLength = searchPrefix.length();
while (low <= high)
int mid = low + (high - low) / 2;
String word = words[mid];
int compare = -1;
if (searchPrefixLength <= word.length())
compare = word.substring(0, searchPrefixLength).compareTo(searchPrefix);
if (compare == 0)
return mid;
else if (compare > 0)
high = mid - 1;
else
low = mid + 1;
return -1;
【讨论】:
【参考方案3】:您是否能够预先计算所有可能的前缀?
如果是这样,您可以这样做,然后使用二进制搜索在预先计算的表中查找前缀。将下标存储为带有前缀的所需值。
【讨论】:
【参考方案4】:如果您只想执行一次,请使用binary search,另一方面,如果您需要针对许多不同的前缀但在同一个字符串数组上执行此操作,则构建radix tree 可能是个好主意,之后你已经建立了树,每次查找都会非常快。
【讨论】:
基数树的最佳情况查找时间是 O(n),其中 n 是字符数,而二分查找需要 O(log m),其中 m 是列表的大小。基数搜索不一定会更快。 Arachnid:这不是真的。即使在最好的情况下(匹配),二分查找也是 O(n + log m),因为它必须读取整个前缀来检查它是否匹配。在最坏的情况下,它是 O(nlogm)。 @Arachnid:Brian 的评论 +1,加上 O(n) 是基数树的最坏情况查找,其中存在匹配项,您需要阅读整个前缀。 嗯,二分查找几乎很容易实现,并且不占用额外的内存。基数树实现起来很复杂,需要额外的内存,而且好处并不大 _.sortedIndex 让它变得更加简单:)【参考方案5】:我目前想到的解决方案是,尝试查找“虚拟前缀”,而不是查找“前缀”。
例如,前缀是“abd”,尝试查找虚拟前缀“abc(255)”。 (255) 仅表示最大字符数。找到“abc(255)”后。如果有的话,下一个单词应该是第一个匹配“abd”的单词。
【讨论】:
【参考方案6】:可以使用Suffix Tree 在线性时间内完成。构建后缀树需要线性时间。
【讨论】:
他也可以在线性时间内比较每个元素的前缀。 没错,但如果他要检查多个前缀,那不是一个好主意。【参考方案7】:这只是修改后的二分搜索:
仅检查每个元素中的字符数与搜索字符串中的字符数一样多;和 如果找到匹配项,请继续向后搜索(线性搜索或进一步二分搜索),直到找到不匹配的结果,然后返回最后一个匹配结果的索引。【讨论】:
【参考方案8】:FreeBSD 内核使用Radix tree 作为其路由表,您应该检查一下。
【讨论】:
以上是关于从排序的字符串数组中找到第一个前缀匹配的最有效算法?的主要内容,如果未能解决你的问题,请参考以下文章