十、集合

1. 集合的表示

  • 集合运算:交、并、补、差,判定一个元素是否属于某一集合

  • 并查集:集合并、查某元素属于什么集合

  • 并查集问题中集合存储如何实现?
    可以用树结构表示集合,树的每个结点代表一个集合元素


  • 采用静态链表存储



typedef int ElementType;
typedef  struct {
    ElementType data;
    int parent;
}SetType;

2. 集合运算

(1) 查找某个元素所在的集合(用根结点表示)

#define MaxSize 10
int Find(SetType S[], ElementType X)
{
    int i;
    for (i = 0; i < MaxSize && S[i].data != X; i++) ;
    if(i >= MaxSize) return -1;
    for (; S[i].parent >= 0; i = S[i].parent) ;
    return i;
}

(2) 集合的并运算

  • 分别找到X1和X2两个元素所在集合树的根结点。
  • 如果他们不同根,则将其中一个根结点的父结点指针设置成另一个根结点的数组下标。
void Union(SetType S[], ElementType X1, ElementType X2)
{
    int root1, root2;
    root1 = Find(S, X1);
    root2 = Find(S, X2);
    if (root1 != root2) {
        S[root2].parent = root1;
    }
}

3. 集合的简化表示

  • 元素值和数组下标一一对应,数组内存放父结点索引。
typedef int ElementType;
typedef int SetName; /*默认用根结点的下标作为集合名称*/
typedef ElementType SetType[];
SetName Find(SetType S, ElementType X)
{
    for (; S[X]>=0; X=S[X]);
    return X;
}
void Union(SetType S, SetName root1, SetName root2)
{
    S[root2] = root1;
}
案例

有5个电脑,输入C表示检查两个电脑是否连通,输入I表示连接两个电脑,输入S表示结束并输出有几个连通网络。



void Input_connection(SetType S)
{
    ElementType u, v;
    SetName root1, root2;
    cin >> u >> v;
    root1 = Find(S, u-1);
    root2 = Find(S, v-1);
    if (root1 != root2) {
        Union(S, root1, root2);
    }
}

void Check_connection(SetType S)
{
    ElementType u, v;
    SetName root1, root2;
    cin >> u >> v;
    root1 = Find(S, u-1);
    root2 = Find(S, v-1);
    if (root1 == root2) {
        cout<<"yes"<<endl;
    }else{
        cout<<"no"<<endl;
    }
}

void Check_network(SetType S, int n)
{
    int i, count = 0;
    for (i = 0; i < n; i++) {
        if (S[i] < 0) {
            count++;
        }
    }
    if (count == 1) {
        cout<<"The network is connected."<<endl;
    }else{
        cout<<"There are "<<count<< " components."<<endl;
    }
}
int main(int argc, const char * argv[]) {
    // insert code here...
    SetType s = {-1, -1, -1, -1, -1};
    char in;
    do{
        cout<<"请输入标识:"<<endl;
        cin>>in;
        switch (in) {
            case 'I':
                Input_connection(s);
                break;
            case 'C':
                Check_connection(s);
                break;
            case 'S':
                Check_network(s, 5);
                break;
            default:
                break;
        }
    }while(in != 'S');
    
    return 0;
}

4. 按秩归并

  • 前提是没有简化前的集合
  • 因为我们用根结点表示集合,每次将root2 挂载到 root1上 因此树越来越高。每次查找Find(1)的根结点时需要T=O(n), 因此进行n次Union的时候时间复杂度为T = O(n2)

为什么树会越来越高?

  • 将较高的树贴到较低的树上,会使树变高一层。
  • 应该将较低树贴到高树上!

树高应该存在哪里呢?
可以将树根的值改成负的树高
S[Root] = -树高;

 if (S[Root2] < S[Root1]) {
        S[Root1] = Root2;
    }else{
        if (S[Root1] == S[Root2]) S[Root1]--;
        S[Root2] = Root1;
    }

另一种做法:比规模
把小树贴到大树上 S[Root] = -元素个数;

if (S[Root2] < S[Root1]) {
        S[Root2] += S[Root1];
        S[Root1] = Root2;
    }else{
       S[Root1] += S[Root2];
        S[Root2] = Root1;
    }

5. 路径压缩

路径压缩
SetName compressionPath(SetType S, ElementType X)
{
    if (S[X] < 0) {
        return X;
    }else{
        return S[X] = compressionPath(S, S[X]);
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容