이 함수 O (n ^ 2)의 최악의 이유는 무엇입니까? 차지 합니다.

임의의 함수에 대해 BigO 표기법을 계산하는 방법을 스스로 가르치려고합니다. 교과서에서이 기능을 찾았습니다. 이 책은 함수가 O (n 2 ) 라고 주장합니다 . 이것이 왜 그런지에 대한 설명을 제공하지만, 따르기 위해 고심하고 있습니다. 이것이 왜 그런지에 대한 수학을 보여줄 수 있을지 궁금합니다. 기본적으로, 나는 그것이 O (n 3 ) 보다 작은 것을 이해 하지만, O (n 2 ) 에 독립적으로 착륙 할 수 없었습니다

우리가 A, B, C의 3 개의 시퀀스를 가지고 있다고 가정하자. 우리는 개별 시퀀스가 ​​중복 값을 포함하지 않는다고 가정하지만, 시퀀스 중 2 개 또는 3 개의 숫자가있을 수 있다고 가정 할 것이다. 3 방향 집합 분리 문제는 3 개의 시퀀스의 교집합이 비어 있는지, 즉 x x A, x ∈ B 및 x ∈ C와 같은 요소 x가 없는지 확인하는 것입니다.

우연히도 이것은 숙제 문제가 아닙니다. 그 배는 몇 년 전에 항해했습니다.

def disjoint(A, B, C):
        """Return True if there is no element common to all three lists."""
        for a in A:
            for b in B:
                if a == b: # only check C if we found match from A and B
                   for c in C:
                       if a == c # (and thus a == b == c)
                           return False # we found a common value
        return True # if we reach this, sets are disjoint

[편집] 교과서에 따르면 :

개선 된 버전에서 운이 좋으면 시간을 절약 할 수있는 것은 아닙니다. 분리에 대한 최악의 실행 시간은 O (n 2 ) 라고 주장합니다 .

내가 따르기 어려운이 책의 설명은 다음과 같습니다.

전체 실행 시간을 설명하기 위해 각 코드 줄을 실행하는 데 소요 된 시간을 검사합니다. A를 통한 for 루프 관리에는 O (n) 시간이 필요합니다. B에 대한 for 루프 관리는 루프가 n 번 실행되므로 총 O (n 2 ) 시간을 차지 합니다. 테스트 a == b는 O (n 2 ) 번 평가 됩니다. 남은 시간은 일치하는 (a, b) 쌍의 수에 따라 다릅니다. 우리가 언급했듯이, 그러한 쌍은 최대 n 개이므로 C를 통한 루프 관리와 해당 루프의 본문 내 명령은 최대 O (n 2 ) 시간을 사용합니다. 소요 된 총 시간은 O (n 2 )입니다.

(그리고 적절한 신용을주기 위해 …)이 책은 : Michael T. Goodrich et al. 모두, Wiley Publishing, pg. 135

[편집] 정당화; 아래는 최적화 전의 코드입니다.

def disjoint1(A, B, C):
    """Return True if there is no element common to all three lists."""
       for a in A:
           for b in B:
               for c in C:
                   if a == b == c:
                        return False # we found a common value
return True # if we reach this, sets are disjoint

위에서 각 루프가 최대한 실행되어야하므로 이것이 O (n 3 ) 임을 분명히 알 수 있습니다 . 이 책은 단순화 된 예에서 (첫 번째로 주어진) 세 번째 루프는 O (n 2 )의 복잡성이므로 복잡도 방정식은 k + O (n 2 ) + O (n 2 ) 로 간다. O (n 2 ).

이것이 사실임을 증명할 수는 없지만 (문제), 독자는 단순화 된 알고리즘의 복잡성이 최소한 원래의 것보다 작다는 데 동의 할 수 있습니다.

[편집] 그리고 단순화 된 버전이 2 차임을 증명하려면 :

if __name__ == '__main__':
    for c in [100, 200, 300, 400, 500]:
        l1, l2, l3 = get_random(c), get_random(c), get_random(c)
        start = time.time()
        disjoint1(l1, l2, l3)
        print(time.time() - start)
        start = time.time()
        disjoint2(l1, l2, l3)
        print(time.time() - start)

수율 :

0.02684807777404785
0.00019478797912597656
0.19134306907653809
0.0007600784301757812
0.6405444145202637
0.0018095970153808594
1.4873297214508057
0.003167390823364258
2.953308343887329
0.004908084869384766

두 번째 차이가 같으므로 단순화 된 함수는 실제로 2 차입니다.

여기에 이미지 설명을 입력하십시오

그리고 추가 증거 :

최악의 경우 (A = B! = C)를 가정하면

if __name__ == '__main__':
    for c in [10, 20, 30, 40, 50]:
        l1, l2, l3 = range(0, c), range(0,c), range(5*c, 6*c)
        its1 = disjoint1(l1, l2, l3)
        its2 = disjoint2(l1, l2, l3)
        print(f"iterations1 = {its1}")
        print(f"iterations2 = {its2}")
        disjoint2(l1, l2, l3)

수율 :

iterations1 = 1000
iterations2 = 100
iterations1 = 8000
iterations2 = 400
iterations1 = 27000
iterations2 = 900
iterations1 = 64000
iterations2 = 1600
iterations1 = 125000
iterations2 = 2500

두 번째 차이 검정을 사용하면 최악의 결과는 정확히 2 차입니다.

여기에 이미지 설명을 입력하십시오



답변

이 책은 실제로 정확하며 좋은 주장을 제공합니다. 타이밍은 알고리즘 복잡성의 신뢰할 수있는 지표가 아닙니다. 타이밍은 특수한 데이터 배포 만 고려하거나 테스트 사례가 너무 작을 수 있습니다. 알고리즘 복잡도는 리소스 사용 또는 런타임이 일부 큰 입력 크기를 넘어 확장되는 방식 만 설명합니다.

이 책에서는 if a == b분기가 최대 n 번 입력 되므로 복잡도가 O (n²)라는 주장을합니다 . 루프는 여전히 중첩으로 작성되기 때문에 이것은 명백하지 않습니다. 추출하면 더 분명합니다.

def disjoint(A, B, C):
  AB = (a
        for a in A
        for b in B
        if a == b)
  ABC = (a
         for a in AB
         for c in C
         if a == c)
  for a in ABC:
    return False
  return True

이 변형은 생성기를 사용하여 중간 결과를 나타냅니다.

  • 발전기에서 AB, 우리는 것이다 많아야 n 개 (인해 입력리스트는 중복을 포함하지 않을 것을 보증) 요소 및 발전기를 생산하기 O (n²) 복잡성이 걸린다.
  • 발전기를 생산하는 ABC발전기 돌이 포함 AB길이 N 이상 C길이의 N , 알고리즘의 복잡성이되도록 O (n²)도있다.
  • 이러한 연산은 중첩되지 않지만 독립적으로 발생하므로 총 복잡도는 O (n² + n²) = O (n²)입니다.

입력리스트의 쌍을 순차적으로 점검 할 수 있기 때문에, 임의의 수의리스트가 분리되어 있는지를 결정하는 것은 O (n²) 시간 내에 수행 될 수있다.

이 목록은 모든 목록의 길이가 같다고 가정하기 때문에 정확하지 않습니다. 우리는 AB최대 길이 min (| A |, | B |)을 가지고 그것을 생성하는 것이 복잡함 O (| A | • | B |)를 더 정확하게 말할 수 있습니다 . 생성 ABC에는 복잡도 O (min (| A |, | B |) • | C |)가 있습니다. 그러면 총 복잡성은 입력 목록의 순서에 따라 다릅니다. | A | ≤ | B | ≤ | C | 우리는 O (| A | • | C |)의 최악의 총 복잡성을 얻습니다.

입력 컨테이너가 모든 요소를 ​​반복하지 않고 빠른 멤버쉽 테스트를 허용하는 경우 효율성이 향상 될 수 있습니다. 이진 검색을 수행 할 수 있도록 또는 해시 세트 인 경우에 정렬 될 수 있습니다. 명시적인 중첩 루프가 없으면 다음과 같습니다.

for a in A:
  if a in B:  # might implicitly loop
    if a in C:  # might implicitly loop
      return False
return True

또는 발전기 기반 버전에서 :

AB = (a for a in A if a in B)
ABC = (a for a in AB if a in C)
for a in ABC:
  return False
return True


답변

가정 된 각 목록에서 모든 요소가 다른 경우 A의 각 요소에 대해 C를 한 번만 반복 할 수 있습니다 (B에 요소가 같은 경우). 따라서 내부 루프는 총 O (n ^ 2)입니다.


답변

개별 시퀀스에 중복이 포함되어 있지 않다고 가정합니다.

매우 중요한 정보입니다.

그렇지 않으면 A와 B가 같고 n 번 복제 된 하나의 요소를 포함하는 경우 최적화 된 버전의 최악의 경우는 여전히 O (n³)입니다.

i = 0
def disjoint(A, B, C):
    global i
    for a in A:
        for b in B:
            if a == b:
                for c in C:
                    i+=1
                    print(i)
                    if a == c:
                        return False
    return True

print(disjoint([1] * 10, [1] * 10, [2] * 10))

어떤 출력 :

...
...
...
993
994
995
996
997
998
999
1000
True

따라서 기본적으로 저자는 O (n³) 최악의 경우는 발생하지 않아야한다고 가정하고 (이유는 무엇입니까), 최악의 경우는 O (n²)임을 “증명”합니다.

실제 최적화는 O (1)의 포함을 테스트하기 위해 세트 또는 dicts를 사용하는 것입니다. 이 경우 disjoint모든 입력에 대해 O (n)이됩니다.


답변

책에서 사용하는 용어를 사용하려면 :

나는 검사 a == b가 최악의 경우 O (n 2 ) 라는 것을 이해하는 데 아무런 문제가 없다고 생각합니다 .

이제 세 번째 루프의 최악의 경우 모든 aA일치하는가 B있으므로 세 번째 루프는 매번 호출됩니다. 에 a존재하지 않는 경우 C전체 C세트를 통해 실행됩니다 .

즉, 1 회 a, 1 회 c또는 n * n입니다. O (n 2 )

그래서 당신의 책이 지적 하는 O (n 2 ) + O (n 2 )가 있습니다.


답변

최적화 된 방법의 비결은 모서리를 자르는 것입니다. a와 b가 일치하는 경우에만 c에 대한 가치가 있습니다. 이제 최악의 경우에도 각 c를 평가해야한다는 것을 알 수 있습니다. 사실이 아닙니다.

아마도 최악의 경우는 a == b에 대한 모든 검사가 일치를 반환하기 때문에 a == b에 대한 모든 검사 결과가 C를 초과하는 결과가된다는 것입니다. 그러나 이에 대한 조건이 모순되기 때문에 불가능합니다. 이것이 작동하려면 동일한 값을 포함하는 A와 B가 필요합니다. 그것들은 다르게 주문 될 수 있지만 A의 각 값은 B의 일치 값을 가져야합니다.

이제 키커가 있습니다. 이러한 값을 구성 할 방법이 없으므로 각 a에 대해 일치하는 항목을 찾기 전에 모든 b를 평가해야합니다.

A: 1 2 3 4 5
B: 1 2 3 4 5

일치하는 1이 두 시리즈의 첫 번째 요소이므로 즉시 수행됩니다. 는 어때

A: 1 2 3 4 5
B: 5 4 3 2 1

그것은 A에 대한 첫 번째 실행에 효과적입니다 .B의 마지막 요소만이 히트를 산출합니다. 그러나 B의 마지막 스팟이 이미 1을 점유하고 있기 때문에 A에 대한 다음 반복은 이미 더 빨라야합니다. 그리고 이것은 매번 반복 할 때마다 조금 나아집니다.

이제 저는 수학자가 아니므로 이것이 O (n2)로 끝나는 것을 증명할 수는 없지만 나막신에서 느낄 수 있습니다.


답변

처음에는 당황했지만 Amon의 대답은 정말 도움이됩니다. 정말 간결한 버전을 사용할 수 있는지 확인하고 싶습니다.

ain 의 지정된 값에 A대해이 함수는 a가능한 모든 bin 과 비교 하고 B한 번만 수행합니다. 따라서 주어진 시간에 정확히 시간을 a수행 a == b합니다 n.

B그래서 주어진 위해, (목록 중 어느 것도하지 않는다) 모든 중복을 포함하지 않는 a있을 것입니다 에서 가장 일 개 일치합니다. (이것이 열쇠입니다). 일치하는 부분 a이있을 경우 가능한 모든 항목과 비교됩니다. c즉, a == c정확히 n 번 수행됩니다. 일치 a == c하지 않는 곳은 전혀 없습니다 .

따라서 주어진 a에 대해 n비교 또는 2n비교가 있습니다. 이것은 모든 경우에 발생 a하므로 가능한 가장 좋은 경우는 (n²)이고 최악의 경우는 (2n²)입니다.

TLDR : 모든 값 a의 모든 값과 비교됩니다 b및 모든 값에 대해 c아니지만 모든에 대하여, 조합bc. 두 가지 문제가 더해 지지만 배가되지는 않습니다.


답변

이런 식으로 생각하면 일부 숫자는 두 개 또는 세 개의 시퀀스에있을 수 있지만 일반적인 경우는 세트 A의 각 요소에 대해 철저한 검색이 b에서 수행된다는 것입니다. 세트 A의 모든 요소가 반복되는 것이 보장되지만 세트 b의 요소 중 절반 미만이 반복된다는 것을 암시합니다.

세트 b의 요소가 반복되면 일치하는 경우 반복이 발생합니다. 이는이 분리 함수의 평균 경우는 O (n2)이지만 절대 최악의 경우는 O (n3) 일 수 있음을 의미합니다. 책이 자세하게 설명되지 않았다면 아마도 평균적인 사례가 될 것입니다.