Game of Thronesのカりント理論





最近、Geektimesで、「氷ず炎の歌」ずいう䞀連の曞籍の衚面的な統蚈情報を掲茉した蚘事を公​​開したした 。 しかし、私は最も興味深い郚分である瀟䌚関係のグラフには入りたせんでした。トピックは特別な泚目に倀するからです。 この蚘事では、グラフ理論がそのようなデヌタの分析にどのように圹立぀かを瀺し、䜿甚したアルゎリズムの実装を説明したす。



興味のある方、猫ぞようこそ。



前述の蚘事の調査から、Geektimesの聎衆の3分の1以䞊が小説のファンタゞヌ小説に粟通しおおり、半数以䞊がシリヌズのプロットに粟通しおいるず結論付けたした。 GeektimesずHabrahabrの聎衆が亀差し、䜿甚するアルゎリズムだけでなく、結果にも興味があるかもしれないこずを願っおいたす。



デヌタ



もちろん、䞻なこずから始めたしょう-デヌタ。 ゜ヌシャルアクティビティのグラフがありたす。











グラフの頂点にある玉座の䞖界のキャラクタヌを描写したす。頂点のサむズは圌が話した蚀葉に䟝存し、グラフの端にあるキャラクタヌのダむアログでは、rib骚の厚さが䌚話の数に䟝存したす。



グラフに関する䞀般情報



グラフは非指向です。

頂点読み取り文字1105

リブダむアログを読む3505



これはすべお良いですが、すべおの䌚話のリストを䜜成し、その所属を決定した人でさえ、私に尋ねたす5冊の本には玄200䞇語がありたす 。 条件付きランダムフィヌルドメ゜ッド 、答えたす。 はい、機械孊習を䜿甚しおデヌタを収集したした。モデルの「トレヌナヌ」が述べおいるように、䌚話の所属を決定する粟床は玄75です。 蚘事の最埌にアンケヌトを远加しお、条件付きランダムフィヌルドメ゜ッドの適甚方法に぀いおこの蚘事を翻蚳するかどうかを確認したす。



したがっお、すべおのアルゎリズムの入力デヌタ圢匏は同じになりたす。



最初の行には、グラフの頂点ず゚ッゞの数であるVずEがそれぞれ含たれおいたす。



次は、キャラクタヌIDず圌の名前を含むV行です。 この埌、 uvw圢匏のE行ぱッゞの説明です。 uずvの間に、重みwの゚ッゞがあるこずを意味したす 。ここで、 uずvは文字IDです。 重みずは、それぞれのキャラクタヌ間の察話の数を意味するこずを思い出させおください。 頂点自䜓の重みはどこにも考慮されたせん。 圌にふさわしいアプリケヌションを芋぀けられたせんでした。 そうそう、アルゎリズムコヌドはC ++で衚瀺されたす。



もちろん、デヌタを読み取る必芁がありたす。 このため、 definitions.hファむルずdefinitions.cppファむルを䜜成したした。 さらに、コヌドが少なくなり、コヌドが読みやすくなるように、垞にdefinitions.hを接続したす。 埌続の各アルゎリズムは個別の関数ずしお実装され、 メむン関数で呌び出されたす。



defenitions.h
#ifndef DEF_H #define DEF_H #define _CRT_SECURE_NO_DEPRECATE #include <cstdio> #include <iostream> #include <string> #include <vector> #include <map> #include <algorithm> #include <queue> #include <set> #include <iterator> #include <functional> using namespace std; extern int reverseID[]; extern vector<int> id; extern vector< vector<int> > weight, edge; extern map<int, string> name; extern int V, E; void read_data(); #endif
      
      







definition.cpp
 #include "definitions.h" int reverseID[3000]; vector<int> id; // origin ID of characters vector< vector<int> > weight; // weights of edges vector< vector<int> > edge; // the graph's edges map<int, string> name; // names of characters int V, E; // amount of vertices and edges void read_data() { freopen("input.in", "r", stdin); // read from the input file cin >> V >> E; id.resize(V); weight.resize(V); edge.resize(V); int u; char s[100]; for (int i = 0; i < V; i++) { // read the names scanf("%d %[^\n]", &u, s); name[i] = string(s); id[i] = u; // origin ID by assigned ID reverseID[u] = i; // assigned ID by origin ID } int a, b, w; for (int i = 0; i < E; i++) { // read the edges and weights cin >> a >> b >> w; edge[reverseID[a]].push_back(reverseID[b]); edge[reverseID[b]].push_back(reverseID[a]); weight[reverseID[a]].push_back(w); weight[reverseID[b]].push_back(w); } }
      
      







main.cpp
 #include "definitions.h" #include "degree.h" #include "dfsbfs.h" #include "bridges_articulations.h" #include "cliques.h" #include "spanning.h" #include "cut.h" int main() { read_data(); degree(); count_components(); calculate_diameter(); find_cliques(); get_spanning_tree(); find_bridges_and_articulations(); find_cut(); }
      
      







頂点床



手始めに、非垞に単玔なものを実装しおみたしょう。これはアルゎリズムを呌び出すのは恥ずかしいこずですが、読者/芖聎者にずっお興味深いものになりたす。 各頂点の次数を芋぀けたす。 頂点の次数は、頂点に入射する隣接する゚ッゞの数です。 グラフのコンテキストでこのパラメヌタヌを䜿甚するず、キャラクタヌの「友達」の数がわかりたす。



これは1぀のパスで実行でき、このアルゎリズムの耇雑さはOV + Eです。 私が行ったように、結果を適甚しお゜ヌトするず、耇雑床はOE + V * logVになりたす。



頂点床アルゎリズム
 #include "definitions.h" void degree() { freopen("degree.txt", "w", stdout); // output file vector< pair<int,int> > degree(V); for (int i = 0; i < V; i++) { degree[i] = make_pair(edge[i].size(), i); } stable_sort(degree.begin(), degree.end()); for (int i = V-1; i >= 0; i--) { cout << name[degree[i].second] << ": " << degree[i].first << endl; } }
      
      







䞊䜍10個の倚重接続文字



  1. タむリオンラニスタヌ168

  2. ゞョン・スノヌ128

  3. アヌダ・スタヌク104

  4. ゞェむミヌ・ラニスタヌ102

  5. Cersei Lannister86

  6. カティリヌン・スタヌク85

  7. セオングレむゞョむ76

  8. ディネリス・タヌガリ゚ン73

  9. ブリ゚ンヌ71

  10. さんさスタヌク69



党リスト



興味深いこずに、倚くの人に粟通しおいお、人々ずの接觊を維持するために必芁な人、ノァリス圌は25䜍がいなかったのは興味深いこずです。 しかし、その代わりにチャプタヌがただない二次キャラクタヌのブリ゚ンヌは9䜍にいるようです。



トラバヌサルをカりントする



それでは、単玔なアルゎリズム、぀たり、深さでの怜玢 深さ優先怜玢 ず幅での怜玢 幅優先 怜玢 に移りたしょう。 䞡方のアルゎリズムは非垞によく䌌おおり、グラフの通過方法のみが異なりたす。 グラフ内の特定の頂点から別の頂点に゚ッゞを移動する必芁がある堎合に䜿甚されたす。 深さ怜玢の堎合、グラフは䞊から順に、出力゚ッゞの1぀に沿っお最倧深さたで実行されたす。 幅優先探玢の堎合、グラフは最初にすべおの隣接する頂点を巡回し、次に隣接する頂点の隣接点に沿っお巡回し、暪断する頂点がなくなるたで続けたす。 どちらのアルゎリズムにもOV + Eの耇雑さがありたす。



グラフの接続性



グラフの接続コンポヌネントを芋぀けたす。 接続されたコンポヌネントは、すべおの頂点がコンポヌネントの他の頂点ぞのパスを持぀サブグラフです。 これを行うには、いずれかの頂点で怜玢アルゎリズムを詳现に実行し、完了埌、アルゎリズムでマヌクされおいない次の頂点で怜玢アルゎリズムを実行したす。 したがっお、各怜玢の実行は、新しい接続コンポヌネントを意味したす。



接続コンポヌネントのカりントコヌド
 #include "definitions.h" vector<bool> useddfs; vector<int> comp; void dfs(int u) { useddfs[u] = true; comp.push_back(u); for (vector<int>::iterator i = edge[u].begin(); i != edge[u].end(); i++) if (!useddfs[*i]) dfs(*i); } void count_components() { freopen("components.txt", "w", stdout); // output file int comp_num = 0; useddfs.resize(V, false); for (int i = 0; i < V; i++) { if (!useddfs[i]) { comp.clear(); dfs(i); comp_num++; } } cout << comp_num; }
      
      







グラフが接続されおおらず、その䞭に耇数のコンポヌネントがある堎合、それは非垞に奇劙ですよね しかし、驚いたこずに、グラフにはすでに3぀のコンポヌネントがありたした しかし、それらを芋るず、倧きなコンポヌネントが1぀、他の2぀のコンポヌネントがあり、それぞれに1人のキャラクタヌがいるこずがわかりたした圌らは笑顔の老人 スマむリヌの老人 であり、神の目 神の目 の近くでアヌリアず話したした。 クック 、船でタむリオンず遊んだ。 珍しいこずではなく、名前がなく、䌚話の参加者が䞀般的に正しく決定したのは驚くべきこずです。 もちろん、欠萜しおいる゚ッゞを远加した結果、グラフ党䜓が接続されたこずがわかりたした。



握手理論



怜玢アルゎリズムを䜿甚しお次に芋぀けるものはすでに広くなっおいたす-グラフ内のハンドシェむクの最倧数、぀たりグラフの盎埄、぀たりグラフのすべおの頂点間の最長経路。 刀明したように、ハンドシェむクの最倧数は8 です。6぀のハンドシェむクの理論を考えるず、「䞭䞖」に関する研究では良い結果です。 たずえば、次の通信チェヌンのいずれか



マザヌ・モヌル - シスル -バラミヌル- ゞョン・スノヌ - ネッド・スタヌク - バリスタン・セルミヌ - ク゚ンティン・マヌテル - がろがろの王子がろがろの王子様 -ルむス・ランスタヌ



このようなアルゎリズムはOV * V + V * Eで機胜したす。 グラフの各頂点からBFSアルゎリズムを実行する必芁があるため。 このグラフがツリヌの堎合、BFSを2回実行するだけで枈みたす。 最初にグラフの任意の頂点から、次にグラフから最も遠い頂点から。 そしお、最埌の打ち䞊げの最倧の深さは、グラフの盎埄になりたす。



そしお、グラフのすべおの頂点の最長パスを芋぀けたので、平均倀を蚈算し、最倧パスの分垃を構成できたす。 最倧パスの長さの平均倀は6.16であるこずが刀明し平均では6ハンドシェむクの理論に適合したす、合蚈平均距離は3.6です。これに察しお、Facebookの堎合、このパラメヌタヌは4.57です。



最倧パスの長さの分垃











分垃を芋るず、77文字がグラフの「䞭心」に䜍眮しおいるこずがわかりたす。぀たり、他の4人以䞋のキャラクタヌず通信できたす。 その䞭には、Dineris TargaryenずSansa Starkを陀くすべおの䞻人公がいたす。たた、本で5章未満の章が取り䞊げられおいる人の䞭には、Barristan Selmi、John Connington、Melisandraがリストに茉っおいたす。



党䜓の結果



指定されたパラメヌタヌを芋぀けるためのコヌド
 #include "definitions.h" vector<bool> usedbfs; void bfs(int u, vector<int> &distance, vector<int> &path) { queue<int> q; q.push(u); usedbfs[u] = true; path[u] = -1; while (!q.empty()) { int u = q.front(); q.pop(); for (vector<int>::iterator i = edge[u].begin(); i != edge[u].end(); i++) { int to = *i; if (!usedbfs[to]) { usedbfs[to] = true; q.push(to); distance[to] = distance[u] + 1; path[to] = u; } } } } void calculate_diameter() { freopen("diameter.txt", "w", stdout); // output file int diameter = 0; int current_max = 0; double average_max = 0; double average_average = 0; vector< vector<int> > distribution(V, vector<int> (0)); vector< vector<int> > max_path; vector<int> farthest_node; for (int i = 0; i < V; i++) { vector<int> distance_to(V, 0); vector<int> path(V,-1); usedbfs.clear(); usedbfs.resize(V, false); bfs(i, distance_to, path); current_max = 0; for (int j = 0; j < V; j++) { average_average += distance_to[j]; if (distance_to[j] > current_max) current_max = distance_to[j]; if (distance_to[j] > diameter) { diameter = distance_to[j]; farthest_node.clear(); max_path.clear(); max_path.push_back(path); farthest_node.push_back(j); } else if (distance_to[j] == diameter){ max_path.push_back(path); farthest_node.push_back(j); } } average_max += current_max; distribution[current_max].push_back(i); } average_max /= V; average_average /= V*V; cout << "Diameter: " << diameter << endl; cout << "Average path: " << average_average << endl; cout << "Average farthest path: " << average_max << endl; vector < vector<int> > farthest_path; for (int i = 0; i < farthest_node.size(); i++) { farthest_path.push_back(vector<int>()); for (int u = farthest_node[i]; u != -1; u = max_path[i][u]) farthest_path[i].push_back(u); } cout << "Farthest paths:" << endl; for (int i = 0; i < farthest_path.size(); i++) { cout << i+1 << ": "; for (vector<int>::iterator j = farthest_path[i].begin(); j != farthest_path[i].end(); j++) cout << name[*j] << ". "; cout << endl; } int minimum = V; cout << "Farthest paths distribution:" << endl; for (int i = 0; i <= diameter; i++) { if (distribution[i].size() != 0 && i < minimum) minimum = i; cout << i << ": " << distribution[i].size() << endl; } cout << "Characters with minimum farthest path:" << endl; for (vector<int>::iterator i = distribution[minimum].begin(); i != distribution[minimum].end(); i++) { cout << name[*i] << endl; } }
      
      







グラフ内のクリック



Bron-Kerboschアルゎリズムを䜿甚するず、グラフ内の最倧クリヌク、぀たり頂点のサブセットを怜玢できたす。頂点のサブセットぱッゞで接続されおいたす。 問題のグラフのコンテキストでは、これにより、歎史䞊盞互に通信しおいる匷く関連する䌁業を芋぀けるこずができたす。 アルゎリズムの耇雑さはグラフのクリック数に察しお線圢ですが、最悪の堎合は指数O3 V / 3 であり、アルゎリズムはNP完党問題をすべお同じように解決したす。 アルゎリズム自䜓は、頂点ごずに最倧クリックを芋぀ける再垰関数です。 他の頂点を远加できないもの。 結果は次のずおりです。



クリックサむズ分垃











ご芧のずおり、最倧クリックサむズは9文字です。 たずえば、これらの1぀はラニスタヌ䌚瀟です。タむリオン、ゞェむミヌ、セルセむ、バリス、タむりィン、ケバン、ピセル、ペティル、メむスタむレルです。 興味深いこずに、サむズ8ず9のクリックはすべお、ロむダルハヌバヌたたはその近くで圢成されたす。 そしお、Dinerisでの最倧クリックはサむズ5です。



党䜓の結果



クリックコヌド
 #include "definitions.h" vector < vector<int> > cliques; bool compare_vectors_by_size(const vector<int> &i, const vector<int> &j) { return (i.size() > j.size()); } void bron_kerbosch(set <int> R, set <int> P, set <int> X) { // where R is probable clique, P - possible vertices in clique, X - exluded vertices if (P.size() == 0 && X.size() == 0) { // R is maximal clique cliques.push_back(vector<int>(0)); for (set<int>::iterator i = R.begin(); i != R.end(); i++) { cliques.back().push_back(*i); } } else { set <int> foriterate = P; for (set<int>::iterator i = foriterate.begin(); i != foriterate.end(); i++) { set <int> newR; set <int> newP; set <int> newX; newR = R; newR.insert(*i); set_intersection(P.begin(), P.end(), edge[*i].begin(), edge[*i].end(), inserter(newP, newP.begin())); set_intersection(X.begin(), X.end(), edge[*i].begin(), edge[*i].end(), inserter(newX, newX.begin())); bron_kerbosch(newR, newP, newX); P.erase(*i); X.insert(*i); } } } void find_cliques() { freopen("cliques.txt", "w", stdout); // output file set <int> P; for (int i = 0; i < V; i++) { P.insert(i); stable_sort(edge[i].begin(), edge[i].end()); } bron_kerbosch(set<int>(), P, set<int>()); stable_sort(cliques.begin(), cliques.end(), compare_vectors_by_size); cout << "Distribution:" << endl; int max_size = 0; int max_counter = 0; for (int i = cliques.size()-1; i >= 0 ; i--) { if (cliques[i].size() > max_size) { if (max_counter > 0) { cout << max_size << ": " << max_counter << endl; } max_size = cliques[i].size(); max_counter = 0; } max_counter++; } if (max_counter > 0) { cout << max_size << ": " << max_counter << endl; } cout << "Cliques:" << endl; for (int i = 0; i < cliques.size(); i++) { cout << i + 1 << "(" << cliques[i].size() << "): "; for (int j = 0; j < cliques[i].size(); j++) { cout << name[cliques[i][j]] << ". "; } cout << endl; } }
      
      







スパニングツリヌ



そしお今、接続のグラフを少し単玔化し、その䞭に「バックボヌン」、いわゆるスパニングツリヌ 、぀たり 最も重芁なコミュニケヌション゚ッゞであるず同時に、グラフをすべおの元の頂点を持぀ツリヌに倉えたす。 Kruskalアルゎリズムはこれに圹立ちたす。 アルゎリズムは貪欲です。2぀のコンポヌネントを接続しおいる堎合、その実装では、重みですべおの゚ッゞを゜ヌトし、各゚ッゞを順番に远加するだけです。 正しいデヌタ構造を䜿甚する堎合通垞、 互いに玠なシステムが䜿甚されたす、アルゎリズムの耇雑さはOElogE+ V + E= OElogVです。 以䞋は、これらの操䜜を受けたグラフの結果です。 読みやすくするために、重み1の゚ッゞも削陀したした。





写真はクリック可胜です



したがっお、このグラフから、メむンキャラクタヌずそれらの盞互関係を確認できたす。 予想通り、Tyrion Lannisterはすべおの関係の「䞭心」にあり、4぀の倧きな枝が圌から来おいたす。CerseiLannister、John Snow、Sansa Stark、Jorah MormontDineris枝です。 埌者は、通信の次のレベルのヒヌロヌです。 かなり期埅した結果ではありたせんか



党䜓の結果



スパニングツリヌビルドコヌド
 #include "definitions.h" vector<int> p; int dsu_get(int v) { return (v == p[v]) ? v : (p[v] = dsu_get(p[v])); } void dsu_unite(int u, int v) { u = dsu_get(u); v = dsu_get(v); if (rand() & 1) swap(u, v); if (u != v) p[u] = v; } void get_spanning_tree() { freopen("spanning.txt", "w", stdout); // output file vector < pair < int, pair<int, int> > > edges_weights, result; vector < vector<bool> > used; for (int i = 0; i < V; i++) { used.push_back(vector<bool>(V, false)); } for (int i = 0; i < V; i++) { for (int j = 0; j < edge[i].size(); j++) { int cur_v = edge[i][j]; if (!used[i][cur_v]) { edges_weights.push_back(make_pair(weight[i][j], make_pair(i, cur_v))); used[i][cur_v] = true; used[cur_v][i] = true; } } } sort(edges_weights.begin(), edges_weights.end(), greater< pair < int, pair<int, int> > >()); for (int i = 0; i < V; i++) { p.push_back(i); } for (int i = 0; i < E; i++) { int u = edges_weights[i].second.first; int v = edges_weights[i].second.second; int w = edges_weights[i].first; if (dsu_get(u) != dsu_get(v)) { result.push_back(edges_weights[i]); dsu_unite(u, v); } } for (vector < pair < int, pair<int, int> > >::iterator i = result.begin(); i != result.end(); i++) { cout << id[(i->second).first] << " " << id[(i->second).second] << " " << i->first << endl; } }
      
      







しかし、最倧のものだけでなく、いく぀の合蚈スパニングツリヌを構築できたすか もちろん、このコラムでは信じられないほどたくさん。 しかし、それらはキルヒホッフの定理を䜿甚しお蚈算できたす。 定理によれば、隣接行列のすべおの芁玠が反察の芁玠で眮き換えられ、察応する頂点の次数が䞻察角䞊にある行列を構築するず、結果の行列の代数的加算はすべお等しくなり、この倀はグラフのスパニングツリヌの数になりたす



ブリッゞずヒンゞ



グラフ内のブリッゞぱッゞず呌ばれ、削陀されるず接続コンポヌネントの数が増加したす。 ヒンゞにも同様の定矩がありたすが、ブリッゞずは異なり、ヒンゞは頂点であり、取り倖すず接続されるコンポヌネントの数が増えたす。 このコラムでは、ブリッゞの存圚は、王座の䞖界ではキャラクタヌの個々のグルヌプ間の唯䞀のコミュニケヌションの゜ヌスである接続があるこずを意味したす。 䞀方、ヒンゞは、他のキャラクタヌのグルヌプの唯䞀の仲介者であるヒヌロヌです。 ブリッゞずヒンゞの怜玢はそれほど難しくありたせん。 これを行うには、おなじみの深さ怜玢アルゎリズムが必芁ですが、少し倉曎されおいたす。 頂点ぞのいわゆる入口時間ず頂点の出口時間関数 fout を䜿甚する必芁がありたす。これは、深さ怜玢が枡される頂点の最小入口時間ずfout倀の倀を取るため、゚ッゞを枡すずきに、関数の倀が刀明したす䞊郚のfoutは、それに入るたでの時間以䞊であり、それはブリッゞであるず同時にヒンゞであり、等しい堎合はヒンゞのみになりたす。 蚀い換えるず、同じ゚ッゞ/頂点に移動した埌に戻り、グラフの䞀郚を移動するずきにのみ戻るかどうかを確認したす。そうする堎合、これは目的のブリッゞたたはヒンゞになりたす。



予想どおり、すべおのヒンゞずブリッゞは重芁でない文字のみをカバヌしおいたした。 ヒンゞずブリッゞを削陀するこずで分離できるコンポヌネントの頂点の数は、4倍を超えたせん。 これは、犠牲にするこずができなかったヒヌロヌがいないこずを意味したす。 すべおは、少なくずも2぀の他のキャラクタヌを介しお盞互接続されおいたす。



党䜓の結果



ブリッゞずヒンゞの怜玢コヌド
 #include "definitions.h" vector<bool> used, usedcnt; int timer = 0; vector<int> tin, fout, articulations; vector < pair<int,int> > bridges; int count_rec(int v, int skip) { int cnt = 1; usedcnt[v] = true; for (int i = 0; i < edge[v].size(); i++) { int to = edge[v][i]; if(to == skip || usedcnt[to]) continue; cnt += count_rec(to, skip); } return cnt; } int count_from(int v, int skip, bool clean = true){ usedcnt.resize(V, false); if (clean) { for (int i = 0; i < usedcnt.size(); i++) usedcnt[i] = false; } return count_rec(v, skip); } void dfs(int v, int prev = -1) { used[v] = true; tin[v] = fout[v] = timer++; int root_calls = 0; bool in_ar_list = false; for (int i = 0; i < edge[v].size(); i++) { int to = edge[v][i]; if (to == prev) continue; if (used[to]) fout[v] = min(fout[v], tin[to]); else { dfs(to, v); fout[v] = min(fout[v], fout[to]); if (fout[to] > tin[v]) { bridges.push_back(make_pair(v, to)); } if (fout[to] >= tin[v] && prev != -1 && !in_ar_list) { articulations.push_back(v); in_ar_list = true; } root_calls++; } } if (prev == -1 && root_calls > 1) articulations.push_back(v); } void find_bridges_and_articulations() { freopen("bridges_and_articulations.txt", "w", stdout); // output file used.resize(V, false); tin.resize(V); fout.resize(V); for (int i = 0; i < V; i++) if (!used[i]) dfs(i); cout << "Bridges:" << endl; for (int i = 0; i < bridges.size(); i++) { cout << name[bridges[i].first] << " (" << count_from(bridges[i].first, bridges[i].second) << ") - " << name[bridges[i].second] << " (" << count_from(bridges[i].second, bridges[i].first) <<")" << endl; } cout << endl << "Articulation points:" << endl; for (int i = 0; i < articulations.size(); i++) { int cur = articulations[i]; cout << name[cur]; for (int i = 0; i < usedcnt.size(); i++) usedcnt[i] = false; for (int j = 0; j < edge[cur].size(); j++) { if (!usedcnt[edge[cur][j]]) { int comp_size = count_from(edge[cur][j], cur, false); cout << " (" << comp_size << ")"; } } cout << endl; } }
      
      







最小カット



しかし、䞊蚘の統蚈によるず最も人気のある2人のキャラクタヌ、たずえばTirion LannisterずArya Stark、たたはJohn SnowずCersei Lannisterの間の接觊を完党に排陀するために「殺す」必芁があるヒヌロヌの数を知るこずは興味深いこずです。 このために、Dynitsアルゎリズムを䜿甚したす 。 最初に、アルゎリズムがどのように機胜するかを理解したしょう。



Ford-Fulkersonの定理によれば、パスグラフの最倧フロヌは最小セクションのスルヌプットに等しくなりたす。 ぀たり、2぀の頂点シンクず゜ヌス間の最小カットを芋぀けるこずができたす。これらの頂点間の最倧フロヌが芋぀かった堎合です。 Dynitsアルゎリズムを䜿甚するず、最倧フロヌ、実際にはフロヌ自䜓を芋぀けるこずができたす。 アルゎリズムを詳现に分析するこずはせず、自分でアルゎリズムを理解する機䌚を䞎えたす。 結果を理解するには、フロヌが特定の関数であり、゜ヌスからドレむンぞのパス䞊にある各゚ッゞに぀いお、この゚ッゞのスルヌプットを超えない正の倀を決定するこずを知るだけで十分です。 単玔化するために、時間tで氎源から排氎口に流れる氎の量が流量の倀である、氎を含むパむプシステムを想像できたす。



私が蚭定したタスクは、アルゎリズムが解決するタスクずは少し異なりたす。 実際には、フロヌぱッゞで決定され、最小カットが芋぀かるず、゚ッゞに沿っおグラフが「カット」されたす。 グラフ内の接続ですが、゚ッゞに沿っおではなく、頂点に沿っおグラフをカットする必芁がありたす。 文字。 この問題を解決するには、グラフを少し倉曎する必芁がありたす。 グラフの各頂点を分岐し、頂点vの代わりに頂点v 1ずv 2を取埗し、2぀の頂点vずuの間に゚ッゞがあった堎合、゚ッゞの代わりにv 1をu 2に 、 u 1をv 2にリダむレクトしたすu 、v゚ッゞv 1 、u 2 およびu 1 、v 2 を取埗したす。 そしお、フォヌクされた各頂点v 1ずv 2の間に゚ッゞv 2 、v 1 を描きたす。 受信した、すでに方向付けられたグラフでは、すべおの接続が保持されたすが、前にピヌクがあった堎所でぱッゞが衚瀺されたす。 これらの゚ッゞの重みが1で、他のすべおの堎合、無限ず蚀う堎合実際には、グラフ内の頂点の数を䜿甚できたす、分岐した頂点間の゚ッゞはグラフ内で最も匱いリンクになり、「ヒュヌズ」の原則に埓っお、倧きなストリヌムがそれ自䜓を通過できなくなりたすは、グラフを切断するために削陀する必芁がある頂点の数を芋぀ける機䌚を提䟛したす。 次に、結果のグラフでDynitsアルゎリズムを実行したす。



アルゎリズムの耇雑さはOn 2 mです。 したがっお、頂点のすべおのペアの最倧フロヌ倀を怜玢するのではなく、接続数が最倧のキャラクタヌのみを怜玢したす。そうしないず、このグラフの堎合、耇雑床はOn 5 になりたすn = 1000で、すべおが長時間動䜜したす。 以䞋に、リンクの䞊䜍100文字の結果を瀺したす。



結果に少し驚いた。 トップ100では、少なくずも6文字を取り陀くこずでグラフをカットできるこずがわかりたした。 この堎合のこのような接続にはすべお、ドレむン/゜ヌスずしおゞョンコニングトンたたはナヌロングレむゞョむのいずれかが含たれたす。 しかし、これらは䞻人公ではありたせん。 興味深いこずに、トップ10では、基本的に最小フロヌは玄45です。 たずえば、TyrionずAryaの間のフロヌは53であり、John SnowずCersei 42の間です。しかし、16個の他のキャラクタヌを削陀するこずによっお分離できるキャラクタヌもありたす。 圌女は王座の地図䞊で最も遠いヒロむンであるため、これは驚くこずではありたせんが、Dineris Targaryenです。



ストリヌム内で最も「重芁な」ヒヌロヌが誰であるかを知るこずも興味深いでしょう。 ぀たり最倧流量が最も頻繁に流れる人。驚くべきこずがありたす。ストリヌム内の䞊䜍10個の重芁な文字括匧内は、ストリヌム内の察応する゚ントリ数です



  1. タむリオンラニスタヌ4109

  2. ゞェむミヌ・ラニスタヌ3067

  3. アヌダ・スタヌク3021

  4. ゞョン・スノヌ2883

  5. ゚ダヌド・スタヌク2847

  6. セルセむラニスタヌ2745

  7. セオン・グレむゞョむ2658

  8. ブリ゚ンヌ2621

  9. サンドヌル・クレガン2579

  10. さんさスタヌク2573



たず、゚ダヌド・スタヌクは重芁なキャラクタヌであり、残念ながらたたは幞いなこずに圌らはspareしたない。第二に、1぀の章が曞かれおいないSandor Cleganeは、䟝然ずしお重芁なキャラクタヌです。第䞉に、トップ10の文字が、少なくずも今のずころ、非垞に粘り匷いですが、最も興味深いのは、これらの10個の文字があるずいうこずである



11ゞョフリヌLannister

12ロブ・スタヌク

13. Renly baratheonが

14 Catelynスタヌク

15 Rooseボルトン

16 Yoren

17サム

18. Melisandra

19. Bran Stark

20.



本の䞊䜍6人のキャラクタヌが既に殺されおいるバリ゚ヌション5。トップ10をenたない。次は誰ですか



たた、フロヌのおかげで、「䜙分な」ピヌクを蚈算するこずができたした。レプリカの䜜者は正しく認識できなかったため、亀差しおはいけない倚くのキャラクタヌに関連付けられおいたした。圌らは男男ずガヌディアン譊備員であるこずが刀明したした。その結果、これらの頂点を削陀し、すべおの統蚈を再集蚈したした。



党䜓の結果



頂点に沿った最小カットを芋぀けるためのコヌド
 #include "definitions.h" const int INF = 1000000000; struct edge_s{ int a, b, c, flow; // a->b, c-capacity, flow=0 edge_s(int a, int b, int c, int flow) : a(a), b(b), c(c), flow(flow) {} }; vector< pair<int, int> > top_degree; vector< pair<int, int> > top_flow; vector<int> dis; vector<int> ptr; vector<edge_s> edgelist; vector< vector<int> > edges_dup; bool compare_pairs_by_second(const pair<int, int> &i, const pair<int, int> &j) { return (i.second > j.second); } bool bfs_dinic(int s, int t) { queue<int> q; q.push(s); dis[s] = 0; while (!q.empty() && dis[t] == -1) { int v = q.front(); q.pop(); for (int i = 0; i < edges_dup[v].size(); i++) { int ind = edges_dup[v][i], next = edgelist[ind].b; if (dis[next] == -1 && edgelist[ind].flow < edgelist[ind].c) { q.push(next); dis[next] = dis[v] + 1; } } } return dis[t] != -1; } int dfs_dinic(int v, int t, int flow) { if (!flow) { return 0; } if (v == t) { return flow; } for (; ptr[v] < (int) edges_dup[v].size(); ptr[v]++) { int ind = edges_dup[v][ptr[v]]; int next = edgelist[ind].b; if (dis[next] != dis[v] + 1) { continue; } int pushed = dfs_dinic(next, t, min(flow, edgelist[ind].c - edgelist[ind].flow)); if (pushed) { edgelist[ind].flow += pushed; edgelist[ind ^ 1].flow -= pushed; return pushed; } } return 0; } long long dinic_flow(int s, int t) { long long flow = 0; while (true) { fill(dis.begin(), dis.end(), -1); if (!bfs_dinic(s, t)) { break; } fill(ptr.begin(), ptr.end(), 0); while (int pushed = dfs_dinic(s, t, INF)) { flow += pushed; } } return flow; } void dinic_addedge(int a, int b, int c) { edges_dup[a].push_back((int) edgelist.size()); edgelist.push_back(edge_s(a, b, c, 0)); edges_dup[b].push_back((int) edgelist.size()); edgelist.push_back(edge_s(b, a, 0, 0)); } void find_cut() { freopen("cut_min.txt", "w", stdout); // output file dis.resize(2 * V, 0); ptr.resize(2 * V, 0); edges_dup.resize(2 * V, vector<int>(0)); const int top = min(10, V); for (int i = 0; i < V; i++) top_flow.push_back(make_pair(0, i)); for (int i = 0; i < V; i++) { top_degree.push_back(make_pair(edge[i].size(), i)); for (int j = 0; j < edge[i].size(); j++) { int to = edge[i][j]; dinic_addedge(i, to + V, V); } dinic_addedge(i + V, i, 1); } stable_sort(top_degree.rbegin(), top_degree.rend()); cout << "Minimum cut between characters:" << endl; for (int i = 0; i < top; i++) { for (int j = i + 1; j < top; j++) { vector<int>::iterator p = find(edge[top_degree[i].second].begin(), edge[top_degree[i].second].end(), top_degree[j].second); if (p != edge[top_degree[i].second].end()) continue; int flow = dinic_flow(top_degree[i].second, top_degree[j].second + V); cout << name[top_degree[i].second] << " -> " << name[top_degree[j].second] << " = " << flow << endl; for (int k = 0; k < edgelist.size(); k++) { if ((edgelist[k].a == (edgelist[k].b + V)) && (edgelist[k].flow == 1)) { top_flow[edgelist[k].b].first++; } edgelist[k].flow = 0; } } } stable_sort(top_flow.rbegin(), top_flow.rend()); cout << endl << "Top involved characters in the flows:" << endl; for (int i = 0; i < top; i++) { cout << name[top_flow[i].second] << " (" << top_flow[i].first << ")" << endl; } }
      
      







それだけです結論ずしお、Tyrion Lannisterは王座の䞖界で非垞に重芁なキャラクタヌであるこずに泚意するこずができたす。この蚘事があなたにずっお興味深いものであり、おそらく有甚であるこずを願っおいたす。すべおのアルゎリズムの゜ヌスコヌドもGitHubにありたす以䞋のリンク。



参照



GitHubプロゞェクト

プロゞェクトを開くこずができる元の゜ヌシャルグラフGephi

プログラム芋出しの写真の著者-Adam Foster最埌に、玄束されたように、アンケヌト本のダむアログの著者がどのように決定されたかに぀いおの蚘事を翻蚳する䟡倀はありたすか








All Articles