热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python之并查集洛谷蓝桥杯

同时正在备战蓝桥杯题解如有不足请多批评指正大一双非本科在读目标是进大厂洛谷:亲戚关系题目链接问题分析:这是一道考察并查集的经典例题。何为并查集ÿ

同时正在备战蓝桥杯 题解如有不足请多批评指正  

大一双非本科在读

目标是进大厂 

洛谷:亲戚关系 题目链接

问题分析:这是一道考察并查集的经典例题。

何为并查集?并查集是一种(树型)数据结构 ,用于处理一些不相交集合的合并查询问题。

思想:用一个数组表示了整片森林,树的根节点唯一标识了一个集合,我们只要找到了某个元素的的树根,就能确定它在哪个集合里。

例如给出数组parent=[0,1,5,1,3,1],parent[i]代表结点i的父结点(很形象吧!?)

观察数组,我们知道:parent[2]=5 ,说明了结点2的父节点是5

再来:parent[5]=1,说明了结点5的父节点是1

再来:parent[1]=1,说明了结点1的父节点是1

对上面这行可能有疑惑:我们先记住,若parent[i]=i  则i是一个集合内的根节点

那么他有什么作用:如果一个结点j 它的父节点的父节点的父节点的父节点.....是i

说明了j和它的父节点和它的父节点的父节点和....一共这么多结点,对于每一个结点,都可以访问到i结点。我们不如把它叫做祖先(能够访问 类似于 含有血缘关系,你和你的祖先,你的父亲的祖先,你的父亲的父亲的祖先都有血缘关系。)

因此这么多结点就构成了一个集合(代表集合的对象是根节点(祖先))

所以对于刚刚提到的数组,由于结点5可以访问到祖先>>1,所以5和1有血缘关系,即5属于祖先1代表的集合,由于结点2的父节点是5,所以它可以通过父节点5访问到祖先>>1,所以2和1有血缘关系,即2属于祖先1代表的集合。

因此我们要判断两个结点是否属于同一个集合,可以借助访问各自的祖先加以判断。

如果祖先相同,那么属于同一集合,否则不属于同一集合。

对于上述数组结点0和结点1就不属于同一集合

所以下面给出访问祖先的代码:(关于优化下面会阐述)

def find_root(x):while x!=parent[x]:3如果不是祖先 就访问自己的父节点x=parent[x]return x

 那么上面这段代码就是代表了并查集的思想之一:查询

那么下面我们研究并查集的另一个思想:合并


再次给出上面那个例子parent=[0,1,5,1,3,1]

我们知道结点1,2,3,4,5同属于一个集合(用结点1来标识)

由于parent[0]=0 说明0是一个集合的祖先,这个集合用结点0来标识

由于以结点0为代表的集合元素个数为1,比较特殊,我们不妨给他(集合)添加两个结点

分别是结点6,结点7,因此parent=[0,1,5,1,3,1,0,0]

所以现在 结点0,6,7同属于一个集合(用结点0来标识)

下面研究合并,何为合并?就是将两个集合变为为一个集合

容易想到,我们可以把结点0(祖先)作为结点1的子结点,换句话说,把结点1作为结点0的父节点。

那么这样子有什么用:我们知道用结点0来标识的集合中的结点,都可以访问到结点0,现在结点0又可以访问到结点1,所以用结点0来标识的集合中的结点,都可以访问到结点1,因此用结点1来标识的集合 结点数目扩大了! 这不正达到了我们想要的效果吗?

简言之 集合A和集合B原本分属两大家族,把A的祖先作为B祖先的孩子,因而集合A和B中的所有结点都有了血缘关系,实现了家族合并。

 所以下面给出合并的代码:

def union(x,y):#合并x_root,y_root=find(x),find(y)parent[x_root]=y_root#一般的x_root!=y_root,直接合并过去#如果x_root=y_root,合并本身,没有发生任何变化

 所以 代码就可写出了:

def find_root(x):while x!=parent[x]:x=parent[x]return xdef union(x,y):x_root=find_root(x)y_root=find_root(y)parent[x_root]=y_rootn,m,p=map(int,input().strip().split())parent=[i for i in range(n+1)]#初始化for i in range(m):#h合并tmp=list(map(int,input().strip().split()))union(tmp[0],tmp[1])for j in range(n):#判断tmp=list(map(int,input().strip().split()))if find_root(tmp[0])==find_root(tmp[1]):print('Yes')else:print('No')

 但是这样超时 所以需要进行优化:

先分析超时的原因:还是利用上面给出的数组parent=[0,1,5,1,3,1,0,0](未合并)

我们可以画出下面这样的关系图:

 所以科学家们给出了一种方法:路径压缩。

简言之,对于上图,比如在访问4的根节点的时候,经过图中标识的''很长''一段路径,这一段路径由许许多多的结点构成,它们有一个共同特点就是根节点都是1,那么路径压缩要做的就是把这条路径上的所有结点的父节点设为结点1

这样子的作用:比如我下一次查询结点3的根节点的时候,只需要1次,原来需要9999次!

def find_root(x):#返回根节点if x!=parent[x]:#只要不是根节点parent[x]=find_root(parent[x])#修改父节点为根结点return parent[x]#返回根节点(父节点)

 第一次接触路径压缩如果不太好理解,可以先和小郑一样把这个模板记下来~


n,m,p=map(int,input().strip().split())parent=[i for i in range(n+1)]def find_root(x):#返回根节点if x!=parent[x]:#只要不是根节点parent[x]=find_root(parent[x])#把x的父节点设为根节点return parent[x]def union(x,y):x_root=find_root(x)y_root=find_root(y)parent[x_root]=y_rootfor i in range(m):tmp=list(map(int,input().strip().split()))union(tmp[0],tmp[1])for j in range(p):tmp=list(map(int,input().strip().split()))if find_root(tmp[0])==find_root(tmp[1]):print('Yes')else:print('No')

 

 可见路径压缩帮助我们优化了算法

蓝桥杯真题实战:七段码(第十一届试题E填空压轴)>>考察并查集

代码设计分析:题目就是在问一个连通性问题,从7条边选几个出来判断是不是连通图。

也就是判断是不是属于同一个集合的问题。首先明确一点,并查集两大功能:查找与合并。要先合并,再查找(没有合并一个集合那拿什么来查找?)。所以,相当于我们要先构建亲戚关系网,最后判断所有结点的祖先是否都是同一个,如果是则连通,否则不连通。

那么构建亲戚关系的条件:如果两个顶点直接相连,那么把那个点所属的集合与另外一个点所属的集合合并。当所有的点合并完了,只需要遍历所有的点的祖先,判断是否都是同一个,如果是,大功告成,count加1

def find_root(x):if x!=parent[x]:parent[x]=find_root(parent[x])return parent[x]def union(x,y):x_root,y_root=find_root(x),find_root(y)if x_root!=y_root:parent[x_root]=y_rootedge=[[0]*7 for i in range(7)]#设一个0号进去,无边与其相连l=[0,1,2,3,4,5,6]
edge[0][1]=edge[0][2]=edge[1][3]=edge[1][4]=edge[2][3]=1
edge[3][4]=edge[3][5]=edge[4][6]=edge[5][6]=edge[2][5]=1#有边相连记作1for i in range(7):for j in range(7):edge[i][j]=max(edge[i][j],edge[j][i])#无向图对称import itertoolscount=0for i in range(1,8):#灯泡数目for j in itertools.combinations(l,i):#比如[(1,2,3),[1,2,4]]parent=[i for i in range(7)]#判断两两是否连通for k in range(0,i):for p in range(k+1,i):if edge[j[k]][j[p]]==1:#如果连通,合并union(j[k],j[p])for m in range(i-1):#判断属于是否同一集合(最终所有的)if find_root(j[m])!=find_root(j[m+1]):breakelse:print(j)#显示符合条件的数据,可删去count+=1
print(count)
#答案80

 我是小郑 正在奔赴热爱 奔赴山海!


推荐阅读
  • Redux入门指南
    本文介绍Redux的基本概念和工作原理,帮助初学者理解如何使用Redux管理应用程序的状态。Redux是一个用于JavaScript应用的状态管理库,特别适用于React项目。 ... [详细]
  • 本文将探讨2015年RCTF竞赛中的一道PWN题目——shaxian,重点分析其利用Fastbin和堆溢出的技巧。通过详细解析代码流程和漏洞利用过程,帮助读者理解此类题目的破解方法。 ... [详细]
  • 本文详细介绍了 Python 中的条件语句和循环结构。主要内容包括:1. 分支语句(if...elif...else);2. 循环语句(for, while 及嵌套循环);3. 控制循环的语句(break, continue, else)。通过具体示例,帮助读者更好地理解和应用这些语句。 ... [详细]
  • 本文深入探讨了MySQL中常见的面试问题,包括事务隔离级别、存储引擎选择、索引结构及优化等关键知识点。通过详细解析,帮助读者在面对BAT等大厂面试时更加从容。 ... [详细]
  • 社交网络中的级联行为 ... [详细]
  • 实用正则表达式有哪些
    小编给大家分享一下实用正则表达式有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下 ... [详细]
  • 本文探讨了如何在 F# Interactive (FSI) 中通过 AddPrinter 和 AddPrintTransformer 方法自定义类型(尤其是集合类型)的输出格式,提供了详细的指南和示例代码。 ... [详细]
  • 深入理解Vue.js:从入门到精通
    本文详细介绍了Vue.js的基础知识、安装方法、核心概念及实战案例,帮助开发者全面掌握这一流行的前端框架。 ... [详细]
  • 历经三十年的开发,Mathematica 已成为技术计算领域的标杆,为全球的技术创新者、教育工作者、学生及其他用户提供了一个领先的计算平台。最新版本 Mathematica 12.3.1 增加了多项核心语言、数学计算、可视化和图形处理的新功能。 ... [详细]
  • 如何使用Ping命令来测试网络连接?当网卡安装和有关参数配置完成后,可以使用ping命令来测试一下网络是否连接成功。以winXP为例1、打开XP下DOS窗口具体操作是点击“开始”菜 ... [详细]
  • 深入解析SpringMVC核心组件:DispatcherServlet的工作原理
    本文详细探讨了SpringMVC的核心组件——DispatcherServlet的运作机制,旨在帮助有一定Java和Spring基础的开发人员理解HTTP请求是如何被映射到Controller并执行的。文章将解答以下问题:1. HTTP请求如何映射到Controller;2. Controller是如何被执行的。 ... [详细]
  • 探讨 HDU 1536 题目,即 S-Nim 游戏的博弈策略。通过 SG 函数分析游戏胜负的关键,并介绍如何编程实现解决方案。 ... [详细]
  • 本文介绍了 Python 的 Pmagick 库中用于图像处理的木炭滤镜方法,探讨其功能和用法,并通过实例演示如何应用该方法。 ... [详细]
  • 探讨ChatGPT在法律和版权方面的潜在风险及影响,分析其作为内容创造工具的合法性和合规性。 ... [详细]
  • 深入解析Java多线程与并发库的应用:空中网实习生面试题详解
    本文详细探讨了Java多线程与并发库的高级应用,结合空中网在挑选实习生时的面试题目,深入分析了相关技术要点和实现细节。文章通过具体的代码示例展示了如何使用Semaphore和SynchronousQueue来管理线程同步和任务调度。 ... [详细]
author-avatar
帝·姬
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有