어떤 자체 균형 이진 트리를 추천 하시겠습니까?


18

나는 Haskell을 배우고 운동으로 이진 트리를 만들고 있습니다. 정기적 인 이진 트리를 만들었으므로 자체 균형 조정에 맞게 조정하고 싶습니다. 그래서:

  • 어느 것이 가장 효율적인가요?
  • 어느 것이 가장 구현하기 쉬운가?
  • 어느 것이 가장 자주 사용됩니까?

그러나 결정적으로, 당신은 어느 것을 추천합니까?

토론이 가능하기 때문에 여기에 있다고 가정합니다.


효율성과 구현 용이성 측면에서 일반적인 효율성은 잘 정의되어 있지만 구현에있어 가장 좋은 점은 가능한 한 많은 것을 구현 한 다음 가장 적합한 방법을 알려주는 것입니다.
glenatron

답변:


15

Red-Black tree 또는 AVL tree로 시작하는 것이 좋습니다 .

레드-블랙 트리는 삽입 속도가 빠르지 만 AVL 트리는 조회를 위해 약간의 가장자리가 있습니다. AVL 트리는 구현하기가 다소 쉬울 수 있지만 내 경험을 바탕으로 한 것은 아닙니다.

AVL 트리는 각 삽입 또는 삭제 후 트리가 균형을 유지하도록합니다 (하위 트리는 1 / -1보다 큰 균형 계수를 갖지 않는 반면, 빨강-검정 트리는 트리가 항상 균형을 유지하도록합니다.


1
개인적으로, 저는 AVL 인서트보다 레드-블랙 인서트를 더 쉽게 발견합니다. 그 이유는 B- 트리와의 (불완전한) 비유를 통해서입니다. 삽입은 어리석지 만 삭제는 악하다 (많은 경우를 고려해야한다). 사실 나는 더 이상 내 자신의 C ++ 레드 블랙 삭제 구현을 가지고 있지 않습니다. (1) 절대 사용하지 않았을 때 삭제했습니다. 삭제 할 때마다 여러 항목을 삭제하고 있었으므로 트리에서 목록에서 삭제 한 다음 다시 트리로 변환하고 (2) 어쨌든 고장났습니다.
Steve314

2
@ Steve314, 빨강-검은 나무가 더 쉽지만 작동하는 구현을 할 수 없었습니까? 당시 AVL 트리는 무엇입니까?
dan_waterworth

@dan_waterworth-아직 작동하는 삽입 방법조차도 구현하지 않았습니다. 메모를하고 기본 원칙을 이해하지만 동기 부여, 시간 및 자신감의 올바른 조합을 얻지 못했습니다. 방금 작동하는 버전을 원한다면, 그것은 교과서에서 의사 코드를 복사하고 번역하는 것입니다 (그리고 C ++에는 표준 라이브러리 컨테이너가 있음을 잊지 마십시오).
Steve314

BTW-상당히 인기있는 교과서에 균형 잡힌 이진 트리 알고리즘 중 하나의 버그가있는 구현이 포함되어 있다고 생각합니다 (그러나 참조를 제공 할 수는 없습니다). 그래서 그것은 단지 나만이 아니다 ;-)
Steve314

1
@ Steve314, 나는 나무가 명령형 언어로 엄청나게 복잡 할 수 있지만 놀랍게도 Haskell에서 나무를 구현하는 것은 바람이 불었다. 주말 동안 일반 AVL 트리와 1D 공간 변형을 작성했으며 모두 약 60 줄입니다.
dan_waterworth

10

무작위 데이터 구조에 문제가 없다면 대안을 고려할 것입니다 : Skip Lists .

높은 수준의 관점에서 볼 때 트리 구조는 트리로 구현되지 않고 여러 계층의 링크가있는 목록으로 구현된다는 점을 제외하면 트리 구조입니다.

O (log N) 삽입 / 검색 / 삭제가 발생하므로 까다로운 재조정 사례를 모두 처리하지 않아도됩니다.

함수형 언어로 구현하는 것을 고려한 적이 없으며 wikipedia 페이지에 아무것도 표시되지 않으므로 쉽지 않을 수 있습니다 (불변)


나는 건너 뛰기 목록을 정말로 좋아하고 기능적 언어는 아니지만 이전에 구현했습니다. 나는 이것 후에 그들을 시도 할 것이라고 생각하지만, 지금은 자기 균형 나무에 있습니다.
dan_waterworth

또한 사람들은 종종 동시 데이터 구조에 스킵리스트를 사용합니다. 불변성을 강요하는 대신 haskell의 동시성 프리미티브 (MVar 또는 TVar와 같은)를 사용하는 것이 좋습니다. 그러나 이것은 기능 코드 작성에 대해 많이 가르쳐주지 않습니다.
dan_waterworth

2
@ Fanatic23, 건너 뛰기 목록은 ADT가 아닙니다. ADT는 집합이거나 연관 배열입니다.
dan_waterworth

@ dan_waterworth 내 나쁜, 당신은 맞습니다.
Fanatic23

5

상대적으로 쉬운 구조로 시작하려는 경우 (AVL 트리와 빨강-검정 트리 모두 미묘하게), 하나의 옵션은 "tree"와 "heap"의 조합으로 명명 된 treap입니다.

각 노드는 "우선 순위"값을 얻습니다. 종종 노드가 생성 될 때 무작위로 할당됩니다. 노드는 키 순서가 존중되고 힙과 같은 우선 순위 값의 순서가 존중되도록 트리에 배치됩니다. 힙과 같은 순서는 부모의 두 자녀가 부모보다 우선 순위가 낮다는 것을 의미합니다.

위의 "키 값 내"에서 삭제 된 편집 -우선 순위와 키 순서가 함께 적용되므로 고유 키의 경우에도 우선 순위가 중요합니다.

흥미로운 조합입니다. 키가 고유하고 우선 순위가 고유 한 경우 모든 노드 세트에 고유 한 트리 구조가 있습니다. 그럼에도 불구하고 삽입 및 삭제가 효율적입니다. 엄밀히 말하면 트리는 효과적으로 연결된 목록 인 지점까지 균형을 맞출 수 없지만 표준 이진 트리와 달리 순서대로 삽입 된 키와 같은 일반적인 경우를 포함하여 (표준 이진 트리와 같이) 극히 가능성이 적습니다.


1
+1. Treaps는 제 개인적 선택이며, 구현 방법에 대한 블로그 게시물작성했습니다 .
P Shved

5

어느 것이 가장 효율적인가요?

모호하고 대답하기 어렵다. 계산 복잡성은 모두 잘 정의되어 있습니다. 그것이 효율성이라는 의미라면 실제 토론은 없습니다. 실제로 모든 좋은 알고리즘에는 증거와 복잡성 요소가 있습니다.

"런타임"또는 "메모리 사용"을 의미하는 경우 실제 구현을 비교해야합니다. 그런 다음 언어, 런타임, OS 및 기타 요소가 작용하여 질문에 대답하기가 어렵습니다.

어느 것이 가장 구현하기 쉬운가?

모호하고 대답하기 어렵다. 일부 알고리즘은 복잡해 보이지만 나에게는 사소한 것입니다.

어느 것이 가장 자주 사용됩니까?

모호하고 대답하기 어렵다. 먼저 "누가?" 이것의 일부? 하스켈 만? C 또는 C ++는 어떻습니까? 둘째, 설문 조사를 위해 소스에 액세스 할 수없는 독점 소프트웨어 문제가 있습니다.

그러나 결정적으로, 당신은 어느 것을 추천합니까?

토론이 가능하기 때문에 여기에 있다고 가정합니다.

옳은. 다른 기준은 그다지 도움이되지 않기 때문에 이것이 전부입니다.

많은 수의 트리 알고리즘에 대한 소스를 얻을 수 있습니다. 무언가를 배우고 싶다면 찾을 수있는 모든 것을 간단하게 구현할 수 있습니다. "권장"을 요청하는 대신 찾을 수있는 모든 알고리즘을 수집하십시오.

목록은 다음과 같습니다.

http://en.wikipedia.org/wiki/Self-balancing_binary_search_tree

6 개의 인기있는 것들이 정의되어 있습니다. 그것들로 시작하십시오.


3

Splay 나무에 관심이 있다면 Allen과 Munroe의 논문에서 처음으로 묘사 된 것보다 간단한 버전이 있습니다. 동일한 성능 보장은 없지만 "zig-zig"와 "zig-zag"재조정을 처리 할 때 복잡성을 피할 수 있습니다.

기본적으로, 검색 할 때 (삽입 지점 또는 삭제할 노드 검색 포함) 찾은 노드는 루트쪽으로 바로 회전합니다 (예 : 재귀 검색 기능이 종료 됨). 각 단계에서 루트쪽으로 다른 단계를 끌어 올리려는 자식이 오른쪽 자식인지 왼쪽 자식인지에 따라 단일 왼쪽 또는 오른쪽 회전을 선택합니다 (회전 방향을 올바르게 기억하면 각각 다릅니다).

Splay 트리와 마찬가지로 최근에 액세스 한 항목은 항상 트리의 루트 근처에 있으므로 다시 빠르게 액세스 할 수 있습니다. 이 Allen-Munroe 회전식 루트 (더 이상 공식 이름을 모르는)는 더 간단 할 수 있지만 더 빠른 성능 보증을 제공하지는 않습니다.

한 가지-정의에 의한이 데이터 구조는 찾기 작업에도 영향을 미치기 때문에 아마도 모나드 방식으로 구현해야 할 것입니다. 기능 프로그래밍에는 적합하지 않을 수 있습니다.


스플레이는 찾을 때조차 트리를 수정하면 약간 성가시다. 이것은 다중 스레드 환경에서 매우 고통 스러울 것입니다. 이는 처음에 Haskell과 같은 기능적 언어를 사용하는 큰 동기 중 하나입니다. 그런 다음 다시 한 번도 기능적 언어를 사용한 적이 없으므로 아마도 이것이 아닙니다.
빠른 조 스미스

@Quick-트리 사용 방법에 따라 다릅니다. 실제 기능 스타일 코드에서 사용하는 경우 모든 찾기에서 돌연변이를 제거하거나 (Splay 트리를 약간 바보로 만들거나) 각 조회에서 이진 트리의 상당 부분을 복제하게됩니다. 작업이 진행됨에 따라 작업중인 트리 상태 (모 노드 스타일을 사용하는 이유)를 추적합니다. 새 트리가 작성된 후 이전 트리 상태를 더 이상 참조하지 않으면 (기능 프로그래밍에서 유사한 가정이 일반적 임) 컴파일러에서 해당 복사를 최적화 할 수 있지만 그렇지 않을 수도 있습니다.
Steve314

두 가지 방법 모두 노력할만한 가치가있는 것은 아닙니다. 그런 다음에도 순전히 기능적인 언어를 사용하지 마십시오.
빠른 조 스미스

1
@Quick-트리 복제는 삽입과 같은 알고리즘을 변경하기 위해 순수 기능 언어로 모든 트리 데이터 구조에 대해 수행하는 작업입니다. 소스 용어로, 코드는 전체 업데이트를 수행하는 명령형 코드와 다르지 않습니다. 불균형 이진 트리에 대한 차이점은 이미 처리되었습니다. 부모 링크를 노드에 추가하려고 시도하지 않는 한 복제본은 최소한 공통 하위 트리를 공유하며 Haskell의 심층 최적화는 완벽하지는 않지만 하드 코어입니다. 나는 원칙적으로 반 하스켈입니다. 그러나 이것이 반드시 문제는 아닙니다.
Steve314

2

아주 간단한 균형 잡힌 나무는 AA 나무 입니다. 불변은 더 간단하고 구현하기 쉽습니다. 단순성으로 인해 성능은 여전히 ​​좋습니다.

고급 연습으로 GADT 를 사용 하여 유형 시스템 유형에 따라 불변이 적용되는 균형 트리의 변형 중 하나를 구현할 수 있습니다 .

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