要解决的问题称为“Word Morph”。你的导师对使用无向图给出了一些限制,其中相邻节点与原点只有一个字符不同。不幸的是,要求不够明确。 “一个字符的不同是模棱两可的。如果我们使用替换插入删除习语,那么我们可以通过比较两个相等大小的字符串来使用其他功能。我假设完整的方法。
而且,最后,您需要找到一条通过图表的最短路径。
我可以向您介绍一种可能的解决方案。一个完整的工作代码示例。
顺便说一下,这个图是无权的,因为从一个节点到下一个节点的成本总是 1。所以实际上我们在谈论一个无向的无权图。
我们这里需要用到的主要算法有:
- 莱文森。计算 2 个字符串的距离
- 和广度优先搜索,通过图找到短路路径
请注意,如果单词的长度应该相同,则不需要 Levensthein。只需逐个字符比较字符并计算差异。这很简单。 (但如前所述:说明有点不清楚)
这两种算法都可以修改。例如:您不需要大于 1 的 Levensthein 距离。您可以在找到距离 1 后终止距离计算。而且,在广度优先搜索中,您可以显示您要经过的路径。
好的,现在,如何实现无向图。有两种可能:
- 一个矩阵(我不会解释)
- 列表或向量
我会推荐这种情况下的矢量方法。矩阵会比较稀疏,所以最好是向量。
您需要的基本数据结构是包含顶点和邻居的节点。所以你有这个词 (a std::string) 作为顶点和“邻居”。这是图中其他节点的索引位置std::vector。
图是节点的向量。并且节点邻居指向该向量中的其他节点。我们使用向量中的索引来表示邻居。我们将所有这些打包成一个结构并将其称为“UndirectedGraph”。我们添加了一个“构建”函数来检查邻接关系。在这个函数中,我们将每个字符串与任何字符串进行比较并检查,如果差值小于 2,则为 0 或 1。0 表示相等,1 是给定的约束。如果我们发现这样的差异,我们将其添加为相应节点中的邻居。
此外,我们添加了广度优先搜索算法。描述于Wikipedia
为了简化该算法的实现,我们为节点添加了一个“已访问”标志。
请看下面的代码:
#include <sstream>
#include <iostream>
#include <vector>
#include <string>
#include <iterator>
#include <iomanip>
#include <numeric>
#include <algorithm>
#include <queue>
std::istringstream textFileStream(R"#(peach
peace
place
plane
plans
plays
slays
stays
stars
sears
years
yearn
)#");
using Vertex = std::string;
using Edge = std::vector<size_t>;
// One node in a graph
struct Node
{
// The Vertex is a std::string
Vertex word{};
// The edges are the index of the neighbour nodes
Edge neighbour{};
// For Breath First Search
bool visited{ false };
// Easy input and output
friend std::istream& operator >> (std::istream& is, Node& n) {
n.neighbour.clear();
std::getline(is, n.word);
return is;
}
friend std::ostream& operator << (std::ostream& os, const Node& n) {
os << std::left << std::setw(25) << n.word << " --> ";
std::copy(n.neighbour.begin(), n.neighbour.end(), std::ostream_iterator<int>(os, " "));
return os;
}
};
// The graph
struct UndirectedGraph
{
// Contains a vector of nodes
std::vector<Node> graph;
// build adjacenies
void build();
// Find Path
bool checkForPathFromStartToEnd(size_t start, size_t end);
bool checkForPath() {bool result = false;if (graph.size() > 1) {size_t s = graph.size() - 2;size_t e = s + 1;result = checkForPathFromStartToEnd(s, e); }return result; }
// Easy input and output
friend std::istream& operator >> (std::istream& is, UndirectedGraph& ug) {
ug.graph.clear();
std::copy(std::istream_iterator<Node>(is), std::istream_iterator<Node>(), std::back_inserter(ug.graph));
return is;
}
friend std::ostream& operator << (std::ostream& os, const UndirectedGraph& ug) {
size_t i{ 0 };
for (const Node& n : ug.graph)
os << std::right << std::setw(4) << i++ << ' ' << n << '\n';
return os;
}
};
// Distance between 2 strings
size_t levensthein(const std::string& string1, const std::string& string2)
{
const size_t lengthString1(string1.size());
const size_t lengthString2(string2.size());
if (lengthString1 == 0) return lengthString2;
if (lengthString2 == 0) return lengthString1;
std::vector<size_t> costs(lengthString2 + 1);
std::iota(costs.begin(), costs.end(), 0);
for (size_t indexString1 = 0; indexString1 < lengthString1; ++indexString1) {
costs[0] = indexString1 + 1;
size_t corner = indexString1;
for (size_t indexString2 = 0; indexString2 < lengthString2; ++indexString2) {
size_t upper = costs[indexString2 + 1];
if (string1[indexString1] == string2[indexString2]) {
costs[indexString2 + 1] = corner;
}
else {
const size_t temp = std::min(upper, corner);
costs[indexString2 + 1] = std::min(costs[indexString2], temp) + 1;
}
corner = upper;
}
}
size_t result = costs[lengthString2];
return result;
}
// Build the adjacenies
void UndirectedGraph::build()
{
// Iterate over all words in the graph
for (size_t i = 0; i < graph.size(); ++i)
// COmpare everything with everything (becuase of symmetries, omit half of comparisons)
for (size_t j = i + 1; j < graph.size(); ++j)
// Chec distance of the 2 words to compare
if (levensthein(graph[i].word, graph[j].word) < 2U) {
// And store the adjacenies
graph[i].neighbour.push_back(j);
graph[j].neighbour.push_back(i);
}
}
bool UndirectedGraph::checkForPathFromStartToEnd(size_t start, size_t end)
{
// Assume that it will not work
bool result = false;
// Store intermediate tries in queue
std::queue<size_t> check{};
// Set initial values
graph[start].visited = true;
check.push(start);
// As long as we have not visited all possible nodes
while (!check.empty()) {
// Get the next node to check
size_t currentNode = check.front(); check.pop();
// If we found the solution . . .
if (currentNode == end) {
// The set resultung value and stop searching
result = true;
break;
}
// Go through all neighbours of the current node
for (const size_t next : graph[currentNode].neighbour) {
// If the neighbour node has not yet been visited
if (!graph[next].visited) {
// Then visit it
graph[next].visited = true;
// And check following elements next time
check.push(next);
}
}
}
return result;
}
int main()
{
// Get the filename from the user
std::cout << "Enter Filename for file with words:\n";
std::string filename{};
//std::cin >> filename;
// Open the file
//std::ifstream textFileStream(filename);
// If the file could be opened . . .
if (textFileStream) {
// Create an empty graph
UndirectedGraph undirectedGraph{};
// Read the complete file into the graph
textFileStream >> undirectedGraph;
Node startWord{}, targetWord{};
std::cout << "Enter start word and enter target word\n"; // teach --> learn
std::cin >> startWord >> targetWord;
// Add the 2 words at the and of our graph
undirectedGraph.graph.push_back(startWord);
undirectedGraph.graph.push_back(targetWord);
// Build adjacency graph, including the just added words
undirectedGraph.build();
// For debug purposes: Show the graph
std::cout << undirectedGraph;
std::cout << "\n\nMorph possible? --> " << std::boolalpha << undirectedGraph.checkForPath() << '\n';
}
else {
// File could not be found or opened
std::cerr << "Error: Could not open file : " << filename;
}
return 0;
}
请注意:虽然我已经实现了询问文件名,但在这个例子中我没有使用它。我从一个字符串流中读取。您需要稍后删除 istringstream 并在现有语句中进行注释。
关于导师的要求:我没有使用任何 STL/Library/Boost 搜索算法。 (为什么?在这个例子中?)但我当然使用其他 C++ STL 容器。我不会重新发明轮子并想出一个新的“向量”或队列。而且我绝对不会使用“新”或 C 样式数组或指针算法。
玩得开心!
对所有其他人:对不起:我忍不住要写代码。 . .