Guava TreeRangeSet:区间运算的数学之美!

一、什么是TreeRangeSet?

TreeRangeSet是Guava中基于红黑树实现的区间集合。它专门用于处理区间的并集、交集、补集等数学运算,让复杂的区间逻辑变得简单直观。简单说,它就像是给区间运算装上了"数学大脑"!

二、为什么需要TreeRangeSet?

先来看个震撼对比:
传统区间判断:

// 判断时间是否在多个允许区间内
List<TimeRange> allowedRanges = Arrays.asList(    
	new TimeRange("09:00", "12:00"),    
	new TimeRange("14:00", "18:00"));
public boolean isAllowed(LocalTime time) {   
	for (TimeRange range : allowedRanges) {        
		if (range.contains(time)) {            
			return true;        
			}    
		}    
		return false; // O(n)时间复杂度,还要处理区间重叠!
}

使用TreeRangeSet:

RangeSet<LocalTime> allowedTimes = TreeRangeSet.create();
allowedTimes.add(Range.closedOpen(
	LocalTime.of(9, 0), LocalTime.of(12, 0)));
allowedTimes.add(Range.closedOpen(    
	LocalTime.of(14, 0), LocalTime.of(18, 0)));

// 一行代码搞定!
boolean allowed = allowedTimes.contains(LocalTime.of(10, 30)); // true
// 自动处理区间合并:[9:00-12:00] ∪ [14:00-18:00]

数学般的优雅,这才是编程的艺术!

三、TreeRangeSet的核心设计思想

  1. 区间代数的编程实现

在这里插入图片描述

  1. 规范化的区间存储
    在这里插入图片描述

  2. 基于红黑树的高效运算
    在这里插入图片描述

四、TreeRangeSet的核心API详解

  1. 创建和基础操作
    创建TreeRangeSet
    在这里插入图片描述
    添加和移除区间
    在这里插入图片描述

  2. 查询操作
    点查询
    在这里插入图片描述
    区间关系查询
    在这里插入图片描述

  3. 集合运算
    补集运算
    在这里插入图片描述
    子范围视图
    在这里插入图片描述
    区间视图操作
    在这里插入图片描述

五、实际开发中的使用场景

场景一:数学区间运算引擎

/** * 数学区间运算引擎 - 使用TreeRangeSet实现复杂区间计算 */
public  class  MathIntervalEngine 
{   
    private  final RangeSet < Double > domain;   
    private  final Map < String, RangeSet < Double >> namedSets;   
    public  MathIntervalEngine() 
    {     
        this.domain = TreeRangeSet.create();     
        this.namedSets =  new  HashMap < > ();      
      	// 默认定义实数域        
        domain.add(Range.all());  
    }    

	/**     * 定义命名的区间集合     */    
    public  void  defineSet(String name, RangeSet < Double >  set) 
    {    
        namedSets.put(name,  set);  
    }    
	/**     * 执行集合运算     */    
    public  RangeSet < Double >  evaluateExpression(String expression) 
    {      
      	// 简单的表达式解析器(实际应用可以使用更复杂的解析器)        
        if (expression.contains("∪"))
        {      
            String[] parts = expression.split("∪");       
            return  union(evaluateExpression(parts[0].trim()),              evaluateExpression(parts[1].trim()));    
        } 
        else  if (expression.contains("∩"))
        {      
            String[] parts = expression.split("∩");       
            return  intersection(evaluateExpression(parts[0].trim()),                 evaluateExpression(parts[1].trim()));    
        } 
        else  if (expression.contains("\\"))
        {      
            String[] parts = expression.split("\\\\");       
            return  difference(evaluateExpression(parts[0].trim()),                evaluateExpression(parts[1].trim()));    
        } 
        else  if (expression.startsWith("!"))
        {       
            return  complement(evaluateExpression(expression.substring(1).trim()));    
        } 
        else  if (namedSets.containsKey(expression))
        {       
            return  namedSets.get(expression);    
        } 
        else 
        {       
            return  parseRange(expression);    
        }  
    }    

	/**     * 并集运算     */    
    public  RangeSet < Double >  union(RangeSet < Double > set1, RangeSet < Double > set2) 
    {    
        RangeSet < Double > result = TreeRangeSet.create(set1);    
        result.addAll(set2);     
        return  result;  
    }    
	/**     * 交集运算     */    
    public  RangeSet < Double >  intersection(RangeSet < Double > set1, RangeSet < Double > set2) 
    {    
        RangeSet < Double > result = TreeRangeSet.create(set1);    
        result.removeAll(set2.complement());     
        return  result;  
    }    
	/**     * 差集运算     */    
    public  RangeSet < Double >  difference(RangeSet < Double > set1, RangeSet < Double > set2) 
    {    
        RangeSet < Double > result = TreeRangeSet.create(set1);    
        result.removeAll(set2);     
        return  result;  
    }    
	/**     * 补集运算     */    
    public  RangeSet < Double >  complement(RangeSet < Double >  set) 
    {     
        return  set.complement();  
    }    
	/**     * 解析区间表达式     */    
    private  RangeSet < Double >  parseRange(String expression) 
    {     
        try 
        {       
            if (expression.equals("R"))
            {        
                RangeSet < Double > realNumbers = TreeRangeSet.create();        
                realNumbers.add(Range.all());         
                return  realNumbers;      
            }        
          	// 解析类似 "[1, 5]"  "(2, 8)" 的表达式            
            expression = expression.trim();       
            char  leftBracket = expression.charAt(0);       
            char  rightBracket = expression.charAt(expression.length() -  1);      
            String[] parts = expression.substring(1, expression.length() -  1).split(",");       
            double  lower = Double.parseDouble(parts[0].trim());       
            double  upper = Double.parseDouble(parts[1].trim());      
            Range < Double > range;       
            if (leftBracket ==  '['  && rightBracket ==  ']')
            {        
                range = Range.closed(lower, upper);      
            } 
            else  if (leftBracket ==  '('  && rightBracket ==  ')')
            {        
                range = Range.open(lower, upper);      
            } 
            else  if (leftBracket ==  '['  && rightBracket ==  ')')
            {        
                range = Range.closedOpen(lower, upper);      
            } 
            else  if (leftBracket ==  '('  && rightBracket ==  ']')
            {        
                range = Range.openClosed(lower, upper);      
            } 
            else 
            {         
                throw  new  IllegalArgumentException("无效的区间表达式: "  + expression);      
            }      
            RangeSet < Double > result = TreeRangeSet.create();      
            result.add(range);       
            return  result;    
        } 
        catch (Exception e)
        {       
            throw  new  IllegalArgumentException("无法解析区间表达式: "  + expression, e);    
        }  
    }    
	/**     * 计算区间的度量(长度)     */    
    public  double  measure(RangeSet < Double >  set) 
    {     
        return  set.asRanges().stream()      .mapToDouble(range - >
        {         
            if (!range.hasLowerBound() || !range.hasUpperBound())
            {           
                return  Double.POSITIVE_INFINITY;        
            }         
            return  range.upperEndpoint() - range.lowerEndpoint();      
        })      .sum();  
    }    
    	/**     * 检查集合关系     */    
    public  SetRelation  checkRelation(RangeSet < Double > set1, RangeSet < Double > set2) 
    {    
        boolean subset = set1.enclosesAll(set2);    
        boolean superset = set2.enclosesAll(set1);    
        boolean disjoint = !set1.intersects(set2);     
        if (subset && superset)
        {       
            return  SetRelation.EQUAL;    
        } 
        else  if (subset)
        {       
            return  SetRelation.SUBSET;    
        } 
        else  if (superset)
        {       
            return  SetRelation.SUPERSET;    
        } 
        else  if (disjoint)
        {       
            return  SetRelation.DISJOINT;    
        } 
        else 
        {       
            return  SetRelation.OVERLAP;    
        }  
    }    
	/**     * 求解不等式系统     */    
    public  RangeSet < Double >  solveInequalitySystem(List < String > inequalities) 
    {    
        RangeSet < Double > solution = TreeRangeSet.create();    
        solution.add(Range.all()); 
        // 初始化为全体实数
             
        for (String inequality: inequalities)
        {      
            RangeSet < Double > inequalitySet = parseInequality(inequality);      
            solution = intersection(solution, inequalitySet);    
        }     
        return  solution;  
    }   
    private  RangeSet < Double >  parseInequality(String inequality) 
    {    
        inequality = inequality.trim();    
        RangeSet < Double > result = TreeRangeSet.create();     
        if (inequality.contains(">="))
        {      
            String[] parts = inequality.split(">=");       
            double  value  = Double.parseDouble(parts[1].trim());      
            result.add(Range.atLeast(value));    
        } 
        else  if (inequality.contains("<="))
        {      
            String[] parts = inequality.split("<=");       
            double  value  = Double.parseDouble(parts[1].trim());      
            result.add(Range.atMost(value));    
        } 
        else  if (inequality.contains(">"))
        {      
            String[] parts = inequality.split(">");       
            double  value  = Double.parseDouble(parts[1].trim());      
            result.add(Range.greaterThan(value));    
        } 
        else  if (inequality.contains("<"))
        {      
            String[] parts = inequality.split("<");       
            double  value  = Double.parseDouble(parts[1].trim());      
            result.add(Range.lessThan(value));    
        } 
        else  if (inequality.contains("="))
        {      
            String[] parts = inequality.split("=");       
            double  value  = Double.parseDouble(parts[1].trim());      
            result.add(Range.singleton(value));    
        }     
        return  result;  
    }    

	/**     * 生成区间可视化描述     */    
    public  String  visualize(RangeSet < Double >  set,  double  from,  double  to,  int  width) 
        {    
            StringBuilder sb =  new  StringBuilder();    
            sb.append("数轴: ").append(from).append(" 到 ").append(to).append("\n");     
            double  step = (to -  from) / width;     
            for (int  i =  0; i <= width; i++)
            {       
                double  x =  from  + i * step;       
                if (set.contains(x))
                {        
                    sb.append("█");      
                } 
                else 
                {        
                    sb.append(" ");      
                }    
            }    
            sb.append("\n刻度: ");     
            for (int  i =  0; i <= width; i += width /  10)
            {       
                double  x =  from  + i * step;      
                sb.append(String.format("%.1f ", x));    
            }     
            return  sb.toString();  
        }    

	// 集合关系枚举    
    public  enum  SetRelation
    {    
        EQUAL, SUBSET, SUPERSET, DISJOINT, OVERLAP  
    }    

	/**     * 演示复杂的区间运算     */    
    public  void  demonstrateComplexOperations() 
    {      
      
      	// 定义几个集合        
        defineSet("A", parseRange("[0, 5]"));    
        defineSet("B", parseRange("[3, 8]"));    
        defineSet("C", parseRange("[1, 10]"));     
        // 执行复杂运算: (A ∪ B) ∩ !C        
        RangeSet < Double > result = evaluateExpression("(A ∪ B) ∩ !C");    
        System.out.println("A = "  + namedSets.get("A"));    
        System.out.println("B = "  + namedSets.get("B"));    
        System.out.println("C = "  + namedSets.get("C"));    
        System.out.println("(A ∪ B) ∩ !C = "  + result);    
        System.out.println("度量: "  + measure(result));      // 可视化结果        
        System.out.println(visualize(result,  -2,  12,  80));  
    }
}
<think>我们被要求解释DisjointSet(并查集)的定义和用途。根据之前的对话历史和提供的引用,我们可以总结如下:定义:并查集(DisjointSet)是一种用于处理不相交集合的数据结构,它主要支持两种操作:1.查找(Find):确定某个元素属于哪一个子集(或者说是确定某个元素的“根”)。2.合并(Union):将两个子集合并成一个集合。在并查集中,每个集合用一个代表元素(通常是集合的根节点)来标识。当我们需要合并两个集合时,只需将一个集合的根节点指向另一个集合的根节点。用途:并查集的主要用途是高效地管理元素的连通性。例如:-在图中检查两个节点是否连通,以及合并两个连通分量。-在Kruskal算法中用于判断边的两个顶点是否属于同一个连通分量以避免形成环。-在一些动态连通性问题中(如网络连接、社交网络的朋友圈等)。实现:根据引用[1][3][4],我们通常用一个数组(称为father或parent)来存储每个元素的父节点。初始时,每个元素自成一个集合,即father[i]=i。操作细节:1.初始化:每个元素都是自己的父节点(自己就是根)。2.查找(Find):通过不断向上查找直到找到根节点(根节点的特点是father[x]==x)。为了优化,我们使用路径压缩:在查找的过程中,将沿途上的节点的父节点都直接指向根,以减少后续查找的深度。3.合并(Union):先分别找到两个元素的根,如果根相同则已经在同一个集合,无需合并;否则,将一个根的父节点设为另一个根。为了平衡树的高度,我们还可以使用按秩合并(rank):总是将深度较小的树合并到深度较大的树上,这样树的高度不会增加。如果两棵树高度相同,合并后高度加一。根据引用[1]的描述,合并操作是将一个集合的根的父亲指向另一个集合的根。在引用[3]和[4]中,我们看到了具体的代码实现。我们接下来给出一个并查集的完整实现,包括路径压缩和按秩合并(虽然引用[3]和[4]的代码没有按秩合并,但引用[1]中提到了合并操作,我们在代码中增加按秩合并以优化)。同时,我们将用中文解释其定义和用途。此外,根据指令,我们需要在末尾生成相关问题。注意:用户现在想了解定义和用途,所以我们重点放在基础概念上,避免深入算法细节(如Kruskal算法已经讨论过,这里只需简单提及)。结构:1.定义并查集2.核心操作3.代码实现(参考多个引用,编写一个优化版本)4.并查集的用途5.相关问题</think> ### 并查集(Disjoint Set)定义与核心用途 #### 一、基本定义 并查集是一种**处理不相交集合合并与查询问题**的高效数据结构[^1],包含两个核心操作: 1. **Find(查询)**:确定元素所属的集合(通常返回集合代表元素) 2. **Union(合并)**:将两个不相交集合合并为一个新集合 ```mermaid graph TB subgraph 初始状态 A[0]-->A B[1]-->B C[2]-->C end subgraph 合并Union(0,1) A-->B B-->B C-->C end subgraph 合并Union(1,2) A-->B B-->B C-->B end ``` #### 二、核心操作原理 1. **存储结构** 使用`father[]`数组(又称`parent[]`)存储元素的父节点引用[^1][^3]: ```python # 初始化:每个元素独立成集合 father = [0,1,2,...,n-1] # father[i] = i ``` 2. **Find操作** - 功能:查找元素所在集合的根节点(代表元素) - 优化:**路径压缩**(Path Compression) ```python def find(x): if father[x] != x: father[x] = find(father[x]) # 递归压缩路径 return father[x] ``` 3. **Union操作** - 功能:合并两个元素所在集合 - 优化:**按秩合并**(Union by Rank) ```python rank = [0]*n # 存储树的高度 def union(x, y): rx, ry = find(x), find(y) if rx == ry: return # 已属同集合 # 矮树合并到高树上(避免树过高) if rank[rx] < rank[ry]: father[rx] = ry elif rank[rx] > rank[ry]: father[ry] = rx else: father[ry] = rx rank[rx] += 1 # 高度相同时树高+1 ``` #### 三、关键性质与性能 | 特性 | 说明 | |------|------| | **连通性检测** | 判断元素连通性:$find(a) = find(b)$ [^2] | | **时间复杂度** | 单次操作均摊$O(\alpha(n))$(阿克曼反函数)[^4] | | **空间复杂度** | $O(n)$(存储father和rank数组) | | **动态性** | 支持集合的实时合并与查询 | #### 四、主要应用场景 1. **图论算法** - Kruskal最小生成树算法中的环检测[^2] - 连通分量实时统计 2. **动态连通性问题** - 社交网络好友关系合并(如:合并好友圈) - 图像处理中的像素区域合并 3. **编译器设计** - 变量等价关系推断(如:并查集实现类型等价) 4. **棋盘类游戏** - 围棋/象棋的连通区域判定 #### 五、典型代码实现 ```python class DisjointSet: def __init__(self, n): self.father = list(range(n)) self.rank = [0] * n def find(self, x): # 路径压缩查询 if self.father[x] != x: self.father[x] = self.find(self.father[x]) return self.father[x] def union(self, x, y): # 按秩合并 rx, ry = self.find(x), self.find(y) if rx == ry: return False if self.rank[rx] < self.rank[ry]: self.father[rx] = ry elif self.rank[rx] > self.rank[ry]: self.father[ry] = rx else: self.father[ry] = rx self.rank[rx] += 1 return True ``` #### 六、性能优化意义 | 优化方法 | 未优化复杂度 | 优化后复杂度 | 原理 | |---------|------------|------------|------| | 普通并查集 | $O(\log n)$ | $O(\alpha(n))$ | 避免树退化成链 | | 路径压缩 | - | 查询扁平化 | 降低后续查询深度 | | 按秩合并 | - | 控制树高度 | 合并时平衡树结构 | > 其中$\alpha(n)$是阿克曼反函数,在$n ≤ 10^{100}$时$\alpha(n) ≤ 4$[^4] --- ### 相关问题 1. **路径压缩与按秩合并能否同时使用?它们如何相互影响时间复杂度?** 2. **如何用并查集检测图中是否存在环?时间复杂度是多少?** 3. **在动态图连通性问题中,并查集相比DFS/BFS有哪些优势?** 4. **并查集能否高效支持集合的分裂操作?为什么?** 5. **如何处理带权并查集问题(如食物链问题)?** [^1]: 并查集通过father数组组织树形结构维护集合关系 [^2]: 集合操作本质是通过操作树根实现的 [^3]: 路径压缩是保证查询高效的核心技术 [^4]: 按秩合并与路径压缩共同实现次线性复杂度
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值