순회가 하나 뿐인 방향이없는 트리에서 가장 긴 경로


44

두 가지 깊이 우선 검색을 사용하여 방향이 지정되지 않은 트리에서 가장 긴 경로를 찾는이 표준 알고리즘이 있습니다.

  • 임의의 정점 에서 DFS를 시작 하고 그로부터 가장 먼 정점을 찾으십시오. 라고 말하십시오 .vv
  • 이제 에서 DFS를 시작하여 가장 먼 정점을 찾으십시오. 이 경로는 그래프에서 가장 긴 경로입니다.v

문제는 이것이 더 효율적으로 이루어질 수 있는가하는 것입니다. 단일 DFS 또는 BFS로 수행 할 수 있습니까?

(이것은 비 방향 트리 의 직경 을 계산하는 문제로 동등하게 설명 될 수 있습니다 .)


2
당신이 추구 하는 것을 나무 의 지름 이라고도합니다 . (나무에서 두 ​​노드를 연결하는 하나의 경로 만 있기 때문에 "가장 짧은 최단 경로"와 "가장 긴 경로"는 동일합니다.)
Raphael

답변:


22

우리는 포스트 순서로 깊이 우선 검색을 수행하고 도중에 결과를 집계합니다. 즉, 문제를 재귀 적으로 해결합니다.

검색 트리에서 하위 모든 노드 에 대해 두 가지 경우가 있습니다.vu1,,uk

  • 에서 가장 긴 경로 는 서브 트리 중 하나에 있습니다 .TvTu1,,Tuk
  • 에서 가장 긴 경로 는 포함 합니다.Tvv

두 번째 경우, 에서 하나의 가장 긴 경로를 에서 하위 트리 중 하나로 결합해야합니다 . 이것들은 확실히 가장 깊은 잎에있는 것들입니다. 경로의 길이는 인 경우 이고 인 경우 이고 하위 트리 높이의 다중 세트 ¹.vH(k)+H(k1)+2k>1H(k)+1k=1H={h(Tui)i=1,,k}

의사 코드에서 알고리즘은 다음과 같습니다.

procedure longestPathLength(T : Tree) = helper(T)[2]

/* Recursive helper function that returns (h,p)
 * where h is the height of T and p the length
 * of the longest path of T (its diameter) */
procedure helper(T : Tree) : (int, int) = {
  if ( T.children.isEmpty ) {
    return (0,0)
  }
  else {
    // Calculate heights and longest path lengths of children
    recursive = T.children.map { c => helper(c) }
    heights = recursive.map { p => p[1] }
    paths = recursive.map { p => p[2] }

    // Find the two largest subtree heights
    height1 = heights.max
    if (heights.length == 1) {
      height2 = -1
    } else {
      height2 = (heights.remove(height1)).max
    }

    // Determine length of longest path (see above)        
    longest = max(paths.max, height1 + height2 + 2)

    return (height1 + 1, longest)
  }
}

  1. A(k) 는 (순서 통계) 에서 가장 작은 값입니다 .kA

@JeffE 두 번째 주석과 관련하여 : 실제로, 그리고 이것은 마지막 행에서 처리됩니다 : height1 + height2이 경로의 길이입니다. 실제로 가장 긴 경로 인 경우에 의해 선택됩니다 max. 위의 텍스트에도 설명되어 있으므로 문제가 잘 보이지 않습니까? 확실히 당신은 그것이 가장 긴 길인지 아닌지, 그리고 재발하기 위해 상처를 입히지 않더라도 (재질 정확성) 찾기 위해 재귀를해야합니다.
Raphael

@JeffE 첫 번째 주석과 관련하여 계산 에서 고려 사항 을 height2명시 적으로 제거 height1하므로 어떻게 동일한 자녀를 두 번 선택할 수 있습니까? 그것은 또한 서론에서 설명되었습니다.
Raphael

1
분명히 우리는 다른 의사 코드 방언을 사용합니다. 왜냐하면 나는 당신을 이해하기가 어렵 기 때문입니다. 그것은 명시 적으로 영어 선언 추가 도움이 될 longestPathHeight(T)한 쌍의 반환 (h,d), h의 높이 Td직경입니다 T. (오른쪽?)
JeffE

@ 제프 맞아; 나는 설명을 감안할 때 코드에서 명확하다고 생각했지만 다른 의사 코드 패러다임에 대한 "명확한"에 대한 나의 외삽은 충분하지 않았습니다 (광산은 스칼라 스크입니다). 혼란을 드려 죄송합니다. 코드를 명확하게 설명하고 있습니다.
Raphael

8

더 나은 방법으로 해결할 수 있습니다. 또한 데이터 구조를 약간 수정하고 반복적 인 접근 방식을 사용하여 시간 복잡성을 O (n)으로 줄일 수 있습니다. 다양한 데이터 구조로이 문제를 해결하는 자세한 분석 및 다양한 방법.

다음 은 블로그 게시물 에서 설명하고 싶은 내용을 요약 한 것입니다 .

재귀 적 접근 – 트리 직경 이 문제에 접근하는 또 다른 방법은 다음과 같습니다. 위에서 언급했듯이 직경이

  1. 왼쪽 하위 트리에 완전히 있거나
  2. 오른쪽 하위 트리에 완전히 있거나
  3. 루트에 걸쳐있을 수 있습니다

이는 직경이 이상적으로 도출 될 수 있음을 의미합니다.

  1. 왼쪽 나무의 지름
  2. 오른쪽 나무의 지름
  3. 왼쪽 하위 트리의 높이 + 오른쪽 하위 트리의 높이 + 1 (직경이 루트 노드를 가로 질러있을 때 루트 노드를 추가하려면 1)

그리고 우리는 지름이 가장 긴 경로라는 것을 알고 있습니다. 그래서 우리는 그것이 한쪽에 있거나 3을 root 경우에 1과 2를 취합니다.

반복적 접근 – 나무 지름

우리는 트리를 가지고 있으며 각 노드가 다음을 알 수 있도록 각 노드에 대한 메타 정보가 필요합니다.

  1. 왼쪽 아이의 키,
  2. 오른쪽 아이의 키와
  3. 리프 노드 사이의 가장 먼 거리입니다.

각 노드에이 정보가 있으면 최대 경로를 추적하기위한 임시 변수가 필요합니다. 알고리즘이 완료 될 때까지 temp 변수의 직경 값이 있습니다.

이제 루트의 세 가지 값에 대해 전혀 알지 못하므로 상향식 접근 방식으로이 문제를 해결해야합니다. 그러나 우리는 잎에 대한 이러한 가치를 알고 있습니다.

해결 단계

  1. leftHeight 및 rightHeight를 1로 사용하여 모든 리프를 초기화하십시오.
  2. maxDistance를 0으로 사용하여 모든 잎을 초기화합니다. 우리는 leftHeight 또는 rightHeight 중 하나가 1이면 maxDistance = 0으로 만드는 지점을 만듭니다.
  3. 한 번에 하나씩 위로 이동하여 직계 부모의 값을 계산하십시오. 이제 우리는 아이들을위한 이러한 가치를 알고 있기 때문에 쉬울 것입니다.
  4. 주어진 노드에서

    • leftHeight를 최대 값으로 지정합니다 (왼쪽 자식의 leftHeight 또는 rightHeight).
    • rightHeight를 최대 값으로 지정하십시오 (오른쪽 하위의 leftHeight 또는 rightHeight).
    • 이 값 (leftHeight 또는 rightHeight) 중 하나가 1이면 maxDistance를 0으로 만듭니다.
    • 두 값이 모두 0보다 큰 경우 maxDistance를 leftHeight + rightHeight – 1로 설정하십시오.
  5. maxDistance를 임시 변수에 유지하고 4 단계에서 maxDistance가 변수의 현재 값보다 큰 경우 새 maxDistance 값으로 바꾸십시오.
  6. 알고리즘의 끝에서 maxDistance의 값은 직경입니다.

1
덜 일반적인 것 외에도 이진 트리를 다루는 것 외에도 이전 답변을 어떻게 추가합니까?
Raphael

9
이 대답은 내 의견으로는 더 읽기 쉽고 이해하기 쉽습니다 (의사 코드는 매우 혼란 스럽습니다).
reggaeguitar

-3

아래는 단일 DFS 순회 만 사용하여 지름 경로를 반환하는 코드입니다. 트리의 특정 노드에서 시작하는 가장 긴 경로뿐만 아니라 지금까지 본 최고의 직경을 추적하려면 추가 공간이 필요합니다. 이것은 가장 긴 직경 경로에 루트가 포함되어 있지 않거나 루트 이웃의 가장 긴 두 경로의 조합이라는 사실을 기반으로 한 동적 프로그래밍 방식입니다. 따라서이 정보를 추적하려면 두 개의 벡터가 필요합니다.

 int getDiam(int root, vector<vector<int>>& adj_list, int& height, vector<int>& path, vector<int>& diam) {
    visited[root] = true;
    int m1 = -1;
    int m2 = -1;
    int max_diam = -1;
    vector<int> best1 = vector<int>();
    vector<int> best2 = vector<int>();
    vector<int> diam_path = vector<int>();
    for(auto n : adj_list[root]) {
        if(!visited[n]) {
            visited[n] = true;
            int _height = 0;
            vector<int> path1;
            vector<int> path2;
            int _diam = getDiam(n, adj_list, _height, path1, path2);
            if(_diam > max_diam) {
                max_diam = _diam;
                diam_path = path2;
            }
            if(_height > m1) {
                m2 = m1;
                m1 = _height;
                best2 = best1;
                best1 = path1;
            }
            else if(_height > m2) {
                m2 = _height;
                best2 = path1;
            }
        }
    }

    height = m1 + 1;

    path.insert( path.end(), best1.begin(), best1.end() );
    path.push_back(root);

    if(m1 + m2 + 2 > max_diam) {
        diam = path;
        std::reverse(best2.begin(), best2.end());
        diam.insert( diam.end(), best2.begin(), best2.end() );
    }
    else{
        diam = diam_path;
    }


    return max(m1 + m2 + 2, max_diam);
}

2
이것은 코딩 사이트가 아닙니다. 우리는 주로 코드 블록으로 구성된 답변을 권장하지 않습니다. 대신 알고리즘 뒤에있는 아이디어를 설명하고 간결한 의사 코드를 제공하는 답변을 원합니다 (이해하기 위해 특정 프로그래밍 언어에 대한 지식이 필요하지 않음). 트리의 특정 노드에서 시작하는 가장 긴 경로를 어떻게 계산합니까? (특히 가장 긴 경로는 DFS 트리의 "위로"즉 루트로 돌아가서 시작될 수 있기 때문에)
DW
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.