数据结构(四)树---集合的表示及查找(并查集)

一:集合运算

交,并,补,差,判断一个元素是否属于某一集合
并查集将在判断连通性和是否成环上面起到至关重要的作用

二:并查集

(一)集合并

并集间有一元素相连

(二)查某元素属于什么集合

可用树来表示集合,每个结点代表一个集合元素

    S1={1,2,4,7}            S2={3,5,8}          S3={6,9,10}

我们可以使用双亲表示法来判断某个结点的根结点,从而判断是否是使用某个集合
#define MAX_TREE_SIZE 100

typedef int TElemType;

typedef struct PTNode    //结点结构
{
    TElemType data;    //结点数据
    int parent;        //双亲位置
}PTNode;

typedef struct //树结构
{
    PTNode nodes[MAX_TREE_SIZE];    //结点数组
    int r, n;    //r是根位置,n是结点数
}PTree;

三:查找(根)的实现

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100

typedef int TElemType;

typedef struct _PTNode
{
    int data;
    int parent;
}PTNode;

typedef struct
{
    PTNode set[MAX_SIZE];
    int n;
}SetType;

结构定义

void CreateSets(SetType *Set)
{
    //输入数据,使第一个数变为根节点
    int d=0,parent=0;
    int i = 0;
    Set->n = 0;
    printf("input number(data,parent) to create set Number(<-1,-1> to exit):\n");
    while (1)
    {
        scanf("%d,%d", &d,&parent);
        if (d == -1&&parent==-1)
            break;
        Set->set[i].data = d;
        Set->set[i].parent = parent;
        Set->n++;
        i++;
    }
    printf("create set finish\n");
}

创建集合

void ShowSet(SetType s)
{
    int i;
    for (i = 0; i < s.n; i++)
        printf("%d %d %d\n", i, s.set[i].data, s.set[i].parent);
}

显示集合数据

int Find(SetType s, TElemType e)
{
    int i;
    for (i = 0; i < s.n&&e != s.set[i].data; i++)
        ;
    if (i >= s.n)
        return -1;
    for (; s.set[i].parent >= 0; i = s.set[i].parent)
        ;
    return i;
}
int main()
{
    SetType set;
    TElemType n;
    int root;
    CreateSets(&set);
    ShowSet(set);
    printf("input number to find root:\n");
    scanf("%d", &n);
    root=Find(set, n);
    printf("Finf Root in %d\n", root);
    system("pause");
    return 0;
}
在下面创建了两个集合,我们判断其中一个数是属于哪个集合

四:并运算

(一)步骤

1.找到x1,x2两个元素根节点
2.将其中一个根添加到另一个树下面

(二)改进

用小树合入大数,尽可能将新合并的树的高度变小,提高查找效率,如果树太高,find效率会变低

1.大树合入小树:我们对于新合并的数据查找根的效率会很低

2.小树合入大树:效率并未降低多少

(三)问题

树高我们应该如何获取?是在每次合并前去计算一遍吗?似乎计算量还不小。那我们应该如何做?
想到根节点了吗?原来是存放-1的,我们现在将其数据存放为"-高",这样获取高度将变得十分方便

(四)代码实现

1.修改代码,在创建集合后面,更新根节点的树高度

void CreateSets(SetType *Set)
{
    //输入数据,使第一个数变为根节点
    int d=0,parent=0;
    int j,i = 0;
    int min,high;
    Set->n = 0;
    printf("input number(data,parent) to create set Number(<-1,-1> to exit):\n");
    while (1)
    {
        scanf("%d,%d", &d,&parent);
        if (d == -1&&parent==-1)
            break;
        Set->set[i].data = d;
        Set->set[i].parent = parent;
        Set->n++;
        i++;
    }

    //更新每个根节点的数据
    for (i = Set->n; i >= 0;i--)
    {
        j = i;
        high = 1;
        for (; Set->set[j].parent >= 0; j = Set->set[j].parent)
            high++;
        if (-high<Set->set[j].parent)
            Set->set[j].parent = -high;
    }

    printf("create set finish\n");
}

2.根据高度实现合并操作

void Union(SetType* s, SetName Root1, SetName Root2)
{
    if (s->set[Root1].parent<s->set[Root2].parent)//由于高度存放是负数,所以我们比较大小是不一样的
    {
        //说明Root1高
        s->set[Root2].parent = Root1;
    }
    else if (s->set[Root1].parent == s->set[Root2].parent)
    {
        //随便合并,不过高度加一
        s->set[Root2].parent = Root1;
        s->set[Root1].parent -= 1;
    }
    else
    {
        //说明Root2高
        s->set[Root1].parent = Root2;
    }
}

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100

typedef int TElemType;
typedef int SetName;    //默认根节点下标作为集合名称

typedef struct _PTNode
{
    int data;
    int parent;
}PTNode;

typedef struct
{
    PTNode set[MAX_SIZE];
    int n;
}SetType;

void CreateSets(SetType *Set)
{
    //输入数据,使第一个数变为根节点
    int d=0,parent=0;
    int j,i = 0;
    int min,high;
    Set->n = 0;
    printf("input number(data,parent) to create set Number(<-1,-1> to exit):\n");
    while (1)
    {
        scanf("%d,%d", &d,&parent);
        if (d == -1&&parent==-1)
            break;
        Set->set[i].data = d;
        Set->set[i].parent = parent;
        Set->n++;
        i++;
    }

    //更新每个根节点的数据
    for (i = Set->n; i >= 0;i--)
    {
        j = i;
        high = 1;
        for (; Set->set[j].parent >= 0; j = Set->set[j].parent)
            high++;
        if (-high<Set->set[j].parent)
            Set->set[j].parent = -high;
    }

    printf("create set finish\n");
}

void Union(SetType* s, SetName Root1, SetName Root2)
{
    if (s->set[Root1].parent<s->set[Root2].parent)//由于高度存放是负数,所以我们比较大小是不一样的
    {
        //说明Root1高
        s->set[Root2].parent = Root1;
    }
    else if (s->set[Root1].parent == s->set[Root2].parent)
    {
        //随便合并,不过高度加一
        s->set[Root2].parent = Root1;
        s->set[Root1].parent -= 1;
    }
    else
    {
        //说明Root2高
        s->set[Root1].parent = Root2;
    }
}

int Find(SetType s, TElemType e)
{
    int i;
    for (i = 0; i < s.n&&e != s.set[i].data; i++)
        ;
    if (i >= s.n)
        return -1;
    for (; s.set[i].parent >= 0; i = s.set[i].parent)
        ;
    return i;
}

void ShowSet(SetType s)
{
    int i;
    for (i = 0; i < s.n; i++)
        printf("%d %d %d\n", i, s.set[i].data, s.set[i].parent);
}

int main()
{
    SetType set;
    TElemType n;
    int root;
    CreateSets(&set);
    ShowSet(set);
    printf("input number to find root:\n");
    scanf("%d", &n);
    root=Find(set, n);
    printf("Finf Root in %d\n", root);
    printf("Union root1 and root2:\n");
    Union(&set, Find(set, 5), Find(set, 9));
    ShowSet(set);
    printf("Union root1 and root2 finished\n");
    system("pause");
    return 0;
}

全部代码

五:实例一:检测网络是否连通

局域网中有5台主机,我们要检测这五台主机之间是否联网,并实现实时连接和检测

C代表检测,I代表连接
C 3 2代表检测3,2号两台主机是否联网
I 3 2代表将3,2两台主机连接
C 1 5
I 4 5
I 2 4
C 3 5
S表示结束
实现方法和上面并查集相似,只是调用方法不一样而已

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

#define MAX_SIZE 100

typedef int TElemType;
typedef int SetName;    //默认根节点下标作为集合名称

typedef struct _PTNode
{
    int data;
    int parent;
}PTNode;

typedef struct
{
    PTNode set[MAX_SIZE];
    int n;
}SetType;

void CreateSets(SetType *Set)
{
    //输入数据,使第一个数变为根节点
    int d=0,parent=0;
    int i = 0;
    Set->n = 0;
    printf("input number of hosts to operate:\n");
    scanf("%d", &d);
    while (d)
    {
        Set->set[i].data = i + 1;
        Set->set[i].parent = -1;
        Set->n++;
        i++;
        d--;
    }
    //由于是初始n台独立的主机,所以我们不需要去初始其高

    printf("create hosts finish\n");
}

void Union(SetType* s, SetName Root1, SetName Root2)
{
    if (s->set[Root1].parent<s->set[Root2].parent)//由于高度存放是负数,所以我们比较大小是不一样的
    {
        //说明Root1高
        s->set[Root2].parent = Root1;
    }
    else if (s->set[Root1].parent == s->set[Root2].parent)
    {
        //随便合并,不过高度加一
        s->set[Root2].parent = Root1;
        s->set[Root1].parent -= 1;
    }
    else
    {
        //说明Root2高
        s->set[Root1].parent = Root2;
    }
}

int Find(SetType s, TElemType e)
{
    int i;
    for (i = 0; i < s.n&&e != s.set[i].data; i++)
        ;
    if (i >= s.n)
        return -1;
    for (; s.set[i].parent >= 0; i = s.set[i].parent)
        ;
    return i;
}

void ShowSet(SetType s)
{
    int i;
    for (i = 0; i < s.n; i++)
        printf("%d %d %d\n", i, s.set[i].data, s.set[i].parent);
}

void OperateNetwork(SetType *set)
{
    TElemType n;
    int host1, host2;
    char op;
    CreateSets(set);
    ShowSet(*set);
    while (1)
    {
        scanf("%c %d %d", &op, &host1, &host2);
        switch (op)
        {
        case ‘C‘:
            if (Find(*set, host1) != Find(*set, host2))
                printf("No\n");
            else
                printf("Yes\n");
            break;
        case ‘I‘:
            Union(set, Find(*set, host1), Find(*set, host2));
            break;
        case ‘S‘:
            return;
        default:
            break;
        }
    }
}

void CheckNetWork(SetType s)
{
    int i,n=0;
    for (i = 0; i < s.n; i++)
        if (s.set[i].parent < 0)
            n++;
    if (n == 1)
        printf("The network is connection\n");
    else
        printf("There are %d components\n", n);
}

int main()
{
    SetType set;

    OperateNetwork(&set);
    printf("CheckNetwork Finished\n");
    ShowSet(set);
    CheckNetWork(set);
    system("pause");
    return 0;
}

全部代码

六:实例二:在克鲁斯卡尔算法中判断一棵生成树中若是加入一条边是否形成回路

问:我们现在连接边v4-v2是否形成回路?如何判断?我们若是连接v4-v6是否还是一棵树?

解决思路:

使用并查集
我们通过判断我们要加入的边的两个顶点是否在同一集合来决定是否生成环,若是不在一个集合,我们连接后,依旧是一个生成树,但是如果在一个集合中,连接两顶点,一定是形成回路

实现代码基本和上面是一致的,不再赘述

1.将各个结点的parent置为-1
2.开始连接各个树,使用并操作
3.在连接的同时去判断连接的那条边的两个顶点对于的树的根对应的下标是不是同一位置,若是一个位置,就会生成环,不是同一个根,我们就可以正常连接

原文地址:https://www.cnblogs.com/ssyfj/p/9490764.html

时间: 2024-10-03 05:27:41

数据结构(四)树---集合的表示及查找(并查集)的相关文章

[ACM] POJ 2513 Colored Sticks (Trie树,欧拉通路,并查集)

Colored Sticks Time Limit: 5000MS   Memory Limit: 128000K Total Submissions: 29736   Accepted: 7843 Description You are given a bunch of wooden sticks. Each endpoint of each stick is colored with some color. Is it possible to align the sticks in a st

查找 --- 并查集

Ubiquitous Religions Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 22601   Accepted: 11134 Description There are so many different religions in the world today that it is difficult to keep track of them all. You are interested in findi

Codeforces 938G 线段树分治 线性基 可撤销并查集

Codeforces 938G Shortest Path Queries 一张连通图,三种操作 1.给x和y之间加上边权为d的边,保证不会产生重边 2.删除x和y之间的边,保证此边之前存在 3.询问x到y的路径异或最小值 保证图在任意时刻连通 首先连通图路径异或相当于从x到y的任意一条路径再异或上若干个环得到的,只要在dfs过程中把非树边成的环丢到线性基里就好了,其他环一定可以通过这些环异或组合出来 有加边删边操作怎么做呢?线段树时间分治!注意到不能保证在线段树的任意一个节点图是连通的,需要用

线段树、最短路径、最小生成树、并查集、二分图匹配、最近公共祖先--C++模板

线段树(区间修改,区间和): #include <cstdio> #include <iostream> #include <cstring> using namespace std; int c[1000000],n,m; char s; void update(int p,int l,int r,int x,int add) { int m=(l+r) / 2; if (l==r) { c[p]+=add; return; } if (x<=m) update

并查集数据结构java源码

在网上看到一个题目: 给定一个字符串的集合,格式如:.要求将其中交集不为空的集合合并,要求合并完成的集合之间无交集,例如上例应输出. (1) 请描述你解决这个问题的思路: (2) 给出主要的处理流程,算法,以及算法的复杂度: (3) 请描述可能的改进. 其中一个解决方案是使用并查集,(数据结构中有,但已经忘了囧) 所以,百度了一下,主要参考了一个博主的 文章http://blog.csdn.net/dm_vincent/article/details/7655764  ,思路讲得很清楚,但是在代

UVALive 4730 线段树+并查集

点击打开链接 题意:在坐标上给n个点,r的操作是将两个点连起来,l的操作是问你y=u的这条线连接的集合块数和这些集合内的点的个数 思路:很麻烦的一道题,在网上看了题意和做法后,开始了一下午的调bug过程,做法很好懂,我开了两个线段树,一个维护点代表的直线的集合个数,另一个则是路过集合内的点的个数,然后集合的判断直接用并查集就行了,这是两个核心,然后就是自己瞎写的了,代码丑的可以而且好像除了本人别人看着可能要骂人了,有兴趣研究的可以留言我来解答,那难的部分其实就是并查集合并时该怎么将这两个要维护的

bzoj3694: 最短路(树链剖分/并查集)

bzoj1576的帮我们跑好最短路版本23333(双倍经验!嘿嘿嘿 这题可以用树链剖分或并查集写.树链剖分非常显然,并查集的写法比较妙,涨了个姿势,原来并查集的路径压缩还能这么用... 首先对于不在最短路径树上的边x->y,设t为最短路径树上lca(x,y),则t到y上的路径上的点i到根的距离都可以用h[x]+dis[x][y]+h[y]-h[i](h[]为深度)来更新,因为h[i]一定,只要让h[x]+dis[x][y]+h[y]最小就行,这里用树剖直接修改整条链上的数,就可以过了. 并查集的

bzoj2049 线段树 + 可撤销并查集

https://www.lydsy.com/JudgeOnline/problem.php?id=2049 线段树真神奇 题意:给出一波操作,拆边加边以及询问两点是否联通. 听说常规方法是在线LCT,留坑. 如果说这个删边的操作是删除上一条边,那这自然是可撤销并查集的模板题,直接在线维护就可以了. 但是问题在于删除边的顺序是不可能固定的,要知道并查集是不可以随意撤销的. 万万没想到还有更加高妙的手法. 首先可以证明一条边的存在一定是一段或者多段连续的区间. 建立一条时间节点长度的线段树,结点维护

Codeforces 1140F Extending Set of Points 线段树 + 按秩合并并查集 (看题解)

Extending Set of Points 我们能发现, 如果把x轴y轴看成点, 那么答案就是在各个连通块里面的x轴的个数乘以y轴的个数之和. 然后就变成了一个并查集的问题, 但是这个题目里面有撤销的操作, 所以我们要把加入和撤销操作变成 这个点影响(L , R)之间的询问, 然后把它丢到线段树里面分成log段, 然后我们dfs一遍线段树, 用按秩合并并查集取维护, 回溯的时候将并查集撤销. #include<bits/stdc++.h> #define LL long long #def