算法的运行时间复杂度 - 我如何计算并进一步优化算法?

时间:2011-01-28 14:32:09

标签: python algorithm big-o time-complexity exponential

我设计了一个递归算法并用Python写下来。当我用不同的参数测量运行时间时,它似乎需要指数时间。此外;以50这样的小数字结束需要半个多小时。(我没等到它完成,但它似乎没有在合理的时间内完成,猜测它是指数级的。)

所以,我很好奇这个算法的运行时复杂性。有人可以帮我推导出方程T(n,m)吗?或者计算大哦?

该算法如下:

# parameters:
# search string, the index where we left on the search string, source string, index where we left on the source string,
# and the indexes array, which keeps track of the indexes found for the characters
def find(search, searchIndex, source, sourceIndex, indexes):
    found = None
    if searchIndex < len(search): # if we haven't reached the end of the search string yet
        found = False
        while sourceIndex < len(source): # loop thru the source, from where we left off
            if search[searchIndex] == source[sourceIndex]: # if there is a character match
                # recursively look for the next character of search string 
                # to see if it can be found in the remaining part of the source string
                if find(search, searchIndex + 1, source, sourceIndex + 1, indexes):
                    # we have found it
                    found = True # set found = true
                    # if an index for the character in search string has never been found before.
                    # i.e if this is the first time we are finding a place for that current character
                    if indexes[searchIndex] is None:
                        indexes[searchIndex] = sourceIndex # set the index where a match is found
                    # otherwise, if an index has been set before but it's different from what
                    # we are trying to set right now. so that character can be at multiple places.
                    elif indexes[searchIndex] != sourceIndex: 
                        indexes[searchIndex] = -1 # then set it to -1.
            # increment sourceIndex at each iteration so as to look for the remaining part of the source string. 
            sourceIndex = sourceIndex + 1
    return found if found is not None else True

def theCards(N, colors):
    # allcards: a list 1..N of characters where allcards[i] is 'R' if i is a prime number, 'B' otherwise.
    # so in this example where N=7, allcards=['B','R','R','B','R','B','R']
    allcards = ['R' if isPrime(i) else 'B' for i in range(1, N + 1)]
    # indexes is initially None.
    indexes = [None] * len(colors)

    find(colors, 0, allcards, 0, indexes)
    return indexes    

if __name__ == "__main__":
    print theCards(7, list("BBB"))

我不知道是否必须理解问题和算法以获得最坏情况的运行时间,但这是我试图解决的问题:

问题:

给定源字符串SRC和搜索字符串SEA,在SRC中找到子序列SEA并返回在SRC中找到SEA的每个字符的索引。如果SEA中的字符可以位于SRC中的多个位置,则为该字符位置返回-1。

例如; 如果源字符串是BRRBRBR(N = 7)并且搜索字符串是BBB: 然后'BBB'中的第一个'B'可以出现在搜索字符串中的索引0处。第二个“B”可以位于搜索字符串的索引3处,而最后一个“B”可以位于第5个位置。此外;字符'BBB'的位置没有其他选择,因此算法返回[0,3,5]。

在另一种情况下,源字符串是BRRBRB(N = 6),搜索字符串是RBR: 'RBR'的第一个'R'可以位于1或2位置。这只留下'B'的位置3和最后'R'的位置4。然后,第一个'R'可以在多个地方,它的位置是不明确的。另外两个字符B和R只有一个地方。所以算法返回[-1,4,5]。

算法没有完成并永远占用的情况是源字符串是['B','R','R','B','R','B','R', 'B','B','B','R','B','R','B','B','B','R','B','R','B ','B','B','R','B','B','B','B','B','R','B','R','B', 'B','B','B','B','R','B','B','B','R','B','R','B','B ','B','R','B','B','B','B','B','R','B','B','B','B', 'B'](N = 58)  搜索字符串是RBRRBRBBRBRRBBRRBBBRRBBBRR。它应该返回[-1,-1,-1,-1,-1,-1,-1,-1,17,18,19,23,-1,-1,-1,-1,-1,-1 ,-1,-1,-1,-1,-1,-1,-1,47,53],但不幸的是它不=(

优化:

我想在'索引'列表完全充满-1时停止搜索。但这只会影响最佳情况(或平均情况),但不会影响最坏情况。如何进一步优化该算法。我知道存在这个问题的多项式解决方案。

比优化更重要的是,我对运行时间的T(n,m)方程非常好奇,其中n和m是源和搜索字符串的长度。

如果你能够在这里阅读,非常感谢你! =)

编辑 - IVIad的解决方案已实施:

def find2(search, source):
    indexes = list()
    last = 0
    for ch in search:
        if last >= len(source):
            break
        while last < len(source) and source[last] != ch:
            last = last + 1
        indexes.append(last)
        last = last + 1
    return indexes

def theCards(N, colors):
    # allcards: a list 1..N of characters where allcards[i] is 'R' if i is a prime number, 'B' otherwise.
    allcards = ['R' if isPrime(i) else 'B' for i in range(1, N + 1)]

    indexes = find2(colors, allcards) # find the indexes of the first occurrences of the characters
    colors.reverse() # now reverse both strings
    allcards.reverse()
    # and find the indexes of the first occurrences of the characters, again, but in reversed order
    indexesreversed = find2(colors, allcards)
    indexesreversed.reverse() # reverse back the resulting list of indexes 
    indexesreversed = [len(allcards) - i - 1 for i in indexesreversed] # fix the indices

    # return -1 if the indices are different when strings are reversed
    return [indexes[i] + 1 if indexes[i] == indexesreversed[i] else - 1 for i in range(0, len(indexes))]

if __name__ == "__main__":
    print theCards(495, list("RBRRBRBBRBRRBBRRBBBRRBBBRR"))

3 个答案:

答案 0 :(得分:4)

您可以在O(n + m)中执行此操作,其中mSEA中的字符数,n SRC中的字符数:< / p>

last = 1
for i = 1 to m do
    while SRC[last] != SEA[i]
        ++last

    print last
    ++last (skip this match)

基本上,对于SEA中的每个字符,请在SRC中找到它的位置,但只能在找到前一个字符的位置后扫描。

  
    

例如;如果源字符串是BRRBRBR(N = 7)并且搜索字符串是BBB

  

然后:在B中找到SRC:在last = 1找到 打印1,设置last = 2

B中找到SRC:在last = 4找到,打印4,设置last = 5

B中找到SRC:在last = 6找到,打印6,设置last = 7。完成。


至于算法的复杂性,我无法提供非常正式的分析,但我会尝试解释如何进行分析。

假设SRCSEA以及它们之间的所有字符都相同。因此,我们可以消除while循环中的条件。另请注意,您的while循环执行n次。

请注意,对于第一个字符,您将调用find(1, 1), ... find(m, n)。但是这些也会启动while循环并进行自己的递归调用。对于find(i, j),每个O(m)将在其while循环中进行i = 1 to n递归调用。但这些反过来会自己进行更多的递归调用,从而产生一种导致指数复杂性的“雪崩效应”。

所以你有:

i = 1: calls find(2, 2), find(3, 3), ..., find(m, n)
       find(2, 2) calls find(3, 3), ..., find(m, n)
       find(3, 3) calls find(4, 4), ..., find(m, n)
       find(4, 4) calls find(5, 5), ..., find(m, n)
       ...
       total calls: O(m^m)
i = 2: same, but start from find(2, 3).
...
i = n: same

总复杂度因此看起来像O(n*m^m)。我希望这是有道理的,我没有犯任何错误。

答案 1 :(得分:3)

这只是最常见的子序列问题。这可以通过动态编程实现,以获得比指数时间少很多的时间。在您的情况下,当LCS返回SEA的长度时,您知道SRC中存在序列SEA,在修改算法时保存它们的索引是一件微不足道的事情。这是一个很好解释的链接。 http://en.wikipedia.org/wiki/Longest_common_subsequence_problem

答案 2 :(得分:0)

快速浏览,您正在搜索,递归和回溯?

我相信为你的源字符串创建一个suffix array是个好主意 构造suffix array具有O(nlogn)复杂度。定位子字符串具有O(logn)时间复杂度。