2011-01-28 1 views
3

재귀 알고리즘을 설계하여 파이썬에서 작성했습니다. 다른 매개 변수로 실행 시간을 측정 할 때 기하 급수적 인 시간이 걸릴 것 같습니다. 더욱이; 50과 같은 작은 숫자로 끝나는 데는 30 분 이상이 소요됩니다. (끝날 때까지 기다리지는 않았지만 합리적인 시간 내에 끝내지 않는 것으로 보입니다. 기하 급수적이라고 생각하십시오).내 알고리즘의 실행 시간 복잡성 -이를 어떻게 계산하고 알고리즘을 최적화 할 수 있습니까?

그래서이 알고리즘의 실행 시간 복잡성에 대해 궁금합니다. 누군가가 방정식 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를 발견하고 난을 반환 해결하기 위해 시도 SEA의 각 문자가 SRC에서 발견 된 위치의 ndexes. SEA의 문자가 SRC의 여러 위치에있을 수 있으면 해당 문자 위치에 대해 -1을 반환합니다.

예를 들면; 소스 문자열이 BRRBRBR (N = 7)이고 검색 문자열이 BBB 인 경우 : 'BBB'의 첫 번째 'B'는 검색 문자열의 색인 0에 나타날 수 있습니다. 두 번째 'B'는 검색 문자열의 색인 3에 있고 마지막 'B'는 다섯 번째 위치에있을 수 있습니다. 더욱이; 문자 'BBB'의 위치에 대한 다른 대안이 없으므로 알고리즘은 [0,3,5]를 반환합니다.

소스 문자열이 BRBRBRB (N = 6)이고 검색 문자열이 RBR 인 경우 : 'RBR'의 첫 번째 'R'은 1 또는 2 위치에있을 수 있습니다. 마지막 'R'은 'B'이고 위치는 4입니다. 그러면 첫 번째 'R'은 여러 위치에있을 수 있습니다. 그 장소는 모호합니다. 다른 두 문자 인 B와 R은 단 하나의 장소 만 가지고 있습니다. 따라서 알고리즘은 [-1,4,5]를 반환합니다.

소스 스트링이 [ 'B', 'R', 'B', 'R', 'B', 'R' B, B, R, B, R, B, B, B, R, B, R, B ','B ','B ','B ','B ','R ','B ','R ','B ' 'B', 'R', 'B', 'B', 'B', 'R', 'B', 'R', 'B' B ','B ','B ','B ','R ','B ','B ','B ','B ' , 'B'] (N = 58) 이고 검색 문자열은 RBRRBRBBRBRRBBRRBBBRRBBBRR입니다. [-1, -1, -1, -1, -1, -1, -1, -17, 18, 19, 23, -1, -1, -1, -1, -1 -1, -1, -1, -1, -1, -1, -1, (47), (53), 그러나 불행히도 = (

최적화는 않는다 :

I는 생각 'indexes'리스트가 -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")) 
+0

아직 알고리즘을 이해하지 못했지만 O (n^2) 인 것처럼 보입니다. – Gabe

+1

@ Murat- "이 문제에 대한 다항식 해결책이 있다는 것을 알고 있습니다." 나는 호기심이 많지 않다. 어떻게 알 수 있니? – Justin

+0

| V | ad의 대답을 받아 들여야합니다. 정확한 답입니다. 코드와 설명 때문에 당신이하려는 일을 정확히 이해하지 못했습니다. 그래서 제가 제공 한 답은 약간 더 복잡한 문제입니다. –

답변

4

당신은 SRC의 문자 수 mSEA의 문자 수는 O(n + m), 그리고 n에서 할 수 있습니다 이전 문자를 찾은 위치 이후 만 스캔합니다.

예를 들어

; 소스 문자열이 BRRBRBR이다 (N = 7) 및 검색 문자열은 다음

BBB

경우 : SRC에서 B을 찾을 : last = 1 인쇄 1에서 발견 last = 2을 설정합니다.

SRC에서 B 찾기 : last = 4에서 발견, 인쇄 4last = 5을 설정합니다.

SRC에서 B 찾기 : last = 6에서 발견, 인쇄 6last = 7을 설정합니다. 끝난. 알고리즘의 복잡성에 관해서는


, 나는 아주 형식적인 분석을 제공 할 수 아니에요,하지만 난 그것에 대해 가고 싶어 방법을 설명하려고합니다.

모든 문자가 SRCSEA 및 그 사이에 모두 있다고 가정합니다. 따라서 while 루프에서 조건을 제거 할 수 있습니다. 또한 while 루프는 n 번을 실행합니다.

첫 번째 문자는 find(1, 1), ... find(m, n)입니다. 그러나 이것들은 while 루프를 시작하고 그들 자신의 재귀 호출을 할 것입니다. 각 find(i, j)은 에 대해 while 루프에서 O(m) 재귀 호출을 만듭니다. 그러나 이것들은 차례 차례로 재귀 적 호출을 더 만들어 지수 적 복잡성을 야기하는 일종의 "눈사태 효과"를 발생시킵니다.

그래서 당신은 :

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) 것 같습니다. 나는 이것이 의미가 있고 나는 어떤 실수도하지 않았기를 바랍니다.

+0

IVlad, 대답 해줘서 고맙지 만 한 가지가 있습니다. 첫 번째 'B'는 last = 1에서 발견되고 다른 두 개의 B는 SRC의 나머지 부분에서 발견됩니다. 좋아,하지만 하위 시퀀스가 ​​두 번 이상 나타나기를 바란다. SRC가 BRBRBRBR (추가로 'B'가 추가됨)이라고 해봅시다. 알고리즘에서 "B는 여러 위치에있을 수 있으며, 두 번째와 세 번째 B는 여러 위치에있을 수 있습니다."라고 말하고 싶습니다. " [-1, -1, -1]을 반환합니다. 따라서 첫 번째 사건을 검사 할뿐만 아니라 모든 사건을 확인해야합니다. 이 상황에 적응하기 위해 알고리즘을 어떻게 바꿀 수 있습니까? –

+0

@ Murat - 죄송합니다. 귀하의 질문을 제대로 읽지 않았습니다. 그러나 이것은 쉽게 처리 할 수 ​​있어야합니다. 'SRC'와'SEA'를 반대로하고 같은 알고리즘을 다시 실행하십시오. 두 번 실행 사이에 다른 값을 얻는 위치에 대해 -1을 인쇄하십시오. 알고리즘은 항상 왼쪽에서 오른쪽으로가는 첫 번째 가능한 일치 항목을 찾습니다. 따라서 문자열을 뒤집어서 다른 일치 항목을 얻는다면 여러 가지 가능성이있는 곳을 알 수 있습니다. 예를 들어,'1, 3, 5'을 얻을 수있는 뒤집힌 문자열을 실행합니다. 반전에 대한 설명은 '4 6 8' =>'-1 -1 -1'로 쉽게 변환됩니다. – IVlad

+0

나는 이것이보기 흉하지 않은 접근이라고 생각한다. 고맙습니다. 내가 제안한 알고리즘의 Python 구현을 게시하고 있습니다. N = 58 및 검색 문자열이 20자를 초과하여 완료하는데도 초당 시간이 필요하지 않습니다! –

3

이 단순히 최장 공통 부분 수열이다. 이것은 다이나믹 프로그래밍으로 구현되어 지수 적 시간보다 훨씬 적게 걸릴 수 있습니다. 귀하의 경우에 LCS가 SEA의 길이를 반환하면 시퀀스 SEA가 SRC에 존재한다는 것을 알게됩니다. 알고리즘을 수정할 때 인덱스를 저장하는 것이 쉬운 일이 아닙니다. 다음은 좋은 설명에 대한 링크입니다. http://en.wikipedia.org/wiki/Longest_common_subsequence_problem

+0

@ Murat : 현재 알고리즘은 컨테스트 문제 해결 환경에서 받아 들일 수없는 O (2^n) 시간 (실수로 오해하지 않거나 오해하지 않는 경우) 시간으로 실행됩니다. LCS는 첫 번째 문자열의 길이와 두 번째 문자열의 길이가 동일한 경우 두 번째 또는 O (n^2) 최악의 경우 길이 인 O (n * m)에서 실행됩니다. 하나의 추가 최적화는 전체 DP 매트릭스 대신 문제를 해결하기 위해 DP 테이블의 2 행만 필요하다는 것을 깨닫는 것입니다. –

+0

이것은 과잉입니다. 어떤 방식 으로든 "가장 긴"개념과 관련이없는 경우 LCS를 사용할 필요가 없습니다. – IVlad

+0

@ | V | ad : 당신이 맞습니다. 코드를 읽었으며,이 경우 O (n)에서 해결할 수 있다고 생각합니다. 여기서 n은 긴 문자열의 길이입니다. 이것을 반영하기 위해 나의 대답을 편집 할 것이고, 이것을 지적 해 주셔서 다시 한번 감사드립니다. –

0

빠른 검색을 통해 재귀 및 재귀를 검색하고 있습니까?

소스 문자열에 suffix array을 작성하는 것이 좋습니다.
suffix array을 생성하는 것은 O (nlogn) 복잡성을 갖습니다. 부분 문자열 찾기에는 O (logn) 시간 복잡도가 있습니다. , SEA

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

    print last 
    ++last (skip this match) 

기본적으로, 각 문자에 대한 SRC에서의 위치를 ​​찾을 수 :

+0

하지만 내가 만든 서 픽스 배열을 어떻게 활용할 수 있습니까? –

+0

@ Murat : 이진 검색과 일치하는 항목을 찾을 수 있습니다. 일치하는 항목을 찾으면 그 위치에서 순차적으로 앞뒤로 스캔하여 더 많은 하위 문자열을 일치시킬 수 있습니다. –

+0

과잉 공격. 또한 OP가 하위 시퀀스를 원할 때 하위 문자열에 대해 이야기하고 있습니다. 둘은 동일하지 않습니다. -1. – IVlad

관련 문제