std :: map이 레드-블랙 트리로 구현 된 이유는 무엇입니까?


195

레드-블랙 트리std::map 로 구현 된 이유는 무엇 입니까?

몇 가지 균형 잡힌 이진 검색 트리 (BST)가 있습니다. 레드-블랙 트리를 선택할 때 디자인 트레이드 오프는 무엇입니까?


26
내가 본 모든 구현이 RB- 트리를 사용하지만 이것이 여전히 구현에 의존한다는 점에 유의하십시오.
토마스

3
@도마. 구현에 따라 다르므로 모든 구현에서 RB- 트리를 사용하는 이유는 무엇입니까?
Denis Gorodetskiy

1
STL 구현자가 스킵 목록 사용에 대해 생각했는지 알고 싶습니다.
Matthieu M.

2
C ++의 맵과 세트는 실제로 정렬 된 맵과 정렬 된 세트입니다. 해시 함수를 사용하여 구현되지 않습니다. 모든 쿼리는 걸릴 것 O(logn)이 아니라 O(1),하지만 값은 항상 정렬됩니다. C에서 시작 ++ 11 (내가 생각하는)가 unordered_mapunordered_set, 해시 함수를 사용하여 구현되는 그들이 분류되지 않은 반면, 대부분의 질의 및 운영이 가능하다 O(1)(평균적으로)
SomethingSomething

@Thomas는 사실이지만 실제로 흥미로운 것은 아닙니다. 표준은 특정 알고리즘 또는 알고리즘 세트를 염두에두고 복잡성을 보장합니다.
저스틴 메이 너스

답변:


126

아마도 가장 일반적인 두 가지 자체 밸런싱 트리 알고리즘은 Red-Black 트리AVL 트리 입니다. 삽입 / 업데이트 후 트리의 균형을 맞추기 위해 두 알고리즘 모두 트리의 노드가 회전되어 재 밸런싱을 수행하는 회전 개념을 사용합니다.

두 알고리즘 모두 삽입 / 삭제 작업은 O (log n)이지만, Red-Black tree 리 밸런싱 회전의 경우 회전은 O (1) 작업이며 AVL에서는 O (log n) 작업입니다. 재조정 단계의이 측면에서 레드-블랙 트리가 더 효율적이며 더 일반적으로 사용되는 가능한 이유 중 하나입니다.

Red-Black 트리는 Java 및 Microsoft .NET Framework의 제품을 포함하여 대부분의 컬렉션 라이브러리에서 사용됩니다.


54
레드-블랙 트리가 O (1) 시간 안에 트리를 수정할 수있는 것처럼 들리지만, 사실이 아닙니다. 트리 수정은 빨강-검정 및 AVL 트리 모두에 대해 O (log n)입니다. 메인 연산이 이미 O (log n)이기 때문에 트리 수정의 밸런싱 부분이 O (1)인지 O (log n)인지를 모의합니다. AVL 트리가 수행하는 약간의 추가 작업이 있더라도 트리의 균형이보다 엄격 해져 조회 속도가 약간 빨라집니다. 따라서 완벽하게 유효한 트레이드 오프이며 AVL 트리가 레드 블랙 트리보다 열등하지 않습니다.
necromancer

35
차이를 보려면 실제 런타임에 대한 복잡성을 넘어서야합니다. AVL 트리는 일반적으로 삽입 / 삭제보다 조회가 많은 경우 총 런타임이 낮습니다. 삽입 / 삭제가 더 많으면 RB 트리의 총 런타임이 줄어 듭니다. 중단이 발생하는 정확한 비율은 구현, 하드웨어 및 정확한 사용법에 대한 많은 세부 사항에 따라 달라 지지만, 라이브러리 작성자는 광범위한 사용 패턴을 지원해야하므로 교육적인 추측을해야합니다. AVL도 구현하기가 약간 어렵 기 때문에 AVL을 사용하면 입증 된 이점을 원할 수 있습니다.
Steve Jessop

6
RB 트리는 "기본 구현"이 아닙니다. 각 구현자는 구현을 선택합니다. 우리가 아는 한, 그들은 모든 그래서, RB 나무를 선택했습니다 아마도 이 성능 중 하나 또는 구현 / 유지 보수의 용이성을 위해. 내가 말했듯이 성능의 중단 점은 조회보다 삽입 / 삭제가 더 많다고 생각하지 않을 수도 있습니다. 단, 둘 사이의 비율이 RB가 AVL을 능가한다고 생각하는 수준보다 높다는 것입니다.
Steve Jessop

9
@Denis : 불행히도 숫자를 얻는 유일한 방법은 std::map구현 목록을 만들고 , 개발자를 추적하고, 결정에 사용한 기준을 물어 보는 것입니다.
Steve Jessop

4
균형 결정을 내리는 데 필요한 보조 정보를 저장하는 데 필요한 비용은 노드 당 비용입니다. 레드-블랙 트리는 색상을 표현하기 위해 1 비트가 필요합니다. AVL 트리에는 2 비트 이상이 필요합니다 (-1, 0 또는 1을 나타냄).
SJHowe

47

실제로 사용법에 따라 다릅니다. AVL 트리는 일반적으로 재조정의 회전이 더 많습니다. 따라서 응용 프로그램에 삽입 및 삭제 작업이 너무 많지 않지만 검색시 가중치가 큰 경우 AVL 트리가 적합합니다.

std::map Red-Black 트리는 노드 삽입 / 삭제 속도와 검색 속도 사이에서 적절한 균형을 유지하므로 Red-Black 트리를 사용합니다.


1
확실합니까 ??? 개인적으로 Red-Black 트리는 더 복잡하거나 결코 단순하지 않다고 생각합니다. Rd-Black 트리에서는 AVL보다 재조정이 덜 자주 발생합니다.
Eric Ouellet

1
@Eric 이론적으로 R / B 트리와 AVL 트리는 삽입 및 삭제를위한 복잡성 O (log n)을 갖습니다. 그러나 운영 비용의 큰 부분은 회전이며,이 두 트리는 서로 다릅니다. 다음을 참조하십시오 : discussion.fogcreek.com/joelonsoftware/… 인용 : "AVL 트리를 밸런싱하려면 O (log n) 회전이 필요할 수 있지만, 빨간색 검은 색 트리는 균형을 잡기 위해 최대 두 번의 회전이 필요합니다. 회전이 필요한 위치를 결정하려면 O (log n) 노드를 검사하십시오. " 이에 따라 내 의견을 편집했습니다.
webbertiger

27

AVL 트리의 최대 높이는 1.44logn이고 RB 트리의 최대 높이는 2logn입니다. AVL에 요소를 삽입하면 트리의 한 지점에서 균형이 다시 잡힐 수 있습니다. 리 밸런싱이 삽입을 완료합니다. 새 리프를 삽입 한 후에는 해당 리프의 상위 항목을 루트까지 또는 두 하위 트리의 깊이가 같은 지점까지 업데이트해야합니다. k 개의 노드를 업데이트해야 할 확률은 1 / 3 ^ k입니다. 재조정은 O (1)입니다. 요소를 제거하면 둘 이상의 재조정 (나무 깊이의 최대 절반)이 포함될 수 있습니다.

RB- 트리는 이진 검색 트리로 표시되는 차수 4의 B- 트리입니다. B- 트리의 4 노드는 동등한 BST에서 두 가지 수준으로 나타납니다. 최악의 경우 트리의 모든 노드는 2 노드이며 3 노드 체인 하나만 잎사귀로 만듭니다. 그 잎은 뿌리에서 2log 떨어진 거리에 있습니다.

루트에서 삽입 지점으로 내려 가면 4 개의 노드를 2 개의 노드로 변경하여 삽입으로 리프가 포화되지 않도록해야합니다. 삽입에서 돌아와서이 모든 노드는 4 노드를 올바르게 나타내는 지 확인해야합니다. 이것은 트리에서 내려갈 수도 있습니다. 글로벌 비용은 동일합니다. 무료 점심은 없습니다! 트리에서 요소를 제거하는 순서는 같습니다.

이 모든 나무들은 노드가 높이, 무게, 색상 등에 관한 정보를 가지고 있어야합니다. Splay 나무 만이 그러한 추가 정보가 없습니다. 그러나 대부분의 사람들은 구조의 난폭성 때문에 스플레이 트리를 두려워합니다!

마지막으로 트리는 노드에 가중치 정보를 전달하여 가중치 균형을 조정할 수 있습니다. 다양한 방식이 적용될 수 있습니다. 하위 트리에 다른 하위 트리의 요소 수의 3 배 이상이 포함되어 있으면 균형을 다시 조정해야합니다. 재조정은 단일 회전 또는 이중 회전을 통해 다시 수행됩니다. 이것은 최악의 경우 2.4logn을 의미합니다. 훨씬 더 좋은 비율 인 3 대신 2 배로 벗어날 수 있지만, 여기저기서 하위 트리의 1 %보다 약간 적은 균형을 유지하는 것을 의미 할 수 있습니다. 교활한!

어떤 종류의 나무가 가장 좋습니까? 확실히 AVL. 코드를 작성하는 것이 가장 간단하고 최악의 높이를 기록 할 수 있습니다. 1000000 개의 요소로 구성된 트리의 경우 AVL은 높이에 따라 높이 29, RB 40 및 가중치 균형이 36 또는 50입니다.

임의성, 추가 비율, 삭제 비율, 검색 등의 다른 변수가 많이 있습니다.


2
좋은 대답입니다. 그러나 AVL이 최고라면 표준 라이브러리가 std :: map을 RB 트리로 구현하는 이유는 무엇입니까?
Denis Gorodetskiy

14
나는 AVL 나무가 의심 할 여지없이 최고라고 동의하지 않는다. 높이는 낮지 만 빨강 / 검정 나무 (O (log n) 재조정 작업 대 O (1) 상각 된 재조정 작업)보다 재조정을 위해 더 많은 작업이 필요합니다. 플레이 트리는 훨씬 더 좋을 수 있으며 사람들이 두려워한다는 주장은 근거가 없습니다. 보편적 인 "최상의"트리 밸런싱 체계는 없습니다.
templatetypedef

거의 완벽한 답변. 왜 AVL이 최고라고 말했습니까? 그것은 단순히 틀렸고 그래서 대부분의 일반적인 구현은 Red-Black tree를 사용합니다. AVL을 선택하려면 읽기 오버 조작 비율이 상당히 높아야합니다. 또한 AVL은 RB보다 메모리 사용량이 약간 적습니다.
Eric Ouellet

대부분의 경우 AVL이 더 나은 경향이 있다는 데 동의합니다. 보통 나무를 삽입하는 것보다 더 자주 검색하기 때문입니다. RB 트리가 대부분 쓰기의 경우 약간의 이점이 있고 더 중요한 경우 읽기의 경우 약간의 단점이있는 경우 RB 트리가 더 나은 것으로 간주되는 이유는 무엇입니까? 당신이 찾은 것보다 더 많이 삽입 할 것이라고 정말로 믿습니까?
doug65536

25

위의 답변은 트리 대안 만 다루고 빨간색은 아마도 역사적인 이유로 남아있을 것입니다.

왜 해시 테이블이 아닌가?

유형은 <연산자 (비교) 만 트리의 키로 사용해야합니다. 그러나 해시 테이블에는 각 키 유형에 hash함수가 정의 되어 있어야합니다 . 일반적인 프로그래밍에서는 유형 요구 사항을 최소로 유지하는 것이 매우 중요하므로 다양한 유형 및 알고리즘과 함께 사용할 수 있습니다.

좋은 해시 테이블을 디자인하려면 사용될 컨텍스트에 대한 친밀한 지식이 필요합니다. 개방형 주소 지정 또는 연결된 체인을 사용해야합니까? 크기를 조정하기 전에 어떤 수준의 부하를 수용해야합니까? 충돌을 피하는 고가의 해시 또는 거칠고 빠른 해시를 사용해야합니까?

STL은 애플리케이션에 가장 적합한 선택을 예상 할 수 없으므로 기본값이 더 유연해야합니다. 나무는 "그냥 작동"하고 잘 확장됩니다.

(C ++ 11은을 사용하여 해시 테이블을 추가했습니다 unordered_map. 설명서 에서 이러한 많은 옵션을 구성하기 위해 정책을 설정해야한다는 것을 알 수 있습니다.)

다른 나무는 어떻습니까?

레드 블랙 트리는 BST와 달리 빠른 조회를 제공하고 자체 균형을 유지합니다. 다른 사용자는 자체 균형 AVL 트리보다 장점을 지적했습니다.

Alexander Stepanov (STL의 창시자)는 std::map다시 쓰면 레드-블랙 트리 대신 B * 트리를 사용할 것이라고 말했다 . 현대 메모리 캐시에 더 친숙하기 때문이다.

그 이후로 가장 큰 변화 중 하나는 캐시의 성장이었습니다. 캐시 미스는 비용이 많이 들기 때문에 참조의 지역성이 훨씬 중요합니다. 참조 지역성이 낮은 노드 기반 데이터 구조는 훨씬 덜 이해됩니다. 오늘 STL을 디자인하고 있다면 다른 컨테이너 세트가있을 것입니다. 예를 들어, 인 메모리 B *-트리는 연관 컨테이너를 구현하기 위해 레드-블랙 트리보다 훨씬 낫습니다. - 알렉산더 스테파노 프

지도는 항상 나무를 사용해야합니까?

또 다른 가능한 맵 구현은 정렬 된 벡터 (삽입 정렬) 및 이진 검색입니다. 이것은 자주 수정되지는 않지만 자주 쿼리되는 컨테이너에는 효과적입니다. 나는 종종 C에서이 작업을 수행 qsort하고 bsearch내장되어 있습니다.

지도를 사용해야합니까?

캐시 고려 사항은 학교에서 배운 상황 (예 : 목록의 중간에서 요소를 제거하는 경우)에 사용 std::list하거나 그 std::deque이상 사용하는 것이 거의 의미가 없음을 의미합니다 std:vector. 동일한 추론을 적용하면서 for 루프를 사용하여 목록을 선형 검색하는 것이 몇 가지 조회를 위해 맵을 작성하는 것보다 더 효율적이고 깔끔합니다.

물론 읽기 가능한 컨테이너를 선택하는 것이 일반적으로 성능보다 중요합니다.


3

2017-06-14 업데이트 : webbertiger는 댓글을 달고 답변을 편집합니다. 나는 그 대답이 이제 내 눈에 훨씬 나아 졌다는 것을 지적해야한다. 그러나 나는 추가 정보처럼 대답을 유지했습니다 ...

첫 번째 답변이 잘못되었다고 생각하기 때문에 (수정 : 더 이상 둘다는 아님) 세 번째 답변에는 잘못된 확인이 있습니다. 나는 물건을 명확히해야한다고 생각합니다 ...

가장 인기있는 2 개의 트리는 AVL과 Red Black (RB)입니다. 주요 차이점은 활용률에 있습니다.

  • AVL : 상담 비율 (읽기)이 조작 (수정)보다 큰 경우에 좋습니다. 메모리 풋 프린트는 RB보다 약간 적습니다 (컬러에 필요한 비트로 인해).
  • RB : 상담 (읽기)과 조작 (수정) 또는 상담에 대한 수정 사이에 균형이있는 일반적인 경우에 더 좋습니다. 레드-블랙 플래그 저장으로 인해 약간 더 큰 메모리 풋 프린트.

주요 차이점은 채색에서 비롯됩니다. RB 트리에서 AVL보다 재조정 작업이 적습니다. 색상을 사용하면 상대적으로 높은 비용이 드는 재조정 작업을 건너 뛰거나 짧게 할 수 있기 때문입니다. 채색으로 인해 RB 트리는 검은 색 노드 사이에 빨간 노드를 수용 할 수 있기 때문에 (~ 2 배 더 많은 가능성을 가짐) 검색 효율을 조금 떨어 뜨릴 수 있기 때문에 노드 레벨이 더 높습니다. 상수 (2x), O (log n)에 유지됩니다.

트리 수정에 대한 성능 적중 (유의 한) 대 트리에 대한 상담의 성능 적중 (거의 중요하지 않은)을 고려하면 일반적인 경우 AVL보다 RB를 선호하는 것이 당연합니다.


2

그것은 당신의 구현의 선택 일뿐입니다-그것들은 균형 잡힌 나무로 구현 될 수 있습니다. 다양한 선택은 모두 약간의 차이와 비슷합니다. 그러므로 어느 것이나 다른 것만 큼 좋습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.