优化O(n ^ 2)算法所需的建议

时间:2011-07-12 13:44:19

标签: algorithm optimization hadoop

我希望优化目前相当简单的算法 为O(n 2 。我有一个记录文件,每个人都需要 在同一个文件中相互比较。如果两个是 '相同'(比较器功能相当复杂),匹配 记录输出。请注意,可能有几个匹配的记录 彼此, 并且没有秩序感 - 只有当匹配为真或假时。

伪代码:


For (outRec in sourceFile) {
  Get new filePointer for targetFile //starting from the top of the file for inner loop
  For (inRec in targetFile) {
    if (compare(outRec, inRec) == TRUE ) {
      write outRec
      write inRec
    }
    increment some counters
  }
  increment some other counters
}

数据没有以任何方式排序,也没有预处理 可以订购数据。

关于如何成为这样的东西的任何想法 为O(n 2 ?我正在考虑应用MapReduce范例 在代码上,分解外部和内部循环,可能使用a 链式地图功能。我很确定我已经找到了代码 Hadoop,但在我花时间编码之前想要检查备选方案 它

建议赞赏!

补充:记录类型。基本上,我需要匹配名称/字符串。该 匹配类型如下例所示。


1,Joe Smith,Daniel Foster
2,Nate Johnson,Drew Logan
3,Nate Johnson, Jack Crank
4,Joey Smyth,Daniel Jack Foster
5,Joe Morgan Smith,Daniel Foster

Expected output: Records 1,4,5 form a match set End of output

补充:这些文件非常大。最大的文件是 预计将有大约2亿条记录。

10 个答案:

答案 0 :(得分:4)

我不确定比较器和数据集的属性,但假设你的比较器在你的行上定义了等价关系,这里什么都没有:

  1. 为输入文件创建一个映射,并使用比较器函数作为映射的关键比较器。映射值是行的序列/列表,即所有“相同”的行被连续地添加到相同的映射条目中。需要O(n * log n)时间。
  2. 浏览其他文件的行,检查每行是否与地图中的键匹配。在这种情况下,由于比较器隐含的等价关系,您知道该行与该映射条目的值中的所有行“相同”。取O(n * log n + C),取决于你输出的匹配数。
  3. 请注意,在最坏的情况下,根据您的问题描述,您不能比O(n ^ 2)更好,只是因为可能存在O(n ^ 2)匹配记录的结果,您必须输出!

答案 1 :(得分:2)

假设文件不是很大,我会完整地浏览文件,计算行的哈希值,并跟踪哈希/行#(或文件指针位置)组合。然后对哈希列表进行排序,并识别出现多次的哈希值。

答案 2 :(得分:2)

我们需要了解您的比较功能。你的比较是否具有传递性? (也就是说,A == B和B == C是否意味着A == C?)它是反身的吗? (A == B是否意味着B == A?)

如果您的比较函数具有传递性和反身性,并且许多记录相同,那么您可以将记录与组中的一个“代表性样本”进行比较,从而将记录分组。在最好的情况下,这可能接近O(N)。

请注意,散列记录会假设散列(A)==散列(B)< => compare(A,B)== true,但是如果比较(A,B)即使字节(A)!=字节(B)也可以为真,那么设计合适的散列算法可能会很棘手。

答案 3 :(得分:2)

FYI MapReduce将改善解决方案的算法复杂性。它增加了一些开销,但随后将其并行化,以便您可以在更少的挂钟时间内使用必要的资源。

为了改善您的挂钟时间,首先要做的是找到避免运行比较的方法。任何这样做都将是一场胜利。即使您的比较逻辑很复杂,您仍然可以使用排序来提供帮助。

例如,假设您有一些数据分散的维度。在该维度中变化太大的数据保证不会比较相等,尽管在该维度中接近并不保证相等。然后,您可以做的是按该维度对数据进行排序,然后仅在该维度中接近的元素之间进行比较。瞧!大多数O(n*n)比较现已消失。

让它变得更复杂。假设您可以识别两个彼此独立的维度。按照第一个这样的维度对数据进行排序。将第一维中的数据划分为条带。 (使条带重叠的最大值可以在该维度上变化,并且仍然比较相等。)现在取出每个条带并按第二个维度对其进行排序。然后在该维度中可接受地接近的元素对之间进行比较,并且如果它比较相等则在答案中包括该对,并且这是它可能出现的第一个条带。(需要该重复数据删除逻辑,因为重叠可能意味着比较相等的对可以出现在多个条带中。)这可能比第一种方法更好,因为你已经设法缩小了范围,因此你只是将行与少量的“附近”行进行比较。 / p>

如果您想减少使用资源,则需要关注避免实际进行个别比较的方法。你在这条道路上提出的任何建议都会有所帮助。

答案 4 :(得分:1)

只需浏览文件的每条记录并将其插入哈希表即可。在每个步骤中,检查记录是否已经在哈希表中。如果是,则输出它。这可以在O(n)中完成。

答案 5 :(得分:1)

正如你已经提到过的,你将不会有幸比O(n ^ 2)好,但你可以平行化。

我有一个可以使用HDFS的工作解决方案,您可以使用分布式缓存来扩展它。

public class MatchImporter extends Mapper<LongWritable, Text, Text, Text> {

FileSystem fs;
private BufferedReader stream;

@Override
protected void setup(Context context) throws IOException,
        InterruptedException {
    fs = FileSystem.get(context.getConfiguration());
}

private void resetFile() throws IOException {
    if (stream != null)
        stream.close();
    stream = new BufferedReader(new InputStreamReader(fs.open(new Path(
            "files/imp/in/target.txt"))));
}

private boolean compare(Text in, String target) {
    return target.contains(in.toString());
}

enum Counter {
    PROGRESS
}

@Override
protected void map(LongWritable key, Text value, Context context)
        throws IOException, InterruptedException {

    resetFile();
    String line = null;
    while ((line = stream.readLine()) != null) {
        // increment a counter to don't let the task die
        context.getCounter(Counter.PROGRESS).increment(1);
        context.progress();
        if (compare(value, line)) {
            context.write(new Text(line), value);
        }
    }
}

public static void main(String[] args) throws Exception {
    Configuration conf = new Configuration();
    Job job = new Job(conf);

    job.setMapperClass(MatchImporter.class);
    job.setReducerClass(Reducer.class);
    job.setJarByClass(MatchImporter.class);

    Path in = new Path("files/imp/in/source.txt");
    Path out = new Path("files/imp/out/");

    FileInputFormat.addInputPath(job, in);
    FileSystem fs = FileSystem.get(conf);
    if (fs.exists(out))
        fs.delete(out, true);

    SequenceFileOutputFormat.setOutputPath(job, out);
    job.setInputFormatClass(TextInputFormat.class);
    job.setOutputFormatClass(TextOutputFormat.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(Text.class);

    job.waitForCompletion(true);
}

}

使用source.txt中的输入:

thomas
phil
jen
james
christian
stefan
stephan
john

和target.txt

john meat
jay hardly

将导致减速器输出:

john meat   john

诀窍在于您可以拆分source.txt并并行比较。这会给你加速,但在大O中不会让你更好。

这里有一个重要的注意事项: 您必须使用计数器报告进度,因为与整个文件的比较可能需要永久。这将防止您的任务在分布式环境中失败。

小提示: 尝试将source.txt拆分为64m块,并将target.txt设为sequencefile。这将获得大量的加速,然后你必须重写阅读内容。

祝你好运!

答案 6 :(得分:0)

您尚未提及预期匹配的输入百分比,或者您与不精确匹配的匹配频率。如果你可以做一些预处理来减少问题的大小,那可能会有很大的帮助。

如果您只是对输入进行排序并在相邻条目上运行比较函数,则可能会剔除足够多的重复项以使n ^ 2秒通过可以忍受。

答案 7 :(得分:0)

如果真的不能做到比不透明的等价关系更好,那么你最坏的情况将永远是O(n ^ 2) - 例如,对于没有匹配的情况,你需要比较每一对,以确保这一点。 (正如人们所提到的,你可以将其平行化,但对于数以亿计的记录而言,这仍然不会特别容易处理;可能不值得这样做所需的资源成本。)

通常,有一些更好的方法可以做到。

如果你确实有一个等价关系(也就是说,如果你有一些逻辑保证,如果匹配(a,b)=匹配(b,c)=真,那么匹配(a,c)也是如此) ,可能有一些规范形式,您可以将您的记录转换为,可以进行散列和/或排序。

在您的示例中,您似乎匹配“Joe Smith”的变体。如果是这种情况,您可以扩充比较标准,以选择等价类的一个特定成员来表示整体。例如,选择“JOSEPH”代表相当于“Joe”,“SMITH”的所有名称,代表相当于“Smythe”的所有名称等。

进行转换后,您可以使用哈希表将操作减少到O(n),而不是O(n ^ 2)。

答案 8 :(得分:0)

感谢所有好的建议 在完成选项之后,考虑到我的时间线,似乎最好的方法是使用MapReduce框架来并行化问题并为其投入更多硬件。我意识到这并没有降低O(n 2 )的复杂性 我能想到的唯一可能的解决方案是在数据上运行某种类型的minHash,将数据条带化为重叠的部分,并在条带和重叠内进行比较。这应该减少比较次数,但我不确定散列运行的成本是多少。

答案 9 :(得分:0)

正如btilly所说,你实际上并不需要传递性来对记录进行分类。对于英文人名,您可以用两个首字母表示每个名字,每个记录用一个排序的首字母列表表示。然后,您只需要在同一个类中的记录之间运行完整的O(N ^ 2)比较。还有一个问题是同一对记录可以出现在多个类中,但通过维护一个单独的匹配记录对集合(由记录索引标识)很容易检测到。

在示例中,您将记录1放在“DF,JS”类中,记录2放在“DL​​,NJ”类中,记录3放在“JC,NJ”类中,记录4放在“DJ,JS”类中,“JF,JS”和“DF,JS”,以及“DF,JM”,“DF,JS”和“DF,MS”类中的记录5。你总共有7个班级:“DF,JM”,“DF,MS”,“DF,JS”,“DJ,JS”,“DL,NJ”,“JC,NJ”,“JF,JS”,其中只有“DF,JS”类包含多条记录,即记录1,4和5.因此在本例中,您只需要运行两次完整的比较功能。

另一方面,存在人们有奇怪名字的问题。 This blog entry on the subject is worth a look if you haven't seen it before.无论你做什么,你都会错过一些比赛。