1/*
2最短路径的一个举例:
3
4问题描述:在字梯游戏中,每一个词语都是通过将字梯的前一个词改变一个字母形成的。
5例如,我们可以通过一系列的单字母替换将zero转换成five:zero, hero, here, hire, fire, five。
6
7这个是一个无权最短路径问题,每个词语是个顶点,如果两个顶点可以通过一个字母的替换相互转换的话,
8这两个顶点就有一个双向的边。
9
10首先,我们要从一个字典中生成这么一个图:使用STL中的map,其中键是单词(string),
11值是一个通过此单词一次变换能够得到的单词的集合vector<string>。
12问题的关键在于:如何从一个包含89000个单词的词组来构造map。
13可以用蛮力法,但是时间太慢,所有单词之间都要比较。一个优化是:避免比较长度不同的单词。
14这个可以通过将单词按照长度先分组来做到。
15
16一个方法是:对每个相同长度的数组用蛮力法。
17另一个方法是:附加一个map,其中的键是一个反映单词长度的int,值是所有的具有这个长度的单词集合。
18思想还是同上。
19第三个方法是:使用附加map。将单词按照长度分组,对每个组分别操作。例如对长度为4的小组。
20对每个长度为4的单词删除第一个字母,保留剩下的三个字母的样本,生成一个map,其中的键是这个样本,
21值是所有的有这个样本单词的vector。例如单词"wine",去掉'w'生成样本"ine",则样本"ine"对应的
22vector是"dine", "fine", "wine", "nine".,样本"oot"对应:boot, foot, hoot, loot..最后的这个map的值(vector)就是
23一组单词,其中每个单词都能够通过一个字符的替换生成其他的单词。
24*/
25
26#include <iostream>
27#include <sstream>
28#include <algorithm>
29#include <string>
30#include <map>
31#include <set>
32#include <list>
33#include <stack>
34#include <queue>
35#include <cctype>
36#include <vector>
37#include <bitset>
38#include <cmath>
39//#include <hash_map>
40
41#define FORR(i, a, b) for(int i = a; i < b; ++i)
42#define BE(x) x.begin(),x.end()
43#define MP(a, b) make_pair(a, b)
44
45using namespace std;
46//using namespace stdex;
47
48typedef vector<string> VS;
49typedef vector<int> VI;
50
51//words是所有的单词
52map<string, VS> computerAdjacentWords(const VS& words)
53{
54 map<string, VS> adjWords;
55 map<int, VS> wordsByLength;
56
57 //按长度分组
58 for (int i = 0; i < words.size(); i++)
59 {
60 wordsByLength[words[i].length()].push_back(words[i]);
61 }
62 map<int, VS>::const_iterator itr;
63 //对每个分组操作
64 for (itr = wordsByLength.begin(); itr != wordsByLength.end(); ++itr)
65 {
66 const VS& groupsWords = itr->second;
67 int groupNum = itr->first;
68
69 //操作每个组中的每个位置
70 for (int i = 0; i < groupNum; i++)
71 {
72 //形如:"ine" -> "fine", "wine", "nine".
73 map<string, VS> repToWord;
74
75 for (int j = 0; j < groupsWords.size(); j++)
76 {
77 string rep = groupsWords[j];
78 rep.erase(i, 1);
79 //map的无重复性就是好,而且如果没有对应的可以直接新建一个
80 //wine加到ine中,fine也加到ine中
81 //wine也加到wne中
82 repToWord[rep].push_back(groupsWords[j]);
83 }
84
85 // 然后查找map中的值不止一个单词的(就是有像wine,fine这种)
86 map<string, VS>::const_iterator itr2;
87 for (itr2 = repToWord.begin(); itr2 != repToWord.end(); ++itr2)
88 {
89 const VS& clique = itr2->second;
90 if (clique.size() >= 2)
91 {
92 for (int p = 0; p < clique.size(); p++)
93 {
94 for (int q = p + 1; q < clique.size(); q++)
95 {
96 adjWords[clique[p]].push_back(clique[q]);
97 adjWords[clique[q]].push_back(clique[p]);
98 }
99 }
100 }
101 }
102 }
103 }
104 return adjWords;
105}
106
107/*
108下面就要使用单元无权最短路径算法了:
109上面生成的map就相当是一个邻接链表的图表示:
110一个节点和所有与它相连的节点集合。
111由于,单源无权最短路径算法只能给出每个节点在路径中的前驱节点,所以,
112这里返回的map<string,string>值就是最短路径中每个节点的相应前驱节点,而map中所有的键就是
113最短路径中的所有节点。例如:将zero转换成five:zero, hero, here, hire, fire, five,则,
114findChain返回的map<string,string>previousWord就是previousWord[five] = fire, previousWord[here] = hero。
115注意:这里是找到从zero出发到所有节点的路径(中节点的前驱集合,所以没有second参数)
116*/
117map<string, string> findChain(const map<string, VS> & adjacentWords, const string& first)
118{
119 map<string, string> previousWord;
120 queue<string> q;
121
122 q.push(first);
123 while (!q.empty())
124 {
125 string current = q.front();
126 q.pop();
127
128 map<string, VS>::const_iterator itr;
129 itr = adjacentWords.find(current);
130
131 const VS& adj = itr->second;
132 for (int i =0; i < adj.size(); i++)
133 {
134 if (previousWord[adj[i]] == "")
135 {
136 previousWord[adj[i]] = current;
137 q.push(adj[i]);
138 }
139 }
140 }
141 previousWord[first] = "";
142
143 return previousWord;
144}
145
146VS getChainFromPrevMap(const map<string, string>& previous, const string& second)
147{
148 VS result;
149 //类型转换,因为操作符[]不能用在不可变的map中
150 map<string, string>& prev = const_cast<map<string, string> &> (previous);
151
152 for (string current = second; current != ""; current = prev[current])
153 result.push_back(current);
154
155 reverse(result.begin(), result.end());
156
157 return result;
158}
159
160int main()
161{
162 string a[] = {"five", "hero", "boyfriend", "james", "zero", "good", "hire", "thank", "fire",
163 "here", "pen", "ccbb", "greatman", "come", "great", "greet", "gold", "glad"};
164 VS input(a, a+18);
165 string first("zero"), second("five");
166
167 //先构造一个图
168 map<string, VS> adjWords = computerAdjacentWords(input);
169
170 //再构造某个节点到所有节点最短路径中先驱集合
171 map<string, string> previousWords = findChain(adjWords, first);
172
173 //在找出到某个终点的最短路径
174 VS result = getChainFromPrevMap(previousWords, second);
175
176 FORR(i, 0, result.size())
177 {
178 cout << result[i] << " ";
179 }
180 cout << endl;
181
182 return 0;
183}
2最短路径的一个举例:
3
4问题描述:在字梯游戏中,每一个词语都是通过将字梯的前一个词改变一个字母形成的。
5例如,我们可以通过一系列的单字母替换将zero转换成five:zero, hero, here, hire, fire, five。
6
7这个是一个无权最短路径问题,每个词语是个顶点,如果两个顶点可以通过一个字母的替换相互转换的话,
8这两个顶点就有一个双向的边。
9
10首先,我们要从一个字典中生成这么一个图:使用STL中的map,其中键是单词(string),
11值是一个通过此单词一次变换能够得到的单词的集合vector<string>。
12问题的关键在于:如何从一个包含89000个单词的词组来构造map。
13可以用蛮力法,但是时间太慢,所有单词之间都要比较。一个优化是:避免比较长度不同的单词。
14这个可以通过将单词按照长度先分组来做到。
15
16一个方法是:对每个相同长度的数组用蛮力法。
17另一个方法是:附加一个map,其中的键是一个反映单词长度的int,值是所有的具有这个长度的单词集合。
18思想还是同上。
19第三个方法是:使用附加map。将单词按照长度分组,对每个组分别操作。例如对长度为4的小组。
20对每个长度为4的单词删除第一个字母,保留剩下的三个字母的样本,生成一个map,其中的键是这个样本,
21值是所有的有这个样本单词的vector。例如单词"wine",去掉'w'生成样本"ine",则样本"ine"对应的
22vector是"dine", "fine", "wine", "nine".,样本"oot"对应:boot, foot, hoot, loot..最后的这个map的值(vector)就是
23一组单词,其中每个单词都能够通过一个字符的替换生成其他的单词。
24*/
25
26#include <iostream>
27#include <sstream>
28#include <algorithm>
29#include <string>
30#include <map>
31#include <set>
32#include <list>
33#include <stack>
34#include <queue>
35#include <cctype>
36#include <vector>
37#include <bitset>
38#include <cmath>
39//#include <hash_map>
40
41#define FORR(i, a, b) for(int i = a; i < b; ++i)
42#define BE(x) x.begin(),x.end()
43#define MP(a, b) make_pair(a, b)
44
45using namespace std;
46//using namespace stdex;
47
48typedef vector<string> VS;
49typedef vector<int> VI;
50
51//words是所有的单词
52map<string, VS> computerAdjacentWords(const VS& words)
53{
54 map<string, VS> adjWords;
55 map<int, VS> wordsByLength;
56
57 //按长度分组
58 for (int i = 0; i < words.size(); i++)
59 {
60 wordsByLength[words[i].length()].push_back(words[i]);
61 }
62 map<int, VS>::const_iterator itr;
63 //对每个分组操作
64 for (itr = wordsByLength.begin(); itr != wordsByLength.end(); ++itr)
65 {
66 const VS& groupsWords = itr->second;
67 int groupNum = itr->first;
68
69 //操作每个组中的每个位置
70 for (int i = 0; i < groupNum; i++)
71 {
72 //形如:"ine" -> "fine", "wine", "nine".
73 map<string, VS> repToWord;
74
75 for (int j = 0; j < groupsWords.size(); j++)
76 {
77 string rep = groupsWords[j];
78 rep.erase(i, 1);
79 //map的无重复性就是好,而且如果没有对应的可以直接新建一个
80 //wine加到ine中,fine也加到ine中
81 //wine也加到wne中
82 repToWord[rep].push_back(groupsWords[j]);
83 }
84
85 // 然后查找map中的值不止一个单词的(就是有像wine,fine这种)
86 map<string, VS>::const_iterator itr2;
87 for (itr2 = repToWord.begin(); itr2 != repToWord.end(); ++itr2)
88 {
89 const VS& clique = itr2->second;
90 if (clique.size() >= 2)
91 {
92 for (int p = 0; p < clique.size(); p++)
93 {
94 for (int q = p + 1; q < clique.size(); q++)
95 {
96 adjWords[clique[p]].push_back(clique[q]);
97 adjWords[clique[q]].push_back(clique[p]);
98 }
99 }
100 }
101 }
102 }
103 }
104 return adjWords;
105}
106
107/*
108下面就要使用单元无权最短路径算法了:
109上面生成的map就相当是一个邻接链表的图表示:
110一个节点和所有与它相连的节点集合。
111由于,单源无权最短路径算法只能给出每个节点在路径中的前驱节点,所以,
112这里返回的map<string,string>值就是最短路径中每个节点的相应前驱节点,而map中所有的键就是
113最短路径中的所有节点。例如:将zero转换成five:zero, hero, here, hire, fire, five,则,
114findChain返回的map<string,string>previousWord就是previousWord[five] = fire, previousWord[here] = hero。
115注意:这里是找到从zero出发到所有节点的路径(中节点的前驱集合,所以没有second参数)
116*/
117map<string, string> findChain(const map<string, VS> & adjacentWords, const string& first)
118{
119 map<string, string> previousWord;
120 queue<string> q;
121
122 q.push(first);
123 while (!q.empty())
124 {
125 string current = q.front();
126 q.pop();
127
128 map<string, VS>::const_iterator itr;
129 itr = adjacentWords.find(current);
130
131 const VS& adj = itr->second;
132 for (int i =0; i < adj.size(); i++)
133 {
134 if (previousWord[adj[i]] == "")
135 {
136 previousWord[adj[i]] = current;
137 q.push(adj[i]);
138 }
139 }
140 }
141 previousWord[first] = "";
142
143 return previousWord;
144}
145
146VS getChainFromPrevMap(const map<string, string>& previous, const string& second)
147{
148 VS result;
149 //类型转换,因为操作符[]不能用在不可变的map中
150 map<string, string>& prev = const_cast<map<string, string> &> (previous);
151
152 for (string current = second; current != ""; current = prev[current])
153 result.push_back(current);
154
155 reverse(result.begin(), result.end());
156
157 return result;
158}
159
160int main()
161{
162 string a[] = {"five", "hero", "boyfriend", "james", "zero", "good", "hire", "thank", "fire",
163 "here", "pen", "ccbb", "greatman", "come", "great", "greet", "gold", "glad"};
164 VS input(a, a+18);
165 string first("zero"), second("five");
166
167 //先构造一个图
168 map<string, VS> adjWords = computerAdjacentWords(input);
169
170 //再构造某个节点到所有节点最短路径中先驱集合
171 map<string, string> previousWords = findChain(adjWords, first);
172
173 //在找出到某个终点的最短路径
174 VS result = getChainFromPrevMap(previousWords, second);
175
176 FORR(i, 0, result.size())
177 {
178 cout << result[i] << " ";
179 }
180 cout << endl;
181
182 return 0;
183}