Самый длинный путь между 2 узлами

рассчитать самый длинный путь между двумя узлами.
Путь лежит под аркой.
Подпись метода:

public static int longestPath(Node n)

в Примере двоичного дерева ниже, это 4 (через 2-3-13-5-2).

это то, что я сейчас и для данного дерева, он просто возвращает 0.

public static int longestPath(Node n) {
    if (n != null) {
        longestPath(n, 0);
    }
    return 0;
}
private static int longestPath(Node n, int prevNodePath) {

    if (n != null && n.getLeftSon() != null && n.getRightSon() != null) {
        int currNodePath = countLeftNodes(n.getLeftSon()) + countRightNodes(n.getRightSon());
        int leftLongestPath = countLeftNodes(n.getLeftSon().getLeftSon()) + countRightNodes(n.getLeftSon().getRightSon());
        int rightLongestPath = countLeftNodes(n.getRightSon().getLeftSon()) + countRightNodes(n.getRightSon().getRightSon());

        int longestPath = currNodePath > leftLongestPath ? currNodePath : leftLongestPath;
        longestPath = longestPath > rightLongestPath ? longestPath : rightLongestPath;

        longestPath(n.getLeftSon(), longestPath);
        longestPath(n.getRightSon(), longestPath);

        return longestPath > prevNodePath ? longestPath : prevNodePath;
    }
    return 0;
}
private static int countLeftNodes(Node n) {
    if (n != null) {
        return 1+ countLeftNodes(n.getLeftSon());
    }
    return 0;
}
private static int countRightNodes(Node n) {
    if (n != null) {
        return 1+ countRightNodes(n.getRightSon());
    }
    return 0;
}

Я понимаю, что мне где-то не хватает ключевой концепции... Мой мозг сходит с ума, когда я пытаюсь отслеживать поток исполнение...
Я прав, говоря, что, найдя самый длинный путь среди корня, его левые и правые узлы, а затем рекурсивно на его левых и правых узлах, передавая им самый длинный путь из предыдущего вызова метода и, наконец, (когда?) верните самый длинный путь, я не уверен, как вы собираетесь его вернуть...

9 ответов


может быть, это так же просто:

public static int longestPath(Node n) {
    if (n != null) {
        return longestPath(n, 0); // forgot return?
    }
    return 0;
}

это сложнее, чем можно было бы подумать на первый взгляд. Рассмотрим следующее дерево:

      1
     / \
    2   3
   / \
  4   5
 / \   \
6   7   8
   / \   \
  9   a   b

в этом случае корневой узел даже не находится в самом длинном пути (a-7-4-2-5-8-b).

Итак, вы должны сделать следующее: Для каждого узла n вы должны вычислить следующее:

  • вычислить самый длинный путь в левом поддереве, начиная с корня левого поддерева (называется L)
  • вычислить самый длинный путь в правом поддереве, начиная с корня правого поддерева (так называемый R)
  • вычислить самый длинный путь в левом поддереве (не обязательно начиная с корня левого поддерева) (называется l)
  • вычислить самый длинный путь в правом поддереве (не обязательно начиная с корня правого поддерева) (называется r)

затем решите, какая комбинация максимизирует путь длина:

  • L+R+2, т. е. переход от подпространства в левом поддереве к текущему узлу и от текущего узла через подпространство в правом поддереве
  • l, т. е. просто возьмите левое поддерево и исключите текущий узел (и, следовательно, правое поддерево) из пути
  • r, т. е. просто возьмите правое поддерево и исключите текущий узел (и, следовательно, левое поддерево) из пути

поэтому я бы сделал немного hack, и для каждого узла не вернуться всего один int, но тройка целых чисел, содержащий (L+R+2, l, r). Затем абонент должен решать, что делать с этим результатом в соответствии с вышеуказанными правилами.


правильный алгоритм:

  1. запустите DFS с любого узла, чтобы найти самый дальний листовой узел. Метки этого узла т.
  2. запустите другой DFS, чтобы найти самый дальний узел от T.
  3. путь, который вы нашли на Шаге 2, является самым длинным путем в дереве.

этот алгоритм определенно будет работать,и вы не ограничены только двоичными деревьями. Я не уверен в вашем алгоритме:

Я прав, говоря, что, найдя самый длинный путь среди корня, его левых и правых узлов, а затем рекурсивно на его левых и правых узлах, передавая им самый длинный путь из предыдущего вызова метода и, наконец, (когда???) верните самый длинный путь, я не уверен, как вы собираетесь его вернуть...

потому что я не понимаю, что именно ты описываешь. Можете ли вы работать с ним вручную на примере или попытаться объяснить его лучше? Таким образом, вы можете получить лучшую помощь в понимании, если это правильно или не.

вы, похоже, пытаетесь рекурсивную реализацию в основном того же самого, что просто упрощено для двоичных деревьев. Ваш код кажется довольно сложным для этой проблемы. Проверьте обсуждение здесь для более простой реализации.


public int longestPath() {
    int[] result = longestPath(root);
    return result[0] > result[1] ? result[0] : result[1];
}

// int[] {self-contained, root-to-leaf}
private int[] longestPath(BinaryTreeNode n) {
    if (n == null) {
        return new int[] { 0, 0 };
    }
    int[] left = longestPath(n.left);
    int[] right = longestPath(n.right);

    return new int[] { Util.max(left[0], right[0], left[1] + right[1] + 1),
            Util.max(left[1], right[1]) + 1 };
}

Простая Реализация:

int maxDepth(Node root) {
    if(root == null) {
        return 0;
    } else {
        int ldepth = maxDepth(root.left);
        int rdepth = maxDepth(root.right);
        return ldepth>rdepth ? ldepth+1 : rdepth+1;
    }
}

int longestPath(Node root)
{
   if (root == null)
     return 0;

  int ldepth = maxDepth(root.left);
  int rdepth = maxDepth(root.right);

  int lLongPath = longestPath(root.left);
  int rLongPath = longestPath(root.right);

  return max(ldepth + rdepth + 1, max(lLongPath, rLongPath));
}

вот мое рекурсивное решение на C++:

int longest_dis(Node* root) {
    int height1, height2;

    if( root==NULL)
        return 0;

    if( root->left == NULL ) && ( root->right == NULL )
        return 0;

    height1 = height(root->left); // height(Node* node) returns the height of a tree rooted at node
    height2 = height(root->right);

    if( root->left != NULL ) && ( root->right == NULL )
        return max(height1+1, longest_dis(root->left) );

    if( root->left == NULL ) && ( root->right != NULL )
        return max(height2+1, longest_dis(root->right) );

    return max(height1+height2+2, longest_dis(root->left), longestdis(root->right) );
}

Я думаю, что вы все усложняете.

подумайте о самом длинном пути, который проходит через узел n и не доходит до родителя n. Какова связь между длиной этого пути и высотами обеих подсетей, связанных с n?

после выяснения этого проверьте дерево рекурсивно рассуждая так:

самый длинный путь для поддерева с корнем n является самым длинным путем из следующих трех:

  1. в самый длинный путь в поддереве, корень которого n.left_child
  2. самый длинный путь в поддереве, корнем которого является n.right_child
  3. самый длинный путь, который проходит через узел n и не доходит до родителя n

что делать, если для каждого узла n, вашей целью было вычислить эти два числа:

  • f (n): длина самого длинного пути в дереве с корнем в n
  • h (n): высота дерева, которое коренится в n.

для каждого терминального узла (узлов, имеющих null левый и правый узлы), очевидно, что f и h оба равны 0.

теперь, h каждого узла n - это:

  • 0 если n.left и n.right как null
  • 1 + h (n.left) если только n.left неnull
  • 1 + h (n.right) если только n.right неnull
  • 1 + max (h (n.left), h (n.right)) если оба n.left и n.right неnull

и f (n) составляет:

  • 0, если n.left и n.right как null
  • max (f (n.left), h (n)) если только n.left is неnull
  • ?? если бы только!--9--> неnull
  • ?? если оба n.left и n.right неnull

(вам нужно выяснить, что заменяет эти два "??" подстановки. Есть выбор, который заставляет эту стратегию работать. Я лично проверил его.)

затем, longestPath(Node n) просто f (n):

public class SO3124566
{
    static class Node
    {
        Node left, right;

        public Node()
        {
            this(null, null);
        }

        public Node(Node left, Node right)
        {
            this.left = left;
            this.right = right;
        }
    }

    static int h(Node n)
    {
        // ...
    }

    static int f(Node n)
    {
        // ...
    }

    public static int longestPath(Node n)
    {
        return f(n);
    }

    public static void main(String[] args)
    {
        { // @phimuemue's example
            Node n6 = new Node(),
                n9 = new Node(),
                a = new Node(),
                n7 = new Node(n9, a),
                n4 = new Node(n6, n7),
                b = new Node(),
                n8 = new Node(null, b),
                n5 = new Node(null, n8),
                n2 = new Node(n4, n5),
                n3 = new Node(),
                n1 = new Node(n2, n3);
            assert(longestPath(n1) == 6);
        }{ // @Daniel Trebbien's example: http://pastebin.org/360444
            Node k = new Node(),
                j = new Node(k, null),
                g = new Node(),
                h = new Node(),
                f = new Node(g, h),
                e = new Node(f, null),
                d = new Node(e, null),
                c = new Node(d, null),
                i = new Node(),
                b = new Node(c, i),
                a = new Node(j, b);
            assert(longestPath(a) == 8);
        }



        assert(false); // just to make sure that assertions are enabled.
            // An `AssertionError` is expected on the previous line only.
    }
}

вы должны иметь возможность писать рекурсивные реализации f и h, чтобы этот код работал; однако это решение ужасно неэффективно. Его цель - просто понять расчет.

для повышения эффективности, вы можете использовать memoization или преобразуйте это в нерекурсивный расчет, который использует стек(ы).


Ну, Умм, если я правильно понял ваш вопрос, вот мое решение [но на C++(извините)]:

int h(const Node<T> *root)
{
    if (!root)
        return 0;
    else
        return max(1+h(root->left), 1+h(root->right));
}

void longestPath(const Node<T> *root, int &max)
{
    if (!root)
        return;
    int current = h(root->left) + h(root->right) + 1;
    if (current > max) {
        max = current;
    }
    longestPath(root->left, max);
    longestPath(root->right, max);
}

int longest()
{
    int max = 0;
    longestPath(root, max);
    return max;
}

принимая во внимание пример @phimuemue и решение @IVlad, я решил проверить его сам, поэтому вот моя реализация решения @IVlad в python:

def longestPath(graph,start, path=[]):
    nodes = {}
    path=path+[start]
    for node in graph[start]:
        if node not in path:
            deepestNode,maxdepth,maxpath = longestPath(graph,node,path)
            nodes[node] = (deepestNode,maxdepth,maxpath)
    maxdepth = -1
    deepestNode = start
    maxpath = []
    for k,v in nodes.iteritems():
        if v[1] > maxdepth:
            deepestNode = v[0]
            maxdepth = v[1]
            maxpath = v[2]
    return deepestNode,maxdepth +1,maxpath+[start]

if __name__ == '__main__':
    graph = { '1' : ['2','3'],
              '2' : ['1','4','5'],
              '3' : ['1'],
              '4' : ['2','6','7'],
              '5' : ['2','8'],
              '6' : ['4'],
              '7' : ['4','9','a'],
              '8' : ['5','b'],
              '9' : ['7'],
              'a' : ['7'],
              'b' : ['8']
    }
    """
          1
         / \
        2   3
       / \
      4   5
     / \   \
    6   7   8
       / \   \
      9   a   b
    """
    deepestNode,maxdepth,maxpath = longestPath(graph,'1')
    print longestPath(graph, deepestNode)

>>> ('9', 6, ['9', '7', '4', '2', '5', '8', 'b'])