[algorithm] 힙을 어떻게 복잡하게 만들 수 있습니까?
누군가 힙을 만드는 것이 어떻게 복잡성을 설명 할 수 있습니까?
항목을 힙 O(log n)
에 삽입하면 삽입이 n / 2 번 반복됩니다 (나머지는 나뭇잎이며 힙 속성을 위반할 수 없음). 따라서 이것은 복잡성이되어야한다는 것을 의미합니다 O(n log n)
.
다시 말해, 우리가 “힙화”하는 각 항목에 대해 지금까지 힙의 각 레벨 (로그 n 레벨)에 대해 한 번 필터링해야 할 가능성이 있습니다.
내가 무엇을 놓치고 있습니까?
답변
이 주제에 묻힌 몇 가지 질문이 있다고 생각합니다.
- O (n) 시간에
buildHeap
실행되도록 어떻게 구현 합니까? - 올바르게 구현했을 때 O (n) 시간에
buildHeap
실행되는 것을 어떻게 표시 합니까? - 힙 정렬 이 O (n log n)가 아닌 O (n) 시간에 실행되도록 동일한 논리가 작동하지 않는 이유는 무엇 입니까?
O (n) 시간에 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을 무시할 수 있습니다.
각 단계가 왜 작동하는지 확실하지 않은 경우 다음과 같이 프로세스에 대한 정당화가 있습니다.
- 항은 모두 양수이므로 유한 합은 무한 합보다 작아야합니다.
- 이 계열은 x = 1 / 2로 평가 된 검정력 계열과 같습니다 .
- 이 검정력 계열은 f (x) = 1 / (1-x) 에 대한 Taylor 계열의 도함수와 같습니다 (상수 ) .
- x = 1 / 2 는 해당 Taylor 계열의 수렴 간격 내에 있습니다.
- 따라서 Taylor 계열을 1 / (1-x)로 바꾸고 , 차별화하여 평가하여 무한 계열의 값을 찾을 수 있습니다.
무한 합은 정확히 n 이므로 유한 합은 더 크지 않으므로 O (n) 입니다.
힙 정렬에 O (n log 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
한다.
답변
분석이 정확합니다. 그러나 빡빡하지는 않습니다.
왜 힙을 만드는 것이 선형 작업인지 설명하는 것은 쉽지 않습니다. 더 잘 읽으십시오.
알고리즘에 대한 훌륭한 분석 은 여기에서 볼 수 있습니다 .
주요 아이디어는 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)
입니다.
답변
직관적으로 :
“복잡성이 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)
답변
힙을 구축하는 동안 상향식 접근 방식을 사용한다고 가정 해 봅시다.
- 각 요소를 가져 와서 하위 요소와 비교하여 쌍이 힙 규칙을 준수하는지 확인하십시오. 따라서 잎은 힙에 무료로 포함됩니다. 아이들이 없기 때문입니다.
- 위로 올라가면 나뭇잎 바로 위의 노드에 대한 최악의 시나리오는 1 비교입니다 (최대 한 세대의 어린이와 비교할 때).
- 더 나아가서 직계 부모는 최대 두 세대의 자녀와 비교할 수 있습니다.
- 같은 방향으로 계속하면 최악의 시나리오에서 루트에 대한 log (n) 비교가 이루어집니다. 직계 자녀에 대해서는 log (n) -1, 직계 자녀에 대해서는 log (n) -2 등.
- 요약하자면 log (n) + {log (n) -1} * 2 + {log (n) -2} * 4 + ….. + 1 * 2 ^ {( logn) -1} 이것은 O (n)에 지나지 않습니다.