C# 如何优化这个递归方法

C# 如何优化这个递归方法,c#,optimization,recursion,C#,Optimization,Recursion,我正在尝试做一个字谜游戏,为此我使用递归方法找到给定字母中所有可能的单词。 这些字母在一个4x4板上 像这样: ABCD EFGH HIJK LMNO 在此循环内调用递归方法: for (int y = 0; y < width; y++) { for (int x = 0; x < height; x++) { myScabble.Search(letters, y, x, width

我正在尝试做一个字谜游戏,为此我使用递归方法找到给定字母中所有可能的单词。 这些字母在一个4x4板上

像这样:

ABCD
EFGH
HIJK
LMNO
在此循环内调用递归方法:

for (int y = 0; y < width; y++)
        {
            for (int x = 0; x < height; x++)
            {
                myScabble.Search(letters, y, x, width, height, "", covered, t);
            }
        }

我意识到我可以添加字母作为属性,但由于它是一种引用类型,因此不需要花费太多时间

相反的方向可能更有效。迭代你可能的单词列表,在网格中查找单词的第一个字母,如果你发现它,在相邻的正方形中查找第二个字母,如果你发现它,继续沿着这条线进一步匹配

通过这种方式,您可以快速地完全删除单词,而不是在每个可能的位置重复检查每个单词

只要看看你在某个将要重复数千次的程序中使用的代码,LINQ可能就是一个禁忌。你在使用字符串操作,因此你在内存中生成了数千个字符串,并可能导致垃圾收集器在递归过程中运行,您应该切换到字符串生成器或某种字符数组

在这一点上,你可以做大量的优化,例如,在你的列表上做一个快速的第一步,检查每个单词的每个字母是否都在网格中的某个位置(只需将网格中的所有字母放入一个字符串,以使这一点变得简单),然后,您可以快速删除并非所有字母都在网格中的单词,甚至不用担心它们的顺序是否正确


您可以将网格转换为一系列字符串,每个字符串表示网格的特定方向,然后只使用普通字符串搜索。如果您找到匹配项,您只需确保匹配项没有跨越网格边界,这可以通过快速检查匹配的起点和终点来完成。

一个优化是在您的定向搜索中。只需按对角线方向向下、向左和向下搜索,因为按对角线方向向上、向右和向上搜索将得到与简单反转字符串相同的结果

编辑:

有趣的还有使用C#和LINQ编写的解决方案

这是我试图描述的一个完全未经测试的例子

    static List<string> Solve()
{
    // Declaring a empty list of strings to hold our results up front.
    List<string> words = new List<string>();

    // I'm using set as the term for your grid of letters.
    string set = @"ABCD
                   EFGH
                   HIJK
                   LMNO";

    // i'm explicitly defining the dimensions, you need to figure this out.
    int sizeX = 4;
    int sizeY = 4;

    // i'm also specifying a maximum word length. you might find a word like 
    // supercalifragilisticexpialidocious, but i doubt it so lets not waste time.
    int maximumWordSize = 3;

    // first, our trie/wordlist/etc. assume `GetWordList()` gets a list of all
    // valid words with indicated number of characters.
    List<string> wordList = GetWordList(3);

    // second, we load a character array with the set.
    char[,] data = new char[sizeX, sizeY];
    string[] lines = set.Split('\n');
    for (int i = 0; i <= lines.Count() -1; i++)
    {
        string line = lines[i].Trim();
        for (int j = 0; j <= line.Length - 1; j++)
        {
            char[j,i] = lines[j];
        }
    }

    // third, we iterate over the data
    for(int x = 0; x <= sizeX - maximumWordSize; x++)
    {
        for (int y = 0; y <= sizeY - maximumWordSize; y++)
        {
            // check to see if we even have any words starting with our cursor
            var validWords = wordList.Where(w=>w.Contains(data[x,y]));
            if (validWords.Count() > 0)
            {
                // ok, we have words. continue on our quest!
                // (this is where your initial qualifier changes if you use a trie
                // or other search method)

                char[] characters = char[maximumWordSize];

                // search left
                for (int i = x; i <= x + maximumWordSize - 1; i++)
                    characters[i] = data[i, y];
                words.AddRange(Search(characters, wordList));

                // search down
                for (int i = y + maximumWordSize - 1; i <= y; i--)
                    characters[i] = data[x, y];
                words.AddRange(Search(characters, wordList));

                // search diagonal right
                for (int i = x; i <= x + maximumWordSize - 1; i++)
                    for (int j = y + maximumWordSize - 1; j <= y; j--)
                        characters[i] = data[i, j];
                words.AddRange(Search(characters, wordList));

                // search diagonal left
                for (int i = x; i <= x - MaximumWordSize + 1; i++)
                    for (int j = y + maximumWordSize - 1; j <= y; j--)
                        characters[i] = data[i, j];
                words.AddRange(Search(characters, wordList));
            }
        }
    }

    return words;
}

static List<string> Search(char[] Input, List<string> WordList)
{
    List<string> result = new List<string>();
    string word = "";
    // find forwards
    for (int i = 0; i <= Input.Length - 1; i++)
    {
        word += Input[i];
        if (WordList.Contains(word))
            result.Add(word);
    }

    // find backwards
    Array.Reverse(Input);
    for (int i = 0; i <= Input.Length - 1; i++)
    {
        word += Input[i];
        if (WordList.Contains(word))
            result.Add(word);
    }

    return result;
}
静态列表求解()
{
//声明一个空字符串列表以预先保存结果。
列表单词=新列表();
//我用set作为你的字母网格的术语。
字符串集=@“ABCD
EFGH
HIJK
LMNO”;
//我在明确定义尺寸,你需要弄清楚这一点。
int-sizeX=4;
int-sizeY=4;
//我还指定了一个最大字长
//超级加利福尼亚州的经验丰富,但我对此表示怀疑,所以我们不要浪费时间。
int-maximumWordSize=3;
//首先,我们的trie/wordlist/etc.假设`GetWordList()`获取所有
//具有指定字符数的有效字。
List wordList=GetWordList(3);
//其次,我们用集合加载一个字符数组。
字符[,]数据=新字符[sizeX,sizeY];
string[]line=set.Split('\n');

对于(int i=0;i创建不变参数(字母、宽度、高度和tt)字段。不要使用Linq,它在多次迭代中都很慢。另外,将单词列表更改为更有效的字典或某种类型的排序列表。

另一个优化是数据结构:

List<aWord> t = tt.Where(w => w.word.StartsWith(pass)).ToList();
List t=tt.Where(w=>w.word.StartsWith(pass)).ToList();
这是所有有效字的O(n),您可以通过使用即trie来提高性能

  • 使用准备得更好的数据结构。不要使用
    列表
    来存储所有单词,而是使用某种树状结构。或者至少使用排序列表+二进制搜索。这一点应该会显著提高性能。我建议使用有序集合并通过递归方法传递范围索引。例如,您将我寻找第一个字母,确定以该字母开头的单词有123-456个索引。下次你添加新字母时,你将使这个范围更窄,你不需要遍历整个集合
  • 预先筛选单词集合。您只有16个字母,这意味着缺少10个以上的字母。您可以筛选单词集合并删除所有缺少数字的单词
  • Lazy LINQ。如果您想使用LINQ,那么不要强制迭代整个集合(我的意思是不要调用
    ToList
    方法)。您可以使用它的惰性来迭代,直到您需要为止,因为如果这个数字大于2,您不关心有多少单词以这个子单词开头
  • 不要复制cov。而不是每次都复制整个数组,只需设置
    cov[x,y]=true;
    并在嵌套搜索尝试后放回
    false
    值:
    cov[x,y]=false;
  • 使用探查器;-)
  • <>我不太了解C,但是这里我是如何在C++中完成的(有些东西是伪代码,使它更容易阅读);它应该是容易翻译的:

    struct word_search {
      const set<string>& words; // This is a balanced search tree
      const vector<vector<char> >& letters;
      const int width, height;
      vector<vector<bool> > marks;
      string word_so_far;
    
      // Add constructor
      void search(int x, int y) {
        if (x < 0 || x >= width || y < 0 || y >= height || marks[x][y]) return;
        word_so_far += letters[x][y];
        set<string>::const_iterator it = words.lower_bound(word_so_far);
        if (it == words.end() ||
            it->substr(0, word_so_far.size()) != word_so_far) {
          word_so_far.resize(word_so_far.size() - 1);
          return;
        }
        marks[x][y] = true;
        for (int dx = -1; dx <= 1; ++dx)
          for (int dy = -1; dy <= 1; ++dy)
            search(x + dx, y + dy);
        marks[x][y] = false;
        word_so_far.resize(word_so_far.size() - 1);
      }
    };
    
    struct word\u搜索{
    const set&words;//这是一个平衡的搜索树
    常量向量和字母;
    const int宽度、高度;
    矢量标记;
    到目前为止的字符串单词;
    //添加构造函数
    无效搜索(整数x,整数y){
    如果(x<0 | | x>=宽度| | y<0 | | y>=高度| |标记[x][y])返回;
    单词_sou_far+=字母[x][y];
    set::const\u迭代器it=words.lower\u bound(word\u至今);
    if(it==words.end()||
    it->substr(0,word\u sou\u far.size())!=word\u sou\u far){
    word\u so\u far.resize(word\u so\u far.size()-1);
    返回;
    }
    标记[x][y]=正确;
    
    对于(intdx=-1;dx来说,其他答案是正确的:您应该完全放弃这个算法,重新开始

    在文字游戏中处理这些问题的方法是
        static List<string> Solve()
    {
        // Declaring a empty list of strings to hold our results up front.
        List<string> words = new List<string>();
    
        // I'm using set as the term for your grid of letters.
        string set = @"ABCD
                       EFGH
                       HIJK
                       LMNO";
    
        // i'm explicitly defining the dimensions, you need to figure this out.
        int sizeX = 4;
        int sizeY = 4;
    
        // i'm also specifying a maximum word length. you might find a word like 
        // supercalifragilisticexpialidocious, but i doubt it so lets not waste time.
        int maximumWordSize = 3;
    
        // first, our trie/wordlist/etc. assume `GetWordList()` gets a list of all
        // valid words with indicated number of characters.
        List<string> wordList = GetWordList(3);
    
        // second, we load a character array with the set.
        char[,] data = new char[sizeX, sizeY];
        string[] lines = set.Split('\n');
        for (int i = 0; i <= lines.Count() -1; i++)
        {
            string line = lines[i].Trim();
            for (int j = 0; j <= line.Length - 1; j++)
            {
                char[j,i] = lines[j];
            }
        }
    
        // third, we iterate over the data
        for(int x = 0; x <= sizeX - maximumWordSize; x++)
        {
            for (int y = 0; y <= sizeY - maximumWordSize; y++)
            {
                // check to see if we even have any words starting with our cursor
                var validWords = wordList.Where(w=>w.Contains(data[x,y]));
                if (validWords.Count() > 0)
                {
                    // ok, we have words. continue on our quest!
                    // (this is where your initial qualifier changes if you use a trie
                    // or other search method)
    
                    char[] characters = char[maximumWordSize];
    
                    // search left
                    for (int i = x; i <= x + maximumWordSize - 1; i++)
                        characters[i] = data[i, y];
                    words.AddRange(Search(characters, wordList));
    
                    // search down
                    for (int i = y + maximumWordSize - 1; i <= y; i--)
                        characters[i] = data[x, y];
                    words.AddRange(Search(characters, wordList));
    
                    // search diagonal right
                    for (int i = x; i <= x + maximumWordSize - 1; i++)
                        for (int j = y + maximumWordSize - 1; j <= y; j--)
                            characters[i] = data[i, j];
                    words.AddRange(Search(characters, wordList));
    
                    // search diagonal left
                    for (int i = x; i <= x - MaximumWordSize + 1; i++)
                        for (int j = y + maximumWordSize - 1; j <= y; j--)
                            characters[i] = data[i, j];
                    words.AddRange(Search(characters, wordList));
                }
            }
        }
    
        return words;
    }
    
    static List<string> Search(char[] Input, List<string> WordList)
    {
        List<string> result = new List<string>();
        string word = "";
        // find forwards
        for (int i = 0; i <= Input.Length - 1; i++)
        {
            word += Input[i];
            if (WordList.Contains(word))
                result.Add(word);
        }
    
        // find backwards
        Array.Reverse(Input);
        for (int i = 0; i <= Input.Length - 1; i++)
        {
            word += Input[i];
            if (WordList.Contains(word))
                result.Add(word);
        }
    
        return result;
    }
    
    List<aWord> t = tt.Where(w => w.word.StartsWith(pass)).ToList();
    
    struct word_search {
      const set<string>& words; // This is a balanced search tree
      const vector<vector<char> >& letters;
      const int width, height;
      vector<vector<bool> > marks;
      string word_so_far;
    
      // Add constructor
      void search(int x, int y) {
        if (x < 0 || x >= width || y < 0 || y >= height || marks[x][y]) return;
        word_so_far += letters[x][y];
        set<string>::const_iterator it = words.lower_bound(word_so_far);
        if (it == words.end() ||
            it->substr(0, word_so_far.size()) != word_so_far) {
          word_so_far.resize(word_so_far.size() - 1);
          return;
        }
        marks[x][y] = true;
        for (int dx = -1; dx <= 1; ++dx)
          for (int dy = -1; dy <= 1; ++dy)
            search(x + dx, y + dy);
        marks[x][y] = false;
        word_so_far.resize(word_so_far.size() - 1);
      }
    };
    
             root
          /        \
         A          C
        / \         |
       B   C        A
       |  / \       |
       $ E   T      T
         |   |      |
         $   $      $
    
    public class Trie : Dictionary<char, Trie>
    {
        public int Frequency { get; set; }
    
        public void Add(IEnumerable<char> chars)
        {
            if (chars == null) throw new System.ArgumentNullException("chars");
            if (chars.Any())
            {
                var head = chars.First();
                if (!this.ContainsKey(head))
                {
                    this.Add(head, new Trie());
                }
                var tail = this.GetSafeTail(head, chars.Skip(1));
                if (tail.Any())
                {
                    this[head].Add(tail);
                }
            }
        }
    
        public bool Contains(IEnumerable<char> chars)
        {
            if (chars == null) throw new System.ArgumentNullException("chars");
            var @return = false;
            if (chars.Any())
            {
                var head = chars.First();
                if (this.ContainsKey(head))
                {
                    var tail = this.GetSafeTail(head, chars.Skip(1));
                    @return = tail.Any() ? this[head].Contains(tail) : true;
                }
            }
            return @return;
        }
    
        private IEnumerable<char> GetSafeTail(char head, IEnumerable<char> tail)
        {
            return ((!tail.Any()) && (head != '$')) ? new [] { '$', } : tail;
        }
    }
    
    var trie = new Trie();
    var before = trie.Contains("Hello"); // == false
    trie.Add("Hello");
    var after = trie.Contains("Hello"); // == true
    
    var matches =
        from w in this.GetPossibleWords(letters)
        where trie.Contains(w)
        select w;
    
    public IEnumerable<string> GetPossibleWords(char[,] letters)
    {
        return
            from ws in this.GetPossibleWordLists(letters)
            from w in ws
            select w;
    }
    
    private IEnumerable<IEnumerable<string>> GetPossibleWordLists(char[,] letters)
    {
        Func<int, int> inc = x => x + 1;
        Func<int, int> nop = x => x;
        Func<int, int> dec = x => x - 1;
        for (var r = letters.GetLowerBound(0);
            r <= letters.GetUpperBound(0); r++)
        {
            for (var c = letters.GetLowerBound(1);
                c <= letters.GetUpperBound(1); c++)
            {
                yield return new [] { letters[r, c].ToString(), };
                yield return this.GetPossibleWords(letters, r, c, dec, dec);
                yield return this.GetPossibleWords(letters, r, c, inc, dec);
                yield return this.GetPossibleWords(letters, r, c, nop, dec);
                yield return this.GetPossibleWords(letters, r, c, dec, nop);
                yield return this.GetPossibleWords(letters, r, c, inc, nop);
                yield return this.GetPossibleWords(letters, r, c, nop, inc);
                yield return this.GetPossibleWords(letters, r, c, dec, inc);
                yield return this.GetPossibleWords(letters, r, c, inc, inc);
            }
        }
    }
    
    private IEnumerable<string> GetPossibleWords(char[,] letters,
        int r, int c,
        Func<int, int> rd, Func<int, int> cd)
    {
        var chars = new List<char>();
        while (r >= letters.GetLowerBound(0) && r <= letters.GetUpperBound(0)
            && c >= letters.GetLowerBound(1) && c <= letters.GetUpperBound(1))
        {
            chars.Add(letters[r, c]);
            if (chars.Count > 1)
            {
                yield return new string(chars.ToArray());
            }
            r = rd(r);
            c = cd(c);
        }
    }