【问题标题】:How do create a word ladder?如何创建一个字梯?
【发布时间】:2021-05-07 20:15:22
【问题描述】:

我正在尝试创建一个单词阶梯,使用链表作为单词字典,并使用队列来保存要更改的单词。

在队列的while循环中,它到达字典中的第一个单词(单词"toon"并更改为"poon")并停止。我怎样才能让它继续直到它到达目标单词?

代码如下:

#include <iostream>
#include <queue>
#include <stack>
#include <string>
using namespace std;

struct Node
{
    string data;
    Node* next;
};

void insert(string ele, Node*& head)
{
    Node* newnode = new Node;
    newnode->data = ele;
    newnode->next = head;
    head = newnode;
}

void del(string key, Node*& head)
{
    Node* temp = head;
    Node* prev = NULL;

    if (temp != NULL && temp->data == key)
    {
        head = temp->next; 
        delete temp;            
        return;
    }
    else
    {
        while (temp != NULL && temp->data != key)
        {
            prev = temp;
            temp = temp->next;
        }
        if (temp == NULL)
            return;

        prev->next = temp->next;

        delete temp;
    }
}

bool find(string key,Node *&head)
{
    Node* p = head;
    while (p != NULL)
    {
        if (p->data == key)
        {
            return true;
        }
        else
        {
            p = p->next;
        }
    }
    if (p == NULL)
        return false;
}

void print(Node*& head)
{
    Node* p = head;
    while (p != NULL)
    {
        cout << p->data;
        p = p->next;
    }
}

void WordLadder(string start, string target, Node*& head)
{
    if (start == target)
        cout << "They are the same";

    if (find(target, head) != true)
        cout << "Target Not found in dicionary";
    //start word size
    int wordlength = start.size();
    //counter
    int level = 0;
    queue<string> q;
    //push word in queue
    q.push(start);
    int len = 0;
    while (!q.empty())
    {
        int wordlength = start.size();
        int sizeofq = q.size();
       
        string word = q.front();
        q.pop();
        for (int i = 0; i < wordlength ; i++)
        {
            for (char c = 'a'; c <= 'z'; c++)
            {
                word[i] = c;
                   
                if (word == target)
                {
                    q.pop();
                }
                if (find(word, head) == true)
                {
                    del(word, head);
                    q.push(word);
                    break;
                }
            }
        }
    }
    cout << len;
}

int main()
{
    Node* head = NULL;
    insert("poon", head);
    insert("plee", head);
    insert("same", head);
    insert("poie", head);
    insert("plie", head);
    insert("poin", head);
    insert("plea", head);
    string start = "toon";
    string target = "plea";
    
    WordLadder(start, target, head);
   
    return 0;
}

【问题讨论】:

  • 看起来您正在尝试使用与 BFS 非常相似的算法。您可以尝试搜索它是如何构建的。我会尝试修复您的代码。
  • 您的代码看起来像是 C 和 C++ 的混合体。你确定一个。你应该重新实现一个链表(STL 中已经有std::list,你正在使用它来表示std::stringstd::queue),b。你为什么要使用像NULLoperator new 和裸指针而不是智能指针和移动语义和c。你为什么首先使用链表?链表在现代机器上效率极低,与vectordeque 之类的东西相比几乎毫无意义。此外,我会远离指针引用(即 T*&) - 事情会变得非常混乱。
  • 如果有比链表更好的方法,请告诉我 - 如果你真的需要,请使用std::vectorstd::list一个链表(提示:你没有)。在没有充分理由的情况下重新发明轮子通常会导致代码质量较差,难以理解。
  • 在这里,我建议使用一些具有良好“查找”功能的数据结构(尽管您可以保持 std::vector 排序)。我会使用std::set
  • List of and documentation for the containers offered out of the box by modern C++。阅读每一种的推荐用法,然后选择最适合这项工作的一种。但是,如果std::vector 在输入小数据集时甚至优于最佳拟合,请不要感到惊讶。算法越智能,通常在开始获得奖励之前要克服的开销就越多,vector 是如此“愚蠢”,几乎是中小型数据量的王者。

标签: c++ linked-list queue


【解决方案1】:

算法

看来你是在正确的轨道上,试图实现像 BFS 这样的东西,所以我不打算详细解释“字梯”的算法。 但高层次的概述是:

  1. 将起始词推入队列
  2. 循环运行直到队列为空
  3. 遍历与当前单词仅相差一个字符的所有单词,并 将单词推入队列(用于 BFS)
  4. 重复直到找到目标单词或遍历所有单词

您犯的错误以及我如何修复它们:

在“wordlength”循环之前,您需要一个循环来遍历队列中的所有元素。虽然while 循环似乎正在这样做,但事实并非如此。我想你意识到了这一点,因为你创建了一个 sizeofq 变量并且从未使用过它。循环如下所示:

for(int j = 0; j < sizeofq; j++) {

并封装接下来的两个循环。您还需要添加一个临时变量来存储word[i] 的初始状态。您还犯了一些错误,因为您使用了错误的变量。

正如 cmets 中所讨论的,我从使用您创建的自定义链接列表切换到 std::set,但如果需要,您可以轻松切换回它,因为它似乎不是导致问题的原因。 完整代码如下:

#include <iostream>
#include <queue>
#include <string>
#include <set>
using namespace std;

void WordLadder(string start, string target, set<string>& myset)
{
    if(start == target) {
        cout << "They are the same" << "\n";
        return;
    }

    if(myset.find(target) == myset.end()) {
        cout<<"Target Not found in dicionary" << "\n";
        return;
    }
    
    int wordlength = start.size();
    int level = 0;

    queue<string> q;
    q.push(start);

    while (!q.empty())
    {
        level++;
        int sizeofq = q.size();
        for(int j = 0; j < sizeofq; j++) {
            string word = q.front();
            q.pop();
            for(int i = 0; i < wordlength; ++i) 
            {
                char temp_ch = word[i];
                for (char c = 'a'; c <= 'z'; c++)
                {
                    word[i] = c;
                    if (word == target) 
                    {            
                        cout << level + 1 << endl;
                        return;
                    }
                    if (myset.find(word) == myset.end())
                    {
                        continue;
                    }
                    myset.erase(word);
                    q.push(word);
                }

                 word[i] = temp_ch;
            }
            
        }      
    }

    return;   
}


int main()
{
   
    std::set<string> myset;
    myset.insert("poon");
    myset.insert("plee");
    myset.insert("same");
    myset.insert("poie");
    myset.insert("plie");
    myset.insert("poin");
    myset.insert("plea");
    string start = "toon";
    string target = "plea";
    
    WordLadder(start, target, myset);
    return 0;
} 

风格建议

你似乎是一个新的 C++ 程序员,所以我想我会把我对代码的想法留在这里。

将函数return 作为结果而不是打印它被认为是一种很好的做法。它使您的代码更加灵活。

您实现了自己的容器来完成这项工作,这有点像重新发明轮子。 C++ STL 几乎总是包含一些可以让您的生活更轻松的东西,因此请在开始工作之前搜索它。

如果您正在编写更大的项目,请不要使用using namespace,但对于像这样的小玩具就可以了。

【讨论】:

    【解决方案2】:

    正如我在一个答案中已经说过的,在 C++ 中滚动自己的容器通常会导致代码混乱且难以阅读,这在建模问题时根本没有帮助。

    在现代 C++ 中(即在 C++11 之后,现在理想情况下你应该使用 C++17,甚至开始胆怯地涉足 C++20)你几乎不需要像 operator newNULL 和通常是指针(尽管它们仍然可以作为缺少std::optional&lt;T&amp;&gt; 的权宜之计)。

    顺便说一句,我认为不需要使用队列来解决您的问题;单个“当前项目” 变量就足够了;然后,您可以通过在列表中搜索与当前元素的汉明距离为 1 的字符串来找到下一个元素。由于循环的可能性,它在一般情况下不起作用,但如果您只处理有限的单词列表,例如您的单词列表,其中实际上只有一个可能的阶梯。

    这就是我在 C++20 中快速建模您的问题的方法:

    #include <algorithm>
    #include <cstdlib>
    #include <iostream>
    #include <ranges>
    #include <stdexcept>
    #include <string>
    #include <string_view>
    #include <vector>
    
    using namespace std::literals;
    
    // Simple implementation of Hamming's distance (see https://en.wikipedia.org/wiki/Hamming_distance)
    std::size_t hamming_distance(const std::string_view s1, const std::string_view s2) {
        std::size_t dist {};
    
        if (s1.size() != s2.size()) {
            throw std::runtime_error { "strings have different lengths, which is unsupported" };
        }
    
        auto it1 { s1.begin() };
        auto it2 { s2.begin() };
    
        const auto end1 { s1.end() };
    
        while (it1 != end1) {
            dist += *it1++ != *it2++;
        }
    
        return dist;
    }
    
    bool word_ladder(std::string_view start, const std::string_view target, std::vector<std::string> words) {
        if (start == target) {
            std::cout << "noop: start and target are the same\n";
            
            return true;
        }
    
        // C++20's ranges - use std::find(std::begin(words), std::end(words), target) in older revisions
        if (std::ranges::find(words, target) == std::end(words)) {
            std::cerr << "error: target Not found in dictionary\n";
    
            return false;
        }
    
        std::size_t len { 1U };
    
        // Current word in the ladder - must be string because we are deleting them from the vector,
        // so we must copy them
        std::string word { start };
    
        std::cout << word;
    
        while (word != target) {
            std::cout << "->";
    
            // find an element in the dictionary has hamming(el, word) == 1 (i.e. 'cord' and 'core').
            // this won't work if there's more than one match, because it will cause a loop.
            // This is also based on C++20's ranges, and it can be replaced with std::find_if
            const auto next_it { std::ranges::find_if(words, [word] (const auto el) { return hamming_distance(el, word) == 1; }) };
    
            // no match, it means the chain can't be completed
            if (next_it == std::end(words)) {
                std::cout << "X (no result)\n";
    
                return false;
            }
    
            // print the next element
            std::cout << *next_it;
          
            // set the next word as the newly found item
            word = *next_it;
    
            // remove it from the vector
            // while this is O(n), it's empirically as fast as using a more "optimized" container
            // due to how hecking fast vectors and memcpy are on modern CPUs
            words.erase(next_it); 
    
            ++len; // chain length counter
        }
    
        std::cout << "\nChain was " << len << " elements long\n";
    
        return true;
    }
    
    int main() {
        std::vector<std::string> words {
            "poon",
            "plee",
            "same",
            "poie",
            "plie",
            "poin",
            "plea",
        };
    
        const auto start { "toon"sv };
        const auto target { "plea"sv };
        
        const bool success { word_ladder(start, target, std::move(words)) };
       
        return success ? EXIT_SUCCESS : EXIT_FAILURE;
    } 
    

    输出是:

    toon->poon->poin->poie->plie->plee->plea
    Chain was 7 elements long
    
    

    我使用了std::vector,而不是更“优化”的容器,因为它通常是最好的全能选择,即使在严重受限的系统上,由于现代 CPU 的速度有多快。链表特别糟糕,应该(几乎)总是避免使用,因为它们来自指针间接的大量开销,这抵消了您可能从它们中获得的任何理论收益。

    一般情况下,您通常应该默认使用向量和哈希映射(即 std::unordered_map),并且仅在您真正需要其他容器时才考虑它们。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-25
      • 1970-01-01
      • 2021-09-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多