词梯 (图)

[问题描述]

词梯:给定两个长度相等的单词,一个作为起点,一个作为终点。每次只改变一个字母,最终,作为起点的单词可以转变为终点的单词。

例如:code->cade->cate->date->data

词梯不是唯一的,如work->play:

work fork form foam flam flay play

work pork perk peak pean plan play

work pork perk peak peat plat play

work pork perk pert peat plat play

work pork porn pirn pian plan play

work pork port pert peat plat play

work word wood pood plod ploy play

work worm form foam flam flay play

work worn porn pirn pian plan play

work wort bort boat blat plat play

work wort port pert peat plat play

work wort wert pert peat plat play

[基本要求]

(1)从https://github.com/dwyl/english-words中下载words.txt作为词汇表;

(2)给定两个长度相等的单词,一个作为起点,一个作为终点,寻找长度最短的词梯。

解题方法:

从一个单词向外发散,这看起来像一个树的结构。但观察示例可以发现,有些单词与多个单词相连,这就形成了回路,所以它的结构是一个有向图,并不是树。所以,我们要根据词梯的定义来构建一张图。

1、首先,我们要先对所有单词进行一次筛选,选出字母数与起点单词和终点单词相等的单词

2、然后从起点单词出发,逐次选出与前一轮单词(这里将起始单词视为第一轮)只相差一个字母的单词,加入图中;

3、第一次碰到终点单词时,此轮即为最后一轮,记录这是第几轮即可得到词梯的最短长度,这一轮结束后,图的构建就完成了;

4、最后,利用DFS遍历图:从起点单词出发,设置递归深度为词梯的最短长度,沿途保存经过的点,到达指定深度时,若单词为终点单词,则说明这是一个词梯,打印输出。

当然,两个单词之间也可能没有词梯,这时候直接输出没有即可。

程序代码:

# include <iostream>
# include <string.h>
# include <vector>
# include <fstream>
# define maxn 500000
# define SIZE 100000
using namespace std;

//用邻接表构建有向图
typedef struct ArcNode {  //边的结点结构类型
    int adjvex;           //该边的终点编号
    struct ArcNode* nextarc;  //指向下一条边的指针
}ArcNode;
typedef struct VexNode {  //顶点结构
    int sno;              //顶点编号
    int layer;            //保存单词位于第几环(层)
    char word[50];        //单词
    ArcNode* firstarc;    //指向第一条与该顶点有关的边的指针
}VexNode;
typedef struct Graph {    //邻接表结构类型
    VexNode* VNode;       //定义邻接表
    int vexnum;           //顶点数
}Graph;

//创建符合字母数的单词表
void CreateForm();
//判断与上一单词是否只差一个字母
bool Judge(char* a, char* b);
//寻找词梯
void FindLadder(Graph& G, char* start, char* end);
//打印词梯
void DFS(Graph G, int n, int m, vector<int>t);

int res = 1;           //是否存在词梯
int len;               //单词长度
int Num = 1;           //符合字母数的单词总数
int Layer = 1;         //记录经过多少轮到达终点单词
char word1[50];        //起点单词
char word2[50];        //终点单词
char words[maxn][50];  //保存符合字母数的所有单词
int vis[maxn] = { 0 }; //记录单词表中的单词是否已匹配,若已匹配,则保存编号
int vis1[maxn] = { 0 };//记录图中的单词是否已访问过

int main()
{
    cout << "请输入起点单词:";
    cin >> word1;
    cout << "请输入终点单词:";
    cin >> word2;
    len = strlen(word1);
    CreateForm();
    Graph G;
    vector<int>t;
    FindLadder(G, word1, word2);
    if (res) {
        cout << "\n最短的词梯长度为" << Layer << endl;
        cout << "以下是所有符合要求的词梯:" << endl;
        DFS(G, 1, 1, t);
    }
    else {
        cout << "\n两个单词间不存在词梯" << endl;
    }
    return 0;
}

//创建符合字母数的单词表
void CreateForm()
{
    fstream file;
    file.open("words.txt", ios::in);
    if (file.fail()) {
        cout << "文件打开失败" << endl;
        exit(0);
    }
    char str[50];
    int n;
    file.getline(str, 50);
    while (!file.eof()) {
        n = strlen(str);
        if (n == len) {
            strcpy(words[Num], str);
            Num++;
        }
        file.getline(str, 50);
    }
    strcpy(words[Num], str);
    file.close();
}

//判断与上一单词是否只差一个字母
bool Judge(char* a, char* b)
{
    int k = 0;
    char a1[50], b1[50];
    strcpy(a1, a);
    strcpy(b1, b);
    strlwr(a1);  //将a中的大写字母转为小写字母
    strlwr(b1);
    for (int i = 0; i < len; i++) {
        if (a1[i] != b1[i]) {
            k++;
        }
    }
    if (k == 1) {
        return true;
    }
    else {
        return false;
    }
}

//寻找词梯
void FindLadder(Graph& G, char* start, char* end)
{
    //初始化
    G.VNode = (VexNode*)malloc(SIZE * sizeof(VexNode));
    G.vexnum = 1;   //顶点数
    G.VNode[0].layer = 0;
    ArcNode* p, * q;
    char a[50];
    int n = 1;   //邻接表中顶点的编号
    int k = 1;   //保存这一层有几个单词
    int t = 1;   //记录是否已出现终点单词
    //初始化起点单词
    strcpy(G.VNode[n].word, start);
    G.VNode[n].firstarc = NULL;
    G.VNode[n].sno = n;
    G.VNode[n].layer = 1;
    n++;
    while (t) {
        if (!k) {
            //未找到词梯
            res = 0;
            break;
        }
        int m = 0;        //记录这一层有多少个单词
        int b = n - 1 - k;
        Layer++;          //轮数加1
        for (int i = 0; i < k; i++) {
            //对这一层的所有单词均进行依次匹配查找
            b = b + 1;
            strcpy(a, G.VNode[b].word);  //a代表当前进行查询的单词
            //开始匹配查找
            for (int j = 1; j <= Num; j++) {
                if (Judge(a, words[j])) {
                    //匹配成功
                    if (!vis[j] && strcmp(words[j], start) != 0) {
                        //该单词未添加,在邻接表中添加新的单词
                        if (strcmp(words[j], end) == 0) {
                            //匹配到终点单词
                            t = 0;
                        }
                        m++;
                        G.vexnum++;
                        strcpy(G.VNode[n].word, words[j]);
                        G.VNode[n].firstarc = NULL;
                        G.VNode[n].sno = n;
                        G.VNode[n].layer = Layer;
                        vis[j] = n;   //该单词已加入邻接表
                        n++;
                    }
                    //在当前单词后添加新边
                    if (Layer == G.VNode[vis[j]].layer) {
                        //只有单词层数与当前层数相同时才建立边
                        p = (ArcNode*)malloc(sizeof(ArcNode)); //创建一个用于存放当前边的结点p
                        p->nextarc = NULL;
                        p->adjvex = vis[j];
                        q = G.VNode[b].firstarc;
                        //将新边按顺序插入到链表末尾
                        if (q == NULL) {
                            G.VNode[b].firstarc = p;
                        }
                        else {
                            while (q->nextarc != NULL) {
                                q = q->nextarc;
                            }
                            q->nextarc = p;
                        }
                    }
                }
            }
        }
        k = m;
    }
}

//打印词梯
//n:点的编号  m:轮数  t:保存经过的单词编号,用于后面输出
void DFS(Graph G, int n, int m, vector<int>t)
{
    t.push_back(n);
    if (m == Layer) {
        if (strcmp(G.VNode[n].word, word2) == 0) {
            //到达最后一层且该层单词为起始单词
            for (int i = 0; i < t.size(); i++) {
                cout << G.VNode[t[i]].word << " ";
            }
            cout << endl;
        }
        return;
    }
    vis1[n] = 1;             //将起始点标记为已被访问过的状态
    ArcNode* p = G.VNode[n].firstarc;
    while (p) {
        //遍历起始点的所有邻接点,若邻接点已被访问过,则p继续向后遍历
        //否则,以当前邻接点为新的起始点递归进行深度优先遍历
        if (!vis1[p->adjvex]) {
            DFS(G, p->adjvex, m + 1, t);
            vis1[p->adjvex] = 0;
        }
        p = p->nextarc;
    }
}

运行结果:(示例)

请输入起点单词:cold
请输入终点单词:warm

最短的词梯长度为5
以下是所有符合要求的词梯:
cold Cord Card ward warm
cold Cord corm WORM warm
cold Cord Word ward warm
cold Cord Word WORM warm
cold wold Wald ward warm
cold wold Word ward warm
cold wold Word WORM warm

总结:

使用上述方法,运行大概需要2秒左右的时间。个人认为应该还有更快的方法,有兴趣的小伙伴可以自行探索。

以上便是我对这道题的看法,很高兴与大家分享。

猜你喜欢

转载自blog.csdn.net/CXR_XC/article/details/128772431