最快的搜索算法开始

时间:2017-08-04 14:16:45

标签: c# .net algorithm search

我需要实现一种搜索算法,该搜索算法只搜索字符串的开头而不是字符串中的任何位置。

我是算法的新手,但从我所看到的情况来看,似乎他们通过字符串并发现任何事件。

我有一个字符串集合(超过100万),每次用户输入击键时都需要搜索。

编辑:

这将是增量搜索。我目前使用以下代码实现它,我的搜索范围从超过100万个可能的字符串返回300-700ms。收集不是有序的,但没有理由不能。

private ICollection<string> SearchCities(string searchString) {
        return _cityDataSource.AsParallel().Where(x => x.ToLower().StartsWith(searchString)).ToArray();
    }

3 个答案:

答案 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之前是否转换了这些值,或者数字是否有任何差异。