【问题标题】:Red-Black tree rebalance problem?红黑树再平衡问题?
【发布时间】:2011-04-29 07:27:32
【问题描述】:

compareTo 方法是否有任何限制,该方法将对象放入标准 java TreeSet(即集合实现为红黑树)?我有

public class RuleTuple implements Comparable {
    String head;
    String[] rhs;
    public String toString() {
        StringBuffer b = new StringBuffer();
        b.append(head+":"); 
        for( String t: rhs ) 
           b.append(" "+t); 
        return b.toString();
    }
    public int compareTo(Object obj) {
        RuleTuple src = (RuleTuple)obj;
        int cmp = head.compareTo(src.head);
        if( cmp!=0 )
            return cmp;
        if( rhs.length != src.rhs.length )
            return rhs.length - src.rhs.length;
        for( int i=0; i<rhs.length; i++ )
            if( rhs[i].compareTo(src.rhs[i]) != 0 )
                return rhs[i].compareTo(src.rhs[i]);
        return 0;
    }
    ...
}

我假设任何将对象映射为线性顺序的方法都可以,只要它满足偏序标准:自反性、不对称性和传递性。其中唯一的传递性并不是立即显而易见的,但在我看来,如果对象通过排名标准进行比较,传递性就会遵循。 (我首先比较标题,如果相同,然后比较 rhs 的长度,如果相同,然后比较数组的元素。)

显然,RuleTuple.compareTo() 方法并不一致,因为在删除“test: test[22,33)”时它会按以下顺序遍历树:

test[22,33): 'HAVING' condition               <-- comparison#1
   test: test[4,19) group_by_clause           <-- comparison#2
       test: model_clause                     <-- comparison#3
           test: group_by_clause
              test:
              test: test[22,33)
           test: group_by_clause test[22,33)  <-- comparison#4; wrong branch!
              test: test[4,19)                <-- comparison#5
              test: group_by_clause model_clause
                  ...
       test: test[4,19) group_by_clause model_clause
        ...
   test[4,19): test[5,8) test[8,11)
        ...

因此,它无法找到并删除树上的对象。我的直觉正确吗?

【问题讨论】:

  • 另一个条件是“临时一致性”,即两个对象的比较结果不应该改变,只要它们被用作TreeMap中的键。这意味着您的密钥本质上不应该改变。
  • 这可能是关键(双关语)!我确实转换(替代规则)到位。唉,走捷径总是有问题……
  • this.head == nullsrc.head != null 时会发生什么?我认为您在切换视点时会收到 NullPointerException。此外,您可能会考虑不调用rhs[i].compareTo(src.rhs[i]) 两次。 (这只是一个优化,不是你的问题的原因,我不太明白。)除此之外(你应该在这里使用泛型),你的 compareTo 方法看起来不错。
  • 为避免一致性问题:从集合/映射中删除规则,更改它,重新添加。
  • 我编辑了帖子,因为 null 问题不相关。在按照 Paulo 的建议修复突变问题后,代码似乎运行良好。

标签: java collections


【解决方案1】:

Comparator 和 Comparable 的另一个(经常被忽视的)条件是“临时一致性”,即只要将两个对象用作 TreeMap(或您使用Comparator/Comparable,例如用于 Collections.binarySearch 的排序数组,或由最小堆实现的 PriorityQueue - 即使对于 Arrays.sort,您也不应该在排序完成之前更改元素。

这实质上意味着您的密钥不应该改变,至少不应该改变顺序。

原因是 TreeMap 假定二叉树的节点始终处于正确的顺序 - 正因为如此,它才能在 O(log(n)) 而不是 O(n) 中进行查找和更改。

如果你必须改变一个键,你应该先把它从结构中移除,然后改变它,然后再添加它。

(顺便说一下,对于基于哈希的结构(如 HashMap)中的键的 equalshashCode 也是有效的。)


作为额外的奖励,这里有一个使用泛型的代码变体:

public class RuleTuple implements Comparable<RuleTuple> {
    String head;
    String[] rhs;
    public String toString() {
        StringBuilder b = new StringBuilder();
        b.append(head+":"); 
        for( String t: rhs ) 
           b.append(" "+t); 
        return b.toString();
    }
    public int compareTo(RuleTuple src) {
        int cmp = head.compareTo(src.head);
        if( cmp!=0 )
            return cmp;
        if( rhs.length != src.rhs.length )
            return rhs.length - src.rhs.length;
        for( int i=0; i<rhs.length; i++ ) {
            int diff = rhs[i].compareTo(src.rhs[i]);
            if(diff != 0)
                return diff;
        }
        return 0;
    }
    ...
}

我还将 StringBuffer 更改为 StringBuilder(此处不使用同步会更有效),并且在循环中仅使用了一个 compareTo。您还可以通过在此处为每个 + 运算符使用两个附加来进一步优化 toString 方法。

【讨论】:

  • 这是从cmet中的讨论到问题的本质。我碰巧在这里用我的第一句话随机击中了解决方案。
猜你喜欢
  • 2015-08-10
  • 2015-04-16
  • 2011-08-03
  • 2019-09-07
  • 2016-07-15
  • 2011-08-06
  • 2018-06-30
  • 2015-07-21
  • 2013-10-27
相关资源
最近更新 更多