-
Notifications
You must be signed in to change notification settings - Fork 0
323. Number of Connected Components in an Undirected Graph #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| ## step1: | ||
| それぞれの集合の数を求めていくということで、Union Findを使うんだろなと思ったがUnion Findの実装方法をすっかり忘れてしまった。そこでDFSを使って各ノードを探索していくことにする。双方向グラフなのでお互いのノードに対してベクトルを辞書型で置いていき、深さ優先探索で辿っていく。edgesをfor文で回してもし辿ったことのないノードだったらそこで探索をし、そのグラフを探索済みのものとしてanswerをインクリメントしていく。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
コードと一致していないように見えますが、どこに相当しますか?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. すいません、途中で方針を変えて言ってることが食い違ってしまうことが多々ありますね... |
||
| ### code | ||
| ```python | ||
| from collections import defaultdict | ||
| class Solution: | ||
| def countComponents(self, n: int, edges: List[List[int]]) -> int: | ||
| x_to_neighbors = defaultdict(list) | ||
| for edge in edges: | ||
| i, j = edge | ||
| x_to_neighbors[i].append(j) | ||
| x_to_neighbors[j].append(i) | ||
| visited = set() | ||
|
|
||
| def explore(x): | ||
| visited.add(neighbor) | ||
| for neighbor in x_to_neighbors[x]: | ||
| if not neighbor in visited: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| explore(neighbor) | ||
|
|
||
| answer = 0 | ||
| for i in range(n): | ||
| if not i in visited: | ||
| explore(i) | ||
| answer += 1 | ||
|
|
||
| return answer | ||
| ``` | ||
| 空間計算量はO(2*E + N), 時間計算量はO(2*E + N)である。(Eはedgesのサイズ、Nはノードの数) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Big O記法で定数倍は省略するのが一般的だと思います。 |
||
| これでひとまずacceptされたが、一応UnionFindのやり方を調べて書いてみる。 | ||
|
|
||
| ```python | ||
| class UnionFind: | ||
| def __init__(self, n): | ||
| self.parent = list(range(n)) | ||
| self.rank = [1]*n | ||
|
|
||
| def find(self, node): | ||
| if node == self.parent[node]: | ||
| return node | ||
| self.parent[node] = self.find(self.parent[node]) | ||
| return self.parent[node] | ||
|
|
||
| def union(self, u, v): | ||
| pu = self.find(u) | ||
| pv = self.find(v) | ||
| if pu == pv: | ||
| return False | ||
| if self.rank[pu] > self.rank[pv]: | ||
| self.parent[pv] = pu | ||
| self.rank[pu] += self.rank[pv] | ||
| else: | ||
| self.parent[pu] = pv | ||
| self.rank[pv] += self.rank[pu] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これだとunion by rankではなく、union by sizeではないですか?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 本当ですね。NeetCodeの回答を参考にしたのですがあちらもrankを使っているけどもunion by sizeをしているみたいです。self.sizeに変名した方がいいですね。 |
||
| return True | ||
|
|
||
|
|
||
| class Solution: | ||
| def countComponents(self, n: int, edges: List[List[int]]) -> int: | ||
| unionFind = UnionFind(n) | ||
| for u, v in edges: | ||
| unionFind.union(u, v) | ||
| roots = set() | ||
| for i in range(n): | ||
| roots.add(unionFind.find(i)) | ||
| return len(roots) | ||
| ``` | ||
| UnionFindを何も見なくても実装できるように言語化してみる。まずUnionFindの初期化ではサイズnと0からn-1までの各ノードの属する集合のルートをフィールドとしてもつ。各ノードの親は最初は各ノード自身を指定させる。findメソッドでは引数に入れたノードの集合のルートを求めて返すのだがノードAを引数にもらった際に単にself.parents[A]を求めた場合に親がノードBとして、ノードBがさらに親を持つ場合、再帰的に親の親を求めてノードが自分自身を親とする所まで遡る必要がある。またノードAの親を求める時に再びその再帰のプロセスを行うのは非効率なので、self.parent[node]を最終的な親に書き換えておく。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
コードと一致していないように見えますが、この説明で合っていますか?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 最初self.n = nと書いていたのですが途中で書き換えてしまったので一致しなくなってしまいました... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
最終的な親=ルートですかね?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. そうですね、最終的な親に名付けるならルートかなと思いました。 |
||
| unionメソッドは2つのノードを引数とし、それぞれが属している集合を合体させるのだが、もし合体させようとしてる2つのノードのルートが同じノードだった場合、それはその集合はすでに同一であることの証なので早期リターンする。もしparentが違ったら集合を合体させる。単に片方のルートのparentをもう片方のルートに設定すればいいのだが、集合のノード数が少ない方のルートの親を集合のノード数が多い方のルートに指定した方がself.parentの更新数が少なくなるのでself.rankを比較してノード数の多い方のルートを中心に結合する。この際に、結合された方のランクを結合する方へ加算するのを忘れない。self.findとself.unionの時間計算量はどちらもO(α(n))でほぼO(1)と同一視できるらしいが、もしself.findでの | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
「self.parentの更新数が少なくなる」とは後のfind()を呼び出した時の話ですか?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. そうですね、findで再帰的に辿ったノードのparentをルート指定するように更新する時のことを言ってますね。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
「中心に結合する」ってどういう意味ですかね?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここちょっと表現に迷いました。単純に、「ノード数の多い方のルートをノード数の少ない方のルートの親に指定する」と書けばよかったですね。 |
||
| `self.parent[node] = self.find(self.parent[node])` | ||
| これとunionのrank比較がないとどちらもlogNになるらしい。証明が難しそうなので軽く流しておく。 | ||
|
|
||
| ## step2: | ||
| ### code | ||
| ```python | ||
| class UnionFind: | ||
| def __init__(self, n): | ||
| self.parent = list(range(n)) | ||
| self.rank = [1]*n | ||
|
|
||
| def find(self, node): | ||
| if node == self.parent[node]: | ||
| return node | ||
| self.parent[node] = self.find(self.parent[node]) | ||
| return self.parent[node] | ||
|
|
||
| def union(self, u, v): | ||
| pu = self.find(u) | ||
| pv = self.find(v) | ||
| if pu == pv: | ||
| return False | ||
| if self.rank[pu] > self.rank[pv]: | ||
| self.parent[pv] = pu | ||
| self.rank[pu] += self.rank[pv] | ||
| else: | ||
| self.parent[pu] = pv | ||
| self.rank[pv] += self.rank[pu] | ||
| return True | ||
|
|
||
|
|
||
| class Solution: | ||
| def countComponents(self, n: int, edges: List[List[int]]) -> int: | ||
| unionFind = UnionFind(n) | ||
| for u, v in edges: | ||
| unionFind.union(u, v) | ||
| roots = set() | ||
| for i in range(n): | ||
| roots.add(unionFind.find(i)) | ||
| return len(roots) | ||
| ``` | ||
|
|
||
| ## step3: | ||
| ### code | ||
| ```python | ||
| class UnionFind: | ||
| def __init__(self, n: int) -> None: | ||
| self.parent = [i for i in range(n)] | ||
| self.rank = [1]*n | ||
|
|
||
| def find(self, x: int) -> int: | ||
| if x == self.parent[x]: | ||
| return x | ||
| self.parent[x] = self.find(self.parent[x]) | ||
| return self.parent[x] | ||
|
|
||
| def union(self, u: int, v: int) -> bool: | ||
| pu, pv = self.find(u), self.find(v) | ||
| if pu == pv: | ||
| return False | ||
| if self.rank[pu] >= self.rank[pv]: | ||
| self.parent[pv] = pu | ||
| self.rank[pu] += self.rank[pv] | ||
| else: | ||
| self.parent[pu] = pv | ||
| self.rank[pv] += pu | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 間違ってますね... |
||
| return True | ||
|
|
||
| class Solution: | ||
| def countComponents(self, n: int, edges: List[List[int]]) -> int: | ||
| unionFind = UnionFind(n) | ||
| for edge in edges: | ||
| u, v = edge | ||
| unionFind.union(u, v) | ||
| roots = set() | ||
| for i in range(n): | ||
| roots.add(unionFind.find(i)) | ||
| return len(roots) | ||
| ``` | ||
|
|
||
| ## 他の人のコードを読んでみる | ||
| https://github.com/h1rosaka/arai60/pull/60/changes | ||
| ブルートフォースで実装されていて、コメントにあったこの書き方がとても単純で分かりやすいと感じた。だが時間計算量でO(E*N)かかってしまう。(Eはエッジの数、Nはノードの数) | ||
| ```python | ||
| class Solution: | ||
| def countComponents(self, n: int, edges: List[List[int]]) -> int: | ||
| connected_sets = [] | ||
| unseen_nums = set([num for num in range(n)]) | ||
| def serach_in_connected_sets(target:int) -> int: | ||
| for i in range(len(connected_sets)): | ||
| if target in connected_sets[i]: | ||
| return i | ||
| return -1 | ||
|
|
||
| for x, y in edges: | ||
| unseen_nums.discard(x) | ||
| unseen_nums.discard(y) | ||
|
|
||
| set_num_x = serach_in_connected_sets(x) | ||
| set_num_y = serach_in_connected_sets(y) | ||
|
|
||
| if set_num_x == -1 and set_num_y == -1: | ||
| connected_sets.append({x, y}) | ||
| continue | ||
|
|
||
| if set_num_x != -1 and set_num_y == -1: | ||
| connected_sets[set_num_x].add(y) | ||
| continue | ||
|
|
||
| if set_num_x == -1 and set_num_y != -1: | ||
| connected_sets[set_num_y].add(x) | ||
| continue | ||
|
|
||
| if set_num_x != set_num_y: | ||
| connected_sets[set_num_x] |= connected_sets[set_num_y] | ||
| connected_sets.pop(set_num_y) | ||
|
|
||
| return len(connected_sets) + len(unseen_nums) | ||
| ``` | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これがどういう意味なのか、文からは分かりませんでした。