边的后序编号<=>逆欧拉路径。类似:点的后序编号=>逆拓扑排序。
把边加入visited[]数组<=>从图中删除边,这就有Hierholzer算法:删除出边再dfs递归,无边可删时把当前点输出到栈。
重构行程
Hierholzer算法
vector<string> findItinerary(vector<vector<string>>& tickets) {
// 已知欧拉路径存在且从JFK开始,一张机票一条边,求遍历所有边的欧拉路径
// Hierholzer算法:删除出边再dfs递归,无边可删时把当前点输出到栈。
unordered_map<string, multiset<string>> adj;
for (auto &ticket : tickets)
adj[ticket[0]].insert(ticket[1]);
vector<string> ans;
dfs("JFK", adj, ans);
reverse(ans.begin(), ans.end()); // 逆欧拉路径
return ans;
}
void dfs(const string &from, unordered_map<string, multiset<string>> &adj, vector<string> &ans) {
auto &tos = adj[from];
while (!tos.empty()) {
auto next = *tos.begin();
tos.erase(tos.begin());
dfs(next, adj, ans);
}
ans.push_back(from); // ans当栈用
}
开组合锁
正常使用visited[]数组
string crackSafe(int n, int k) {
// 要求能覆盖全部密码组合,就是要找欧拉路径
// 每个节点有k条出边和k条入边,入度==出度,一定有欧拉回路
// n-1位数作为节点,边是 (后n-1位数)+any['0'..'k'),下一节点是边的后n-1位数
// 边的后序遍历编号等于逆欧拉回路
unordered_set<string> visited;
string str(n, '0');
visited.insert(str);
string ans;
function<void(const string &)> dfs = [&](const string &prefix) {
for (int i = 0; i < k; i++) { // 遍历所有未访问的边
string x = to_string(i);
auto next = prefix + x;
if (visited.count(next)) continue;
visited.insert(next);
dfs(next.substr(1));
ans += x; // 后序遍历
}
};
dfs(str.substr(1));
ans += str; // 后序遍历
reverse(begin(ans), end(ans)); // 逆欧拉路径
return ans;
}