目錄
- 108. 冗餘連線
- 109. 冗餘連線II
108. 冗餘連線
題目連結:https://kamacoder.com/problempage.php?pid=1181
文章講解:https://www.programmercarl.com/kamacoder/0108.冗餘連線.html
題目狀態:看題解
思路:
構建並查集,然後透過並查集來判斷節點,若當前這對節點(s, t)在同一個集合中,那麼輸出這對節點即可達到題目要求,否則,就將s和t合併到同一個集合中。
程式碼:
#include <iostream>
#include <vector>
using namespace std;
int n; // 節點數量
vector<int> father(1001, 0); // 按照節點大小範圍定義陣列
// 並查集初始化
void init() {
for (int i = 0; i <= n; ++i) {
father[i] = i;
}
}
// 並查集裡尋根的過程
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]);
}
// 判斷 u 和 v是否找到同一個根
bool isSame(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 將v->u 這條邊加入並查集
void join(int u, int v) {
u = find(u); // 尋找u的根
v = find(v); // 尋找v的根
if (u == v) return ; // 如果發現根相同,則說明在一個集合,不用兩個節點相連直接返回
father[v] = u;
}
int main() {
int s, t;
cin >> n;
init();
for (int i = 0; i < n; i++) {
cin >> s >> t;
if (isSame(s, t)) {
cout << s << " " << t << endl;
return 0;
} else {
join(s, t);
}
}
}
109. 冗餘連線II
題目連結:https://kamacoder.com/problempage.php?pid=1182
文章講解:https://www.programmercarl.com/kamacoder/0109.冗餘連線II.html
題目狀態:看題解
思路:
函式
-
init()
:- 初始化並查集,每個節點的父節點指向自身。
-
find(int u)
:- 遞迴查詢節點
u
的根節點,並進行路徑壓縮。
- 遞迴查詢節點
-
join(int u, int v)
:- 合併兩個節點所在的集合。
-
same(int u, int v)
:- 判斷兩個節點是否在同一個集合中。
-
getRemoveEdge(const vector<vector<int>>& edges)
:- 遍歷所有邊,找到構成有向環的邊並輸出。
-
isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int deleteEdge)
:- 刪除指定邊後檢查圖是否為樹。
main()
函式
- 讀取節點數
n
和邊。 - 計算每個節點的入度。
- 找到入度為 2 的節點對應的邊,優先刪除最後出現的一條邊。
- 如果刪除後形成樹,輸出該邊。
- 如果沒有入度為 2 的節點,呼叫
getRemoveEdge
找到並輸出構成環的邊。
邏輯流程
-
處理入度為 2 的情況:
- 找出入度為 2 的節點,嘗試刪除對應的邊,檢查是否能形成樹。
-
處理無入度為 2 的情況:
- 如果沒有入度為 2 的節點,必然存在一個環,找到並刪除構成環的邊。
程式碼:
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<int> father (1001, 0);
// 並查集初始化
void init() {
for (int i = 1; i <= n; ++i) {
father[i] = i;
}
}
// 並查集裡尋根的過程
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]);
}
// 將v->u 這條邊加入並查集
void join(int u, int v) {
u = find(u);
v = find(v);
if (u == v) return ;
father[v] = u;
}
// 判斷 u 和 v是否找到同一個根
bool same(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
// 在有向圖裡找到刪除的那條邊,使其變成樹
void getRemoveEdge(const vector<vector<int>>& edges) {
init(); // 初始化並查集
for (int i = 0; i < n; i++) { // 遍歷所有的邊
if (same(edges[i][0], edges[i][1])) { // 構成有向環了,就是要刪除的邊
cout << edges[i][0] << " " << edges[i][1];
return;
} else {
join(edges[i][0], edges[i][1]);
}
}
}
// 刪一條邊之後判斷是不是樹
bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int deleteEdge) {
init(); // 初始化並查集
for (int i = 0; i < n; i++) {
if (i == deleteEdge) continue;
if (same(edges[i][0], edges[i][1])) { // 構成有向環了,一定不是樹
return false;
}
join(edges[i][0], edges[i][1]);
}
return true;
}
int main() {
int s, t;
vector<vector<int>> edges;
cin >> n;
vector<int> inDegree(n + 1, 0); // 記錄節點入度
for (int i = 0; i < n; i++) {
cin >> s >> t;
inDegree[t]++;
edges.push_back({s, t});
}
vector<int> vec; // 記錄入度為2的邊(如果有的話就兩條邊)
// 找入度為2的節點所對應的邊,注意要倒序,因為優先刪除最後出現的一條邊
for (int i = n - 1; i >= 0; i--) {
if (inDegree[edges[i][1]] == 2) {
vec.push_back(i);
}
}
// 情況一、情況二
if (vec.size() > 0) {
// 放在vec裡的邊已經按照倒敘放的,所以這裡就優先刪vec[0]這條邊
if (isTreeAfterRemoveEdge(edges, vec[0])) {
cout << edges[vec[0]][0] << " " << edges[vec[0]][1];
} else {
cout << edges[vec[1]][0] << " " << edges[vec[1]][1];
}
return 0;
}
// 處理情況三
// 明確沒有入度為2的情況,那麼一定有有向環,找到構成環的邊返回就可以了
getRemoveEdge(edges);
}