누군가 힙을 만드는 것이 어떻게 복잡성을 설명 할 수 있습니까?
항목을 힙 O(log n)
에 삽입하면 삽입이 n / 2 번 반복됩니다 (나머지는 나뭇잎이며 힙 속성을 위반할 수 없음). 따라서 이것은 복잡성이되어야한다는 것을 의미합니다 O(n log n)
.
다시 말해, 우리가 "힙화"하는 각 항목에 대해 지금까지 힙의 각 레벨 (로그 n 레벨)에 대해 한 번 필터링해야 할 가능성이 있습니다.
내가 무엇을 놓치고 있습니까?
누군가 힙을 만드는 것이 어떻게 복잡성을 설명 할 수 있습니까?
항목을 힙 O(log n)
에 삽입하면 삽입이 n / 2 번 반복됩니다 (나머지는 나뭇잎이며 힙 속성을 위반할 수 없음). 따라서 이것은 복잡성이되어야한다는 것을 의미합니다 O(n log n)
.
다시 말해, 우리가 "힙화"하는 각 항목에 대해 지금까지 힙의 각 레벨 (로그 n 레벨)에 대해 한 번 필터링해야 할 가능성이 있습니다.
내가 무엇을 놓치고 있습니까?
답변:
이 주제에 묻힌 몇 가지 질문이 있다고 생각합니다.
buildHeap
실행되도록 어떻게 구현 합니까?buildHeap
실행되는 것을 어떻게 표시 합니까?buildHeap
실행되도록 어떻게 구현 합니까?종종,이 질문에 대한 대답의 차이에 초점을 siftUp
하고 siftDown
. 사이의 올바른 선택을 siftUp
하고하는 것은 siftDown
얻는 것이 중요합니다 O (n)의 성능을 buildHeap
하지만, 도움의 일에 아무 차이 이해하지 않습니다 buildHeap
및 heapSort
일반적입니다. 사실, 모두의 적절한 구현 buildHeap
과 heapSort
것이다 에만 사용 siftDown
. siftUp
가 예를 들어, 이진 힙을 사용하여 우선 순위 큐를 구현하는 데 사용 될 수 있도록 운영은 기존 힙에 삽입을 수행하기 위해 필요합니다.
최대 힙 작동 방식을 설명하기 위해 이것을 작성했습니다. 이는 일반적으로 힙 정렬 또는 높은 값이 높은 우선 순위를 나타내는 우선 순위 큐에 사용되는 힙 유형입니다. 최소 힙도 유용합니다. 예를 들어, 정수 키가있는 항목을 오름차순으로 검색하거나 문자열을 알파벳 순서로 검색 할 때. 원칙은 동일합니다. 정렬 순서를 전환하기 만하면됩니다.
힙 속성 을 지정 이진 힙의 각 노드는 적어도 그 아이 모두 큰대로해야한다는. 특히 이것은 힙에서 가장 큰 항목이 루트에 있음을 의미합니다. 시프 팅 다운 및 시프 팅은 기본적으로 반대 방향으로 동일한 작업입니다.
siftDown
하위 노드가 너무 커질 때까지 하위 노드가 너무 큰 노드를 가장 큰 하위 노드로 바꿉니다 (아래로 이동). siftUp
위의 노드보다 크지 않을 때까지 상위 노드와 너무 큰 노드를 교체 (위로 이동)합니다. 연산의 수는 필요 siftDown
하고 siftUp
, 노드가 이동할 수있는 거리에 비례한다. 의 경우 siftDown
, 트리의 맨 아래까지의 거리이므로 트리 siftDown
의 맨 위에있는 노드의 경우 비용이 많이 듭니다. 을 사용 siftUp
하면 작업이 트리의 상단까지의 거리에 비례하므로 트리 siftUp
의 하단에있는 노드의 경우 비용이 많이 듭니다. 최악의 경우 두 작업이 모두 O (log n) 이지만 힙에서는 하나의 노드 만 맨 위에 있고 반면에 절반은 맨 아래 레이어에 있습니다. 따라서 모든 노드에 작업을 적용해야하는 경우 siftDown
over를 선호한다는 것은 놀라운 일이 아닙니다 siftUp
.
이 buildHeap
함수는 정렬되지 않은 항목의 배열을 가져 와서 모두 힙 특성을 충족 할 때까지 이동하여 유효한 힙을 생성합니다. 위에서 설명한 및 작업 을 buildHeap
사용하기 위해 취할 수있는 두 가지 방법이 있습니다 .siftUp
siftDown
힙 상단 (어레이의 시작)에서 시작 siftUp
하여 각 항목을 호출 합니다. 각 단계에서 이전에 선별 된 항목 (배열에서 현재 항목 이전의 항목)은 유효한 힙을 형성하고 다음 항목을 선별하면 힙에서 유효한 위치에 배치됩니다. 각 노드를 선별 한 후 모든 항목이 힙 특성을 충족시킵니다.
또는 반대 방향으로 이동하십시오. 어레이의 끝에서 시작하여 앞쪽으로 뒤로 이동하십시오. 반복 할 때마다 올바른 위치에 놓일 때까지 항목을 분류합니다.
buildHeap
이 더 효율적입니까?이 두 가지 솔루션 모두 유효한 힙을 생성합니다. 당연히 더 효율적인 방법은를 사용하는 두 번째 작업입니다 siftDown
.
하자 H = 로그를 N 힙의 높이를 나타냅니다. siftDown
접근에 필요한 작업 은 합계로 제공됩니다.
(0 * n/2) + (1 * n/4) + (2 * n/8) + ... + (h * 1).
합계의 각 항에는 주어진 높이에서 노드가 이동해야하는 최대 거리 (하단 레이어의 경우 0, 루트의 경우 h)에 해당 높이의 노드 수를 곱한 값이 있습니다. 반대로 siftUp
각 노드 에서 호출하는 합계 는
(h * n/2) + ((h-1) * n/4) + ((h-2)*n/8) + ... + (0 * 1).
두 번째 합계가 더 큽니다. 첫 번째 항만 hn / 2 = 1/2 n log n이므로이 방법은 최상의 O (n log n) 복잡성을 갖습니다 .
siftDown
접근법 의 합 이 실제로 O (n) 임을 어떻게 증명할 수 있습니까?한 가지 방법 (작동하는 다른 분석도 있음)은 유한 합계를 무한 계열로 변환 한 다음 Taylor 계열을 사용하는 것입니다. 첫 번째 용어 인 0을 무시할 수 있습니다.
각 단계가 왜 작동하는지 확실하지 않은 경우 다음과 같이 프로세스에 대한 정당화가 있습니다.
무한 합은 정확히 n 이므로 유한 합은 더 크지 않으므로 O (n) 입니다.
buildHeap
선형 시간 으로 실행할 수 있다면 왜 힙 정렬에 O (n log n) 시간이 필요합니까? 힙 정렬은 두 단계로 구성됩니다. 먼저 buildHeap
배열 을 호출 하는데, 최적으로 구현 된 경우 O (n) 시간 이 필요합니다 . 다음 단계는 힙에서 가장 큰 항목을 반복적으로 삭제하여 배열의 끝에 배치하는 것입니다. 힙에서 항목을 삭제하기 때문에 힙을 종료 한 직후에는 항목을 저장할 수있는 열린 지점이 항상 있습니다. 따라서 힙 정렬은 다음으로 큰 항목을 연속적으로 제거하고 마지막 위치에서 시작하여 앞쪽으로 이동하여 배열에 배치하여 정렬 된 순서를 달성합니다. 힙 정렬에서 지배하는 것은이 마지막 부분의 복잡성입니다. 루프는 이것을 좋아합니다 :
for (i = n - 1; i > 0; i--) {
arr[i] = deleteMax();
}
분명히 루프는 O (n) 번 실행됩니다 ( 정확하게하려면 n-1 , 마지막 항목은 이미 제자리에 있음). deleteMax
힙 의 복잡도 는 O (log n) 입니다. 일반적으로 루트 (힙에 남아있는 가장 큰 항목)를 제거하고 힙에있는 마지막 항목 (잎) 인 가장 작은 항목 중 하나로 대체하여 구현됩니다. 이 새로운 루트는 힙 속성을 거의 확실히 위반하므로 siftDown
다시 허용 가능한 위치로 이동할 때까지 호출해야합니다 . 이것은 또한 다음으로 큰 항목을 루트까지 이동시키는 효과가 있습니다. 트리의 맨 아래에서 buildHeap
호출하는 대부분의 노드의 위치 와 달리 siftDown
이제는 호출siftDown
각 반복의 트리의 상단에서!나무는 줄어들지 만 충분히 빨리 줄어들지 않습니다 . 나무의 높이는 노드의 전반부를 제거 할 때까지 (하단 레이어를 완전히 지울 때) 일정하게 유지됩니다. 그런 다음 다음 분기의 높이는 h-1 입니다. 이 두 번째 단계의 총 작업은
h*n/2 + (h-1)*n/4 + ... + 0 * 1.
스위치를 확인하십시오. 이제 제로 작업 사례는 단일 노드에 해당 하고 h 작업 사례는 노드의 절반에 해당합니다. 이 합은 siftUp을 사용하여 구현 되는 비효율적 인 버전과 마찬가지로 O (n log n)buildHeap
입니다. 그러나이 경우 정렬을 시도하고 있으므로 다음으로 큰 항목을 제거해야하므로 선택의 여지가 없습니다.
요약하면 힙 정렬 작업은 두 단계의 합입니다. buildHeap의 O (n) 시간과 O (n log n)는 각 노드를 순서대로 제거 하므로 복잡도는 O (n log n) 입니다. 당신은 (정보 이론의 일부 아이디어를 사용하여) 비교 기반 정렬의 경우 O (n log n) 이 어쨌든 기대할 수있는 최선임을 증명할 수 있으므로 이것에 실망하거나 힙 정렬을 기대할 이유가 없습니다. O (N) 시간은 결합 buildHeap
한다.
siftUp
각 항목을 호출 하거나 끝에서 뒤로 이동하여 호출을 시작 siftDown
합니다. 어떤 방법을 선택하든 배열의 정렬되지 않은 부분에서 다음 항목을 선택하고 적절한 작업을 수행하여 배열의 정렬 된 부분에서 유효한 위치로 이동합니다. 유일한 차이점은 성능입니다.
분석이 정확합니다. 그러나 빡빡하지는 않습니다.
왜 힙을 만드는 것이 선형 작업인지 설명하는 것은 쉽지 않습니다. 더 잘 읽으십시오.
알고리즘에 대한 훌륭한 분석 은 여기에서 볼 수 있습니다 .
주요 아이디어는 build_heap
알고리즘에서 실제 heapify
비용이 O(log n)
모든 요소에 대한 것은 아니라는 것 입니다.
언제 heapify
호출, 실행 시간은 요소가 프로세스 종료하기 전에 트리에서 아래로 이동하는 방법까지에 따라 달라집니다. 즉, 힙의 요소 높이에 따라 다릅니다. 최악의 경우 요소가 리프 수준까지 내려갈 수 있습니다.
작업을 레벨별로 계산합니다.
최하위 레벨에는 2^(h)
노드가 있지만 heapify
이들 중 어느 것도 호출하지 않으므로 작업은 0입니다. 레벨 옆에 2^(h − 1)
노드가 있으며 각 노드가 1 레벨 씩 아래로 이동할 수 있습니다. 바닥에서 3 층에는2^(h − 2)
노드가 있으며 각각 2 단계 씩 아래로 이동할 수 있습니다.
당신이 모든 heapify 작업을 볼 수있는 것은 아니지만 O(log n)
, 이것이 당신이 얻는 이유 O(n)
입니다.
i
높이 h의 나무 바닥 에서 높이에있는 노드에 대한 # 비교는 2* log(h-i)
비교를해야하며 @ The111도 고려해야합니다. 어떻게 생각해?
"복잡성이 O (nLog n)이어야합니다."우리가 "힙화"하는 각 항목에 대해 지금까지 힙의 각 레벨 (로그 n 레벨)에 대해 한 번 필터링해야 할 가능성이 있습니다. "
좀 빠지는. 로직은 엄격한 제한을 생성하지 않습니다. 각 힙 파이의 복잡성을 추정합니다. 상향식으로 제작 된 경우 삽입 (힙)이보다 작을 수 있습니다 O(log(n))
. 과정은 다음과 같습니다.
(단계 1) 제 n/2
요소 힙의 아랫 줄에 간다. h=0
따라서 heapify는 필요하지 않습니다.
(2 단계) 다음 요소는 맨 아래부터 1 행으로 이동합니다. , heapify 필터는 1 레벨 아래로 내려갑니다.n/22
h=1
(단계 i )
다음 요소 는 맨 아래부터 행으로 올라 갑니다 . , heapify는 필터 레벨을 낮 춥니 다.n/2i
i
h=i
i
(Step log (n) ) 마지막 요소는 맨 아래부터 위로 올라 갑니다 . , heapify는 필터 레벨을 낮 춥니 다.n/2log2(n) = 1
log(n)
h=log(n)
log(n)
주의 사항 : 1 단계 이후 1/2
에 요소 중 하나 (n/2)
가 이미 힙에 있으므로 heapify를 한 번 호출 할 필요조차 없습니다. 또한 루트라는 단일 요소 만 실제로 전체 log(n)
복잡성을 유발한다는 점에 유의하십시오 .
N
size의 힙을 빌드하는 총 단계 는 n
수학적으로 작성할 수 있습니다.
높이 i
에서 우리는 heapify를 호출 해야하는 요소가 있음을 보여 주었고 (위에서) heapify는 height 입니다 . 이것은 다음을 제공합니다.n/2i+1
i
O(i)
마지막 합에 대한 해는 잘 알려진 기하 계열 방정식의 양변의 도함수를 취하면 찾을 수 있습니다.
마지막으로 x = 1/2
위의 방정식에 꽂으면 됩니다 2
. 이것을 첫 번째 방정식에 꽂으면 다음과 같습니다.
따라서 총 단계 수는 크기입니다. O(n)
반복적으로 요소를 삽입하여 힙을 빌드하면 O (n log n)입니다. 그러나 임의의 순서로 요소를 삽입 한 다음 알고리즘을 적용하여 요소를 적절한 순서로 "힙니다"(물론 힙 유형에 따라 다름) 새 힙을보다 효율적으로 작성할 수 있습니다.
예는 http://en.wikipedia.org/wiki/Binary_heap , "힙 구축"을 참조하십시오 . 이 경우 기본적으로 트리의 맨 아래 레벨에서 작업하여 힙 조건이 충족 될 때까지 상위 및 하위 노드를 교체합니다.
이미 훌륭한 답변이 있지만 약간의 시각적 설명을 추가하고 싶습니다.
이제 이미지를 살펴보고, 거기에
n/2^1
녹색 노드 와 높이를 0 (여기 = 12 2분의 23)를
n/2^2
빨간색 노드 와 높이 1 (여기 = 6 4분의 23)
n/2^3
블루 노드 와 높이 2 (여기 8분의 23 = 3)
n/2^4
보라색 노드 와 높이 3 (여기서 = 2 16분의 23)
그래서 거기에 n/2^(h+1)
높이에 대한 노드 시간
의 시간 복잡도를 찾으려면이 셀 수 있습니다 작업 수행의 양 또는 반복의 최대없이 수행 각 노드를
지금은 발견 할 수있는 각 노드 캔 수행 (최대) 반복 == 노드 높이
Green = n/2^1 * 0 (no iterations since no children)
red = n/2^2 * 1 (heapify will perform atmost one swap for each red node)
blue = n/2^3 * 2 (heapify will perform atmost two swaps for each blue node)
purple = n/2^4 * 3 (heapify will perform atmost three swaps for each purple node)
따라서 높이 h가 있는 모든 노드의 최대 작업 수는 n / 2 ^ (h + 1) * h입니다.
이제 총 작업은
->(n/2^1 * 0) + (n/2^2 * 1)+ (n/2^3 * 2) + (n/2^4 * 3) +...+ (n/2^(h+1) * h)
-> n * ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) )
이제 h의 모든 값에 대해 시퀀스
-> ( 0 + 1/4 + 2/8 + 3/16 +...+ h/2^(h+1) )
따라서 1을 초과하지 않습니다. 따라서 시간 복잡도는힙을 구축하기 위해 O (n) 을초과하지 않습니다.
우리가 알고있는 바와 같이, 힙의 높이 인 로그 (N) N elements.Lets의 총 개수로 표현하고, 시간
후, 최종 레벨 (AT 요소 우리 heapify 작업을 수행 할 때 , h는 ) 심지어 단일 이동하지 단계.
제 마지막 레벨 (AT 요소 수 의 H-1 )은 2 H-1 그들은 최대로 이동할 수 1 개 (heapify시) 수준.
비슷하게, i th 의 레벨에는 hi 위치를 이동할 수 있는 2 개의 i 요소가 있습니다 .
그러므로 총 이동 횟수 = S = 2 h * 0 + 2 h-1 * 1 + 2 h-2 * 2 + ... 2 0 * h
S = 2 시간 {1/2 + 2/2 2 + 3/2 3 + ... h / 2 시간 } ----------------------- -------------------------- 1
이것은 2 S / 2 = 2 h {1/2 로이 나누기를 해결하기 위해 AGP 시리즈입니다. 2 + 2/2 3 h + 1 {1 / 2 + 1 / 2 2 + 1/2 3 + ... + 1/2 h + h / 2 h + 1 }
이제 1 / 2 + 1 / 2 2 + 1/2 3 + ... + 1/2 h 는 합이보다 작은 GP 를 감소시킵니다
+ ... h / 2 h + 1 } --------------------------------- ---------------- 2
식 감산 2 에서 1 주는
S / 2 = 2 H {1 / 2 + 1 / 2 2 + 2 3 + ... + 1 / 2 시간 + h / 2 시간 +1 }
S = 2 1 (h가 무한대 인 경우 합은 1이됩니다). 추가 분석에서 1의 합에 대한 상한을 취합시다.
이것은 S = 2 h + 1 {1 + h / 2 h + 1 } = 2 h + 1 + h ~ 2 h + h 를 h = log 로 나타 냅니다. (n) , 2 시간 = n
따라서 S = n + log (n)
T (C) = O (n)
힙을 구축하는 동안 상향식 접근 방식을 사용한다고 가정 해 봅시다.
힙을 빌드하는 경우, 높이 logn -1 에서 시작합니다 (여기서 logn은 n 요소의 트리 높이입니다). 높이 'h'에있는 각 요소에 대해 최대 up (logn -h) 높이로 내려갑니다.
So total number of traversal would be:-
T(n) = sigma((2^(logn-h))*h) where h varies from 1 to logn
T(n) = n((1/2)+(2/4)+(3/8)+.....+(logn/(2^logn)))
T(n) = n*(sigma(x/(2^x))) where x varies from 1 to logn
and according to the [sources][1]
function in the bracket approaches to 2 at infinity.
Hence T(n) ~ O(n)
@bcorso는 이미 복잡성 분석의 증거를 보여주었습니다. 그러나 여전히 복잡한 학습을 배우기 위해 이것을 추가해야합니다.
원래 실수의 기초는 "힙에 삽입하는 데 O (log n) 시간이 걸립니다"라는 문장의 의미를 잘못 해석했기 때문입니다. 힙에 삽입하는 것은 실제로 O (log n)이지만 , 삽입 중 n이 힙의 크기임을 인식해야 합니다 .
n 개의 객체를 힙에 삽입하는 맥락에서, i 번째 삽입의 복잡성은 O (log n_i)이며, 여기서 n_i는 삽입 i에서와 같이 힙의 크기입니다. 마지막 삽입에만 O (log n)의 복잡성이 있습니다.
힙에 N 개의 요소 가 있다고 가정합니다 . 그러면 높이는 Log (N)입니다.
이제 당신은 그 복잡성은 것, 다른 요소를 삽입 할 : 로그 (N)을 , 우리는 모든 방법을 비교해야 UP 루트를.
이제 N + 1 요소 가 있고 높이 = Log (N + 1)
유도 기법을 사용하면 삽입의 복잡성이 ∑logi 임을 증명할 수 있습니다 .
이제
로그 a + 로그 b = log ab
이것은 다음과 같이 단순화됩니다. ∑logi = log (n!)
실제로는 O (NlogN)입니다
그러나
우리는 모든 경우에 우리가 정상에 도달하지 않기 때문에 여기에서 뭔가 잘못하고 있습니다. 따라서 대부분의 시간을 실행하는 동안 우리는 트리의 절반을 넘지 않을 것입니다. 이 답변은 위의 답변에 제공된 수학을 사용하여 더 엄격한 바인딩을 갖도록 최적화 할 수 있습니다.
이 실현은 힙에 대한 세부 사항 및 실험 후에 나에게 왔습니다.
나는 Jeremy West의 설명을 정말로 좋아합니다 .... 이해하기 쉬운 또 다른 접근법은 여기에 있습니다 http://courses.washington.edu/css343/zander/NotesProbs/heapcomplexity
buildheap의 사용은 heapify에 의존하고 모든 노드의 높이의 합에 의존하는 shiftdown 접근 방식이 사용되기 때문에 사용됩니다. 따라서 S = i에서 0으로의 i = h에서 (2 ^ i * (hi))의 합으로 주어진 노드의 높이의 합을 찾으려면, 여기서 h = logn은 트리 풀기의 높이입니다. n = 2 ^ (h + 1)-1 s = n-h-1 = n-logn-1 s = O (n)이므로 s = 2 ^ (h + 1)-1-(h + 1) 따라서 buildheap의 복잡성은 O (n)입니다.
각 노드가 취할 수있는 최대 이동을 파악하여 힙 빌드에 대한 런타임을 얻습니다. 따라서 각 행에 몇 개의 노드가 있고 각 노드가 얼마나 멀리 떨어져 있는지 알아야합니다.
루트 노드에서 시작하여 다음 각 행은 이전 행보다 두 배의 노드를 가지므로 노드가 없을 때까지 얼마나 자주 노드 수를 두 배로 늘릴 수 있는지에 따라 트리의 높이를 얻습니다. 또는 수학적으로 트리의 높이는 log2 (n)이며 n은 배열의 길이입니다.
하나의 행에서 노드를 계산하여 뒤에서 시작하면 n / 2 개의 노드가 맨 아래에 있음을 알 수 있으므로 2로 나누어 이전 행을 얻습니다.
이를 바탕으로 (0 * n / 2) + (1 * n / 4) + (2 * n / 8) + ... + (log2 (n) * 1)에 대한 공식을 얻습니다.
마지막 마비의 용어는 루트에있는 하나의 노드에 트리의 높이를 곱한 값이며, 첫 번째 마비의 용어는 맨 아래 행의 모든 노드에 이동할 수있는 길이를 곱한 값입니다. 스마트에서 동일한 공식 :
n을 2로 가져 오면 2 * n, 2를 버릴 수 있습니다. 상수는 상수이므로 우리는 Siftdown 방식의 최악의 런타임을 갖습니다. n.
당신이 실수하고 있다고 생각합니다. 이것을보십시오 : http://golang.org/pkg/container/heap/ 힙을 구축하는 것은 O (n)이 아닙니다. 그러나 삽입은 O (lg (n)입니다. 힙 크기를 b / c로 설정하면 힙이 공간을 할당하고 데이터 구조를 설정 해야하는 경우 초기화가 O (n)이라고 가정합니다. 힙에 예, 각 삽입은 lg (n)이고 n 개의 항목이 있으므로 u * n (n)을 얻습니다.