나는 당신이 O (log n) 알고리즘을 처음 볼 때 꽤 이상하다는 것에 동의해야합니다 ... 도대체 그 로그는 어디에서 왔습니까? 그러나 로그 용어를 big-O 표기법으로 표시하는 방법에는 여러 가지가 있습니다. 다음은 몇 가지입니다.
상수로 반복적으로 나누기
임의의 수 n을 취하십시오. 16. 1보다 작거나 같은 숫자를 얻기 전에 n을 2로 몇 번 나눌 수 있습니까? 16 명의 경우
16 / 2 = 8
8 / 2 = 4
4 / 2 = 2
2 / 2 = 1
완료하는 데 4 단계가 필요합니다. 흥미롭게도 log 2 16 = 4 도 있습니다 . 음 ... 128은 어떻습니까?
128 / 2 = 64
64 / 2 = 32
32 / 2 = 16
16 / 2 = 8
8 / 2 = 4
4 / 2 = 2
2 / 2 = 1
이 일곱 개 조치를 취했다 및 로그 2 128 = 7이 우연의 일치인가를? 아니! 이것에 대한 좋은 이유가 있습니다. 숫자 n을 2 i로 나눈다 고 가정합니다. 그런 다음 n / 2 i를 얻습니다 . 이 값이 최대 1 인 i의 값을 구하려면
n / 2 나는 ≤ 1
n ≤ 2 나는
로그 2 N ≤를 난
즉, i ≥ log 2 n 인 정수 i를 선택하면 n을 i로 반으로 나눈 값은 최대 1이됩니다. 이것이 보장되는 가장 작은 i는 대략 log 2입니다. n이므로 숫자가 충분히 작아 질 때까지 2로 나누는 알고리즘이 있으면 O (log n) 단계에서 종료된다고 말할 수 있습니다.
중요한 세부 사항은 n을 (1보다 큰 한) 어떤 상수로 나눌 것인지는 중요하지 않다는 것입니다. 상수 k로 나누면 log k가됩니다. 1에 도달하는 n 단계가 필요합니다. 따라서 입력 크기를 일부 분수로 반복적으로 나누는 알고리즘은 종료하려면 O (log n) 반복이 필요합니다. 이러한 반복은 많은 시간이 걸릴 수 있으므로 순 런타임은 O (log n) 일 필요는 없지만 단계 수는 대수입니다.
그래서 이것은 어디에서 나옵니까? 한 가지 고전적인 예는 정렬 된 배열에서 값을 검색하는 빠른 알고리즘 인 이진 검색 입니다. 알고리즘은 다음과 같이 작동합니다.
- 배열이 비어 있으면 해당 요소가 배열에 없음을 반환합니다.
- 그렇지 않으면:
- 배열의 중간 요소를보십시오.
- 우리가 찾고있는 요소와 같으면 성공을 반환합니다.
- 우리가 찾고있는 요소보다 큰 경우 :
- 우리가 찾고있는 요소보다 작은 경우 :
예를 들어 배열에서 5를 검색하려면
1 3 5 7 9 11 13
먼저 중간 요소를 살펴 보겠습니다.
1 3 5 7 9 11 13
^
7> 5이고 배열이 정렬 되었기 때문에 숫자 5가 배열의 뒷면에있을 수 없다는 사실을 알고 있으므로 그냥 버릴 수 있습니다. 이것은 떠난다
1 3 5
이제 여기서 중간 요소를 살펴 보겠습니다.
1 3 5
^
3 <5이므로 배열의 전반부에 5가 나타날 수 없다는 것을 알고 있으므로 전반 배열을 던져서 떠날 수 있습니다.
5
다시이 배열의 중간을 살펴 봅니다.
5
^
이것은 정확히 우리가 찾고있는 숫자이기 때문에 5가 실제로 배열에 있다고보고 할 수 있습니다.
그렇다면 이것이 얼마나 효율적입니까? 음, 반복 할 때마다 나머지 배열 요소의 절반 이상을 버립니다. 알고리즘은 배열이 비어 있거나 원하는 값을 찾는 즉시 중지됩니다. 최악의 경우 요소가 없기 때문에 요소가 부족할 때까지 배열 크기를 계속 절반으로 줄입니다. 얼마나 걸리나요? 음, 배열을 계속해서 반으로 자르기 때문에, 실행하기 전에 배열을 O (log n) 번보다 절반으로 잘라낼 수 없기 때문에 최대 O (log n) 반복으로 끝날 것입니다. 배열 요소에서.
나누고 정복 하는 일반적인 기법 (문제를 조각으로 자르고, 그 조각을 풀고, 문제를 다시 합치는 것)을 따르는 알고리즘 은 동일한 이유로 로그 용어를 갖는 경향이 있습니다. O (log n) 배의 절반 이상. 이것의 좋은 예로서 병합 정렬 을보고 싶을 것 입니다.
값을 한 번에 한 자리 씩 처리
10 진수 n은 몇 자리입니까? 음, 숫자에 k 자리가 있다면 가장 큰 자리는 10 k의 배수입니다 . 가장 큰 k 자리 수는 999 ... 9, k 번이고 이것은 10 k + 1-1 과 같습니다 . 따라서 n에 k 자리가 있다는 것을 알면 n의 값이 최대 10 k + 1-1. k를 n으로 풀고 싶다면
N ≤ 10 K + 1 - 1
n + 1 ≤ 10 k + 1
로그 10 (n + 1) ≤ k + 1
(로그 10 (n + 1))-1 ≤ k
여기서 k는 n의 밑이 10 인 로그라는 것을 알 수 있습니다. 즉, n의 자릿수는 O (log n)입니다.
예를 들어 너무 커서 기계어에 맞지 않는 두 개의 큰 숫자를 더하는 복잡성에 대해 생각해 봅시다. 10 진수로 표시된 숫자가 있고 숫자 m과 n을 호출한다고 가정합니다. 그것들을 추가하는 한 가지 방법은 초등학교 방법을 사용하는 것입니다. 한 번에 한 자리 씩 숫자를 쓰고 오른쪽에서 왼쪽으로 작업하십시오. 예를 들어 1337과 2065를 더하려면 먼저 숫자를 다음과 같이 작성합니다.
1 3 3 7
+ 2 0 6 5
==============
마지막 숫자를 더하고 1 :
1
1 3 3 7
+ 2 0 6 5
==============
2
그런 다음 두 번째에서 마지막 ( "두 번째") 숫자를 추가하고 1을 수행합니다.
1 1
1 3 3 7
+ 2 0 6 5
==============
0 2
다음으로 마지막에서 세 번째 ( "antepenultimate") 숫자를 추가합니다.
1 1
1 3 3 7
+ 2 0 6 5
==============
4 0 2
마지막으로 마지막에서 네 번째 숫자 ( "preantepenultimate"... I love English)를 추가합니다.
1 1
1 3 3 7
+ 2 0 6 5
==============
3 4 0 2
자, 우리는 얼마나 많은 일을 했습니까? 숫자 당 총 O (1) 작업 (즉, 일정한 작업량)을 수행하고 처리해야하는 총 숫자는 O (max {log n, log m})입니다. 두 숫자의 각 숫자를 방문해야하므로 총 O (max {log n, log m}) 복잡도를 제공합니다.
많은 알고리즘은 어떤 기준에서 한 번에 한 자릿수를 작동하여 O (log n) 항을 얻습니다. 고전적인 예는 정수를 한 번에 한 자리 씩 정렬하는 radix sort 입니다. 기수 정렬에는 많은 종류가 있지만 일반적으로 시간 O (n log U)에서 실행됩니다. 여기서 U는 정렬되는 가능한 가장 큰 정수입니다. 그 이유는 정렬의 각 패스에 O (n) 시간이 걸리고 정렬되는 가장 큰 숫자의 각 O (log U) 자릿수를 처리하는 데 총 O (log U) 반복이 필요하기 때문입니다. Gabow의 최단 경로 알고리즘 또는 Ford-Fulkerson max-flow 알고리즘 의 스케일링 버전 과 같은 많은 고급 알고리즘 은 한 번에 한 자릿수로 작동하기 때문에 복잡성에 로그 용어가 있습니다.
이 문제를 어떻게 해결하는지에 대한 두 번째 질문에 대해서는 더 고급 응용 프로그램을 탐색하는 이 관련 질문 을 살펴볼 수 있습니다 . 여기에 설명 된 문제의 일반적인 구조를 감안할 때 결과에 로그 용어가 있음을 알 때 문제에 대해 생각하는 방법을 더 잘 이해할 수 있으므로 답변을 제공 할 때까지 답을 보지 않는 것이 좋습니다. 약간의 생각.
도움이 되었기를 바랍니다!
O(log n)
다음과 같이 볼 수 있습니다. 문제 크기를 두 배로n
늘리면 알고리즘에 일정한 수의 단계 만 더 필요합니다.