#include using namespace std; const int M = 1010; //字符串数量上限,可以理解为边数 const int N = 26; //a-z映射了26个数字,也就是图中结点的数字上限 vector str; //输入的字符串数组 vector path; //结点的搜索路径 int n; //字符串数量 bool st[M]; //是否使用 int fa[N]; //并查集数组 int in[N]; //入度 int out[N]; //出度 unordered_map _map;//用来记录某个结点号是否出现过 //并查集,用来判断底图的连通性 int find(int x) { if (x != fa[x]) fa[x] = find(fa[x]); return fa[x]; } //加入家族集合中 void join(int a, int b) { int f1 = find(a), f2 = find(b); if (f1 != f2)fa[f1] = f2; } /** * 功能:深度优先搜索 * @param u 从哪个结点ID出发 * @param step 已经走了多少条边 */ void dfs(int u, int step) { //如果成功走了n条边,则是成功的标识 if (step == n) { //输出路径 for (int i = 0; i < path.size(); i++) { cout << str[path[i]]; if (i < path.size() - 1) cout << '.'; //最后一个不输出.,只有前面n-1个输出. } exit(0); } //遍历所有可能性,走多叉树,找出欧拉路径,(1)找到就直接退出,(2)从start开始肯定有,这两条保证了效率的提升 for (int i = 0; i < n; i++) { //找到能和u结点匹配的下一个未使用过的字符串 if (!st[i] && str[i][0] - 'a' == u) { path.push_back(i); //放入到路径中 st[i] = true; //标识为已使用 //开始尝试下一个字符串 dfs(str[i].back() - 'a', step + 1); //回溯 st[i] = false; //未使用 path.pop_back(); //路径弹出 } } } int main() { //输入字符串 cin >> n; string s; for (int i = 0; i < n; i++) cin >> s, str.push_back(s); // 排序确保搜索字典序最小 sort(str.begin(), str.end()); //并查集初始化,每个人都是自己的祖先 for (int i = 0; i < N; i++) fa[i] = i; //检查一下是不是底图连通,采用并查集,通俗点说:就是看看是不是能全部连通在一起,如果不能,最后是多个家族,则不是欧拉图 for (int i = 0; i < n; i++) { //每行是一个由 1 到 20 个"小写"字母组成的单词 int a = str[i][0] - 'a'; //首字母映射号,相当于结点编号 int b = str[i].back() - 'a'; //尾字母映射号,相当于结点编号 //转为数字,建立并查集之间的关系,这个字符映射数字用的很妙。 // join合并并查集 join(a, b); //标识a和b都使用过 _map[a]++, _map[b]++; //记录b的入度++ in[b]++; //记录a的出度++ out[a]++; //之所以记录出度和入度,是为了下一步的欧拉图二次检测 } // 判断欧拉图的第一个要求:底图连通性 int cnt = 0; //家族的数量 for (int i = 0; i < N; i++) if (fa[i] == i && _map[i]) cnt++;//自己是自己的祖先,并且出现过 //并查集的家族个数大于1,表示不可能是欧拉图 if (cnt > 1) { cout << "***"; return 0; } int flag = 0; //出度与入度差是1的个数 int start = str[0][0] - 'a';//默认是第一个字符串的第一个字符对应的结点 // 判断欧拉图的第二个要求:出度与入度的数字关系 for (int i = 0; i < N; i++) { //计算每个结点的出度与入度的差 int k = out[i] - in[i]; //出度与入度差大于1,则肯定不是欧拉图 if (abs(k) > 1) { cout << "***"; return 0; } //如果差是1,那么需要检查是不是2个,2个才是一个入口点,一个出口点 if (abs(k) == 1) { //记录个数 flag++; //如果出度比入度大1,记录下起点是哪个结点 if (k == 1) start = i; } } //如果不是0也不是2,那么不是欧拉图 if (flag != 0 && flag != 2) { cout << "***"; return 0; } //这时,肯定是有一条欧拉路径的了,找出这条欧拉路径 dfs(start, 0); return 0; }