2

この問題を解決する再帰関数を実現しました。

  • 文字 (A...Z) で構成された 4x4 マトリックスを使用すると、すべての文字について、 adiacent セルと斜めセルのみをたどることによって、すべてのn 長の単語を取得します。

私の関数のアイデアは、すべての文字をあらゆる可能な方向に再帰的に呼び出し、それが形成される文字列を連結することです。この文字列の長さがnになると停止します。すでに使用されている文字に戻るのを防ぐために、新しい可能性のあるすべての文字と使用されているすべての文字を比較する文字のクラスへのポインターの配列を作成しました。これを行うために、すべての文字に対して特別なクラスを作成しました。

これが関数です: (申し訳ありませんが、非常に長いです)

dirSu は、上 - dirGiu 下 - dirDx 右 - dirSx 左 - およびその他すべての対角位置を意味します

    void decisione(lettera *start, lettera *next, int count, string seq, vector <lettera*> visitate, int quanti) {

        if (next == nullptr) return ;

        if (count == quanti) {
            bool test = false;

            for (int k = 0; k < start->sequenze.size(); k++) { //To prevent same letter to be used
                if (start->sequenze[k] == seq)  {
                    test = true;
                    break;
                }
            }
            if (!test) start->sequenze.push_back(seq);
            return ;
        }

        seq = seq + next->chiave; //String concatenating every call
        count++; //Counter to decide when stop
        visitate.push_back(next); //Array of used letters

        int i;
        bool test;

        //Decide direction
        test = false;
        for(i = 0; i < visitate.size(); i++) {
            if (visitate[i] == next->dirSu) { 
                test = true;
                break;
            }
        }
        if (!test) decisione(start, next->dirSu, count, seq, visitate, quanti);


        test = false;
        for(i = 0; i < visitate.size(); i++) {
            if (visitate[i] == next->dirSu) { 
                test = true;
                break;
            }
        }
        if (!test) decisione(start, next->dirSu, count, seq, visitate, quanti);

        test = false;
        for(i = 0; i < visitate.size(); i++) {
            if (visitate[i] == next->dirGiu) { 
                test = true;
                break;
            }
        }
        if (!test) decisione(start, next->dirGiu, count, seq, visitate, quanti);

        test = false;
        for(i = 0; i < visitate.size(); i++) {
            if (visitate[i] == next->dirSx) { 
                test = true;
                break;
            }
        }
        if (!test) decisione(start, next->dirSx, count, seq, visitate, quanti);

        test = false;
        for(i = 0; i < visitate.size(); i++) {
            if (visitate[i] == next->dirDx) { 
                test = true;
                break;
            }
        }
        if (!test) decisione(start, next->dirDx, count, seq, visitate, quanti);

        test = false;
        for(i = 0; i < visitate.size(); i++) {
            if (visitate[i] == next->dirAdx) { 
                test = true;
                break;
            }
        }
        if (!test) decisione(start, next->dirAdx, count, seq, visitate, quanti);

        test = false;
        for(i = 0; i < visitate.size(); i++) {
            if (visitate[i] == next->dirAsx) { 
                test = true;
                break;
            }
        }
        if (!test) decisione(start, next->dirAsx, count, seq, visitate, quanti);

        test = false;
        for(i = 0; i < visitate.size(); i++) {
            if (visitate[i] == next->dirBdx) { 
                test = true;
                break;
            }
        }
        if (!test) decisione(start, next->dirBdx, count, seq, visitate, quanti);

        test = false;
        for(i = 0; i < visitate.size(); i++) {
            if (visitate[i] == next->dirBsx) { 
                test = true;
                break;
            }
        }
        if (!test) decisione(start, next->dirBsx, count, seq, visitate, quanti);

    }

さて、3 文字の単語の関数を有効にすると、非常に高速です。4 文字: 行列の 1 文字あたりの計算時間は 0.028 秒、5 文字: 0.225 秒、6 文字: 1.891 秒、7 文字: 14.914 秒です。

関数を最適化して計算時間を短縮する方法はありますか? (または私のばかげた論理エラーを削除しますか?)どうもありがとう!

テストに使用したマトリックスは次のとおりです。

C A S A
C O S A
B A C I
O T A T

私はすべての速度テストにこれを使用し(頻度またはランダムに計算することなく)、これを使用すると(質問以来改善された再帰関数を使用して)、開始文字ごとに5、6、7文字のすべての単語を約90秒で取得します。4×4のマトリックスだけなのに言葉が多い!私が個人的にあなたにそれを送ることができれば、同じ問題に対するあなたの解決策も見るのは興味深いかもしれません. (こちらへの連絡方法がわかりません)

4

3 に答える 3

2

まず、探している解の数は長さとともに指数関数的に増加するため、解は指数関数的になります。あなたが望むことができるのは、定数係数を減らすことだけです。

ただし、既に見つかった文字列を並べ替えたままにし、新しい文字列ごとにバイナリ検索を行うことをお勧めします。長さが 7 の場合、重複を除外すると、60000 をはるかに超えることがわかります。(私が自分自身で行った簡単な実装では、これにより 10 倍以上の差が生じ、探していた文字列が長いほど差が大きくなりました。)どうにかしてそれを調べなければならないのではなく、多くの時間を節約する必要があります.

于 2012-11-30T15:26:09.883 に答える
2

アルゴリズムを再帰的から反復的 (ループあり) に変更します。通常は高速です。その後、複雑さを軽減するために、アルゴリズムとデータ構造に関連するその他の最適化が行われます。そのような最適化を見つけるのははるかに困難です (ただし可能です)。

編集:コードに関するいくつかの考えに続いて、今日行った単語検索のバージョンが続きます:

  • 英語の単語 (またはイタリア語など) のみが必要です。これを確認するには、辞書を使用する必要があります。これは、メモリとパフォーマンスの両方の観点から、単語の検索に最も効率的な構造です。
  • コメントによると、このアルゴリズムは非常に再帰的です。私は同意しません。ベクトルのコピーがはるかに少なく、単語チェックを使用して、反復的なものを作成しました。アルゴリズムを再帰から反復に渡すと、通常、コピーの数が減ります。しかし、私はまだこのコメントの 1 つの点に同意します: 一部のアルゴリズムは再帰形式で高速になる可能性があります (ただし、それほど一般的ではありません)。また、このアルゴリズムを見つけるのは簡単ではありませんでした;)
  • コードを分割する必要があります。はるかに読みやすく、最適化が容易になります。
  • あなたはボーグルリゾルバーをコーディングしているので、辞書は正確でなければならず、動詞のすべての形式を含める必要があることに注意する必要があります (たとえば、「be」の場合: am、are、is、was、were など... )。私が持っている英語の単語リストは 850 語しかなく、短すぎるため、フランス語でテストしました。
  • ディクショナリ内のアクセント付きの文字の条件は重要ではありません。アクセント用のスロットをさらに使用しないようにするためだけにここに記載されています。また、テキスト ファイルから構成されたすべての単語 (キャレットを含む) をストライプ化しました。
  • 私のコンピューターでは、辞書 (350000 のフランス語単語、3 Mb) を読み込み、長さ 5、6、および 7 のすべての単語を検索するのに約 0.6 秒かかります (i7 2660K、8Gb RAM、/0x でリリース、私が測定したものではありません)。コンピューターによって ;) )。パフォーマンスの測定値は、比較のために、お使いのマシンにのみ関連します。

だから、ここにコードがあります。これはスタンドアロンです。コンパイルして、英語 (またはイタリア語またはフランス語) の単語ファイル (テキスト、1 行に 1 単語) を実行するだけです。

#include <string>
#include <iostream>
#include <stdlib.h>
#include <vector>
#include <time.h>
#include <map>
#include <algorithm>

// **************************************************** //
//                                                      //
// Data structure for the words : dictionnary           //
//                                                      //
// **************************************************** //
struct DicNode
{
    const char l;
    bool isEndOfWord;
    DicNode* children[26]; // a - z, case insensitive
    DicNode* parent;
    unsigned int min, max, level;

    DicNode(char c, DicNode* p = 0, bool end = false) : l(c),isEndOfWord(end),parent(p),min((unsigned int)(-1)),max(0), level(parent ? parent->level + 1 : 0)
    {
        for(unsigned int t = 0; t < 26; t++)
            children[t] = 0;
    }

    ~DicNode()
    {
        for(unsigned int t = 0; t < 26; t++)
            if(children[t])
                delete children[t];
    }

    DicNode*& at(char l)
    {
        if(l == 'à' || l == 'ä' || l == 'â')
            l = 'a';
        if(l == 'é' || l == 'è' || l == 'ê' || l == 'ë')
            l = 'e';
        if(l == 'ï' || l == 'î')
            l = 'i';
        if(l == 'ö' || l == 'ô')
            l = 'o';
        if(l == 'ü' || l == 'û' || l == 'ù')
            l = 'u';
        if(l == 'ç')
            l = 'c';
        if(l <= 'Z')
            l = 'a' + l - 'A';

        // Note: undefined behavior if l > 'Z' or l < 'A'.
        return children[(unsigned int)(l - 'a')];
    }

    bool inRange(unsigned int n) const
    {
        return n <= max && n >= min;
    }

    void print(std::string str = "") const
    {
        // Here recursive algorithm is way better because of the object-oriented structure
        if(isEndOfWord)
            std::cout << (str + l) << "\n";

        for(unsigned int t = 0; t < 26; t++)
            if(children[t])
                children[t]->print(str + l);
    }

    std::string toString() const
    {
        std::string str(level, '0');
        const DicNode* node = this;
        while(node && node->level > 0)
        {
            str[node->level - 1] = node->l;
            node = node->parent;
        }
        return str;
    }
};

void addWord(DicNode* root, const std::string& str)
{
    if(!root || str == "") return;
    DicNode* node = root;
    unsigned int i = 0;
    while(i != str.size())
    {
        if(str.size() - i > node->max) node->max = str.size() - i;
        if(str.size() - i < node->min) node->min = str.size() - i;

        char c = tolower(str[i]);
        DicNode*& n = node->at(c);
        if(!n) n = new DicNode(c,node);
        node = n;
        i++;
    }
    node->isEndOfWord = true;
}

// **************************************************** //
//                                                      //
// Data structures for the algorithm                    //
//                                                      //
// **************************************************** //

enum Direction
{
    INVALID,
    NORTH,
    NORTH_EAST,
    EAST,
    SOUTH_EAST,
    SOUTH,
    SOUTH_WEST,
    WEST,
    NORTH_WEST,
    FINISHED
};

Direction incDir(Direction d)
{
    switch(d)
    {
        case NORTH:         return NORTH_EAST;
        case NORTH_EAST:    return EAST;
        case EAST:          return SOUTH_EAST;
        case SOUTH_EAST:    return SOUTH;
        case SOUTH:         return SOUTH_WEST;
        case SOUTH_WEST:    return WEST;
        case WEST:          return NORTH_WEST;
        case NORTH_WEST:    return NORTH;
        default:            return INVALID;
    }
}

Direction operator-(Direction d)
{
    switch(d)
    {
        case NORTH:         return SOUTH;
        case NORTH_EAST:    return SOUTH_WEST;
        case EAST:          return WEST;
        case SOUTH_EAST:    return NORTH_WEST;
        case SOUTH:         return NORTH;
        case SOUTH_WEST:    return NORTH_EAST;
        case WEST:          return EAST;
        case NORTH_WEST:    return SOUTH_EAST;
        default:            return INVALID;
    }
}

std::string toString(Direction d)
{
    switch(d)
    {
        case NORTH:         return "NORTH";
        case NORTH_EAST:    return "NORTH_EAST";
        case EAST:          return "EAST";
        case SOUTH_EAST:    return "SOUTH_EAST";
        case SOUTH:         return "SOUTH";
        case SOUTH_WEST:    return "SOUTH_WEST";
        case WEST:          return "WEST";
        case NORTH_WEST:    return "NORTH_WEST";
        default:            return "INVALID";
    }
}

struct Cell
{
    char l;
    DicNode* node;
    Direction dir, dirParent;
    Cell(char l_ = 'A') : l(l_),node(0),dir(INVALID),dirParent(INVALID) {}
};

struct ProbLetter
{
    char letter;
    float proba;
};

class Map
{
    public:
        Map(unsigned int w, unsigned int h) : width(w), height(h)
        {
            data = new Cell[width * height];
            for(unsigned int t = 0; t < width * height; t++)
                data[t] = randomEnglishLetter();
        }

        static char randomEnglishLetter()
        {
            // Frequency of english letters
            static ProbLetter probas[26] =
            {
                { 'Z', 0.074f },
                { 'Q', 0.095f },
                { 'X', 0.150f },
                { 'J', 0.153f },
                { 'K', 0.772f },
                { 'V', 0.978f },
                { 'B', 1.492f },
                { 'P', 1.929f },
                { 'Y', 1.974f },
                { 'G', 2.015f },
                { 'F', 2.228f },
                { 'W', 2.360f },
                { 'M', 2.406f },
                { 'U', 2.758f },
                { 'C', 2.782f },
                { 'L', 4.025f },
                { 'D', 4.253f },
                { 'R', 5.987f },
                { 'H', 6.094f },
                { 'S', 6.327f },
                { 'N', 6.749f },
                { 'I', 6.966f },
                { 'O', 7.507f },
                { 'A', 8.167f },
                { 'T', 9.056f },
                { 'E', 12.702f }
            };

            // Random number between 0 and 1
            float p = 100.0f * float(rand() % 10000) / 9999.0f;
            float sum = 0.0f;
            for(unsigned int t = 0; t < 26; t++)
            {
                sum += probas[t].proba;
                if(p < sum) return probas[t].letter;
            }
            return probas[25].letter;
        }

        Cell& operator()(unsigned int x, unsigned int y)
        {
            return data[x + y * width];
        }

        bool inRange(int x, int y)
        {
            return x >= 0 && x < (int)width && y >= 0 && y < (int)height;
        }

        void print()
        {
            for(unsigned int y = 0; y < height; y++)
            {
                for(unsigned int x = 0; x < width; x++)
                    std::cout << "  " << data[x + y * width].l;
                std::cout << "\n";
            }
        }

        unsigned int getWidth() const { return width; }
        unsigned int getHeight() const { return height; }

    private:
        unsigned int width, height;
        Cell* data;
};


// **************************************************** //
//                                                      //
// Word-search algorithm                                //
//                                                      //
// **************************************************** //

inline void advance(int& x, int& y, Direction d)
{
    switch(d)
    {
        case NORTH:
            y--;
            return;
        case NORTH_EAST:
            x++;
            y--;
            return;
        case EAST:
            x++;
            return;
        case SOUTH_EAST:
            x++;
            y++;
            return;
        case SOUTH:
            y++;
            return;
        case SOUTH_WEST:
            x--;
            y++;
            return;
        case WEST:
            x--;
            return;
        case NORTH_WEST:
            x--;
            y--;
            return;
        default:
            return;
    }
}

struct Data
{
    Map& map;
    int x;
    int y;
};

bool descend(Data& dat, unsigned int n)
{
    DicNode* parent = dat.map(dat.x, dat.y).node;
    if(n == parent->level) return false;

    Direction dir = dat.map(dat.x, dat.y).dir;
    if(dir == FINISHED) return false;
    else if(dir == INVALID) dir = NORTH;

    int pX = dat.x, pY = dat.y;
    bool firstIteration = true;
    for(; firstIteration || dir != NORTH; dir = incDir(dir))
    {
        firstIteration = false;
        pX = dat.x;
        pY = dat.y;
        advance(pX, pY, dir);

        if(dat.map.inRange(pX, pY) // (pX, pY) is not outside the map
            && dat.map(pX, pY).node == 0 // The cell is not already used
            && parent->at(dat.map(pX, pY).l) != 0) // An entry in the dictionary exists
        {
            // We found the next node
            dat.map(dat.x, dat.y).dir = dir;
            dat.map(pX, pY).node = parent->at(dat.map(pX, pY).l);
            dat.map(pX, pY).dirParent = -dir;
            dat.x = pX;
            dat.y = pY;

            return true;
        }
    }

    return false;
}

bool ascend(Data& dat)
{
    // Go back on the previous node

    Direction dp = dat.map(dat.x, dat.y).dirParent;
    if(dp == INVALID) return false;

    dat.map(dat.x, dat.y).dir = INVALID;
    dat.map(dat.x, dat.y).dirParent = INVALID;
    dat.map(dat.x, dat.y).node = 0;
    advance(dat.x, dat.y, dp);

    dat.map(dat.x, dat.y).dir = incDir(dat.map(dat.x, dat.y).dir);
    if(dat.map(dat.x, dat.y).dir == NORTH)
        dat.map(dat.x, dat.y).dir = FINISHED;

    return true;
}

void findWordsFromPosition(Map& map, DicNode* parent, unsigned int x, unsigned int y, unsigned int n, std::vector<std::string>& output)
{
    if(!parent) return;

    bool ok = true;

    // Setup first node
    map(x, y).node = parent;
    map(x, y).dir = NORTH;

    // while we can find next node with direction
    // If marked node has right order and is end of word, or if condition on n is not verified:
    //     add it to the output (if condition on n is verified)
    //     no need to go further (because of n), so advance direction of parent cell, reset current cell, and go up for the current position
    Data dat = { map, x, y };
    while(ok)
    {
        if(map(dat.x,dat.y).node->toString() == "ane")
            std::cout << "ok\n";
        if(!descend(dat, n))
            ok = ascend(dat);
        else
        {
            DicNode* node = dat.map(dat.x, dat.y).node;
            if(node->level == n && node->isEndOfWord)
            {
                std::string str = node->toString();
                if(std::find(output.begin(), output.end(), str) == output.end())
                    output.push_back(str);
                ok = ascend(dat);
            }
            else if(!node->inRange(n - node->level))
                ok = ascend(dat);
        }
    }

    // Clean first node
    map(x, y).dir = INVALID;
    map(x, y).dirParent = INVALID;
    map(x, y).node = 0;
}

void getWords(Map& map, DicNode& dic, unsigned int n, std::vector<std::string>& output)
{
    if(n > dic.max || n < dic.min) return;

    // Search words for every position
    for(unsigned int y = 0; y < map.getHeight(); y++)
        for(unsigned int x = 0; x < map.getWidth(); x++)
            findWordsFromPosition(map,dic.at(map(x,y).l),x,y,n,output);
}

#include <fstream>

int main()
{
    srand((unsigned int)time(0));
    // Prepare the words, for example, load them from a real dictionary
    DicNode dictionary(0);
    unsigned int maxSize = 0;

    // Note: the following code make sense only if you load words from a file
    {
        std::ifstream dico("english.txt");
        std::string str;
        int count = 0;
        while(dico >> str)
        {
            if(str.size() > maxSize) maxSize = str.size();
            addWord(&dictionary,str);
            count++;
        }
        std::cout << "Read " << count << " words from dictionary, longest with " << maxSize << " letters.\n";
    }

    // Prepare the matrix
    Map mat(4,4);
    /*
    mat(0,0) = 'A';
    mat(1,0) = 'N';
    mat(2,0) = 'F';
    mat(3,0) = 'N';
    mat(0,1) = 'S';
    mat(1,1) = 'L';
    mat(2,1) = 'E';
    mat(3,1) = 'R';
    mat(0,2) = 'G';
    mat(1,2) = 'O';
    mat(2,2) = 'R';
    mat(3,2) = 'R';
    mat(0,3) = 'S';
    mat(1,3) = 'I';
    mat(2,3) = 'U';
    mat(3,3) = 'I';
    */
    std::cout << "\nMatrix:\n";
    mat.print();

    // Search words
    std::vector<std::string> words;
    for(unsigned int t = 5; t <= 7; t++)
        getWords(mat,dictionary,t,words);
    std::cout << "\nWords:\n";
    if(words.size() == 0)
        std::cout << " (no words)\n";
    else
        for(unsigned int t = 0; t < words.size(); t++)
            std::cout << " " << words[t] << "\n";
}

注: 前のコードはコード検索リゾルバーでした。回答から削除されましたが、この回答の編集リビジョンで検索することで引き続き取得できます。

EDIT2:口頭での説明

辞書。これは一種の基数ツリーですが、各ノードには格納する文字が 1 つしかありません。考慮されるアルファベットの文字ごとに 1 つずつ、最大 26 の子 (アクセントや数字を含める場合はそれ以上) を持つことができます。基本的なレイアウトは次のとおりです。

単語の木 http://img571.imageshack.us/img571/2281/wtree.png

例:

辞書の例 http://img706.imageshack.us/img706/1244/wordsr.png

各ノードには、単語の終わりかどうかを示すブール値も含まれています。さらに、各ノードには、その子ブランチ、その親、およびルートからのレベル (= プレフィックスのサイズ) の最小サイズと最大サイズが格納されます。子ブランチが特定の長さの単語を提供できないことがわかった場合、単語の検索を停止できます。

マトリックス。行列は、文字の double 配列です。しかし、より効率的にするために、いくつかのデータを追加しました: ディクショナリ内の対応するノード (アルゴリズムを参照)、その子への方向 (アルゴリズムを参照)、およびその親への方向 (アルゴリズムを参照)。

アルゴリズム。アイデアは、「降順」(現在見つかった文字列を表すパスを増やす)によって、マップ内を「円」にすることです。下降が不可能な場合は、「上昇」する必要があります。

FIND WORD:
----------

while(we can continue)
    try descend
    if(failed to descend)
        ascend
    else
        node = node of current cell
        if node is end of word
            add it to the found words, then ascend
        else if node is out of range (ie, n = 5, but with node you can only have words with 3 characters)
            ascend

DESCEND:
--------

we're from cell (x,y), with node D
for each direction (from cell.direction to north-west, clockwise)
    advance position (x,y) with direction
    if the position is valid
        and we haven't already visited the cell for this word
        and the node got with D.children[cell.letter] exists
        set the current position of the algorithm to the result of the advance
        set the direction of the old cell to the direction used to advance
        set the direction of the new cell to the opposite (pointing to the old cell)
        set the node of the new cell by the found one
        return

ASCEND:
-------

we're at (x,y)
cell.node = 0 (the cell is not considered as visited anymore)
advance the parent direction by one clockwise (if this direction is north-west, we use a special direction that say that we cannot rotate anymore: FINISHED)
cell.dir = INVALID
advance (x,y) following cell.direction_to_parent
cell.direction_to_parent = INVALID
set the current position to the result of advance

画像説明:

展開されたアルゴリズム http://img822.imageshack.us/img822/1374/algow.png

手順 (番号はロサンジュに示されています):

  1. これが私たちのマトリックスです。フランス語の辞書でテストしました。(0,0) セルから始めるとしましょう (アルゴリズムはどこから始めても機能します)。
  2. 最初の有効な方向 (マップ内の単語 +) は東です。それに沿って進みます。ノードは単語の終わりではなく、まだ有効です。
  3. 最初の有効な方向 (マップ内の単語 +) は南東です。それに沿って進みます。ノードは単語の終わりではなく、まだ有効です。
  4. 最初の有効な方向 (マップ内の単語 +) は東です。それに沿って進みます。ノードは単語の終わりではなく、まだ有効です。
  5. 有効な方向 (マップ外、既に訪れたセル (E)、または辞書にない) がないため、上ります。
  6. 最初の有効な方向 (マップ内の単語 +) は南東です。それに沿って進みます。ノードは単語の終わりではなく、まだ有効です。
  7. 最初の有効な方向 (マップ内の単語 +) は南です。それに沿って進みます。ノードは単語の終わりではなく、まだ有効です。
  8. 最初の有効な方向 (マップ内の単語 +) は西です。それに沿って進みます。ノードは単語 (「anerie」) の末尾であるため、見つかった単語のリストに追加し、上に移動します。参考までに、フランス語の「ânerie」は、英語では「ナンセンス」または「愚かさ」です。
  9. 等々...
于 2012-11-30T12:09:53.790 に答える
2

実際の単語を探しているわけではないため、膨大な数の組み合わせを扱っています。もしそうなら、文字の組み合わせを調べて、特定の言語で単語がまだ使用可能かどうかを確認できます。検索すると時間がかかりますが、多くの組み合わせが削除されます。
しかし、最終的に、アプリケーションをもう少し高速化するためにどれだけの時間を費やすことができますか?

そうは言っても、ベクトルと文字列を値で渡すため、最適化の可能性がある可能性があります。これにより、メモリ割り当てが生成されます(確かにベクトルの場合、おそらく文字列の場合)。代わりにを渡すと、std::array<lettera*,wordlength>より高速になり、単語も表示されます。

于 2012-11-30T13:02:02.043 に答える