查找字符串是否是迭代子字符串?

时间:2011-01-14 22:45:59

标签: c algorithm string

我有一个字符串S.如何找到字符串是否遵循S = nT。

示例:
如果是,则函数应返回true 1)S =“abab”
2)S =“abcdabcd”
3)S =“abcabcabc”
4)S =“zzxzzxzzx”

但是如果S =“abcb”返回false。

我想也许我们可以在S的子串上重复调用KMP然后决定。

例如:for“abab”:在“a”上拨打KMP。它返回2(两个实例)。现在2 * len(“a”)!= len(s)
在“ab”上打电话给KMP。它返回2.现在2 * len(“ab”)== len(s)所以返回true

你能建议更好的算法吗?

10 个答案:

答案 0 :(得分:5)

我可以想到一个启发式方法,如果Len(原始字符串)/ Len(子字符串)是一个正整数,则只在子字符串上调用KMP。

此外,子字符串的最大长度必须小于N / 2.

修改

使用这些启发式方法我写了下面的python代码,因为我的C现在生锈了

oldstr='ABCDABCD'    

for i in xrange(0,len(oldstr)/2):
       newslice=oldstr[0:i+1]
         if newslice*(len(oldstr)/len(newslice)) == oldstr:
             print 'pattern found', newslice
             break

答案 1 :(得分:4)

您实际上只需要关心测试子串长度等于完整字符串长度除以素数。原因是:如果S包含n个副本,并且n不是素数,那么n = ab,因此S实际上也包含bT的副本(其中“bT”表示“T重复b次”)。这是anijhaw's answer的扩展名。

int primes[] = { 2, 3, 5, 7, 11, 13, 17 };  /* There are one or two more... ;) */
int nPrimes = sizeof primes / sizeof primes[0];

/* Passing in the string length instead of assuming ASCIIZ strings means we
 * don't have to modify the string in-place or allocate memory for new copies
 * to handle recursion. */
int is_iterative(char *s, int len) {
    int i, j;
    for (i = 0; i < nPrimes && primes[i] < len; ++i) {
        if (len % primes[i] == 0) {
            int sublen = len / primes[i];
            /* Is it possible that s consists of repeats of length sublen? */
            for (j = sublen; j < len; j += sublen) {
                if (memcmp(s, s + j, sublen)) {
                    break;
                }
            }

            if (j == len) {
                /* All length-sublen substrings are equal.  We could stop here
                 * (meaning e.g. "abababab" will report a correct, but
                 * non-minimal repeated substring of length 4), but let's
                 * recurse to see if an even shorter repeated substring
                 * can be found. */
                return is_iterative(s, sublen);
            }
        }
    }

    return len;     /* Could not be broken into shorter, repeated substrings */
}

请注意,在递归查找更短的重复子字符串时,我们不需要再次检查整个字符串,只需要检查第一个较大的重复字符串 - 因为我们已经确定剩余的大重复字符串是, >重复第一个。 :)

答案 2 :(得分:1)

在这种情况下,我没有看到KMP算法有帮助。这不是确定从哪里开始下一场比赛的问题。似乎减少搜索时间的一种方法是从最长的可能性(长度的一半)开始并向下工作。需要测试的唯一长度是均匀分成总长度的长度。这是Ruby中的一个例子。我应该补充一点,我意识到问题被标记为C,但这只是一种简单的方式来显示我正在考虑的算法(并允许我测试它是否有效)。

class String
def IsPattern( )
    len = self.length
    testlen = len / 2
    # the fastest is to start with two entries and work down
    while ( testlen > 0 )
        # if this is not an even divisor then it can't fit the pattern
        if ( len % testlen == 0 )
            # evenly divides, so it may match
            if ( self == self[0..testlen-1] * ( len / testlen ))
                return true
            end

        end
        testlen = testlen - 1
    end
    # must not have matched
    false
end
end

if __FILE__ == $0

   ARGV.each do |str|
       puts "%s, %s" % [str, str.IsPattern ? "true" : "false" ]
   end

end



[C:\test]ruby patterntest.rb a aa abab abcdabcd abcabcabc zzxzzxzzx abcd
a, false
aa, true
abab, true
abcdabcd, true
abcabcabc, true
zzxzzxzzx, true
abcd, false

答案 3 :(得分:0)

我想您可以尝试以下算法:

允许L成为生成原始单词的可能子串长度。对于从L1的{​​{1}},检查第一个字符是否在strlen(s)/2的所有L*i位置从1到i获得。如果确实如此,那么它可能是一个可能的解决方案,您应该使用strlen(s)/L进行检查,如果没有尝试下一个memcmp。当然,您可以跳过一些L未划分的值L

答案 4 :(得分:0)

试试这个:

    char s[] = "abcabcabcabc";
int nStringLength = strlen (s);
int nMaxCheckLength = nStringLength / 2;
int nThisOffset;
int nNumberOfSubStrings;
char cMustMatch;
char cCompare;
BOOL bThisSubStringLengthRepeats;
// Check all sub string lengths up to half the total length
for (int nSubStringLength = 1;  nSubStringLength <= nMaxCheckLength;  nSubStringLength++)
{
    // How many substrings will there be?
    nNumberOfSubStrings = nStringLength / nSubStringLength;

    // Only check substrings that fit exactly
    if (nSubStringLength * nNumberOfSubStrings == nStringLength)
    {
        // Assume it's going to be ok
        bThisSubStringLengthRepeats = TRUE;

        // check each character in substring
        for (nThisOffset = 0;  nThisOffset < nSubStringLength;  nThisOffset++)
        {
            // What must it be?
            cMustMatch = s [nThisOffset];

            // check each substring's char in that position
            for (int nSubString = 1;  nSubString < nNumberOfSubStrings;  nSubString++)
            {
                cCompare = s [(nSubString * nSubStringLength) + nThisOffset];
                // Don't bother checking more if this doesn't match
                if (cCompare != cMustMatch)
                {
                    bThisSubStringLengthRepeats = FALSE;
                    break;
                }
            }

            // Stop checking this substring
            if (!bThisSubStringLengthRepeats)
            {
                break;
            }
        }

        // We have found a match!
        if (bThisSubStringLengthRepeats)
        {
            return TRUE;
        }
    }
}

// We went through the whole lot, but no matches found
return FALSE;

答案 5 :(得分:0)

这是Java代码,但你应该明白这个想法:

        String str = "ababcababc";
    int repPos = 0;
    int repLen = 0;
    for( int i = 0; i < str.length(); i++ ) {
        if( repLen == 0 ) {
            repLen = 1;
        } else {
            char c = str.charAt( i );
            if( c == str.charAt( repPos ) ) {
                repPos = ++repPos % repLen;
            } else {
                repLen = i+1;
            }
        }
    }

如果没有重复,这将返回最短重复块的长度或字符串的长度。

答案 6 :(得分:0)

您可以构建字符串的后缀数组,对其进行排序 现在寻找一系列不断加倍的后缀,当你达到整个字符串(S)的大小时,系列中的第一个会给你T。

例如:

abcd <-- T
abcdabcd <-- S
bcd
bcdabcd
cd
cdabcd
d
dabcd

x
xzzx
xzzxzzx
zx
zxzzx
zxzzxzzx
zzx <-- T
zzxzzx
zzxzzxzzx <-- S

a
apa
apapa
apapapa
pa <-- T
papa
papapa <-- Another T, not detected by this algo
papapapa <-- S

答案 7 :(得分:0)

暴力破解方法是选择所有可能的子字符串,然后查看它们是否可以构成整个字符串。

使用以下观察结果,我们可以做得更好:将子字符串作为有效候选len(str) % len(substr) == 0。这可以从问题陈述中得出。

这是完整的代码:

bool isRational(const string &str){
    int len = str.length();
    const auto &factors = getFactors(len); // this would include 1 but exclude len
    // sort(factors.begin(), factors.end()); To get out of the loop faster. Why? See https://stackoverflow.com/a/4698155/1043773
    for(auto iter = factors.rbegin(); iter != factors.rend(); ++iter){
        auto factor = *iter;
        bool result = true;
        for(int i = 0; i < factor && result; ++i){
            for(int j = i + factor; j < len; j += factor, ++cntr){
                if (str[i] != str[j]) { result = false; break; }
            }
        }

        if (result) { return true;}
    }
    return false;
}

请注意,使用KMP的时间复杂度变化更快。

上述算法为O(N * factorCount(N)) 但是,这种算法的优点在于,它可以比KMP算法快得多。同样,因素的数量并没有增加太多。

这是[i, factorCount(i)] for i <= 10^6的图

enter image description here

这是与KMP算法相比算法的性能。 红色图形为O(N * factorCount(N))蓝色图形为O(N)KMP

here提取KMP代码

enter image description here

答案 8 :(得分:0)

这是一个简单而有效的 C 语言解决方案:

  • 它在 sqrt(len) 次迭代中测试字符串长度的所有因素。
  • 它使用对 memcmp 的单个调用来断言每个潜在模式长度的谓词。
  • 它返回最小模式长度。
#include <string.h>

// check if string s is a repeated pattern.
// return the pattern length or 0.
size_t is_iterative(const char *s) {
    size_t i, j, res = 0, len = strlen(s);
    if (len > 1 && !memcmp(s, s + 1, len - 1))
        return 1;
    for (i = 2; i <= (j = len / i); i++) {
        if (len % i == 0) {
            if (!memcmp(s, s + i, len - i))
                return i;
            if (!memcmp(s, s + j, len - j)) {
                res = len = j;
                i--;  // try the same divisor again
            }
        }
    }
    return res;
}

一种更快的方法是实施这些步骤:

  • a 个整数的数组 UCHAR_MAX+1 初始化为 0
  • 扫描字符串,将所有字节值计数到 a 中。
  • 计算所有非零字节计数和字符串长度的 gcd。
  • 如果 gcd 为 1,则字符串不是迭代的。
  • 否则重复计数必须是 gcd 的除数。
  • 仅使用上述方法测试相应的模式长度。

代码如下:

#include <limits.h>
#include <string.h>

size_t gcd_size(size_t a, size_t b) {
    while (b != 0) {
        size_t t = b;
        b = a % b;
        a = t;
    }
    return a;
}

// check if string s is a repeated pattern.
// return the pattern length or 0.
size_t is_iterative(const char *s) {
    size_t a[UCHAR_MAX+1] = { 0 };
    size_t i, j, plen, rep, len, res = 0;
    for (i = 0; s[i]; i++) {
        a[(unsigned char)s[i]]++;
    }
    rep = len = i;
    if (rep <= 1)
        return 0;
    for (i = 0; i <= UCHAR_MAX; i++) {
        if (a[i]) {
            rep = gcd_size(a[i], rep);
            if (rep == 1)
                return 0;
        }
    }
    plen = len / rep;
    if (!memcmp(s, s + plen, len - plen))
        return plen;
    for (i = 2; i <= (j = rep / i); i++) {
        if (rep % i == 0) {
            plen = len / j;
            if (!memcmp(s, s + plen, len - plen))
                return plen;
            plen = len / i;
            if (!memcmp(s, s + plen, len - plen)) {
                res = len = plen;
                rep = j;
                i--;  // try the same divisor again
            }
        }
    }
    return res;
}

答案 9 :(得分:-1)

navigation stack