C ++ STL이 "트리"컨테이너를 제공하지 않는 이유는 무엇입니까?


373

C ++ STL이 "트리"컨테이너를 제공하지 않는 이유는 무엇이며 대신 가장 좋은 방법은 무엇입니까?

트리를 성능 향상으로 사용하는 대신 객체 계층 구조를 트리로 저장하고 싶습니다 ...


7
계층의 표현을 저장하려면 트리가 필요합니다.
Roddy

20
나는 "올바른"답변을 투표 한 사람과 함께 있습니다. "나무는 쓸모가 없다". 나무를 잘 모르면 중요합니다.
Joe Soul-bringer

나는 그 이유가 사소한 것이라고 생각합니다. 아직 표준 라이브러리에서 아무도 구현하지 않았습니다. 그것은 표준 라이브러리가 없었고 최근까지 std::unordered_map와 같습니다 std::unordered_set. 그리고 그 전에 표준 라이브러리에는 STL 컨테이너가 전혀 없었습니다.
doc

1
내 생각 (관련 표준을 읽지 않았으므로 대답은 아닙니다) STL은 특정 데이터 구조에 신경 쓰지 않고 복잡성과 관련된 사양 및 지원되는 작업에 관심이 있습니다. 따라서 사용되는 기본 구조는 사양을 충족하는 경우 구현 및 / 또는 대상 아키텍처간에 달라질 수 있습니다. 나는 확신 std::map하고 std::set거기 모든 구현에 나무를 사용하지만 그들은 일부 비 트리 구조도 사양을 충족하는 경우 경우에 필요가 없습니다.
Mark K Cowan

답변:


182

트리를 사용하려는 이유는 두 가지입니다.

트리와 같은 구조를 사용하여 문제를 반영하려고합니다.
이를 위해 부스트 그래프 라이브러리가 있습니다.

또는 접근 특성과 같은 트리를 가진 컨테이너를 원합니다.

기본적 으로이 두 컨테이너의 특성은 실제로 트리를 사용하여 구현해야한다는 것입니다 (실제로 요구 사항은 아닙니다).

이 질문도 참조하십시오 : C 트리 구현


64
트리가 가장 일반적인 경우에도 트리를 사용해야하는 많은 이유가 있습니다. 가장 일반적인! 동일합니다.
Joe Soul-bringer

3
트리를 원하는 세 번째 주요 이유는 삽입 / 제거가 빠른 항상 정렬 된 목록이지만 std : multiset이 있기 때문입니다.
VoidStar

1
@ Durga : 정렬 된 컨테이너로 맵을 사용할 때 깊이가 얼마나 관련성이 있는지 잘 모르겠습니다. 맵은 log (n) 삽입 / 삭제 / 조회를 보장하며 정렬 된 순서로 요소를 포함합니다. 이 맵은 모든 맵에 사용되며 일반적으로 빨강 / 검정 트리로 구현됩니다. 빨강 / 검정 나무는 나무가 균형을 이루도록합니다. 따라서 나무의 깊이는 나무의 요소 수와 직접 관련이 있습니다.
Martin York

14
2008 년과 현재에이 답변에 동의하지 않습니다. 표준 라이브러리는 부스트를 "하지"않았으며 부스트에있는 무언가의 가용성이 표준으로 채택하지 않는 이유가되어서는 안됩니다. 또한 BGL은 일반적이며 BGL과 독립적 인 특수 트리 클래스를 활용할만큼 충분히 관련되어 있습니다. 또한 std :: map 및 std :: set에 트리가 필요하다는 사실은 IMO, stl::red_black_tree등 을 갖는 또 다른 인수입니다 . 마지막으로 std::mapand std::set트리는 균형 std::tree이 맞지 않을 수 있습니다.
einpoklum

1
@einpoklum : "부스트에서 무언가의 가용성은 표준으로 채택하지 않는 이유가되어서는 안됩니다"- 부스트 의 목적 중 하나 는 표준에 포함되기 전에 유용한 라이브러리의 입증 근거로 작용하는 것입니다. "절대적으로!"라고 말합니다.
Martin Bonner는 Monica

94

아마도 부스트에 트리 컨테이너가없는 것과 같은 이유로. 이러한 컨테이너를 구현하는 방법에는 여러 가지가 있으며 컨테이너를 사용하는 모든 사람을 만족시키는 좋은 방법은 없습니다.

고려해야 할 몇 가지 문제 :

  • 노드의 자식 수는 고정적이거나 가변적입니까?
  • 노드 당 얼마나 많은 오버 헤드가 있습니까? -즉, 부모 포인터, 형제 포인터 등이 필요합니까?
  • 어떤 알고리즘을 제공해야합니까? -다른 반복자, 검색 알고리즘 등

결국, 문제는 모든 사람에게 충분히 유용한 트리 컨테이너가 그것을 사용하는 대부분의 사람들을 만족 시키기에는 너무 무겁다는 것입니다. 강력한 무언가를 찾고 있다면 Boost Graph Library 는 기본적으로 트리 라이브러리가 사용할 수있는 것의 수퍼 세트입니다.

다른 일반적인 트리 구현은 다음과 같습니다.


5
"... 모두를 만족시키는 좋은 방법은 없습니다 ..."stl :: map, stl :: multimap 및 stl :: set이 stl의 rb_tree를 기반으로하기 때문에 기본 유형만큼 많은 경우를 만족해야합니다. .
Catskul

44
의 노드의 자식을 검색하는 방법이 없다는 것을 고려할 때 std::map해당 트리 컨테이너를 호출하지 않습니다. 그것들은 일반적으로 트리로 구현되는 연관 컨테이너입니다. 큰 차이.
Mooing Duck

2
Mooing Duck에 동의합니다. std :: map에서 너비 우선 검색을 어떻게 구현 하시겠습니까? 정말 비싼 것
마르코 A.

1
Kasper Peeters의 tree.hh를 사용하기 시작했지만 GPLv3 또는 다른 GPL 버전에 대한 라이센스를 검토 한 후에는 상용 소프트웨어가 오염 될 수 있습니다. 상업적 목적으로 구조가 필요한 경우 @hplbsh의 의견에 제공된 트리 트리를 보는 것이 좋습니다.
Jake88

3
나무에 대한 다양한 특정 요구 사항은 전혀 다른 유형의 나무를 갖지 않는다는 주장입니다.
André

50

STL의 철학은 컨테이너 구현 방식이 아니라 보증에 따라 컨테이너를 선택한다는 것입니다. 예를 들어, 컨테이너를 선택하면 빠른 조회가 필요합니다. 검색이 매우 빠르면 컨테이너는 단방향 목록으로 구현 될 수 있습니다. 어쨌든 내부를 건드리지 않고 액세스에 반복기 또는 멤버 함수를 사용하고 있기 때문입니다. 코드는 컨테이너가 구현되는 방식이 아니라 컨테이너의 속도, 고정 및 정의 된 순서가 있는지 또는 공간에서 효율적인지 등입니다.


12
컨테이너 구현에 대해 이야기하고 있다고 생각하지 않고 실제 트리 컨테이너 자체에 대해 이야기하고 있습니다.
Mooing Duck

3
@MooingDuck wilhelmtell의 의미는 C ++ 표준 라이브러리가 기본 데이터 구조를 기반으로 컨테이너를 정의하지 않는다는 것입니다. 단지 인터페이스와 점근 적 성능과 같은 관찰 가능한 특성으로 컨테이너를 정의합니다. 당신이 그것에 대해 생각할 때, 나무는 실제로 (우리가 알듯이) 컨테이너가 아닙니다. 그들은 심지어 똑바로 앞으로 AA이없는 end()begin()있는 경우 등 모든 요소를 통해 반복 할 수
요르단 멜로

7
@ Jordandan : 모든 점에서 말도 안됩니다. 객체를 포함하는 것입니다. begin () 및 end () 및 양방향 반복자가 반복되도록 설계하는 것은 매우 간단합니다. 컨테이너마다 특성이 다릅니다. 추가로 나무 특성을 가질 수 있다면 유용 할 것입니다. 꽤 쉬워야합니다.
Mooing Duck

따라서 자식 및 부모 노드에 대한 빠른 조회와 합리적인 메모리 요구 사항을 제공하는 컨테이너가 필요합니다.
doc

@JordanMelo : 이러한 관점에서, 대기열, 스택 또는 우선 순위 대기열과 같은 어댑터도 STL에 속하지 않습니다 ( begin()및 또한 포함하지 않음 end()). 그리고 우선 순위 큐는 일반적으로 힙이며 이론상 적어도 이론상 트리입니다 (실제 구현 임에도 불구하고). 따라서 다른 기본 데이터 구조를 사용하여 트리를 어댑터로 구현 한 경우에도 STL에 포함시킬 수 있습니다.
andreee

48

"객체의 계층 구조를 트리로 저장하고 싶습니다"

C ++ 11은왔다 갔다 std::tree했지만 아이디어가 나왔지만 여전히을 제공 할 필요가 없었습니다 ( 여기 참조 ). 그들이 이것을 추가하지 않은 이유는 기존 컨테이너 위에 자신의 것을 쉽게 구축 할 수 있기 때문입니다. 예를 들어 ...

template< typename T >
struct tree_node
   {
   T t;
   std::vector<tree_node> children;
   };

간단한 순회는 재귀를 사용합니다 ...

template< typename T >
void tree_node<T>::walk_depth_first() const
   {
   cout<<t;
   for ( auto & n: children ) n.walk_depth_first();
   }

계층 구조를 유지하려는 경우 당신이 작업 할 STL 알고리즘 , 그러면 모든 일이 복잡 할 수 있습니다. 자체 반복자를 빌드하고 호환성을 얻을 수 있지만 많은 알고리즘은 단순히 계층 구조 (예 : 범위의 순서를 변경하는 것)에 대해서는 의미가 없습니다. 계층 구조 내에서 범위를 정의 하더라도 복잡한 비즈니스가 될 수 있습니다.


2
프로젝트에서 tree_node의 자식을 정렬 할 수 있으면 std :: vector <> 대신 std :: set <>을 사용하고 tree <node 객체에 operator <()를 추가하면 크게 향상됩니다 'T'와 같은 객체의 '검색'성능.
J Jorgenson

4
그것들은 게으르고 실제로 첫 번째 예제 인 Undefined Behavior를 만들었습니다.
user541686

2
@Mehrdad : 나는 마침내 당신의 코멘트 뒤에 세부 사항을 요청하기로 결정 여기 .
nobar

many of the algorithms simply don't make any sense for a hierarchy. 해석의 문제. 스택 오버 플로우 사용자의 구조를 상상해보십시오. 매년 평판이 높은 사용자가 평판이 낮은 사용자를 우위에두기를 원합니다. 따라서 매년 방금 실행하는 BFS 반복자와 적절한 비교를 제공합니다 std::sort(tree.begin(), tree.end()).
doc

마찬가지로, 당신은 쉽게 대체하여 (예를 들어 JSON과 같은 구조화되지 않은 키 - 값 기록을 모델링) 연관 트리를 만들 수 vectormap위의 예에서. JSON과 유사한 구조를 완전히 지원하기 variant위해 노드를 정의하는 데 사용할 수 있습니다 .
nobar

43

RB-tree 구현을 찾고 있다면 stl_tree.h 가 적합 할 수도 있습니다.


14
이상하게도 이것은 원래의 질문에 실제로 대답하는 유일한 응답입니다.
Catskul

12
그가 "계층 구조"를 원한다는 것을 고려하면, "밸런싱"이있는 것은 틀린 답이라고 가정하는 것이 안전 해 보입니다.
Mooing Duck

11
"이것은 다른 라이브러리 헤더에 포함 된 내부 헤더 파일입니다. 직접 사용하지 마십시오."
Dan

3
@Dan : 복사한다고해서 직접 사용하는 것은 아닙니다.
einpoklum

12

std :: map은 빨간색 검은 색 트리를 기반으로합니다 . 다른 컨테이너 를 사용 하여 고유 한 유형의 트리를 구현할 수도 있습니다 .


13
일반적으로 적갈색 나무를 사용합니다 (그렇지 않아도 됨).
Martin York

1
GCC는 트리를 사용하여지도를 구현합니다. VC include 디렉토리에서 Microsoft가 무엇을 사용하는지 확인하고 싶은 사람이 있습니까?
JJ

// STL 연관 컨테이너 (세트, 멀티 세트, 맵 및 멀티 맵) 구현에 사용하도록 설계된 레드-블랙 트리 클래스. stl_tree.h 파일에서 가져 왔습니다.
JJ

@JJ 적어도 Studio 2010에서는 ordered red-black tree of {key, mapped} values, unique keys에 정의 된 내부 클래스를 사용합니다 <xtree>. 현재 더 최신 버전에 액세스 할 수 없습니다.
저스틴 타임-복원 모니카

8

어떤 식 으로든 std :: map은 트리 (균형 이진 트리와 동일한 성능 특성을 가져야 함)이지만 다른 트리 기능을 노출하지는 않습니다. 실제 트리 데이터 구조를 포함하지 않는 이유는 아마도 stl에 모든 것을 포함하지 않는 것입니다. stl은 자체 알고리즘 및 데이터 구조를 구현하는 데 사용할 프레임 워크로 볼 수 있습니다.

일반적으로 원하는 기본 라이브러리 기능이 있지만 stl에는없는 경우 수정은 BOOST 를 보는 것입니다. 입니다.

그렇지 않으면, 거기 무리라이브러리를 밖으로 거기 당신의 나무의 필요에 따라.


6

모든 STL 컨테이너는 외부 적으로 하나의 반복 메커니즘으로 "시퀀스"로 표시됩니다. 나무는이 관용구를 따르지 않습니다.


7
트리 데이터 구조는 반복자를 통해 사전 주문, 주문 또는 주문 주문 순회를 제공 할 수 있습니다. 실제로 이것은 std :: map이하는 일입니다.
Andrew Tomazos

3
예, 아니오 ... "트리"의 의미에 따라 다릅니다. std::map내부적으로 btree로 구현되지만 외부 적으로는 PAIRS의 정렬 된 시퀀스로 나타납니다. 전과 후를 누구에게 물어볼 수있는 모든 요소가 주어집니다. 각각 다른 요소를 포함하는 요소를 포함하는 일반적인 트리 구조는 정렬이나 방향을 부과하지 않습니다. 여러 가지 방법으로 트리 구조를 걷는 반복자를 정의 할 수 있지만 (슬로우 | 마지막 ...) 일단 std::tree컨테이너를 수행 하면 컨테이너가 begin함수 에서 그 중 하나를 반환해야합니다 . 그리고 서로를 돌려 줄 명백한 이유가 없습니다.
Emilio Garavaglia

4
std :: map은 일반적으로 B- 트리가 아닌 균형 잡힌 이진 검색 트리로 표시됩니다. 당신이 만든 동일한 주장이 std :: unordered_set에 적용될 수 있지만 자연 순서는 없지만 시작 및 종료 반복자를 나타냅니다. 시작과 끝의 요구는 모든 요소를 ​​결정 론적 순서로 반복하는 것이지 자연적인 요소가 아니라는 것입니다. preorder는 트리에 대해 완벽하게 유효한 반복 순서입니다.
Andrew Tomazos

4
대답의 의미는 "시퀀스"인터페이스가 없기 때문에 stl n-tree 데이터 구조가 없다는 것입니다. 이것은 단순히 잘못된 것입니다.
Andrew Tomazos

3
@EmiloGaravaglia :에 의해 입증 된 것처럼 std::unordered_set, 멤버를 반복하는 "독특한 방법"이 없지만 (실제로 반복 순서는 의사 랜덤 및 구현이 정의되어 있음) 여전히 stl 컨테이너입니다. 이는 요점을 반증합니다. 순서가 정의되지 않은 경우에도 컨테이너의 각 요소를 반복하는 것이 여전히 유용한 작업입니다.
앤드류 토마 조스

4

STL은 "모든 것"라이브러리가 아니기 때문입니다. 본질적으로 물건을 만드는 데 필요한 최소 구조가 포함되어 있습니다.


13
이진 트리는 매우 기본적인 기능이며 실제로 std :: map, std :: multimap 및 stl :: set과 같은 유형이므로 다른 컨테이너보다 기본입니다. 이러한 유형은 해당 유형을 기반으로하므로 기본 유형이 노출 될 것으로 예상합니다.
Catskul

2
OP가 이진 트리를 요구한다고 생각하지 않으며 계층을 저장할 트리를 요구하고 있습니다.
Mooing Duck

뿐만 아니라 트리 "컨테이너"를 STL에 추가하면 트리 탐색기 (일반 반복자)와 같은 많은 새로운 개념을 추가 할 수 있습니다.
alfC

5
"물건을 구축하기위한 최소 구조"는 매우 주관적인 진술입니다. 원시 C ++ 개념으로 물건을 만들 수 있으므로 최소값은 STL이 아닐 것입니다.
doc


3

IMO, 생략. 그러나 STL에 트리 구조를 포함시키지 않는 좋은 이유가 있다고 생각합니다. 트리를 유지 관리하는 데에는 많은 논리가 있으며 , 기본 TreeNode개체에 멤버 함수로 작성하는 것이 가장 좋습니다 . TreeNodeSTL 헤더에 싸여 있으면 더 지저분 해집니다.

예를 들면 다음과 같습니다.

template <typename T>
struct TreeNode
{
  T* DATA ; // data of type T to be stored at this TreeNode

  vector< TreeNode<T>* > children ;

  // insertion logic for if an insert is asked of me.
  // may append to children, or may pass off to one of the child nodes
  void insert( T* newData ) ;

} ;

template <typename T>
struct Tree
{
  TreeNode<T>* root;

  // TREE LEVEL functions
  void clear() { delete root ; root=0; }

  void insert( T* data ) { if(root)root->insert(data); } 
} ;

7
그것은 당신이 가지고있는 많은 원시 포인터를 가지고 있으며, 그중 많은 포인터가 전혀 필요하지 않습니다.
Mooing Duck

이 답변을 철회하십시오. TreeNode 클래스는 트리 구현의 일부입니다.
einpoklum

3

STL 트리가없는 데는 몇 가지 이유가 있다고 생각합니다. 기본적으로 트리는 컨테이너 (리스트, 벡터, 세트)와 같이 재귀 데이터 구조의 형태로, 정확한 선택을 어렵게하는 매우 다른 미세 구조를 갖습니다. 또한 STL을 사용하여 기본 형식으로 구성하기가 매우 쉽습니다.

유한 루트 트리는 값 또는 페이로드를 갖는 컨테이너, 클래스 A의 인스턴스, 그리고 아마도 비어있는 루트 (서브) 트리 컬렉션으로 생각할 수 있습니다. 하위 트리의 빈 컬렉션이있는 나무는 나뭇잎으로 간주됩니다.

template<class A>
struct unordered_tree : std::set<unordered_tree>, A
{};

template<class A>
struct b_tree : std::vector<b_tree>, A
{};

template<class A>
struct planar_tree : std::list<planar_tree>, A
{};

반복자 디자인 등에 대해 약간 생각하고 나무 사이에서 정의하고 효율적으로 사용할 수있는 제품 및 공동 제품 작업-원본 STL을 잘 작성해야하므로 빈 세트, 벡터 또는 목록 컨테이너가 기본 경우 페이로드가 실제로 비어 있습니다.

나무는 많은 수학적 구조에서 중요한 역할을합니다 (정육점, 그로스 만 (Grossman) 및 라슨 (Larsen)의 고전 논문, 또한 합류 할 수있는 사례에 대한 Connes와 Kriemer의 논문 및 열거에 사용되는 방법 참조). 그들의 역할이 단순히 다른 작업을 용이하게하는 것이라고 생각하는 것은 옳지 않습니다. 오히려 데이터 구조로서의 기본 역할로 인해 이러한 작업을 용이하게합니다.

그러나 나무 외에 "공동 나무"도 있습니다. 위의 모든 나무는 루트를 삭제하면 모든 것을 삭제하는 속성을 갖습니다.

트리의 반복자를 고려하십시오. 아마도 그들은 반복자, 노드 및 상위 노드까지 루트까지의 간단한 스택으로 실현 될 것입니다.

template<class TREE>
struct node_iterator : std::stack<TREE::iterator>{
operator*() {return *back();}
...};

그러나 원하는만큼 가질 수 있습니다. 집합 적으로 그들은 "트리"를 형성하지만 모든 화살표가 루트 방향으로 흐르면이 코트 리는 반복자를 통해 사소한 반복기와 루트로 반복 될 수 있습니다. 그러나 모든 인스턴스를 추적하지 않는 한 반복하거나 다른 탐색자를 알 수 없으며 반복자의 앙상블을 삭제할 수 없습니다.

나무는 엄청나게 유용하고 구조가 많기 때문에 결정적으로 올바른 접근 방법을 얻는 것이 심각한 도전입니다. 제 생각에는 이것이 STL에서 구현되지 않은 이유입니다. 또한 과거에는 사람들이 종교적으로 이해하고 자신의 유형의 인스턴스를 포함하는 컨테이너 유형에 대한 아이디어를 찾는 것을 보았습니다. 그러나 그들은 직면해야합니다. 아마도 (더 작은) 나무의 빈 컬렉션입니다. 현재 언어는 기본 생성자를 제공하는 데 어려움없이 언어를 허용합니다 container<B>.B

이것이 좋은 형태로 표준에 도달하면 기뻐할 것입니다.


0

여기에서 대답을 통해 일반적인 명명 된 이유는 트리를 반복 할 수 없거나 트리가 다른 STL 컨테이너와 유사한 인터페이스를 가정하지 않으며 그러한 트리 구조와 함께 STL 알고리즘을 사용할 수 없다는 것입니다.

이를 염두에두고 STL과 같은 인터페이스를 제공하고 가능한 한 기존 STL 알고리즘과 함께 사용할 수있는 자체 트리 데이터 구조를 설계하려고했습니다.

내 생각은 트리가 기존 STL 컨테이너를 기반으로해야하며 컨테이너를 숨겨서는 안되므로 STL 알고리즘과 함께 사용할 수 있도록하는 것입니다.

트리가 제공해야하는 다른 중요한 기능은 순회 반복기입니다.

다음은 내가 생각해 낸 것입니다. https://github.com/igagis/utki/blob/master/src/utki/tree.hpp

다음 테스트가 있습니다 : https://github.com/igagis/utki/blob/master/tests/tree/tests.cpp


-9

모든 STL 컨테이너는 반복자와 함께 사용할 수 있습니다. 트리를 통과하는``올바른 ''방법이 없기 때문에 반복자를 트리로 가질 수 없습니다.


3
그러나 BFS 또는 DFS가 올바른 방법이라고 말할 수 있습니다. 또는 둘 다 지원하십시오. 또는 다른 상상할 수 있습니다. Jut는 사용자에게 그것이 무엇인지 말해줍니다.
tomas789

2
std :: map에는 트리 반복자가 있습니다.
Jai

1
트리는 하나의 "익스트림"에서 다른 노드까지 순서대로 모든 노드를 순회하는 고유 한 사용자 정의 반복기 유형을 정의 할 수 있습니다 (예 : 경로가 0과 1 인 이진 트리의 경우 "모두 0"에서 "모두로 이동하는 반복자를 제공 할 수 있습니다. 1 초 "반대를 수행하는 역 반복자; 3의 깊이와 시작 노드와 나무를 위해 s, 예를 들어,이 노드를 반복 수로 s000, s00, s001, s0, s010, s01, s011, s, s100, s10, s101, s1, s110, s11, s111(" "을"왼쪽 오른쪽 "); 그것은 또한 (깊이 탐색 패턴을 사용할 수 s, s0, s1, s00, s01, s10,s11 ,
저스틴 시간-복원 모니카

각 노드가 한 번만 전달되는 방식으로 모든 노드를 반복하는 한)
저스틴 타임-복원 모니카

1
@doc, 아주 좋은 지적입니다. std::unordered_set우리는 임의의 방법 이외의 요소 (해시 함수에 의해 내부적으로 제공됨) 이외의 요소를 반복하는 더 좋은 방법을 알지 못하기 때문에 시퀀스를 "만들었다" 고 생각 합니다. 나는 그것이 트리의 반대의 경우라고 생각한다. 반복 unordered_set은 명확하지 않다. 이론 상으로는 "무작위"이외의 반복을 정의하는 "방법"이 없다. 나무의 경우 많은 "좋은"(비 랜덤) 방법이 있습니다. 그러나 다시 한번 요점은 유효합니다.
alfC
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.