L2-013红色警报(C语言)与并查集相关知识

#include <stdio.h>
int arr[501], cha[501];
struct shuru
{
    int x, y;
}; struct shuru shuru[5100];
void join(int x, int y)
{
    int t1 = Find(x);
    int t2 = Find(y);
    if (t1 != t2)
    {
        arr[t2] = t1;
    }
    return;
}
int Find(int x)
{
    if (x == arr[x])
    {
        return arr[x];
    }
    else
    {
        arr[x] = Find(arr[x]);
        return arr[x];
    }
}
int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; i++)
    {
        arr[i] = i;
    }
    for (int i = 0; i < m; i++)
    {
        int x, y;
        scanf("%d %d", &x, &y);
        shuru[i].x = x;
        shuru[i].y = y;
        join(x, y);
    }
    int num = 0, num1;
    for (int i = 0; i < n; i++)
    {
        if (arr[i] == i)
        {
            num++;
        }
    }
    int k;
    scanf("%d", &k);
    while (k--)
    {
        num1 = 0;
        for (int i = 0; i < n; i++)
        {
            arr[i] = i;
        }
        int x;
        scanf("%d", &x);
        cha[x] = 1;
        for (int i = 0; i < m; i++)
        {
            if (cha[shuru[i].x] == 1 || cha[shuru[i].y] == 1)
            {
                continue;
            }
            else
            {
                join(shuru[i].x, shuru[i].y);
            }
        }
        for (int i = 0; i < n; i++)
        {
            if (arr[i] == i)
            {
                num1++;
            }
        }
        if (num == num1 || num + 1 == num1)
        {
            printf("City %d is lost.\n", x);
        }
        else
        {
            printf("Red Alert: City %d is lost!\n", x);
        }
        num = num1;
    }
    num = 0;
    for (int i = 0; i < n; i++)
    {
        if (cha[i] == 1)
        {
            num++;
        }
    }
    if (num == n)
    {
        printf("Game Over.\n");
    }
    return 0;
}

如上为该题的代码,以下来简单描述一下关于并查集的相关知识

以下是关于数组arr的解释,关于arr的数组存储数字原因

 当数组没有存储任何连通关系的时候,每一个地址都是都是一个独立的根节点,有连通关系输入时,设两个地址为x,y。会使arr[x]对应的值转变为y,此时的x不再作为一个独立的根节点存在,此时的x是y的一个子节点,由此建立连通关系。

并查集最关键的两个函数,Find()和join()

int Find(int x)
{
    if (x == arr[x])
    {
        return arr[x];
    }
    else
    {
        arr[x] = Find(arr[x]);
        return arr[x];
    }
}
如代码段所示
arr[x]中的x是两个城市中的一个,而arr[x]对应的值则是两个城市中的另一个

Find函数旨在找到其所对应的根节点,如上图的二叉树所对应的根节点就是1,而如果一个没有建立连通关系的二叉树,它本身就是一个根节点,所以该点在arr中所对应的值应当已经被初始化设置为它本身,当x=arr[x]的时候证明他本身就是根节点,这个时候所返回的值就是他自己,而每一个作为子节点存在的地址,都能通过数组的指向关系一直指到最根本的那个根节点上,没有到根节点的时候Find函数就会继续寻找,直到找到满足x=arr[x]的x,此时才会继续返回x的值

void join(int x, int y)
{
    int t1 = Find(x);
    int t2 = Find(y);
    if (t1 != t2)
    {
        arr[t2] = t1;
    }
    return;
}
join函数

先利用Find函数去寻找t1与t2的根节点,如果两者相等则说明二者实际上已经是连通状态了,是通过同一个根节点连接,如果不相等,就使y作为x的子节点存在,原理如图所示

 实质上就是在数组的数组之中构建起一个又一个的二叉树,利用其中所存数据来判断每一个节点之间是否是同一棵树上的节点,如果是一棵树上的节点,则证明二者之间是连通的,如果不是同一棵树上的节点,则证明二者并非连通关系。

接下来的代码段就为具体的查找过程

int main()
{
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; i++)
    {
        arr[i] = i;
    }
//使每个城市都成为一个独立的节点
//等待连通关系的输入,一旦输入连通关系输入,就开始将他们连接成一棵树
    for (int i = 0; i < m; i++)
    {
        int x, y;
        scanf("%d %d", &x, &y);
        shuru[i].x = x;
        shuru[i].y = y;
        join(x, y);
    }
//用结构体数组来记录每一条所输入的城市地址,也就是x,y
//同时利用join函数对这两个节点进行连接
//循环结束后,数据的录入结束,所有需要连接的节点已经完成连接
    int num = 0, num1;
    for (int i = 0; i < n; i++)
    {
        if (arr[i] == i)
        {
            num++;
        }
    }
//统计一下树的个数
//单独存在,没有任何连通关系的节点也算作是一棵树
    int k;
    scanf("%d", &k);
    while (k--)
    {
        num1 = 0;
        for (int i = 0; i < n; i++)
        {
            arr[i] = i;
        }
//将原本连通的数据全部清除
//所有的树全部变为原本单独的节点
        int x;
        scanf("%d", &x);
        cha[x] = 1;
//将查找的点的cha[x]改为1
        for (int i = 0; i < m; i++)
        {
            if (cha[shuru[i].x] == 1 || cha[shuru[i].y] == 1)
            {
                continue;
            }
            else
            {
                join(shuru[i].x, shuru[i].y);
            }
        }
//对录入数据进行遍历,除了将cha[x]的值或者cha[y]的值改为1的地址之外
//继续建立树的关系
        for (int i = 0; i < n; i++)
        {
            if (arr[i] == i)
            {
                num1++;
            }
        }
//再次统计树的个数,记为num1
        if (num == num1 || num + 1 == num1)
        {
            printf("City %d is lost.\n", x);
        }
        else
        {
            printf("Red Alert: City %d is lost!\n", x);
        }
        num=num1
//如果num == num1 || num + 1 == num1,则证明所攻占的城市的消失并没有
//从根本上影响这个城市的连通,因为树的个数是没有改变的
//此处的条件之后会用图解释
    }
    num = 0;
    for (int i = 0; i < n; i++)
    {
        if (cha[i] == 1)
        {
            num++;
        }
//此处寻找已经被攻占的城市,如果城市已经被攻占
//num的数字就会增加
    }
    if (num == n)
    {
        printf("Game Over.\n");
    }
//如果num与n都已经相等了,则证明所有的城市都已经被攻占了
//由此游戏结束
    return 0;
}

解释代码段中的条件num == num1 || num + 1 == num1

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值