著名压缩算法(LZ78)的java慢速实现
我正在写一个方法,通过遵循LZ78算法来近似字符串的Kolmogorov复杂度,除了没有添加到表中之外,我只保留了一个计数器,即我只对压缩的大小感兴趣
问题是,对于大投入来说,这需要几个小时。这是我实施它的方式吗
/**
* Uses the LZ78 compression algorithm to approximate the Kolmogorov
* complexity of a String
*
* @param s
* @return the approximate Kolmogorov complexity
*/
public double kComplexity(String s) {
ArrayList<String> dictionary = new ArrayList<String>();
StringBuilder w = new StringBuilder();
double comp = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (dictionary.contains(w.toString() + c)) {
w.append(c);
} else {
comp++;
dictionary.add(w.toString() + c);
w = new StringBuilder();
}
}
if (w.length() != 0) {
comp++;
}
return comp;
}
更新: 使用
HashSet<String> dictionary = new HashSet<String>();
而不是
ArrayList<String> dictionary = new ArrayList<String>();
使它更快
# 1 楼答案
由于您总是检查prefix+c,我认为一个好的数据结构可以是一个树,其中每个子节点都有其父节点作为前缀:
另一种可能更简单的方法是使用排序列表,然后使用二进制搜索来查找正在查看的字符串。但我仍然认为树方法会更快
# 2 楼答案
ArrayList将具有O(N)搜索复杂性。使用数据结构,如哈希表或字典
# 3 楼答案
您可以尝试的另一个微观优化是用这些fastutil实现http://fastutil.dsi.unimi.it/替换collections对象,这基本上是免费的加速
# 4 楼答案
在我看来,{}并不是保存只包含和添加内容的词典的最佳数据结构
编辑
尝试使用HashSet,将其元素存储在哈希表中,这是Set interface的最佳实现;然而,它不保证迭代的顺序
# 5 楼答案
我想我可以做得更好(抱歉,时间有点长):
结果:
编辑同伴压力:它是如何工作的
弗兰基,我不知道,这似乎是一个加速事情的好方法。我也得想办法,所以我们开始吧
据观察,原始代码生成了很多字符串附录,但是,用
LinkedList<String>
替换它不会有帮助,因为在哈希表中查找集合的压力是恒定的——每次使用hashCode()和equals()时,它都需要遍历整个列表但是如何确保代码不会执行这些不必要的操作呢?答案是:不变性——如果你的类是不可变的,那么至少hashCode是常量,因此可以预先计算。等式检查也可以简化,但在最坏的情况下,它仍将遍历整个集合
这很好,但是如何“修改”一个不可变的类呢。不,你不需要,每次需要不同的内容时,你都会创建一个新的内容。然而,当你仔细查看字典的内容时,你会发现它包含冗余信息:
[]a
,[abc]d
,[abc]e
,[abcd]f
。那么,为什么不将头部存储为指向之前看到的模式的指针,并为当前角色设置一个尾部呢没错。使用不变性和反向引用可以节省空间和时间,甚至垃圾收集器也会喜欢你。我的解决方案的一个特点是,在F#和Haskell中,列表使用head:[tail]签名——head是元素类型,tail是指向下一个集合的指针。在这个问题上,随着列表在尾部的增长,需要相反的结果
从这里开始,它只是一些进一步的优化——例如,使用一个具体的
char
作为尾部类型,以避免泛型版本的常量字符自动装箱我的解决方案的一个缺点是,它在计算列表的各个方面时使用递归。对于相对较小的列表,这是可以的,但较长的列表可能需要增加运行计算的线程堆栈大小。从理论上讲,通过Java7的尾部调用优化,我的代码可以被重写为允许JIT执行优化的方式(或者已经这么做了?很难说)