そしてルックアップ
1. 定義
コンピューター サイエンスでは、ディスジョイント セット(英語: Disjoint-set data structure、文字通りディスジョイント データ構造として翻訳) は、いくつかのディスジョイント セット (繰り返される要素のない一連のセット) のマージと問い合わせの質問に対処するために使用されるデータ構造です。 . ユニオン検索では、次の操作がサポートされています。
クエリ: 要素が属している集合に対するクエリで、通常は集合内の「代表要素」を返します。この操作は、2 つの要素が同じセットにあるかどうかを判断するためのものです。
Merge : 2 つのコレクションを 1 つにマージします。
Add : 新しい要素を持つ新しいコレクションを追加します。追加操作は、クエリおよびマージ操作ほど重要ではなく、見過ごされがちです。
クエリとマージ操作をサポートしているため、ユニオン検索は英語でUnion -find データ構造またはMerge -find setとも呼ばれます。
2. 確認と設定
- 「グラフの動的接続」の問題を解決するために使用できます。
- 特定の問題を解決する場合、適切な「仮想頂点」を設定することで要素を分類できます。つまり、同じクラスの要素が互いに接続されているため、すべての要素を異なる接続ドメインに分割してから、他の操作を実行できます。 ;
- コレクションの最下層は、1 次元配列を使用して複数の「フォレスト」を格納しますが、実際には特定の頂点の祖先頂点のみを考慮し、頂点の直接の親ノードとして祖先頂点を取得します。
- 基になる 1 次元配列の添字は、頂点の祖先頂点が格納されている各頂点の番号に対応します。
- 最初は、頂点の直接の親は頂点そのものです。
- マージ検索コードには 3 つの部分が含まれます: 1) 初期化: 親ノードの配列を作成する; 2) クエリ操作: 頂点の親頂点をクエリする; 3) マージ操作: 2 つの異なるサブツリーをルート ノードの 1 つにマージするマージされたツリーのルート ノード。
- 2 つの頂点が接続されている場合、それらの直接の親ノードは同じです。
3. テンプレートコード
package com.northsmile.union;
/**
* @author NorthSmile
* @version 1.0
* @date 2023/4/19&21:46
* 并查集模板代码
*/
public class Template {
// 存储编号为i的顶点的祖先顶点编号
public int[] parent;
// 记录连通分量的数量
public int count;
/**
* 初始化
*/
public Template(int n){
// 初始化节点数量为n,节点编号依次为0~n-1
parent=new int[n+1];
for (int i=1;i<=n;i++){
// 初始状态:自己做自己的父节点
parent[i]=i;
}
count=n;
}
/**
* 查询某节点的祖先节点
* @param v
* @return
*/
public int find(int v){
// 说明此时该节点独立
if (parent[v]==v){
return v;
} else {
// 无路径压缩
// return find(parent[v]);
// 路径压缩
parent[v]=find(parent[v]);
return parent[v];
}
}
/**
* 合并顶点v、w
* @param v
* @param w
*/
public void union(int v,int w){
// 分别确定两顶点的祖先顶点
int vP=find(v);
int wP=find(w);
if (vP==wP){
return;
}
count--;
// 以wP作为vp的父顶点,此时v的祖先顶点更新为wP,也就是将v、w为根节点的树合并为一棵树
parent[vP]=wP;
}
/**
* 判断v、w之间是否连通
* 如果二者连通,他们的祖先顶点一定相同
* @param v
* @param w
* @return
*/
public boolean connected(int v,int w){
// 分别确定两顶点的祖先顶点
int vP=find(v);
int wP=find(w);
return vP==wP;
}
/**
* 返回连通域的数量
* @return
*/
public int count(){
return count;
}
}
4. レッコのサンプル問題
4.1 オファー II を指す 118. 余分なエッジ
ソード ポインター オファー II 118. エクストラ エッジ
ツリーは、接続された非巡回無向グラフと見なすことができます。n 個のノード (ノード値 1 ~ n) のツリーにエッジを追加した後のグラフを指定します。追加されたエッジの 2 つの頂点は 1 と n の間に含まれており、この追加のエッジはツリー内の既存のエッジに属していません。グラフの情報は長さnの二次元配列edgesに記録されており、edge[i] = [ai,bi]はグラフのaiとbiの間に辺があることを意味します。削除できるエッジを見つけてください. 削除後、残りの部分は n ノードの木になる可能性があります. 複数の回答がある場合は、配列のエッジで最後に発生するエッジを返します。
問題解決のアイデア:
- ツリー内の任意の 2 つのノードが接続されています。
- n ノードのツリーには n-1 個のエッジがあります。
- エッジ配列の長さは n で、多くても 1 つのエッジが冗長であることを示します。
- エッジを形成する 2 つのノードが既に接続されている場合は、2 つの直接の親ノードが同じであることを意味し、この時点で 2 つのノードがマージされている場合は、このエッジが冗長であることを意味します。
class Solution {
/**
*
* @param edges
* @return
*/
public int[] findRedundantConnection(int[][] edges) {
int n=edges.length;
UFData uf = new UFData(n);
for (int i=0; i<n;i++){
if (uf.find(edges[i][0])!=uf.find(edges[i][1])) {
uf.union(edges[i][0], edges[i][1]);
}else {
return edges[i];
}
}
return new int[0];
}
}
class UFData{
public int[] parent;
public UFData(int n){
parent=new int[n+1];
for (int i=1;i<=n;i++){
parent[i]=i;
}
}
public int find(int v){
if (v==parent[v]){
return v;
}else{
parent[v]=find(parent[v]);
return parent[v];
}
}
public void union(int v,int w){
int vP=find(v);
int wP=find(w);
if (vP==wP){
return;
}
parent[vP]=wP;
}
public boolean connected(int v,int w){
return find(v)==find(w);
}
}
4.2 リトゥオ 695. 島の最大面積
サイズ mxn のバイナリ マトリックス グリッドが与えられます。島は隣接する 1 の組み合わせ (土地を表す) であり、ここでの「隣接」とは、2 つの 1 が
水平方向または垂直方向の 4 方向に隣接している必要があります。グリッドの 4 つのエッジはすべてゼロ (水を表す) で囲まれていると想定できます。島の面積は、値が 1 の島の
セルの数です。グリッド内の最大の島の面積を計算して返します。島がない場合、返される面積は 0 です。
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int m=grid.length;
int n=grid[0].length;
UFData uf = new UFData(m * n);
int[][] directions=new int[][]{
{
-1,0},{
1,0},{
0,-1},{
0,1}}; // 上下左右
boolean haveUnion=false;
boolean haveOne=false;
for (int i=0;i<m;i++){
for (int j=0;j<n;j++){
if (grid[i][j]==1){
haveOne=true;
// 连接邻域
for (int k=0;k<4;k++){
int cR=i+directions[k][0];
int cC=j+directions[k][1];
if (cR<0||cR>=m||cC<0||cC>=n){
continue;
}
if(grid[cR][cC]==1){
haveUnion=true;
uf.union(i*n+j,cR*n+cC);
}
}
}
}
}
// System.out.println(Arrays.toString(uf.size));
return haveOne?haveUnion?uf.maxArea:1:0;
}
private static class UFData{
public int[] parent;
public int[] size;
public int maxArea;
public UFData(int n){
parent=new int[n];
size=new int[n];
Arrays.fill(size,1);
for (int i=0;i<n;i++){
parent[i]=i;
}
}
public int find(int v){
if (v==parent[v]){
return v;
}else{
parent[v]=find(parent[v]);
return parent[v];
}
}
public void union(int v,int w){
int vP=find(v);
int wP=find(w);
if (vP==wP){
return;
}
parent[vP]=wP;
size[wP]+=size[vP];
maxArea=Math.max(maxArea,size[wP]);
}
public boolean connected(int v,int w){
return find(v)==find(w);
}
}
}
参考文献:
- マイク氏がアルゴリズムについて語る
- labuladong アルゴリズムチートシート
- ウィキペディア