이 두 가지는 매우 비슷해 보이며 거의 동일한 구조를 가지고 있습니다. 차이점은 무엇입니까? 각 작업에 대한 시간 복잡성은 무엇입니까?
답변
Heap은 상위 레벨의 요소가 하위 레벨의 요소보다 크거나 (최대 힙의 경우) 작게 (최소 힙의 경우) 보장하는 반면 BST는 순서 ( “왼쪽”에서 “오른쪽”으로)를 보장합니다. . 정렬 된 요소를 원하는 경우 BST를 사용하세요. by Dante는 괴짜가 아닙니다
힙은 findMin / findMax (O ( 1)), BST는 모든 발견에 적합하지만 (O (logN)). 두 구조 모두에 대해 Insert는 O (logN)입니다. findMin / findMax 만 신경 쓰는 경우 (예 : 우선 순위 관련) 힙을 사용하십시오. 모든 정렬은 BST로 진행됩니다.
댓글
- BST가 findMin & findMax stackoverflow에서 더 낫다고 생각합니다. .com / a / 27074221 / 764592
- 이것은 단지 통신 인 것 같습니다 오해에. 이진 트리는 Yeo가 가리키는 최소값과 최대 값을 찾기 위해 쉽게 수정할 수 있습니다. 이것은 실제로 힙의 제한 입니다. 유일한 효율적인 찾기는 최소 또는 최대입니다. 힙의 진정한 장점은 제가 설명했듯이 O (1) 평균 삽입 입니다. stackoverflow.com/a/29548834/895245
- 이 동영상 에 따르면 큰 값이 낮은 값의 하위 항목이 아닌 한 낮은 수준에서 더 큰 값을 가질 수 있습니다.
- 힙은 루트에서 리프로 정렬되고 BST는 왼쪽에서 오른쪽으로 정렬됩니다.
- 일정한 시간에 중앙값을 찾고 로그 시간의 중앙값을 제거하려면 어떻게해야합니까? 어떤 데이터 구조로 가야합니까? MinHeap 구현이 작동합니까? 제안 해주세요.
답변
요약
Type BST (*) Heap Insert average log(n) 1 Insert worst log(n) log(n) or n (***) Find any worst log(n) n Find max worst 1 (**) 1 Create worst n log(n) n Delete worst log(n) log(n)
이 테이블의 모든 평균 시간은 삽입을 제외하고 최악의 시간과 동일합니다.
-
*
:이 답변의 모든 곳에서 BST == Balanced BST, unbalanced는 점근 적으로 짜증이 나기 때문입니다. -
**
:이 답변에 설명 된 사소한 수정 사용 -
***
: 포인터 트리 힙의 경우log(n)
,n
동적 배열 힙용
BST보다 바이너리 힙의 장점
-
바이너리 힙에 대한 평균 삽입 시간은
O(1)
이며, BST는 . 이 는 힙의 킬러 기능입니다.O(1)
는 피보나치 힙 과 같이 상각 (강력)하고 Brodal 대기열 (점근 적이 지 않은 성능으로 인해 실용적이지 않을 수 있음) : https://stackoverflow.com/questions/30782636/are-fibonacci-heaps-or-brodal-queues-used-in-practice-anywhere -
이진 힙은 동적 배열 또는 포인터 기반 트리, BST에서만 효율적으로 구현할 수 있습니다. 포인터 기반 트리. 따라서 힙의 경우 가끔 크기 조정 대기 시간을 감당할 수있는 경우 공간 효율적인 배열 구현을 선택할 수 있습니다.
-
바이너리 힙 생성 은
O(n)
최악의 경우 이며, BST의 경우O(n log(n))
입니다.
바이너리 힙보다 BST의 장점
-
임의 요소 검색은
O(log(n))
. 이 는 BST의 킬러 기능입니다.힙의 경우 일반적으로,
O(1)
인 가장 큰 요소는 예외입니다.
BST에 비해 힙의 “거짓”이점
-
힙은 최대 값을 찾기위한
O(1)
, BSTO(log(n))
입니다.가장 큰 요소를 추적하기 위해 BST를 수정하고 해당 요소가 변경 될 수있을 때마다 업데이트하는 것이 사소하기 때문에 일반적인 오해입니다. 더 큰 스왑을 삽입 할 때 제거시 두 번째로 큰 요소를 찾습니다. https://stackoverflow.com/questions/7878622/can-we-use-binary-search-tree-to-simulate-heap-operation ( Yeo가 언급 ).
사실 이것은 BST에 비해 힙의 제한 입니다. 유일한 효율적인 검색은 가장 큰 요소에 대한 검색입니다.
평균 바이너리 힙 삽입은 O(1)
출처 :
- 종이 : http://i.stanford.edu/pub/cstr/reports/cs/tr/74/460/CS-TR-74-460.pdf
- WSU 슬라이드 : http://www.eecs.wsu.edu/~holder/courses/CptS223/spr09/slides/heaps.pdf
직관적 인 인수 :
- 하단 트리 수준은 최상위 수준보다 기하 급수적으로 더 많은 요소를 포함하므로 새 요소는 거의 하단에 위치합니다.
- 힙 삽입 하단에서 시작 , BST는 상단부터 시작해야 함
바이너리 힙에서 주어진 인덱스에서 값을 늘리는 것도 같은 이유로 O(1)
. 그러나 그렇게하려면 힙 작업에 대한 추가 인덱스를 최신 상태로 유지하는 것이 좋습니다. https://stackoverflow.com/questions/17009056/how-to-implement-ologn-decrease-key-operation-for-min-heap-based-priority-queu 예 Dijkstra를 위해. 추가 시간 비용없이 가능합니다.
실제 하드웨어에 대한 GCC C ++ 표준 라이브러리 삽입 벤치 마크
C ++ std::set
( 빨강-검정 나무 BST ) 및 ( 동적 배열 힙 ) 삽입하여 삽입 시간이 옳았는지 확인하면 다음과 같은 결과를 얻었습니다.
- 벤치 마크 코드
- 플롯 스크립트
- 플롯 데이터
- CPU가 장착 된 Lenovo ThinkPad P51 노트북의 Ubuntu 19.04, GCC 8.3.0에서 테스트 : Intel Core i7-7820HQ CPU (4 코어 / 8 스레드 , 2.90GHz 기본, 8MB 캐시), RAM : 2x Samsung M471A2K43BB1-CRC (2x 16GiB, 2400Mbps), SSD : Samsung MZVLB512HAJQ-000L7 (512GB, 3,000MB / s)
명확하게 :
-
heap insert t ime는 기본적으로 일정합니다.
동적 배열 크기 조정 지점을 명확하게 볼 수 있습니다. 위의 시스템 노이즈를 모두 볼 수 있도록 매 10k 삽입마다 평균을 내고 있기 때문에 이러한 피크는 실제로 표시된 것보다 약 10k 배 더 큽니다!
확대 된 그래프는 기본적으로 배열 크기 조정 지점 만 제외하고 거의 모든 삽입이 25 나노초 미만임을 보여줍니다.
-
BST는 로그입니다. 모든 삽입은 평균 힙 삽입보다 훨씬 느립니다.
-
BST 대 해시 맵 상세 분석 : https://stackoverflow.com/questions/18414579/what-data-structure-is-inside-stdmap-in-c/51945119#51945119
gem5의 GCC C ++ 표준 라이브러리 삽입 벤치 마크
gem5 는 전체 시스템 시뮬레이터이므로 m5 dumpstats
. 그래서 개별 삽입물의 타이밍을 추정하는 데 사용하려고했습니다.
해석 :
-
힙은 여전히 일정하지만 이제 몇 줄이 있고 각 높은 줄이 더 희박하다는 것을 더 자세히 알 수 있습니다. .
이것은 더 높고 더 높은 삽입에 대해 수행되는 메모리 액세스 대기 시간과 일치해야합니다.
-
TODO BST를 완전히 해석 할 수는 없습니다. 그렇게 대수적이고 다소 일정 해 보이지는 않습니다.
더욱 자세히 살펴보면 몇 가지 뚜렷한 선을 볼 수 있지만 그 선이 무엇을 나타내는 지 확실하지 않습니다. 상단 하단을 삽입하기 때문에 더 얇아 지나요?
aarch64 iv에서이 Buildroot 설정 으로 벤치마킹 id = “635e322f0e”>
HPI CPU .
BST는 어레이에서 효율적으로 구현할 수 없습니다.
힙 작업은 단일 트리 분기 만 위로 또는 아래로 버블 링하면되므로 O(log(n))
최악의 경우 스왑, O(1)
평균입니다.
BST의 균형을 유지하려면 트리 회전이 필요하며, 이는 다른 요소의 상단 요소를 변경할 수 있으며 전체 배열을 이동해야합니다 (O(n)
).
힙은 배열에서 효율적으로 구현할 수 있습니다.
현재 색인에서 상위 및 하위 색인을 계산할 수 있습니다. 여기에 표시된 것처럼 .
BST와 같은 균형 조정 작업이 없습니다.
최소 삭제는 가장 걱정스러운 작업입니다. 하향식이어야합니다. 그러나 항상 여기에 설명 된대로 힙의 단일 분기를 “퍼콜 다운”하여 수행 할 수 있습니다. 힙이 항상 균형이 잘 잡혀 있기 때문에 O (log (n)) 최악의 경우가 발생합니다.
제거하는 모든 노드에 대해 단일 노드를 삽입하면 점근의 이점을 잃게됩니다. O (1) 힙이 제공하는 평균 삽입은 삭제가 지배적이며 BST를 사용할 수도 있습니다. 그러나 Dijkstra는 제거 할 때마다 노드를 여러 번 업데이트하므로 괜찮습니다.
동적 배열 힙 대 포인터 트리 힙
힙을 효율적으로 구현할 수 있습니다. 포인터 힙 위에 : https://stackoverflow.com/questions/19720438/is-it-possible-to-make-efficient-pointer-based-binary-heap-implementations
동적 배열 구현이 더 공간 효율적입니다. 각 힙 요소에 struct
에 대한 포인터 만 포함되어 있다고 가정합니다.
-
트리 구현은 각 요소에 대해 부모, 왼쪽 아이와 오른쪽 아이. 따라서 메모리 사용량은 항상
4n
입니다 (트리 포인터 3 개 +struct
포인터 1 개).트리 BST도 마찬가지입니다. 추가 균형 정보가 필요합니다. 검은 색-적색.
-
동적 배열 구현은 두 배가 된 직후에
2n
크기가 될 수 있습니다. 따라서 평균적으로1.5n
가됩니다.
반면 트리 힙은 최악의 경우 삽입이 더 좋습니다. 백업 동적 배열을 크기를 두 배로 복사하면 최악의 경우 O(n)
가 걸리지 만 트리 힙은 각 노드에 대해 새로운 작은 할당 만 수행하기 때문입니다.
그래도 백업은 배열 배가는 O(1)
상각되므로 최대 지연 시간을 고려합니다. 여기에서 언급 .
철학
-
BST는 상위 항목과 모든 하위 항목간에 전역 속성을 유지합니다 (왼쪽은 작게, 오른쪽은 크게).
BST의 최상위 노드는 중간 요소입니다. , 유지하려면 글로벌 지식이 필요합니다 (작고 큰 요소가 몇 개 있는지 아는 것).
이 글로벌 속성은 유지 관리 (log n 삽입) 비용이 더 많이 들지만 더 강력한 검색 (log n 검색)을 제공합니다. .
-
힙은 부모와 직계 자식 사이에 로컬 속성을 유지합니다 (부모> 자식).
힙의 가장 중요한 부분은 큰 요소입니다. 유지하기 위해 지역 지식 만 필요합니다 (부모를 아는 것).
BST 대 힙 대 해시 맵 비교 :
-
BST : 다음 중 하나가 합리적 일 수 있습니다.
- 정렬되지 않은 집합 (요소가 이전에 삽입되었는지 여부를 결정하는 구조). 하지만 해시 맵은 O (1) 분할 삽입으로 인해 더 나은 경향이 있습니다.
- 정렬 기계. 하지만 일반적으로 힙이 더 낫기 때문에 heapsort 가 트리 정렬 보다 훨씬 더 널리 알려져 있습니다. a>
-
heap : 단순한 정렬 기계입니다. 가장 작은 / 가장 큰 요소 만 빠르게 확인할 수 있기 때문에 효율적인 정렬되지 않은 집합이 될 수 없습니다.
-
해시 맵 : 효율적인 정렬 시스템이 아닌 정렬되지 않은 집합 만 가능합니다. 해싱이 순서를 뒤섞기 때문입니다.
이중 연결 목록
이중 연결 목록은 첫 번째 항목이 가장 우선 순위가 높은 힙의 하위 집합으로 볼 수 있으므로 여기에서도 비교해 보겠습니다.
- 삽입 :
- 위치 :
- 이중 연결 목록 : 삽입 된 항목은 해당 요소에 대한 포인터 만 있으므로 첫 번째 또는 마지막이어야합니다.
- 바이너리 힙 : 삽입 된 항목 모든 위치에서 끝날 수 있습니다. 연결 목록보다 덜 제한적입니다.
- 시간 :
- 이중 연결 목록 :
O(1)
항목에 대한 포인터가 있고 업데이트가 정말 간단하기 때문에 최악의 경우 - 바이너리 힙 :
O(1)
평균, 따라서 연결 목록보다 나쁩니다. 더 일반적인 삽입 위치가 있습니다.
- 이중 연결 목록 :
- 위치 :
- 검색 :
O(n)
모두
사용 사례는 힙의 키가 현재 타임 스탬프 인 경우입니다.이 경우 새 항목은 항상 목록의 시작 부분으로 이동합니다. 따라서 정확한 타임 스탬프를 모두 잊어 버리고 목록의 위치를 우선 순위로 유지할 수 있습니다.
이는 LRU 캐시를 구현하는 데 사용할 수 있습니다. . Dijkstra와 같은 힙 애플리케이션 과 마찬가지로, 신속하게 업데이트 할 노드를 찾기 위해 키에서 목록의 해당 노드까지 추가 해시 맵을 유지해야합니다. .
다양한 Balanced BST의 비교
점근 삽입 및 찾기 지금까지 보았던 “균형 BST”로 일반적으로 분류되는 모든 데이터 구조의 시간은 동일하고 서로 다른 BBST는 서로 다른 장단점이 있습니다. 아직 완전히 연구하지는 않았지만 요약하는 것이 좋습니다. 이러한 절충점은 다음과 같습니다.
- 빨강-검정 나무 . 2019 년 현재 가장 일반적으로 사용되는 BBST 인 것으로 보입니다. GCC 8.3.0 C ++ 구현
- AVL 트리 에서 사용되는 것입니다. BST보다 약간 더 균형 잡힌 것처럼 보이므로 약간 더 비싼 찾기 비용으로 찾기 지연 시간이 더 나을 수 있습니다.Wiki는 다음과 같이 요약합니다. “둘 다 동일한 작업 집합을 지원하고 기본 작업에 [동일한] 시간이 걸리기 때문에 AVL 트리는 종종 빨강-검정 트리와 비교됩니다. 조회 집약적 인 애플리케이션의 경우 AVL 트리가 빨강-검정 트리보다 빠릅니다. 그들은 더 엄격하게 균형을 이룹니다. 빨강-검정 나무와 유사하게 AVL 나무는 높이 균형이 잡혀 있습니다. 일반적으로 두 가지 모두 mu < 1 / 2; 즉, 형제 노드는 매우 다른 수의 하위 항목을 가질 수 있습니다. “
- WAVL . 원본 문서 는 재조정 및 회전 작업에 대한 경계 측면에서 해당 버전의 장점을 언급합니다.
참조
CS에 대한 유사한 질문 : 무엇 ‘ 이진 검색 트리와 이진 힙의 차이점은 무엇입니까?
댓글
- 좋은 답변 . 힙의 일반적인 응용 프로그램은 중앙값, k min, 상위 k 요소입니다. 이러한 가장 일반적인 작업의 경우 min을 제거한 다음 삽입합니다 (일반적으로 순수 삽입 작업이 거의없는 작은 힙이 있음). 따라서 실제로는 이러한 알고리즘의 경우 BST를 능가하지 않습니다.
- 예외 답변 !!! deque를 기본 힙 구조로 사용하면 크기 조정 시간을 크게 줄일 수 있지만 청크에 대한 포인터 배열을 (더 작은) 재 할당해야하므로 최악의 경우가 여전히 O (n)입니다.
답변
바이너리 검색 트리 및 바이너리 힙 은 트리 기반 데이터 구조입니다.
힙은 노드가 자식보다 우선 순위를 갖도록 요구합니다. 최대 힙에서 각 노드의 하위는 자체보다 작아야합니다. 이는 최소 힙의 반대입니다.
바이너리 검색 트리 (BST)는 형제 노드간에 특정 순서 (선주문, 순서, 후 주문)를 따릅니다. 트리는 반드시 정렬 :
BST는 평균 $ O (\ log n) $ 삽입, 삭제 및 검색.
바이너리 힙은 평균 $ O (1) $ findMin / findMax 및 $ O (\ log n) $ 삽입 및 삭제
댓글
- @FrankW Extraction is $ O (\ log n) $, no?
Answer
데이터 구조를 사용하여 관심 수준을 구분해야합니다.
-
이 q의 추상 데이터 구조 (저장된 개체, 해당 작업) 사용이 다릅니다. 하나는 우선 순위 큐를 구현하고 다른 하나는 세트를 구현합니다. 우선 순위 큐는 임의의 요소를 찾는 데 관심이 없으며 우선 순위가 가장 큰 요소 만 찾습니다.
-
구조의 구체적인 구현 . 첫눈에 둘 다 (이진) 나무이지만 구조적 특성이 다릅니다. 키의 상대적인 순서와 가능한 전역 구조가 모두 다릅니다. (다소 부정확합니다.
BST
키는 왼쪽에서 오른쪽으로 정렬되고 힙에서는 하향식으로 정렬됩니다.) IPlant가 올바르게 설명하므로 힙도 “완전”해야합니다. . -
낮은 수준 구현 에는 마지막 차이가 있습니다. (불균형) 이진 검색 트리에는 포인터를 사용하는 표준 구현이 있습니다. 반대로 바이너리 힙은 배열을 사용하여 효율적으로 구현합니다 (정확히 제한된 구조로 인해).
Answer
이전 답변 위에 힙에는 힙 구조 속성이 있어야합니다. ; 트리는 가득 차 있어야하며 항상 가득 차있을 수없는 맨 아래 레이어는 간격없이 맨 왼쪽에서 맨 오른쪽까지 채워야합니다.