日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

算法学习:最近公共祖先

發布時間:2023/12/19 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 算法学习:最近公共祖先 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近公共祖先

1、暴力算法

復雜度:O(m * logn)(最大O(n * m));

先記錄每個層數,再對層數高的依次向上移動,直到找到相同祖先,此處不寫代碼

2、倍增思想

復雜度:O(nlog(n)+mlog(n));

原理:

記錄其第2^i個父節點的內容,按二進制進位行運算

首先初始化sic[x] [0]為其上第一個父節點,隨后從i到所需1<<n,依次根據上一層父節點的父節點進行記錄:

sic[x][i] = sic[sic[x][i - 1]][i - 1];

查找最近公共祖先時,首先將兩結點放在同一層

for (int i = 15; i > -1; i--){if (len[sic[a][i]] >= len[b])a = sic[a][i]; }

因為父節點層數一定比a與b的最小層數小或相等,因此不會出現有跳過父節點的情況。

之后,同時將a與b進行跳躍操作,跳躍到最近公共父節點的下一層(因每次跳躍的判定條件是)sic[a] [i] != sic[b] [i],因此只能找到最近公共父節點下一層,最后返回sic[a] [0]即可得到最近公共祖先。

代碼如下:
#include<iostream> #include<algorithm> #include<stdio.h> #include<string> #include<string.h> #include<vector> #include<queue>using namespace std;int n, m; int sic[1005][16], len[1005]; vector<int>v[1005];void lca() {queue<int>q;q.emplace(1);while (!q.empty()) {int k = q.front();q.pop();for (auto x : v[k]) {len[x] = len[k] + 1;for (int i = 1; i < 15; i++){if (sic[sic[x][i - 1]][i - 1] == 0)break;sic[x][i] = sic[sic[x][i - 1]][i - 1];}q.emplace(x);}} }int solve(int a,int b) {if (len[a] < len[b])swap(a, b);for (int i = 15; i > -1; i--){if (len[sic[a][i]] >= len[b])a = sic[a][i];}if (a == b)return a;for (int i = 15; i > -1; i--) {if (sic[a][i] != sic[b][i]) {a = sic[a][i]; b = sic[b][i];}}return sic[a][0]; }int main() {int a, b;cin >> n >> m;memset(sic, -1, sizeof sic);sic[1][0] = -1;len[1] = 1;for (int i = 0; i < n; i++){cin >> a >> b;v[a].push_back(b);sic[b][0] = a;}lca();for (int i = 0; i < m; i++){cin >> a >> b;cout << solve(a, b) << endl;}return 0; }
樣例:

7 6

1 2 2 3 3 4 2 5 5 6 6 7 1 8

4 7

3 2

5 2

1 8

7 8

4 6

預期輸出:

2

2

2

1

1

2

3、Tarjan

復雜度:O(n+m);

原理:

此算法可算作是暴力求解算法的優化,是離線查詢,其原理如下:

將樹上的點分為三個部分:

【1】已經遍歷且回溯過的點,如藍色部分

【2】正在遍歷且未回溯的點,如紅色分支

【3】還未遍歷的點

如下圖所示:

此時,與并查集的思想相結合,已經回溯過的點,其根結點記錄在目前正在遍歷分支上。當要查詢的結點中的一個被遍歷到,且另一個點已經被回溯過,則直接找到另一個點的根節點,就是這兩個點的最近公共祖先。

那么,如何記錄已經回溯過的點在目前正在遍歷的點的根節點?

首先初始化每個結點的根節點是其本身,在每次回溯過后,再更改其根節點位置,如此,其已回溯過的分支的根節點在其回溯之前的根節點一定是此結點本身,在遍歷此節點的子節點分支時,這個狀態不會改變。

如上圖所示,當回溯到3時,3的根節點更新為其父節點。

根據如上原理,就可有如下操作:

void tarjan(int u) {sic[u] = 1;for (auto x:v[u]){//st[x] = st[u] + 1;tarjan(x);len[x] = u;}for (auto x : vis[u]) {int y = x.first, p = x.second;if (sic[y] == 2) {ans[p] = fa(y);}}sic[u] = 2; }

sic數組為標記數組,標記點是否被遍歷/已回溯/未遍歷;

len數組為記錄數組,記錄其根節點,在每次回溯后更新當前回溯點的根節點。

注:此代碼建立的是有向圖,因此不須剪枝。

代碼如下:
#include<iostream> #include<algorithm> #include<stdio.h> #include<string> #include<string.h> #include<vector> #include<queue>using namespace std;int n, m; int sic[1005], len[1005]; int st[1005]; int ans[1005], o; vector<int>v[1005]; vector<pair<int,int>>vis[1005];int fa(int x) {if (len[x] != x)len[x] = fa(len[x]);return len[x]; }void tarjan(int u) {sic[u] = 1;for (auto x:v[u]){//st[x] = st[u] + 1;tarjan(x);len[x] = u;}for (auto x : vis[u]) {int y = x.first, p = x.second;if (sic[y] == 2) {ans[p] = fa(y);}}sic[u] = 2; }int main() {int a, b;cin >> n >> m;len[1] = 1;for (int i = 0; i < 1005; i++){len[i] = i;}for (int i = 0; i < n; i++){cin >> a >> b;v[a].push_back(b);}for (int i = 0; i < m; i++){cin >> a >> b;vis[a].push_back({ b,i });vis[b].push_back({ a,i });}//st[1] = 0;tarjan(1);for (int i = 0; i < m; i++) {cout << ans[i] << endl;}return 0; }

總結

以上是生活随笔為你收集整理的算法学习:最近公共祖先的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。