目錄
一,歐拉回路、歐拉鍊路
二,無向圖
三,有向圖
四,Hierholzer算法
1,遞歸版
力扣 332. 重新安排行程(有向圖的歐拉鍊路)
力扣 753. 破解保險箱(有向圖的歐拉回路)
2,非遞歸版
力扣 2097. 合法重新排列數對(有向圖的歐拉回路或鍊路)
一,歐拉回路、歐拉鍊路
從起點A出發,不重複的走完所有的邊,最終回到A,得到的回路叫歐拉回路。
從起點A出發,不重複的走完所有的邊,到達終點B,得到的鍊路叫歐拉鍊路。
尋找歐拉回路或者歐拉鍊路就是經典的一筆畫問題。
二,無向圖
對于無向連通圖,存在歐拉回路的充要條件是奇點數量為0,存在歐拉鍊路的充要條件是奇點數量為2。
換句話說,如果奇點數量大于2則無法一筆畫。
三,有向圖
對于有向圖G,存在歐拉回路的充要條件是,G是強連通圖且所有節點的入度都等于出度。
對于有向圖G,存在歐拉鍊路的充要條件是,G是半連通圖且隻有2個節點的出度-入度=正負1,其他節點都滿足入度都等于出度。
四,Hierholzer算法
1,遞歸版
Hierholzer算法求歐拉回路或鍊路就是從任意一點開始進行dfs周遊所有的邊,把周遊結束的點入棧,最終得到的就是歐拉回路,棧頂就是起點。
class Hierholzer {
public:
stack<int>euler;//歐拉回路或鍊路,棧頂是起點
Hierholzer(int n, map<int, vector<int>>& m,int type,int start=0)//type=0是無向圖 1是有向圖
{
this->n = n;
this->m = m;
this->type = type;
dfs(GetStartPoint(start));
}
private:
int GetStartPoint(int start)//鍊路是唯一起點,回路是指定起點
{
if (type == 0) {
for (auto& mi : m) {
if (mi.second.size() % 2)return mi.first;
for (auto nk : mi.second)num[id(mi.first, nk)]++;
}
for (auto& ni : num)ni.second /= 2;
} else {
map<int, int>m2;
for (auto& mi : m)for (auto nk : mi.second)m2[nk]++, num[id(mi.first, nk)]++;
for (auto& mi : m)if (mi.second.size() > m2[mi.first])return mi.first;
}
return start;
}
void dfs(int k)
{
for (auto nk : m[k]) {
if (num[id(k, nk)]--<=0)continue;
dfs(nk);
}
euler.push(k);
}
long long id(int a, int b)
{
if (type == 0 && a > b)a ^= b ^= a ^= b;
return (long long)a * n + b;
}
int n;
int type;
map<int, vector<int>> m;//鄰接表
map<long long, int>num;//支援多重邊
};
這份代碼能同時适用于無向圖和有向圖,自動判斷是求歐拉回路還是鍊路,并以棧作為傳回值。
另外一個優點是支援多重邊。
缺點是沒有做校驗是否存在歐拉回路或鍊路。
力扣 332. 重新安排行程(有向圖的歐拉鍊路)
給你一份航線清單
tickets
,其中
tickets[i] = [fromi, toi]
表示飛機出發和降落的機場地點。請你對該行程進行重新規劃排序。
所有這些機票都屬于一個從
JFK
(肯尼迪國際機場)出發的先生,是以該行程必須從
JFK
開始。如果存在多種有效的行程,請你按字典排序傳回最小的行程組合。
- 例如,行程
與 ["JFK", "LGA"]
相比就更小,排序更靠前。["JFK", "LGB"]
假定所有機票至少存在一種合理的行程。且所有的機票 必須都用一次 且 隻能用一次。
示例 1:

輸入:tickets = [["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]]
輸出:["JFK","MUC","LHR","SFO","SJC"]
示例 2:
輸入:tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]
輸出:["JFK","ATL","JFK","SFO","ATL","SFO"]
解釋:另一種有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"] ,但是它字典排序更大更靠後。
提示:
-
1 <= tickets.length <= 300
-
tickets[i].length == 2
-
fromi.length == 3
-
toi.length == 3
-
和 fromi
由大寫英文字母組成toi
-
fromi != toi
題意:
給一個帶多重邊的有向圖,指定起點,求歐拉鍊路。
class Solution {
public:
vector<string> findItinerary(vector<vector<string>>& tickets) {
vector<string>v;
map<string, int>m;
for (auto& ti : tickets)m[ti[0]],m[ti[1]];
for (auto& mi : m)v.push_back(mi.first);
sort(v.begin(), v.end());
m.clear();
for (int i = 0; i < v.size(); i++)m[v[i]] = i;
map<int, vector<int>>g;
for (auto& ti : tickets)g[m[ti[0]]].push_back(m[ti[1]]);
for (auto& gi : g)sort(gi.second.begin(), gi.second.end());
stack<int>s = Hierholzer(v.size(), g, 1, m["JFK"]).euler;
vector<string> ans;
while (!s.empty())ans.push_back(v[s.top()]), s.pop();
return ans;
}
};
力扣 753. 破解保險箱(有向圖的歐拉回路)
有一個需要密碼才能打開的保險箱。密碼是 n 位數, 密碼的每一位是 k 位序列 0, 1, ..., k-1 中的一個 。
你可以随意輸入密碼,保險箱會自動記住最後 n 位輸入,如果比對,則能夠打開保險箱。
舉個例子,假設密碼是 "345",你可以輸入 "012345" 來打開它,隻是你輸入了 6 個字元.
請傳回一個能打開保險箱的最短字元串。
示例1:
輸入: n = 1, k = 2
輸出: "01"
說明: "10"也可以打開保險箱。
示例2:
輸入: n = 2, k = 2
輸出: "00110"
說明: "01100", "10011", "11001" 也能打開保險箱。
提示:
n 的範圍是 [1, 4]。
k 的範圍是 [1, 10]。
k^n 最大可能為 4096。
思路:
以前n-1位密碼的所有可能性作為點,以最後一位作為邊,那麼保險箱的枚舉密碼就可以表示成求歐拉回路或鍊路。
節點數量是pow(k, n - 1),邊的數量是pow(k, n)
實際上,由于對稱性,求的是歐拉回路。
class Solution {
public:
string crackSafe(int n, int k) {
if (n == 1) {
string str(k, '0');
for (int i = 0; i < str.size(); i++)str[i] += i;
return str;
}
map<int, vector<int>>m;
int num = pow(k, n - 1);
for (int i = 0; i < num; i++) {
for (int j = 0; j < k; j++)m[i].push_back(i % (num / k)*k + j);
}
auto s = Hierholzer(m.size(), m, 1).euler;
string str(num * k + n - 1, '0');
int id = 0, t = s.top();
char c = '0';
s.pop();
for (int i = 0; i < n - 1; i++)str[id++] = c + s.top() % k, t /= k;
while (!s.empty())str[id++] = c + s.top() % k, s.pop();
return str;
}
};
2,非遞歸版
為了克服遞歸太深導緻爆棧的問題,我把算法改寫成非遞歸版,功能和接口都不變。
class Hierholzer {
public:
stack<int>euler;//歐拉回路或鍊路,棧頂是起點
Hierholzer(int n, map<int, vector<int>>& m,int type,int start=0)//type=0是無向圖 1是有向圖
{
this->n = n;
this->m = m;
this->type = type;
dfs(GetStartPoint(start));
}
private:
int GetStartPoint(int start)//鍊路是唯一起點,回路是指定起點
{
if (type == 0) {
for (auto& mi : m) {
if (mi.second.size() % 2)return mi.first;
for (auto nk : mi.second)num[id(mi.first, nk)]++;
}
for (auto& ni : num)ni.second /= 2;
} else {
map<int, int>m2;
for (auto& mi : m)for (auto nk : mi.second)m2[nk]++, num[id(mi.first, nk)]++;
for (auto& mi : m)if (mi.second.size() > m2[mi.first])return mi.first;
}
return start;
}
void dfs(int k)
{
while (true) {
while (mid[k] < m[k].size()) {
if (num[id(k, m[k][mid[k]])]-- <= 0)mid[k]++;
else sdfs.push(k), k = m[k][mid[k]];
}
euler.push(k);
if (sdfs.empty()) return;
k = sdfs.top(), sdfs.pop();
}
}
inline long long id(int a, int b)
{
if (type == 0 && a > b)a ^= b ^= a ^= b;
return (long long)a * n + b;
}
int n;
int type;
stack<int>sdfs;
map<int, vector<int>> m;//鄰接表
map<int, int>mid;
map<long long, int>num;//支援多重邊
};
力扣 2097. 合法重新排列數對(有向圖的歐拉回路或鍊路)
給你一個下标從 0 開始的二維整數數組
pairs
,其中
pairs[i] = [starti, endi]
。如果
pairs
的一個重新排列,滿足對每一個下标
i
(
1 <= i < pairs.length
)都有
endi-1 == starti
,那麼我們就認為這個重新排列是
pairs
的一個 合法重新排列 。
請你傳回 任意一個
pairs
的合法重新排列。
注意:資料保證至少存在一個
pairs
的合法重新排列。
示例 1:
輸入:pairs = [[5,1],[4,5],[11,9],[9,4]]
輸出:[[11,9],[9,4],[4,5],[5,1]]
解釋:
輸出的是一個合法重新排列,因為每一個 endi-1 都等于 starti 。
end0 = 9 == 9 = start1
end1 = 4 == 4 = start2
end2 = 5 == 5 = start3
示例 2:
輸入:pairs = [[1,3],[3,2],[2,1]]
輸出:[[1,3],[3,2],[2,1]]
解釋:
輸出的是一個合法重新排列,因為每一個 endi-1 都等于 starti 。
end0 = 3 == 3 = start1
end1 = 2 == 2 = start2
重新排列後的數組 [[2,1],[1,3],[3,2]] 和 [[3,2],[2,1],[1,3]] 都是合法的。
示例 3:
-
1 <= pairs.length <= 105
-
pairs[i].length == 2
-
0 <= starti, endi <= 109
-
starti != endi
-
中不存在一模一樣的數對。pairs
- 至少 存在 一個合法的
重新排列。pairs
class Solution {
public:
vector<vector<int>> validArrangement(vector<vector<int>>& pairs) {
map<int, vector<int>>g;
for (auto& p : pairs)g[p[0]].push_back(p[1]);
stack<int>s = Hierholzer(g.size(), g, 1, pairs[0][0]).euler;
int a = s.top(), b;
s.pop();
vector<vector<int>>ans(s.size());
for(int i=0;i<ans.size();i++) {
b = s.top();
ans[i] = vector<int>{ a, b };
a = b;
s.pop();
}
return ans;
}
};