2010-04-12 5 views
27

을 계산하는 방법, 나는 이미 확인했다 :은 더 복잡한 알고리즘을 위해서 (큰 O) (예를 들어 퀵) 나는 큰 O 표기법에 대한 질문이 꽤 무리가 알고

.

내가 "직관"으로 알고 n, n^2, n! 및 정도를 계산하는 방법, 그러나 나는 완전히 log n, n log n, n log log n 등이다 알고리즘을 계산하는 방법에 손실입니다.

즉, 빠른 정렬은 n log n (평균)입니다.하지만 은 왜입니까? 병합/빗 등에 대해서도 마찬가지입니다.

아무도 나에게 설명하지 못했습니다. 수학적이지 않은 방식으로 어떻게 계산합니까?

큰 이유는 내가 큰 인터뷰를 할 예정이며, 나는 그들이 이런 종류의 물건을 요구할 것이라고 확신합니다. 나는 지금 며칠 동안 연구를했는데 모두가 버블 정렬이 왜 n^2인지 또는 읽을 수없는 설명 (나를 위해)에 대한 설명을 가지고있는 것 같다. Wikipedia

답변

38

대수는 지수 연산의 역 연산입니다. 지수화의 예는 각 단계에서 항목 수를 두 배로 늘리는 것입니다. 따라서 로그 알고리즘은 종종 각 단계에서 항목 수를 반으로 줄입니다. 예를 들어, 이진 검색은이 범주에 속합니다.

많은 알고리즘은 큰 단계의 로그 숫자가 필요하지만 각각의 큰 단계에는 O (n) 개의 작업 단위가 필요합니다. Mergesort가이 범주에 속합니다.

일반적으로 균형 이진 트리로 시각화하여 이러한 종류의 문제를 식별 할 수 있습니다. 예를 들어, 병합 정렬은 다음과 같습니다.

6 2 0 4 1 3  7 5 
    2 6  0 4  1 3  5 7 
    0 2 4 6   1 3 5 7 
     0 1 2 3 4 5 6 7 

위쪽에는 트리의 잎이 입력됩니다. 알고리즘은 위의 두 노드를 정렬하여 새 노드를 만듭니다. 균형 이진 트리의 높이는 O (log n)이므로 O (log n) 큰 단계가 있습니다. 그러나 각각의 새로운 행을 만드는 데는 O (n) 작업이 필요합니다. O (log n) O (n)의 큰 단계는 각각 mergesort가 O (n log n)라는 것을 의미합니다.

일반적으로 O (log n) 알고리즘은 아래 함수와 같습니다. 그들은 각 단계에서 데이터의 절반을 폐기합니다.

def function(data, n): 
    if n <= constant: 
     return do_simple_case(data, n) 
    if some_condition(): 
     function(data[:n/2], n/2) # Recurse on first half of data 
    else: 
     function(data[n/2:], n - n/2) # Recurse on second half of data 

O (n log n) 알고리즘은 아래 기능과 유사합니다. 그들은 또한 데이터를 절반으로 나누었지만 두 가지 모두를 고려해야합니다.

def function(data, n): 
    if n <= constant: 
     return do_simple_case(data, n) 
    part1 = function(data[n/2:], n/2)  # Recurse on first half of data 
    part2 = function(data[:n/2], n - n/2) # Recurse on second half of data 
    return combine(part1, part2) 

여기서 do_simple_case()는 O (1) 시간이 소요되고 combine()는 O (n) 시간을 넘지 않습니다.

알고리즘은 데이터를 정확하게 반으로 분할 할 필요가 없습니다. 그들은 그것을 3 분의 1과 2/3로 나눌 수 있습니다. 그러면 괜찮을 것입니다. 평균 - 케이스 성능의 경우 평균으로 절반으로 분할하는 것으로 충분합니다 (예 : QuickSort). 재귀가 (n/무언가)와 (n - n/무언가)의 조각에 대해 수행되는 한, 괜찮습니다. 만약 그것을 (k)와 (n-k)로 나누면 트리의 높이는 O (n)이 될 것이고 O (log n)는 아닌 것입니다.

+4

나는 당신의 설명이 정말 마음에 든다. 이유와 이해 방법을 쉽게 이해할 수있게 해준 덕분이다. –

14

보통 반쪽의 알고리즘에 대해 로그 n을 요구할 수있다. 그것이 실행될 때마다 공간/시간. 좋은 예는 이진 알고리즘 (예 : 이진 검색)입니다. 왼쪽 또는 오른쪽 중 하나를 선택하면 검색중인 공간의 절반을 축으로 표시합니다. 반을 반복적으로하는 패턴은 log n입니다.

+2

예. CS에서'log'는 일반적으로 가정되는 기수 10 대신에 로그 * 밑 2 *를 의미합니다. * log n *은 n에 도달하기 위해 2를 올리려는 숫자를 의미합니다. 그래서 * log 8 *은 3입니다. * log 16 *은 4 등입니다 ... – Segfault

+11

실제로 이것은 다소 잘못된 것입니다. 로그는 일반적으로 기본 2를 참조하지만 큰 오 표기법의 관점에서는 중요하지 않습니다. log_k (n) = log_k (2) * log_2 (n)이므로 O (log_2 (n))는 O (log_k 이것은 기본 로그 수식의 변경을 단순화 한 것입니다 : log_k (a)/log_k (b) = log_b (a). 그러면 log_k (2)가 상수이기 때문에 분명히 큰 오는 동일합니다. – JSchlather

+5

@Segfault 더 중요한 점은, big-O 복잡성은 상수 요소를 고려하지 않고 log_e와 log_2의 차이는 단지 일정한 요소라는 것입니다. –

0

문제가 너무 단순해질 때까지 문제를 하위 문제로 분할 할 때 분할 및 정복 알고리즘을 적용 할 때 분할이 잘되면 각 하위 문제의 크기는 n/2 또는 그 당시. 이것은 대개 복잡성이 큰 log(n)의 기원입니다. O(log(n))은 분할이 잘 진행될 때 필요한 재귀 호출 수입니다.

+0

네,하지만 분열 및 정복 알고리즘은 일반적으로 n log (n)입니다. 문제를 더 작고 작은 비트로 나누는 동안 일반적으로 각 단계에서 수행해야하는 파티션의 길이가 n 시간 걸리는 작업이 있기 때문입니다. – JSchlather

+0

@ Liberalkid - 나는 그것이 옳다고 생각하지 않습니다.각 반복에서 수행되는 작업은 일반적으로 'n'과 아무런 관련이 없으며 일정한 처리량 일뿐입니다. Big-O 표기법에서는 상수가 무시되므로 이진 탐색과 같은 분할 및 정복 알고리즘은 O (log n)입니다. – keithjgrant

+0

이진 검색은 실제로 분단하고 정복하지 않으며, 더 많이 감소 및 정복이라고 불릴 것입니다. Mergesort, Quicksort, FFT 등의 알고리즘은 나누기와 정복입니다. 문제를 작은 하위 문제로 분해하고 해결하지 않으면 큰 문제를 해결하기 위해 이러한 솔루션을 사용하는 것이 아니라 실제로 나누고 정복하는 것이 아닙니다. – JSchlather

4

체크 아웃 "전화 번호부"예를 여기에 주어진 : What is a plain English explanation of "Big O" notation?

큰-O 모든 것을 기억에 대한 규모 : 데이터 세트가 성장함에 따라이 알고리즘은 얼마나 더 작업이 필요합니다?

O는 (로그 n)은 일반적으로 당신이

O (N 로그 n)는 당신이 O를 ​​수행하고 의미 (로그 n은 각각의 반복으로 반으로 (예를 들어, 이진 검색을) 데이터 집합을 줄일 수 있습니다 의미) 작업 데이터 세트의 항목

'O (n log log n)'은 의미가 없습니다. 그렇지 않으면 O (n log n)로 단순화됩니다.

+0

: D 임 꽤 짧은 "일반적으로"스 니펫은 "분석" n log log n에 대해 매우 유용 할 것입니다 ... 나는 그 알고리즘을 실제로 보지 못했지만 그 순서대로 우연히 발견됩니다. 내 검색에서 .. 분명히 한과 thorup의 예입니다 그 http://en.wikipedia.org/wiki/Sorting_algorithm –

+3

'O (로그 로그) 알고리즘이 존재하지 않습니다. 예 : http://portal.acm.org/citation.cfm?id=975984 –

+0

O (n log log n) 알고리즘은 일반적으로 로그 로그 n이 너무 작기 때문에 O (n)로 단순화됩니다. 예를 들어 , log log (2^64) = 6. –

6

일부 알고리즘의 경우 직관을 통해 실행 시간을 엄격히 제한하는 것은 불가능합니다 (예를 들어 O(n log log n) 실행 시간을 직감 할 수 없을 것이라고 생각합니다. 이제까지 당신을 기대하십시오).CLRS Introduction to Algorithms text에 손을 올려 볼 수 있다면 완전히 불투명하지 않고 적절하게 엄격한 점근 표기법을 매우 철저하게 처리 할 수 ​​있습니다.

알고리즘이 반복적이면 경계를 파생시키는 간단한 방법 중 하나는 반복을 작성한 다음 반복적으로 또는 Master Theorem 또는 다른 방법을 사용하여 해결하도록 설정하는 것입니다. 예를 들어, 매우 엄격 해지기를 원하지 않는다면, QuickSort의 실행 시간을 얻는 가장 쉬운 방법은 Master Theorem입니다. QuickSort는 어레이를 두 개의 상대적으로 동일한 서브 어레이로 파티셔닝합니다 (이는 매우 직관적이어야합니다. 이 O(n)), 그리고 그 두 subarrays에 대한 재귀 적으로 QuickSort를 호출. 그런 다음 T(n)이 실행 시간을 나타내도록하면 T(n) = 2T(n/2) + O(n)이되며 마스터 방법은 O(n log n)입니다.

+0

+1 큰 백색 책. (네, 심지어 대부분 녹색이라고 생각했습니다. 항상 TBWB가 될 것입니다.) –

+0

실제로 제 3 판이 최근에 발표되었으므로 지금은 대부분 파란색입니다. –

3

Mergesort가 n log n 인 이유를 직관적으로 분석하려고 시도하고, n log log n 알고리즘의 예를 나에게 줄 수있는 경우이를 통해 작업 할 수도 있습니다.

Mergesort는 요소 만 존재할 때까지 요소 목록을 반복적으로 분할 한 다음이 목록을 병합 할 때까지 작동하는 정렬 예제입니다. 이러한 각 병합의 기본 작업은 비교이며 각 병합에는 n 개의 비교가 필요합니다. 여기서 n은 결합 된 두 목록의 길이입니다. 이로부터 재발을 도출하고 쉽게 풀 수 있지만 그 방법은 피할 수 있습니다.

대신 Mergesort가 어떻게 동작하는지 고려해야합니다. 우리는 길이 1 인 n 개의 파티션이 생길 때까지 목록을 가져 와서 분할 한 다음 그 절반을 취하고 다시 분할 할 것입니다. 쉽게 볼 수 있기를 바랍니다 이 재귀는 log (n)을 n 개의 파티션으로 분할 할 때까지만 진행됩니다.

이 n 개의 파티션을 병합해야 할 필요가 생겼으므로 이제는 이들이 병합 된 후에 다시 길이 n의 목록이 생길 때까지 다음 레벨을 병합해야합니다. 이 과정의 간단한 예를 보려면 wikipedia의 그래픽을 참조하십시오. http://en.wikipedia.org/wiki/File:Merge_sort_algorithm_diagram.svg.

이제이 프로세스에 소요되는 시간을 고려해보십시오. 우리는 log (n) 레벨을 가지며 각 레벨에서 모든 목록을 병합해야합니다. 매번 n 개의 원소가 합쳐지기 때문에 각 레벨이 합쳐지는 데 n 시간이 걸립니다. 그런 다음 비교 연산을 가장 중요한 연산으로 사용하면 mergesort를 사용하여 배열을 정렬하는 데 n log (n) 시간이 걸리는 것을 쉽게 알 수 있습니다.

불명확하거나 어딘가로 건너 뛰면 알려주세요. 좀 더 자세한 정보를 얻으려고 할 수 있습니다.

두 번째 설명 :

내가 이것을 더 잘 설명 할 수 있을지 생각해 봅시다.

문제는 여러 개의 작은 목록으로 나뉘며 작은 목록은 정렬 된 원본 목록으로 돌아갈 때까지 병합되고 병합됩니다.

여러 가지 크기의 문제가있는 경우 먼저 n/2, n/2의 두 가지 크기 목록을 가지며 다음 단계에서는 크기 목록 네 개를 갖게됩니다. 8, n/8, n/8, n/8, n/8, n/8, n/4, n/4, n// 8 이것은 n/2^k가 1과 같을 때까지 계속됩니다 (각 세분은 길이를 2의 제곱으로 나눈 값입니다. 모든 길이가 4로 나눌 수는 없으므로 꽤 좋지 않을 것입니다). 이것은 2로 나누기 반복되며 2^(log_2 (n)) = n이므로 log_2 (n) 회까지 계속할 수 있으므로 2로 나누면 더 큰 크기의 목록이 생성됩니다.

이제 모든 레벨에서 n 개의 요소가 있으므로 각 레벨에 대해 merge는 선형 연산이므로 병합에는 n 시간이 걸리는 점에 유의해야합니다. log (n) 레벨의 재귀가 있으면이 선형 작업 log (n) 번을 수행하므로 실행 시간은 n log (n)이됩니다.

죄송합니다. 도움이되지 않는다면 죄송합니다.

+0

감사합니다, 나는 다른 종류의 알고리즘을 위해 이것을 할 수있는 방법을 제공하기 때문에 나는 당신의 설명을 좋아한다. 그러나이 부분에서 나는 다음과 같이 설명했다 : 그럼 n log (n) 시간이 걸릴 것이라고 쉽게 알 수있다. 비교 연산을 가장 중요한 연산으로 사용한다면 mergesort를 사용하십시오. 내게는보기가 쉽지 않았습니다 ..하지만 다른 사람들의 답변을 보완합니다. (로그 (n) 작업을 n 개의 요소 각각에 적용해야 할 때) ... 그 방법을 쉽게 볼 수 있습니다 "그것은 n * logn이됩니까? –

+0

최선을 다해 업데이트했습니다. – JSchlather

+0

위대한 설명, 고마워! –