我需要实现一种搜索算法,该搜索算法只搜索字符串的开头而不是字符串中的任何位置。
我是算法的新手,但从我所看到的情况来看,似乎他们通过字符串并发现任何事件。
我有一个字符串集合(超过100万),每次用户输入击键时都需要搜索。
编辑:
这将是增量搜索。我目前使用以下代码实现它,我的搜索范围从超过100万个可能的字符串返回300-700ms。收集不是有序的,但没有理由不能。
private ICollection<string> SearchCities(string searchString) {
return _cityDataSource.AsParallel().Where(x => x.ToLower().StartsWith(searchString)).ToArray();
}
答案 0 :(得分:2)
我修改了this article from Visual Studio Magazine中实现Trie
。
以下程序演示了如何使用Trie
进行快速前缀搜索。
要运行此程序,您需要一个名为“words.txt”的文本文件,其中包含大量单词。 You can download one from Github here
编译程序后,将“words.txt”文件复制到与可执行文件相同的文件夹中。
运行程序时,键入前缀(例如prefix
;))并按return
,它将列出以该前缀开头的所有单词。
这应该是一个非常快速的查找 - 有关更多详细信息,请参阅Visual Studio Magazine文章!
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace ConsoleApp1
{
class Program
{
static void Main()
{
var trie = new Trie();
trie.InsertRange(File.ReadLines("words.txt"));
Console.WriteLine("Type a prefix and press return.");
while (true)
{
string prefix = Console.ReadLine();
if (string.IsNullOrEmpty(prefix))
continue;
var node = trie.Prefix(prefix);
if (node.Depth == prefix.Length)
{
foreach (var suffix in suffixes(node))
Console.WriteLine(prefix + suffix);
}
else
{
Console.WriteLine("Prefix not found.");
}
Console.WriteLine();
}
}
static IEnumerable<string> suffixes(Node parent)
{
var sb = new StringBuilder();
return suffixes(parent, sb).Select(suffix => suffix.TrimEnd('$'));
}
static IEnumerable<string> suffixes(Node parent, StringBuilder current)
{
if (parent.IsLeaf())
{
yield return current.ToString();
}
else
{
foreach (var child in parent.Children)
{
current.Append(child.Value);
foreach (var value in suffixes(child, current))
yield return value;
--current.Length;
}
}
}
}
public class Node
{
public char Value { get; set; }
public List<Node> Children { get; set; }
public Node Parent { get; set; }
public int Depth { get; set; }
public Node(char value, int depth, Node parent)
{
Value = value;
Children = new List<Node>();
Depth = depth;
Parent = parent;
}
public bool IsLeaf()
{
return Children.Count == 0;
}
public Node FindChildNode(char c)
{
return Children.FirstOrDefault(child => child.Value == c);
}
public void DeleteChildNode(char c)
{
for (var i = 0; i < Children.Count; i++)
if (Children[i].Value == c)
Children.RemoveAt(i);
}
}
public class Trie
{
readonly Node _root;
public Trie()
{
_root = new Node('^', 0, null);
}
public Node Prefix(string s)
{
var currentNode = _root;
var result = currentNode;
foreach (var c in s)
{
currentNode = currentNode.FindChildNode(c);
if (currentNode == null)
break;
result = currentNode;
}
return result;
}
public bool Search(string s)
{
var prefix = Prefix(s);
return prefix.Depth == s.Length && prefix.FindChildNode('$') != null;
}
public void InsertRange(IEnumerable<string> items)
{
foreach (string item in items)
Insert(item);
}
public void Insert(string s)
{
var commonPrefix = Prefix(s);
var current = commonPrefix;
for (var i = current.Depth; i < s.Length; i++)
{
var newNode = new Node(s[i], current.Depth + 1, current);
current.Children.Add(newNode);
current = newNode;
}
current.Children.Add(new Node('$', current.Depth + 1, current));
}
public void Delete(string s)
{
if (!Search(s))
return;
var node = Prefix(s).FindChildNode('$');
while (node.IsLeaf())
{
var parent = node.Parent;
parent.DeleteChildNode(node.Value);
node = parent;
}
}
}
}
答案 1 :(得分:1)
有几点想法:
首先,你的百万字符串需要被排序,这样你就可以“寻找”第一个匹配的字符串并返回字符串,直到你不再有匹配...按顺序(通过C#List<string>.BinarySearch
寻找,也许)。这就是你触摸尽可能少的字符串的方式。
其次,在输入暂停至少500毫秒(给予或接受)之前,你可能不应该尝试击中字符串列表。
第三,你对浩瀚的问题应该是异步和可取消的,因为一定的努力将会被下一次击键取代。
最后,任何后续查询都应首先检查新搜索字符串是否是最近搜索字符串的附加...以便您可以从上次搜索开始后续搜索(节省大量时间)。
答案 2 :(得分:0)
我建议使用linq。
string x = "searchterm";
List<string> y = new List<string>();
List<string> Matches = y.Where(xo => xo.StartsWith(x)).ToList();
其中x是您的击键搜索文字字词,y是您要搜索的字符串集合,而匹配是您收藏的匹配项。
我用前100万个素数测试了这个,这里是从上面改编的代码:
Stopwatch SW = new Stopwatch();
SW.Start();
string x = "2";
List<string> y = System.IO.File.ReadAllText("primes1.txt").Split(' ').ToList();
y.RemoveAll(xo => xo == " " || xo == "" || xo == "\r\r\n");
List <string> Matches = y.Where(xo => xo.StartsWith(x)).ToList();
SW.Stop();
Console.WriteLine("matches: " + Matches.Count);
Console.WriteLine("time taken: " + SW.Elapsed.TotalSeconds);
Console.Read();
结果是:
匹配:77025
所用时间:0.4240604
当然这是针对数字的测试,我不知道linq之前是否转换了这些值,或者数字是否有任何差异。