레이블이없는 나무의 효율적인 압축


20

레이블이없는 뿌리 이진 트리를 고려하십시오. 우리는 할 수 있습니다 압축 하위 트리에 대한 포인터있을 때마다 : 같은 나무를 와 와 (해석 구조 평등), 우리는 저장 (wlog) 및 모든 포인터 대신 가리키는 포인터와 . 예를 들어 uli의 답변 을 참조하십시오 . T = T ' = T T ' T'='=TTT

위의 의미에서 트리를 입력으로 사용하고 압축 후에 남아있는 (최소) 노드 수를 계산하는 알고리즘을 제공하십시오. 이 알고리즘은 시간에 실행해야합니다 와 (유니폼 비용 모델) 입력 노드의 수.NO(nlogn)n

이것은 시험 문제이며 좋은 해결책을 찾지 못했으며 하나도 보지 못했습니다.


그리고 여기서의 기본 운영 인“비용”,“시간”은 무엇입니까? 방문한 노드 수는? 통과 한 모서리 수는? 그리고 입력의 크기는 어떻게 지정됩니까?
uli

이 트리 압축은 해시 컨싱 의 인스턴스입니다 . 그것이 일반적인 계산 방법으로 이어지는 지 확실하지 않습니다.
Gilles 'SO- 악의를 그만두십시오'

@uli 나는 이 무엇인지 명확히했다 . 그래도 "시간"은 충분히 구체적이라고 생각합니다. 비 동시 설정에서 이는 가장 자주 발생하는 기본 작업을 계산하는 것과 동일한 Landau 용어로 계산 작업을 수행하는 것과 같습니다. n
Raphael

@Raphael 물론 의도 된 기본 동작이 무엇인지 추측하고 다른 사람들과 똑같이 선택할 것입니다. 그러나, 나는“시간 제한”이 주어질 때마다 계산되고있는 것을 밝히는 것이 중요하다는 것을 내가 여기에서 놀랍다는 것을 알고 있습니다. 스왑, 비교, 추가, 메모리 액세스, 검사 된 노드, 트래버스 된 엣지 등이 있습니까? 물리학에서 측정 단위를 생략하는 것과 같습니다. 그것은 또는 1010kg ? 그리고 메모리 액세스는 거의 항상 가장 빈번한 작업이라고 생각합니다. 10ms
uli

@uli 이것들은“균일 비용 모델”이 전달해야하는 세부 사항입니다. 어떤 작업이 기본인지 정확하게 정의하는 것은 고통 스럽지만 99.99 % (이 포함)의 경우 모호성이 없습니다. 복잡성 클래스에는 기본적으로 단위가 없으며 하나의 인스턴스를 수행하는 데 걸리는 시간을 측정하지 않지만 입력이 커질수록이 시간은 달라집니다.
Gilles 'SO- 악마 중지'

답변:


10

예, 시간 에이 압축을 수행 할 수 는 있지만 쉽지는 않습니다. :) 먼저 몇 가지 관찰을 한 다음 알고리즘을 제시합니다. 트리가 처음에 압축되지 않았다고 가정합니다. 실제로 필요하지는 않지만 분석이 더 쉬워집니다.O(nlogn)

첫째, 우리는 '구조적 평등'을 귀납적으로 특징 짓는다. T ' 를 두 개의 (서브 트리) 트리 라고 하자 . 경우 TT가 ' (전혀 정점이없는) 널 나무는 모두, 그들은 구조적으로 동일합니다. 경우 TT는 ' 모두 null이 아닌 나무입니다, 그들은 자신의 왼쪽 아이들이 구조적으로 동일하고 권리 아이들이 구조적으로 동일 IFF에 구조적으로 동일합니다. '구조적 동등성'은 이러한 정의에 대한 최소한의 고정 점입니다.TTTTTT

예를 들어, 두 개의 리프 노드는 둘 다 하위 트리로 널 트리를 가지므로 구조적으로 동일합니다.

'그들의 왼쪽 아이들은 구조적으로 동등하고 그들의 오른쪽 아이들도 마찬가지입니다'라고 말하는 것이 오히려 성가 시므로, 우리는 종종 '자녀들이 구조적으로 동일하다'고 말하고 같은 것을 의도합니다. 또한 '이 꼭짓점에 뿌리를 둔 하위 트리'를 의미 할 때 때때로 '이 꼭짓점'이라고 말합니다.

위의 정의는 압축을 수행하는 방법에 대한 힌트를 즉시 제공합니다. 깊이가 최대 인 모든 하위 트리의 구조적 동등성을 알고 있으면 깊이 d + 1 인 하위 트리의 구조적 동등성을 쉽게 계산할 수 있습니다 . 우리는 O ( n 2 ) 실행 시간 을 피하기 위해 현명한 방법으로이 계산을 수행해야합니다 .dd+1O(n2)

알고리즘은 실행 중에 모든 정점에 식별자를 할당합니다. 식별자는 집합 의 숫자입니다 . 식별자는 고유하며 절대 변경되지 않습니다. 따라서 알고리즘 시작시 일부 (전역) 변수를 1로 설정하고 일부 정점에 식별자를 할당 할 때마다 해당 변수의 현재 값을 정점에 할당하고 증분합니다 해당 변수의 값{1,2,,,}

먼저 입력 트리를 부모에 대한 포인터와 함께 같은 깊이의 정점을 포함하는 (최대 ) 목록 으로 변환합니다 . 이것은 O ( n ) 시간 안에 쉽게 수행됩니다 .영형()

먼저 모든 나뭇잎을 압축합니다 (이 나뭇잎을 깊이 0의 정점으로 목록에서 찾을 수 있음)를 단일 꼭지점으로 압축합니다. 이 정점에 식별자를 할당합니다. 두 정점의 압축은 한 정점의 부모를 대신 다른 정점을 가리 키도록 리디렉션하여 수행됩니다.

우리는 두 가지 관찰을합니다 : 첫째, 모든 정점은 깊이가 더 작은 자식을 가지며, 두 번째로, 보다 작은 모든 깊이의 정점에 대해 압축을 수행 한 경우 (그리고 식별자를 부여한 경우), 깊이 d 의 두 정점 이 구조적으로 동일합니다. 자녀의 식별자가 일치하면 압축 될 수 있습니다. 이 마지막 관찰은 다음과 같은 논증에서 비롯됩니다. 두 정점이 자녀가 구조적으로 동등한 경우 구조적으로 동일하며 압축 후 포인터가 동일한 자식을 가리키고 있음을 의미하며, 이는 자식의 식별자가 동일 함을 의미합니다.

우리는 작은 깊이에서 큰 깊이까지 같은 깊이의 노드로 모든 목록을 반복합니다. 모든 레벨에 대해 정수 쌍의 목록을 작성합니다. 여기서 모든 쌍은 해당 레벨에서 일부 정점의 자식 식별자에 해당합니다. 해당 수준의 두 정점이 해당 정수 쌍이 같으면 구조적으로 동일합니다. 사전 식 순서를 사용하여이를 정렬하고 동일한 정수 쌍 세트를 얻을 수 있습니다. 위와 같이 이러한 집합을 단일 정점으로 압축하고 식별자를 제공합니다.

위의 관찰은이 접근 방식이 작동하고 압축 트리를 생성 함을 증명합니다. 총 실행 시간은 에 우리가 만든 목록을 정렬하는 데 필요한 시간을 더한 것입니다. 우리가 생성하는 정수 쌍의 총 개수는 n 이므로, 필요한 총 실행 시간은 O ( n log n ) 입니다. 프로 시저 마지막에 남은 노드 수를 계산하는 것은 쉽지 않습니다 (유인한 식별자 수를 살펴보십시오).영형()영형(로그)


나는 당신의 대답을 자세하게 읽지 못했지만 이상한 문제 별 방법으로 노드를 찾는 해시 구성을 다소 재창조했다고 생각합니다.
Gilles 'SO- 악한 중지'

@Alex“엄격히 작은 아이들 degree”정도는 아마도 depth? 그리고 CS-tree가 아래로 커지더라도“나무의 높이”가“나무의 깊이”보다 덜 혼란 스럽습니다.
uli

좋은 대답입니다. 정렬을 피할 수있는 방법이 있어야한다고 생각합니다. @Gilles 답변에 대한 두 번째 의견은 여기에서도 유효합니다.
라파엘

@ uli : 네, 맞습니다, 나는 그것을 고쳤습니다 (왜 두 단어를 혼동했는지 확실하지 않습니다). 높이와 깊이는 미묘하게 다른 두 가지 개념이며 후자가 필요합니다. :) 기존의 '깊이'를 고수하여 모든 사람을 바꿔서 혼란스럽게 생각하지 않았습니다.
Alex ten Brink

4

구조적으로 동일한 하위 용어를 복제하지 않도록 변경할 수없는 데이터 구조를 압축하는 것을 해시 consing이라고 합니다. 이것은 함수형 프로그래밍에서 메모리 관리에 중요한 기술입니다. 해시 정리는 데이터 구조에 대한 일종의 체계적인 메모입니다.

우리는 트리를 해시 컨 시닝하고 해시 컨싱 후 노드를 계산합니다. 크기 의 데이터 구조를 구성하는 해시 는 항상 O ( nn 연산; 끝에서 노드 수를 계산하는 것은 노드 수에서 선형입니다.O(nlg(n))

나는 나무가 다음과 같은 구조를 갖는 것으로 간주 할 것이다 (여기서는 Haskell 구문으로 작성 됨).

data Tree = Leaf
          | Node Tree Tree

각 생성자에 대해 가능한 인수에서 이러한 인수에 생성자를 적용한 결과에 대한 매핑을 유지해야합니다. 잎은 사소합니다. 노드를 위해, 우리는 한정된 부분 맵 유지 T는 트리 식별자들의 세트이고, N은 노드 식별자들의 집합이다; T = N nodes:T×TNTN 여기서 는 유일한 리프 식별자입니다. 구체적인 용어로 식별자는 메모리 블록에 대한 포인터입니다.T=N{}

nodes균형 이진 검색 트리와 같은에 대해 로그 시간 데이터 구조를 사용할 수 있습니다 . 아래 lookup nodes에서는 nodes데이터 구조 에서 키를 찾는 insert nodes작업과 새 키 아래에 값을 추가하고 해당 키를 반환하는 작업을 호출 합니다 .

이제 우리는 트리를 통과하고 노드를 추가합니다. 하스켈 유사 의사 코드로 작성하고 있지만 nodes전역 가변 변수로 취급 합니다. 우리는 단지 그것에 추가 할 것이지만 삽입은 전체적으로 스레드되어야합니다. 이 add함수는 트리에서 반복하여 하위 트리를 nodes맵에 추가하고 루트의 식별자를 반환합니다.

insert (p1,p2) =
add Leaf = $\ell$
add (Node t1 t2) =
    let p1 = add t1
    let p2 = add t2
    case lookup nodes (p1,p2) of
      Nothing -> insert nodes (p1,p2)
      Just p -> p

수가 insert또한 최종 크기 통화, nodes데이터 구조, 최대 압축 후 노드의 수이다. (필요한 경우 빈 트리에 하나를 추가하십시오.)


nO(nlg(n))nodes

동일한 트리에 대한 해시를 독립적으로 계산하면 항상 동일한 결과를 얻을 수 있도록 구조화 된 방식으로 숫자에 대한 하위 구조 해싱 만 고려하고있었습니다. 우리가 변경 가능한 데이터 구조를 가지고 있다면 귀하의 솔루션도 좋습니다. 그래도 조금 정리할 수 있다고 생각합니다. 의 인터리빙 insertadd명시 적이어야하며 실제로 문제를 해결하는 기능인 imho를 제공해야합니다.
Raphael

1
@Raphael Hash consing은 포인터 / 식별자의 튜플에 대한 유한지도 구조에 의존하므로 조회 및 추가를위한 로그 시간 (예 : 균형 이진 검색 트리)으로이를 구현할 수 있습니다. 내 솔루션에는 변경이 필요하지 않습니다. 나는 수 있도록 nodes편의를 위해 변경 가능한 변수를, 그러나 당신은 내내 그것을 스레드 수 있습니다. 나는 전체 코드를 제공하지 않을 것입니다.
Gilles 'SO- 악한 중지'

1
@Raphael Hashing 구조는 임의의 숫자를 할당하는 것과 달리 약간 피하다 . 균일 한 비용 모델에서는 어떤 것도 큰 정수로 인코딩하고 상수 시간 작업을 수행 할 수 있습니다. 이는 현실적이지 않습니다. 실제로는 암호화 해시를 사용하여 사실상 무한 세트에서 유한 범위의 정수로 일대일로 매핑 할 수 있지만 속도는 느립니다. 비 암호화 체크섬을 해시로 사용하는 경우 충돌에 대해 생각해야합니다.
Gilles 'SO- 악마 중지'

3

여기 나무의 구조를 임의로 라벨링하는 것이 아니라 숫자로 (주입 적으로) 인코딩하는 것을 목표로하는 또 다른 아이디어가 있습니다. 이를 위해 우리는 어떤 수의 소인수 분해도 고유하다는 것을 사용합니다.

우리의 목적을 위해 는 트리에서 빈 위치를 나타내고, 왼쪽 하위 트리 l 및 오른쪽 하위 트리 r을 가진 노드를 N ( l , r ) 로 지정하십시오 . N ( E , E ) 는 잎이됩니다. 자 이제EN(l,r)lrN(E,E)

f(E)=0f(N(l,r))=2f(l)3f(r)

사용f 트리 상향식에 포함 된 모든 하위 트리 세트를 계산할 수 있습니다. 모든 노드에서 우리는 자식에서 얻은 인코딩 세트를 병합하고 새로운 숫자를 추가합니다 (어린이의 인코딩에서 일정한 시간에 계산할 수 있음).

이 마지막 가정은 실제 기계에 대한 확장입니다. 이 경우 지수 대신 Cantor의 페어링 기능 과 유사한 것을 사용하는 것이 좋습니다.

O(nlogn)O(nlogn)


1

의견에는 사진이 허용되지 않으므로 :

여기에 이미지 설명을 입력하십시오

왼쪽 상단 : 입력 트리

오른쪽 위 : 노드 5와 7에 뿌리를 둔 하위 트리도 동형입니다.

왼쪽 아래 : 압축 된 트리가 고유하게 정의되지 않았습니다.

7+5|T|6+|T|


이것은 실제로 원하는 작업의 예입니다. 감사합니다. 원본 참조와 추가 된 참조를 구별하지 않으면 최종 예제가 동일합니다.
Raphael

-1

편집 : 나는 T와 T '가 같은 부모의 자녀라는 질문을 읽었습니다. 압축 정의도 재귀 적이라고 생각했습니다. 즉, 이전에 압축 된 두 개의 하위 트리를 압축 할 수 있습니다. 그것이 실제 질문이 아니면 내 대답이 효과가 없을 수 있습니다.

O(nlogn)T(n)=2T(n/2)+cn

def Comp(T):
   if T == null:
     return 0
   leftCount = Comp(T.left)
   rightCount = Comp(T.right)
   if leftCount == rightCount:
     if hasSameStructure(T.left, T.right):
       T.right = T.left
       return leftCount + 1
     else
       return leftCount + rightCount + 1    

hasSameStructure()두 개의 압축 된 하위 트리를 선형 시간으로 비교하여 정확히 동일한 구조인지 확인하는 함수는 어디에 있습니까 ? 각각을 순회하고 하나의 하위 트리에 다른 자식이있을 때마다 왼쪽 자식이 있는지 확인하는 선형 시간 재귀 함수 작성은 어렵지 않아야합니다.

nnr

T(n)=T(n1)+T(n2)+O(1) if nnr
2T(n/2)+O(n) otherwise

하위 트리가 형제가 아닌 경우 어떻게합니까? ((T1, T1), (T2, T1)) T1에 대한 관리는 포인터 두 번째 세 번째 발생을 사용하여 두 번 저장할 수 있습니다.
uli

TT

이 질문에는 두 개의 서브 트레스가 동형이라고 식별되어 있습니다. 부모가 같은 사람에 대해서는 아무 말도 없습니다. 이전 예 ((T1, T1), (T1, T2))에서와 같이 트리에서 하위 트리 T1이 세 번 나타나면 세 번째 발생 항목을 가리켜 서 두 가지 발생을 압축 할 수 있습니다.
uli
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.