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


44

임의의 함수에 대해 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 차입니다.

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


6
책이 잘못되었거나 전사가 잘못되었습니다.
candied_orange

6
아니. 얼마나 잘 인용했는지에 관계없이 잘못되었습니다. 큰 O 분석을 수행 할 때 최악의 방법으로 진행할 수 있다고 생각하지 않거나 결과를 받아 들일 수없는 이유를 설명하십시오.
candied_orange

8
@candied_orange; 나는 최선을 다해 정당성을 강화했다. 나는 당신이 정말로 틀렸을 가능성을 다시 허락 해달라고 요청합니다. 당신은 당신의 요점을 정식으로 취했습니다.
SteveJ

8
난수는 최악의 경우가 아닙니다. 그것은 아무것도 증명하지 못한다.
Telastyn

7
아 괜찮아. 다음은 최악의 경우를 변경 않는다 "아무 순서는 중복 값이 없습니다"이후 C 수 죄송 어떤 A. 당 한 번 좌절에 대한 유일한 트리거 - 내가 늦게 토요일에 stackexchange에있는 무엇을 얻을 : D
Telastyn

답변:


63

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

이 책에서는 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

4
만약 우리가이 마법의 n변수를 폐지 하고 실제 변수에 대해 이야기 한다면 이것은 훨씬 더 명확 할 것 입니다.
알렉산더

15
@code_dredd 아니요, 코드에 직접 연결되어 있지 않습니다. len(a) == len(b) == len(c)시간 복잡도 분석의 맥락에서는 사실이지만 대화를 혼동하는 경향이 있는 것은 추상화입니다 .
Alexander

10
아마도 OP의 코드가 최악의 복잡성을 가지고 있다고 말할 수도 있습니다. O (| A | • | B | + min (| A |, | B |) • | C |)는 이해를 유발하기에 충분합니까?
Pablo H

3
타이밍 테스트에 대한 또 하나의 사실 : 알다시피, 진행 상황을 이해하는 데 도움이되지 않았습니다. 반면에, 그들은 그 책이 명백히 잘못되었다는 여러 가지 부정확하지만 강요된 주장에 견딜 수 있다는 추가적인 확신을 갖게 된 것 같습니다. 이해하기 위해보다 효과적인 테스트 방법은 각 루프의 엔트리에서 중단 점을 갖는 디버거에서 실행하거나 변수 값의 인쇄를 추가하는 것입니다.
sdenham

4
"타이밍은 알고리즘 복잡성의 유용한 지표가 아니라는 점에 유의하십시오." "유용한"보다는 "엄격한"또는 "신뢰할 수있는"이라고 말하면 이것이 더 정확할 것이라고 생각합니다.
누적

7

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


3

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

매우 중요한 정보입니다.

그렇지 않으면 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)이됩니다.


당신의 마지막 의견은 아주 흥미 롭습니다. 세 번의 O (n) 연산을 직렬로 수행 할 수 있기 때문이라고 제안하고 있습니까?
SteveJ

2
입력 요소 당 하나 이상의 버킷으로 완벽한 해시를 얻지 않으면 O (1)에 포함되는지 테스트 할 수 없습니다. 정렬 된 집합에는 일반적으로 O (log n) 조회가 있습니다. 평균 비용에 대해 이야기하지 않는 한, 그것은 질문에 관한 것이 아닙니다. 그럼에도 불구하고 균형 잡힌 이진 집합을 얻는 것이 어려운 O (n log n)를 얻는 것은 쉽지 않습니다.
Jan Dorniak

@ JanDorniak : 훌륭한 의견, 감사합니다. 이제는 조금 어색합니다. 저는 key in dict저자와 마찬가지로 최악의 경우를 무시했습니다 . :-/ 내 방어에서, 중복 값을 가진 목록을 만드는 것보다 n키와 n해시 충돌이 있는 dict을 찾는 것이 훨씬 어렵다고 생각 n합니다. 그리고 집합 또는 dict를 사용하면 실제로 중복 값이있을 수 없습니다. 최악의 경우는 실제로 O (n²)입니다. 답변을 업데이트하겠습니다.
Eric Duminil

2
@ JanDorniak 세트와 dicts는 C ++의 빨강-검정 나무와 달리 파이썬의 해시 테이블이라고 생각합니다. 따라서 절대 최악의 경우는 검색에 대해 0 (n)까지 나빠지지만 평균 경우는 O (1)입니다. C ++ wiki.python.org/moin/TimeComplexity의 O (log n)과는 반대로 . 파이썬 질문이고 문제의 영역이 평균 사례 성능의 가능성을 높인다는 점을 감안할 때 O (1) 주장이 나쁜 것으로 생각하지 않습니다.
볼드 릭

3
나는 여기서 문제를 본다고 생각한다. 저자들이 "우리는 어떤 개별 서열이 중복 된 값을 포함하지 않는다고 가정 할 것이다"라고 말할 때, 그것은 질문에 답하는 단계가 아니다. 오히려 문제가 해결 될 전제 조건입니다. 교육적 목적을 위해 이것은 무의미한 문제를 big-O에 대한 사람들의 직관에 도전하는 문제로 바꾸어 놓았으며, O (n²)가 잘못되어야한다고 강력하게 주장한 사람들의 수에 의해 판단하여 성공한 것 같습니다. .. 또한 여기에 문제가 있지만 한 예에서 걸음 수를 세는 것은 설명이 아닙니다.
sdenham

3

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

나는 검사 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 )가 있습니다.


0

최적화 된 방법의 비결은 모서리를 자르는 것입니다. 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)로 끝나는 것을 증명할 수는 없지만 나막신에서 느낄 수 있습니다.


1
요소의 순서는 여기서 역할을하지 않습니다. 중요한 요구 사항은 중복이 없다는 것입니다. 그러면 루프를 두 개의 개별 O(n^2)루프 로 변환 할 수 있다는 주장이 있습니다. 전체를 제공합니다 O(n^2)(상수는 무시됩니다).
AnoE

@AnoE 사실, 요소의 순서는 중요하지 않습니다. 정확히 내가 보여주는 것입니다.
마틴 마트

나는 당신이 무엇을하려고하는지, 그리고 당신이 쓰고있는 것이 틀린 것이 아니라는 것을 알지만, OP의 관점에서, 당신의 대답은 왜 특정한 생각의 기차가 관련이 없는지를 보여줍니다. 실제 솔루션에 도달하는 방법을 설명하지 않습니다. OP는 이것이 실제로 주문과 관련이 있다고 생각한다는 표시를하지 않는 것 같습니다. 따라서이 답변이 OP에 어떤 도움이되는지 불분명합니다.
AnoE

-1

처음에는 당황했지만 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. 두 가지 문제가 더해 지지만 배가되지는 않습니다.


-3

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

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


4
이 책은 O (n2)가 일반적인 경우가 아니라 최악의 경우라는 것을 분명히 알고 있습니다.
SteveJ

큰 O 표기법으로 함수에 대한 설명은 일반적으로 함수의 성장률에 대한 상한 만 제공합니다. 큰 O 표기법과 관련하여 점근선 성장률에 대한 다른 종류의 경계를 설명하기 위해 o, Ω, ω 및 Θ 기호를 사용하는 여러 관련 표기법이 있습니다. 위키 백과-Big O
-Big

5
"책이 자세하게 설명되지 않았다면 아마도 평균적인 사례가 답이 될 것입니다." – 음. 명시적인 자격이 없으면 일반적으로 RAM 모델의 최악의 단계 복잡성에 대해 이야기하고 있습니다. 데이터 구조에 대한 연산에 관해 이야기 할 때, 문맥에서 분명 하면 RAM 모델에서 상각 된 최악의 단계 복잡성에 대해 이야기하고 있을 수 있습니다 . 명시적인 자격이 없으면 일반적으로 최상의 경우, 평균 사례, 예상 사례, 시간 복잡성 또는 RAM을 제외한 다른 모델에 대해서는 이야기 하지 않습니다 .
Jörg W Mittag
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.