1 はじめに
等価クラスとは何ですか?
クラスは、その名前が示すように、共通の特性を持つオブジェクトのグループを記述したもので、ここではクラスをコレクションと呼ぶこともできます。
集合が存在する場合、その集合内のすべてのオブジェクト(要素、データ)は同値関係を満たすと言われます。別の観点から説明すると、オブジェクトまたは要素が同じ集合内にない場合、同値関係は満たされない。
图论
グラフの接続性は、 の等価関係を使用して説明できます。
下図のように、任意の2つの頂点がつながったグラフを連結グラフと呼び、連結性がグラフの特徴となります。そして、このグラフの頂点はすべて 1 つの集合であり、同値関係があり、全員が 1 つの同値クラスに属していると考えることができます。
そして、次の図は切り離されたグラフです。接続されたコンポーネントがある場合2
、すべての頂点は同値クラスに分散されていると見なされます2
。接続コンポーネントは同値クラスです。したがって、同値クラスを使用してグラフの接続性を判断できます。
2. 同値クラス関係
要素が要素と同じ同値クラスに属していることを確認するにはどうすればよいですか?
つまり、s
要素を含むコレクションがある場合n
、要素が 1 つの同値クラスにあるか、異なる同値クラスに分散しているかを判断する方法です。
もちろん、要素が同じ同値クラスに属するかどうかを判断するときは、要素間の何らかの関係情報が必要です。一般に(x,y)
、等価ペアとも呼ばれるフォームの形式を使用します。
たとえば、既存のs={1,2,3,4,5,6,7,8}
と同値対関係があります。
R = {(1,3),(3,5),(3,7),(2,4),(4,6),(2,8)}
。R
に基づいて の同値クラスを見つけてください。
基本的な考え方は次のとおりです。
- 最初に、元のセット内の各要素は、それ自体のみを含む同値クラスであると想定されます。
- ペア関係を順次読み出し、ペア関係を持つ同値類をマージする。以下の図では、
(1,3)
ペア関係が最初に読み取られ、要素1
と3
電流2
が異なる同値クラスに属し2
、同値クラスがマージされます。1 つの同値クラスが別の同値クラスにマージされると、いずれかの同値クラスが空になります。
- 読み込んだペア関係に従って上記の処理を繰り返し、ペア関係の
2
要素が異なる同値クラスに属している場合は結合し、同じ同値クラスに属している場合は現状を維持します。次の図に示すように、最終的な同値クラスは です2
。
上記の検索等価クラスによれば、次のことがわかります。
- 等価クラスにより、要素間の直接的または間接的な関係が可能になります。
- マージすると、同値クラスはますます少なくなります。
3. 同値クラスの実現
同値クラスは集合とみなすことができ、複数の同値クラスが集合グループを形成します。集合グループ内の異なる集合(同値類)を区別するには、同値類ごとに固有の記号を設定する必要があります。
等価クラスは、配列、ツリー、リンク リストを使用して実装できます。いずれの場合も、等価クラスに一意のグリフを設定する必要があります。
3.1 配列の実装
上記の場合に等価クラスを見つける問題を解決するには、配列を使用します。
3.1.1 配列の初期化
最初は同値クラスがいくつあるかわからないため、1 次元配列が作成されます。最初に、数値は同値類に属し、同値類のシンボルは数値そのものであると想定できます。以下に示すように:
ヒント:数値の同値クラスは、配列内の数値と同じ添字位置です。
上記のプロセスはコードで記述できます。
#include <iostream>
using namespace std;
//存储等价类信息的数组
int nums[9]= {0};
/*
* 初始化函数
*/
void init() {
for(int i=1; i<9; i++) {
nums[i]=i;
}
}
3.1.2 同値クラスのマージ
同値クラスのマージは、ペア関係のクエリの結果に基づいて行われます。
ペア関係を読み取る場合は(1,3)
、要素1
と3
それらが属する同値クラスをクエリする必要がありますが、同じ同値クラスに属している場合は何も行いません。異なる同値クラスに属している場合、それらはマージされます。以下に示すように:
セットをマージする場合2
、両方向にマージすることができますが、実際の操作ではどちらかを選択できます。これにより、以下の2つの効果が現れます。
上の図から同値クラスがいくつあるかをどのように判断すればよいでしょうか?
同値クラスの数は、同じ添え字と格納された値を持つセルの数を見つけることで計算できます。
上記の説明では、2
サブロジックを含めて次のようになります。
- クエリ要素の等価クラス。
- 異なる同値クラスをマージします。
2
コードを使用して実装する場合、この2
サブ必要です。
/*
*查找元素所属等价类的标志符号
*/
int find(int data) {
while(nums[data]!=data)
data=nums[data];
return data;
}
/*
*合并等价类
*/
void unionSet(int data,int data_) {
//查找所属等价类
int flag= find(data);
int flag_=find(data_);
if(flag!=flag_) {
//合并
//nums[flag_]=flag;
//或者
nums[flag]=flag_;
}
}
テスト生成のマージ プロセス:
/*
*测试
*/
int main(int argc, char** argv) {
init();
int r[6][2] = {
{1,3},{3,5},{3,7},{2,4},{4,6},{2,8}};
for(int i=0; i<6; i++) {
unionSet(r[i][0],r[i][1] );
}
cout<<"数字:"<<endl;
for(int i=1; i<9; i++) {
cout<<i<<"\t";
}
cout<<endl;
cout<<"等价类"<<endl;
for(int i=1; i<9; i++) {
cout<<nums[i]<<"\t";
}
return 0;
}
出力結果:
3.2 リンクリスト
リンク リストと配列の高レベル ロジックに違いはありません。違いは、基礎となるストレージ メソッドにあります。
配列を格納する場合、初期配列のセルが同値クラスになります。リンク リスト ストレージを使用する場合、最初の要素 (データ) はリンク リストです。同値クラスのシンボルは、リンク リストの先頭ノードに格納できます。これは最初は数値そのものです。
コード記述を使用する場合、ノード タイプを構築する必要があります。
#include <iostream>
using namespace std;
/*
*结点类型
*/
struct Node {
//数据域
int data;
//指向后一个指针
Node *next;
};
管理を容易にするために、すべてのリンクされたリストのヘッド ノード アドレスを格納するために 1 次元配列が使用されます。そして初期化します。
//存储等价类信息的数组
Node* nums[9];
/*
* 初始化函数
*/
void init() {
for(int i=1; i<9; i++) {
nums[i]=new Node();
nums[i]->data=i;
nums[i]->next=NULL;
}
}
同じ同値クラスにないデータをマージする必要があるため、特定の要素が位置する同値クラスを見つけるための検索機能も提供する必要があります。
/*
* 查找元素所属等价类的标志符号
* 因头结点存储标志符号
* 函数返回数据所在链表的头结点
*/
Node* find(int data) {
for(int i=1; i<9; i++) {
if(nums[i]==NULL)continue;
Node* move=nums[i];
while(move!=NULL && move->data!=data ) {
move=move->next;
}
if(move==NULL)continue;
else return nums[i];
}
}
さらに、マージ機能を提供する必要があります。たとえば、ペア関係に従って要素が配置されているリンク リストをマージするプロセスを次の図に示します(1,3)
。1,3
- 要素が配置されているリンクリストは、先頭挿入モードで要素が配置されているリンクリストの先頭ノードの後ろに
3
挿入されます。1
3
元の記憶要素が配置されているリンク リストのヘッド ノードの配列セル間の値は、 に設定されますNULL
。
マージ機能は次のとおりです。
/*
*合并等价类
*/
void unionSet(int data,int data_) {
Node * flag= find(data);
Node * flag_= find(data_);
if( flag->data!=flag_->data ) {
//合并
flag_->next=flag->next;
nums[flag_->data]=NULL;
flag->next=flag_;
}
}
テストコード:
/*
*测试
*/
int main(int argc, char** argv) {
init();
int r[6][2] = {
{1,3},{3,5},{3,7},{2,4},{4,6},{2,8}};
for(int i=0; i<6; i++) {
unionSet(r[i][0] ,r[i][1]);
}
for(int i=1; i<9; i++) {
if(nums[i]!=NULL) {
Node * move=nums[i];
cout<<"等价类:"<<move->data<<endl;
while(move!=NULL){
cout<<move->data<<"\t";
move=move->next;
}
cout<<endl;
}
}
return 0;
}
試験結果:
3.3 木
ツリーの使用とリンク リストの使用には本質的な違いはなく、ツリー自体は抽象データ構造です。物理ストレージとしてリンク リストまたはアレイを選択できます。したがって、以前のリンクリストストレージと比較すると、タイプのマージの論理的な違いのみが変わります。
この記事では、ツリーの物理構造を説明するために引き続きリンク リストを使用します。
そして、リンクリストにはヘッドノードという概念があり、ツリーにはルートノードという対応する概念があります。最初に、その番号をルートとする複数のツリーを作成し、ツリーのルートを使用して同値クラスの一意の識別子 (つまり、値自体) を格納します。
マージを容易にするために、ツリーのノードを設計するときに、データ フィールドの設定に加えて、親ポインターを指すポインター フィールドも設定する必要があります。これは、デザイン リンク リストのノード タイプとは少し異なります。目的は、子ノードから親ノードへのクエリを容易にし、要素が配置されているツリーのルート ノードを見つけることです。
#include <iostream>
using namespace std;
//树结点类型
struct Node{
//数据域
int data;
//指向父指针的指针域
Node *parent;
};
同様に、1 次元配列を使用してすべてのツリーのルート ノードを保存し、初期化することができます。
Node* trees[9];
//初始化
void init() {
for(int i=1; i<9; i++) {
trees[i]=new Node();
trees[i]->data=i;
//根结点的父指针指向自己
trees[i]->parent=trees[i];
}
}
クエリ機能を提供します。クエリ関数のロジックでは、指定されたノードからずっとクエリを実行します。ルートノードを返します。
/*
*查询
*/
Node* find(int data) {
Node * move=trees[data];
while(move->parent->data!=data ) {
move=move->parent;
}
return move->parent;
}
マージ機能:
void unionSet(int data,int data_) {
Node * flag= find(data);
Node * flag_= find(data_);
if( flag->data!=flag_->data ) {
//合并
flag_->parent=flag;
}
}
テストコード:
//测试
int main(int argc, char** argv) {
init();
int r[6][2] = {
{1,3},{3,5},{3,7},{2,4},{4,6},{2,8}};
for(int i=0; i<6; i++) {
unionSet(r[i][0] ,r[i][1]);
}
for(int i=1; i<9; i++) {
if( trees[i]->parent->data==i ) {
cout<<"等价类"<<trees[i]->parent->data<<endl;
}
}
return 0;
}
試験結果:
4. まとめ
この記事では、同値クラスとは何か、および同値クラスの特徴について説明します。
同値クラスの要素には直接的および間接的な関係があります。これは、同値クラス内の要素間の関係が推移的であることも意味します。
同値類の特徴を理解した上で、実現手段が多様化しても、それは同じであり、内在する論理は同じである。違いはコードの表現方法にあります。もちろん、アプリケーションのシナリオには違いがあります。
純粋な配列スキームはデータ自体が複雑でない状況に適しており、ツリーおよびリンク リスト スキームはデータ自体がより複雑な状況に適しています。リンク リストは線形データ構造であるため、1 つのデータ構造に適しています。 1 対 1 の複雑なデータ シナリオ、およびツリーは 1 対多の複雑なデータに適しています。