计算所有1-hamming距离字符串邻居的最快方法?

时间:2015-04-09 15:45:29

标签: java string hamming-distance

我正在尝试计算n个节点图中每个节点之间的汉明距离。该图中的每个节点都有一个相同长度(k)的标签,用于标签的字母是{0,1,*}。 ' *'作为一个无所谓的护理符号。例如,标签101 * 01和1001 * 1之间的汉明距离等于1(我们说它们仅在第3个索引处不同)。

我需要做的是找到每个节点的所有1-hamming-distance邻居,并准确报告这两个标签的不同之处。

我将每个节点标签与所有其他节点标签逐字符进行比较,如下所示:

    // Given two strings s1, s2
    // returns the index of the change if hd(s1,s2)=1, -1 otherwise.

    int count = 0;
    char c1, c2;
    int index = -1;

    for (int i = 0; i < k; i++)
    {
        // do not compute anything for *
        c1 = s1.charAt(i);
        if (c1 == '*')
            continue;

        c2 = s2.charAt(i);
        if (c2 == '*')
            continue;

        if (c1 != c2)
        {
            index = i;
            count++;

            // if hamming distance is greater than 1, immediately stop
            if (count > 1)
            {
                index = -1;
                break;
            }
        }
    }
    return index;

我可能有几百万个节点。 k通常在50左右。我使用JAVA,这个比较需要n * n * k时间并且运行缓慢。我考虑过使用try和VP-tree但是无法确定哪种数据结构适用于这种情况。我还研究了Simmetrics库,但没有任何东西闪现在我的脑海里。我真的很感激任何建议。

3 个答案:

答案 0 :(得分:1)

尝试这种方法:

将密钥转换为三元数(基数为3)。即0 = 0,1 = 1,* = 2 10位三进制给你一个0..59049的范围,它适合16位。

这意味着其中两个会形成32位字。创建一个包含40亿条目的查找表,返回这两个10位三元字之间的距离。

您现在可以使用查找表通过一次查找来检查密钥的10个字符。如果你使用5个字符,那么3 ^ 5会给你243个值,这些值适合一个字节,所以查找表只有64 KB。

通过使用移位操作,您可以创建不同大小的查找表以平衡内存和速度。

这样,您可以优化循环以更快地中止。

要获得第一个差异的位置,您可以使用第二个查找表,其中包含两个关键子串的第一个差异的索引。

如果您有数百万个节点,那么您将拥有许多以相同子字符串开头的节点。尝试将它们分类到桶中,其中一个桶包含以相同键开头的节点。这里的目标是使桶尽可能小(减少n * n)。

答案 1 :(得分:1)

代替/附加字符串,存储1位掩码和*位掩码。可以使用BitSet,但让我们试试吧。

static int mask(String value, char digit) {
    int mask = 0;
    int bit = 2; // Start with bits[1] as per specification.
    for (int i = 0; i < value.length(); ++i) {
        if (value.charAt(i) == digit) {
            mask |= bit;
        }
        bit <<= 1;
    }
    return mask;
}

class Cell {
    int ones;
    int stars;
}

int difference(Cell x, Cell y) {
    int distance = 0;
    return (x.ones & ~y.stars) ^ (y.ones & ~x.stars);
}

int hammingDistance(Cell x, Cell y) {
    return Integer.bitCount(difference(x, y));
}

boolean differsBy1(Cell x, Cell y) {
    int diff = difference(x, y);
    return diff == 0 ? false : (diff & (diff - 1)) == 0;
}

int bitPosition(int diff) {
    return Integer.numberOfTrailingZeroes(diff);
}

答案 2 :(得分:0)

有趣的问题。对于外卡符号来说,这很容易。

如果通配符是字母表中的常规字符,那么对于给定的字符串,您可以枚举所有k汉明距离1字符串。然后在多地图中查看这些字符串。例如,对于101,你查找001,111和100。

不干净的符号使得您无法进行查找。但是,如果构建多映射使得每个节点都按其所有可能的键存储,则可以再次执行该查找。所以例如1 * 1存储为111和101.所以当你查找10 *时,你会查找000,010,011,001,111,它会找到1 * 1,由111存储。

这样做的好处还在于你可以将所有标签存储为整数,而不是三元结构,因此使用int [3]作为键值,你可以使用任何k&lt; 96.

性能取决于多地图的后备实施。理想情况下,您可以使用散列实现来实现密钥大小&lt; 32以及上面任何内容的树实现。通过树实现,所有节点都连接到O(n*k*log(n))中的距离-1邻居。构建多映射需要O(n * 2 ^ z),其中z是任何字符串的最大通配符数。如果通配符的平均数量很少,那么这应该是可接受的性能损失。

编辑:通过将汉明距离1个邻居插入到多地图中,可以将所有节点的查找性能提高到O(n*log(n)),但这可能会使其大小爆炸。

注意:我在午休时间输入此内容。我还没有检查过细节。