一个认为一切根源都是“自己不够强”的INTJ
个人主页:用哲学编程-CSDN博客
专栏:每日一题——举一反三
Python编程学习
Python内置函数
Python-3.12.0文档解读
目录
我的写法
优点
改进建议
时间复杂度分析
空间复杂度分析
总结
我要更强
优化方法
优化后的代码
优化分析
哲学和编程思想
1. 懒惰求值(Lazy Evaluation)
2. 缓存(Memoization)
3. 最小化冗余(Minimization of Redundancy)
4. 对称性和不变性(Symmetry and Invariance)
5. 空间换时间(Space-Time Tradeoff)
6. 分治思想(Divide and Conquer)
7. 避免重复工作(Avoiding Duplication of Work)
总结
题目链接
我的写法
N = int(input()) # 读取整数N,表示集合的数量
sets = [] # 初始化一个空列表,用于存储集合def similarity(set1, set2):# 定义一个函数 similarity,它接收两个集合 set1 和 set2 作为参数# 计算两个集合的相似度(交集大小除以并集大小,并乘以100得到百分比形式)return f"{len(set1 & set2) / len(set1 | set2) * 100:.1f}%"for i in range(N):# 循环N次,读取每个集合# 使用 split() 分割输入,忽略第一个元素(因为它是集合的序号),将剩余元素转换为集合并添加到 sets 列表中sets.append(set(input().split()[1:]))K = int(input()) # 读取整数K,表示查询的数量
for _ in range(K):# 循环K次,读取每个查询i, j = map(int, input().split()) # 读取两个整数 i 和 j,表示查询的两个集合的编号# 调用 similarity 函数计算 sets[i-1] 和 sets[j-1] 的相似度(集合编号从1开始,所以要减1)print(similarity(sets[i - 1], sets[j - 1]))
这段代码的功能是读取多个集合并存储,计算和输出指定集合对的相似度。以下是对这段代码的专业点评和复杂度分析:
优点
- 简洁明了:代码逻辑清晰,结构简单,方便理解和维护。
- 函数封装:使用了similarity函数进行相似度计算,使代码模块化且易于复用。
- 直接输入处理:通过input().split()处理输入,简化了数据的读取过程。
改进建议
- 错误处理:代码没有处理输入的合法性和范围问题,比如负数或超出集合数量的查询。这可能导致运行时错误。
- 性能优化:当集合非常大时,交集和并集的计算可能会导致性能问题,可以考虑使用更高效的数据结构或算法。
- 注释和文档:虽然已经逐行加注释,但在实际应用中,详细的文档和函数说明也是必要的。
时间复杂度分析
- 读取集合:读取每个输入集合的时间复杂度为O(M),其中M是每个集合的平均大小。因此,读取N个集合的总时间复杂度为O(N * M)。
- 计算相似度:
- 交集操作set1 & set2的时间复杂度为O(min(len(set1), len(set2)))。
- 并集操作set1 | set2的时间复杂度为O(len(set1) + len(set2))。
- 因此,单次相似度计算的时间复杂度为O(len(set1) + len(set2)),在最坏情况下是O(2M) = O(M)。
- 总相似度计算:有K次查询,因此所有查询的总时间复杂度为O(K * M)。
综上,总时间复杂度为: [ O(N * M + K * M) ] 在最坏情况下,N和K都非常大时可以简化为: [ O((N + K) * M) ]
空间复杂度分析
- 存储集合:每个集合的空间复杂度为O(M),N个集合的总空间复杂度为O(N * M)。
- 临时变量:交集和并集操作会创建临时集合,其空间复杂度为O(len(set1) + len(set2)),即最坏情况下是O(2M) = O(M)。
因此,总空间复杂度为: [ O(N * M) ] 这包括了存储所有集合所需的空间和计算交集并集时的临时空间。
总结
这段代码在功能实现上是正确的,但在处理大数据集时,性能和空间利用上可能需要进一步优化。可以考虑引入错误处理机制和优化集合操作的方法以提高效率。
我要更强
优化代码的时间复杂度和空间复杂度是非常重要的,对于处理大数据集尤其如此。以下是一些可能的优化方法:
优化方法
- 预计算:可以在读取集合时预先计算并存储每个集合的大小,这样在计算并集大小时就不需要重新计算集合的长度。
- 哈希表存储大小:使用哈希表存储每个集合的大小和每对集合的交集大小,可以避免重复计算。
优化后的代码
N = int(input()) # 读取整数N,表示集合的数量
sets = [] # 初始化一个空列表,用于存储集合
sizes = [] # 初始化一个空列表,用于存储每个集合的大小
intersection_sizes = {} # 初始化一个字典,用于存储集合对的交集大小def similarity(size1, size2, intersection_size):# 定义一个函数 similarity,用于计算两个集合的相似度union_size = size1 + size2 - intersection_sizereturn f"{intersection_size / union_size * 100:.1f}%"for i in range(N):# 循环N次,读取每个集合current_set = set(input().split()[1:]) # 忽略第一个元素(因为它是集合的序号)sets.append(current_set) # 将集合添加到sets列表中sizes.append(len(current_set)) # 存储集合的大小K = int(input()) # 读取整数K,表示查询的数量
queries = [tuple(map(int, input().split())) for _ in range(K)] # 预先读取所有查询并存储为元组列表for i, j in queries:idx1, idx2 = min(i - 1, j - 1), max(i - 1, j - 1) # 确保idx1 < idx2if (idx1, idx2) not in intersection_sizes:# 计算交集大小并存储在字典中intersection_size = len(sets[idx1] & sets[idx2])intersection_sizes[(idx1, idx2)] = intersection_sizeelse:intersection_size = intersection_sizes[(idx1, idx2)]# 计算并打印相似度print(similarity(sizes[idx1], sizes[idx2], intersection_size))
优化分析
优化后,总时间复杂度仍为: [ O(N * M + K * M) ] 但是在查询过程中,利用了缓存的交集大小,减少了重复计算。
- 时间复杂度:
- 读取集合:与原始代码相同,时间复杂度为O(N * M)。
- 预计算和存储大小:每个集合的大小存储一次,时间复杂度为O(N)。
- 查询相似度:每次查询时,只需计算一次交集并存储,后续查询相同对时直接读取,时间复杂度为O(K * M)。
- 空间复杂度:
- 存储集合:与原始代码相同,空间复杂度为O(N * M)。
- 存储集合大小和交集大小:额外使用了O(N)的空间存储每个集合的大小,以及O(K)的空间存储交集大小(最坏情况下每对集合都被查询一次)。
优化后,总空间复杂度为: [ O(N * M + N + K) ]
通过这些优化,代码在处理大数据集时的性能得到了显著提升,尤其是在查询阶段通过缓存交集大小减少了重复计算,提高了效率。
哲学和编程思想
在优化代码以及改善时间复杂度和空间复杂度的过程中,我们应用了多种哲学和编程思想。以下是一些关键思想及其具体应用:
1. 懒惰求值(Lazy Evaluation)
懒惰求值是一种编程技术,它推迟计算直到真正需要结果时才进行计算。在我们的代码中,我们通过以下方式应用了懒惰求值的思想:
具体应用:
- 预计算交集大小:只有在需要计算相似度时才计算交集大小,而不是在读取集合时就计算。
- 缓存结果:存储已经计算过的交集大小,以避免重复计算。
示例:
if (idx1, idx2) not in intersection_sizes:intersection_size = len(sets[idx1] & sets[idx2])intersection_sizes[(idx1, idx2)] = intersection_size
else:intersection_size = intersection_sizes[(idx1, idx2)]
2. 缓存(Memoization)
缓存是一种优化技术,通过存储函数的计算结果以避免重复计算。我们的代码利用字典缓存交集大小,从而减少重复计算,提升性能。
具体应用:
- 缓存交集大小:使用字典存储每对集合的交集大小,避免相同计算的重复。
示例:
intersection_sizes = {}
# 初始化一个字典,用于存储集合对的交集大小
3. 最小化冗余(Minimization of Redundancy)
减少冗余是优化的一种基本原则,通过消除重复计算和数据存储,提升代码的效率。这在我们的代码中通过预计算和缓存体现出来。
具体应用:
- 预计算集合大小:在读取集合时预先计算并存储每个集合的大小,避免在计算相似度时重复计算集合大小。
示例:
sizes.append(len(current_set)) # 存储集合的大小
4. 对称性和不变性(Symmetry and Invariance)
在处理集合对时利用对称性特性,即集合 A 与集合 B 的交集与集合 B 与集合 A 的交集是相同的,从而减少计算和存储的复杂度。
具体应用:
- 保持索引顺序一致:通过保证 idx1 < idx2,我们利用了交集运算的对称性,简化了逻辑。
示例:
idx1, idx2 = min(i - 1, j - 1), max(i - 1, j - 1) # 确保 idx1 < idx2
5. 空间换时间(Space-Time Tradeoff)
通过使用更多的内存来缓存计算结果,从而减少计算时间。这种技术在我们的代码中通过使用字典缓存交集大小得以体现。
具体应用:
- 缓存交集大小:以空间换时间,通过使用字典存储交集大小提升查询效率。
示例:
intersection_sizes[(idx1, idx2)] = intersection_size
6. 分治思想(Divide and Conquer)
分治思想是将复杂问题分解成较小的子问题逐个解决。虽然在我们的具体代码中没有直接使用这种思想,但整体优化的过程包含了将读取集合、预计算、查询分解开来分别处理的思路。
具体应用:
- 分阶段处理:将读取集合、缓存信息和查询相似度分开处理,每个阶段专注于特定任务。
示例:
for i in range(N): # 读取集合并存储大小
for i, j in queries: # 处理查询
7. 避免重复工作(Avoiding Duplication of Work)
通过缓存和预计算来避免重复计算,是一种高效的程序设计策略。
具体应用:
- 预计算和缓存:上述提到的多次预计算和缓存实际应用了避免重复工作的思想。
示例:
if (idx1, idx2) not in intersection_sizes:intersection_size = len(sets[idx1] & sets[idx2])intersection_sizes[(idx1, idx2)] = intersection_size
else:intersection_size = intersection_sizes[(idx1, idx2)]
总结
这些哲学和编程思想共同作用,能够使人编写出更高效、更易维护的代码。通过懒惰求值、缓存、最小化冗余、对称性、空间换时间、分治及避免重复工作等思想,有效地优化了代码的性能。
感谢阅读。