2016-04-03 4 views
3

Так что найти самый длинный путь между двумя узлами в дереве довольно просто. Но я хочу найти самый длинный путь от узла x к другому узлу в дереве, для всех x.Алгоритм - максимальное расстояние в дереве для всех узлов

Эта проблема также может быть выражена следующим образом: вычислить высоты всех корневых деревьев, которые вы можете создать из данного дерева.

Один из способов, конечно, состоит в том, чтобы просто сделать для всех узлов в дереве BFS/DFS и запомнить для каждого из них самый удаленный узел. Однако это приводит к O (N). Можно ли сделать лучше?

EDIT: просто чтобы напомнить, мой вопрос: NOT как найти самый длинный путь на графике. Это как найти самый длинный путь, содержащий данный узел xДЛЯ ВСЕХ узлов x в ЛУЧШЕ O (N 2) сложностей времени, если это возможно.

+0

Возможная Дубликат [Самый длинный путь между узлами 2] (http://stackoverflow.com/questions/3124566/longest-path-between-2-nodes) – Guido

+1

@Guido Nope, это связанная, но другая проблема. –

+0

Вы хотите найти N таких путей (по одному для каждого узла) и иметь каждый такой путь, представленный его конечными точками? – pkacprzak

ответ

0

Ваш вопрос сводится к поиску самого длинного пути в дереве. Это также называется диаметром дерева.

Это хорошо изученная тема, и есть много ресурсов, которые обеспечивают алгоритм O (n), где n - количество узлов на графике.

См this и this

+0

Не могли бы вы объяснить, как он сводится к самому длинному пути в дереве? Я уже знаю, как найти диаметр дерева, но это не то, что я спросил. Я начал свой вопрос: «Итак, найти самый длинный путь между двумя узлами в дереве довольно просто, но я хочу ...» –

1

Да, есть (п) алгоритм вывода.

Подумайте о том, что дерево не тронуто - просто график, где каждый узел имеет двунаправленные ребра, которые не образуют циклов.

Для данного узла p с соседними узлами, скажем a_i, мы вычислим высоты Hpa_i. Высота Hpa_i - это высота поддерева с корнем p (т. Е. Для этой части алгоритма мы временно рассматриваем корневое поддерево), полученное путем рассмотрения узла a_i как родителя p.

Если вас интересует самый длинный путь от каждого узла до листа (ваш вопрос плюс его название оставляет сомнение в том, что вы на самом деле пытаетесь вычислить), это просто max {Hpa_i для всех i}. Соответствующее значение i дает самый длинный путь.

Если с другой стороны, вы заинтересованы в длинном пути через р, что будет суммой наибольших пар, выбранных из {LEN (р - a_i) + Ha_ip для всех я}, и два соответствующих значения i дают самый длинный путь.

Таким образом, если у нас есть высоты для каждого узла, получение конечного результата - это простое задание O (n).

Осталось только вычислить высоты для всех узлов. Для этого начните с специального поиска по глубине. Он принимает 2 узла в качестве параметров. Первый, p, - это поиск узла, а второй q \ in {a_i} - соседний узел, который в настоящее время считается родительским для p. Пусть U будет отображение, пар узлов до высоты: (P, Q) -> Hpq

function search_and_label(p, q) 
    if ((p, q) maps to height Hpq in U) { return Hpq } 
    if (p == null) { add (p, q) -> 0 to U and return 0 } 
    let h = max(all x adjacent to p, not equal to q) { 
      len(p--x) + search_and_label(x, p) 
      } 
    add (p, q) -> h to U 
    return h 

Теперь мы можем найти все вершины.

Add mappings (p, x)->null to U for all nodes p and adjacent nodes x 
Also add a mapping (p, z)->null to U for all nodes p having < 3 adjacent 
while (U contains a mapping of the form (p, x)->null) 
    search_and_label(p, x) // this replaces the null mapping with a height 

Это будет О (п) вычисление, кроме того, потому что она затрачивает постоянную работу по каждому краю, и число ребер в дереве равно п-1.

Код

Шел дождь сегодня, так вот код, который генерирует случайное дерево и маркирует его с длинной информацией пути в O (N) времени. Во-первых, типичный выход. Каждый узел имеет свой собственный номер, а затем длину самого длинного пути, который содержит его, за которым следуют номера соседних узлов на этом пути. Метка маленького края - это информация о высоте. Во-первых есть высота противоположного поддерева вместе с узлом, самый длинный путь к листу в этом поддереве:

Decorated Tree

import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.PrintStream; 
import java.util.HashMap; 
import java.util.Map; 
import java.util.Random; 

/** 
* An undirected graph. It has a builder that fills the graph with a random 
* unrooted tree. And it knows how to decorate itself with longest path 
* information when it's such a tree. 
*/ 
class Graph { 

    /** 
    * Edge p--q is represented as edges[p][q]=dq and edges[q][p]=dp, where dq and 
    * dp are node data. They describe the respective end of the edge: 
    * <ul> 
    * <li>dq.len == dp.len, the edge length 
    * <li>dq.h is the height of subtree rooted at p with q as parent. 
    * <li>dq.next is the child of p (with respect to parent q) rooting the max 
    * height subtree. 
    * </ul> 
    */ 
    final Map<Node, Map<Node, EdgeData>> edges = new HashMap<>(); 

    /** 
    * A node in the graph. 
    */ 
    static class Node { 

    final int id; // Unique node id. 
    Node a, b; // Adjacent nodes on longest path. 
    double len; // Length of longest path. 

    Node(int i) { 
     this.id = i; 
    } 
    } 

    /** 
    * Data associated with one end of an edge in the graph. 
    */ 
    static class EdgeData { 

    final double len; // Edge length. 
    Double h;   // Subtree height. 
    Node next;   // Next node on max length path to leaf. 

    EdgeData(double len) { 
     this.len = len; 
    } 
    } 

    /** 
    * Add a new node to the graph and return it. 
    */ 
    Node addNode() { 
    Node node = new Node(currentNodeIndex++); 
    edges.put(node, new HashMap<>()); 
    return node; 
    } 
    private int currentNodeIndex = 0; 

    /** 
    * Add an undirected edge between nodes x and y. 
    */ 
    void addEdge(Node x, Node y, double len) { 
    edges.get(x).put(y, new EdgeData(len)); 
    edges.get(y).put(x, new EdgeData(len)); 
    } 

    /** 
    * Decorate subtree rooted at p assuming adjacent node q is its parent. 
    * Decorations are memos. No subtree is decorated twice. 
    */ 
    EdgeData decorateSubtree(Node p, Node q) { 
    Map<Node, EdgeData> adjacent = edges.get(p); 
    EdgeData data = adjacent.get(q); 
    if (data.h == null) { 
     data.h = 0.0; 
     for (Map.Entry<Node, EdgeData> x : adjacent.entrySet()) { 
     if (x.getKey() != q) { 
      double hNew = x.getValue().len + decorateSubtree(x.getKey(), p).h; 
      if (hNew > data.h) { 
      data.h = hNew; 
      data.next = x.getKey(); 
      } 
     } 
     } 
    } 
    return data; 
    } 

    /** 
    * Decorate node p with longest path information. Decorations are memos. No 
    * node nor its associated subtrees are decorated twice. 
    */ 
    Node decorateNode(Node p) { 
    if (p.a == null) { 
     double ha = 0.0, hb = 0.0; 
     for (Map.Entry<Node, EdgeData> x : edges.get(p).entrySet()) { 
     double hNew = x.getValue().len + decorateSubtree(x.getKey(), p).h; 
     // Track the largest two heights and corresponding subtrees. 
     if (hNew > ha) { 
      p.b = p.a; 
      hb = ha; 
      p.a = x.getKey(); 
      ha = hNew; 
     } else if (hNew > hb) { 
      p.b = x.getKey(); 
      hb = hNew; 
     } 
     } 
     p.len = ha + hb; 
    } 
    return p; 
    } 

    /** 
    * Decorate the entire tree. This isn't necessary if the lazy decorators are 
    * used as accessors. 
    */ 
    void decorateAll() { 
    for (Node p : edges.keySet()) { 
     decorateNode(p); 
    } 
    } 

    /** 
    * Random tree builder. Parameters are a maximum edge length, tree radius in 
    * number of edges, and partitions p[k] giving probabilities of branching with 
    * degree k. 
    */ 
    class RandomTreeBuilder { 

    final Random gen = new Random(); 
    final long seed; 
    final float[] partitions; 
    final int maxLen; 
    final int radius; 

    RandomTreeBuilder(long seed, float[] partitions, int maxLen, int radius) { 
     this.seed = seed; 
     this.partitions = partitions; 
     this.maxLen = maxLen; 
     this.radius = radius; 
    } 

    private void growTree(Node p, int radius) { 
     if (radius > 0) { 
     float random = gen.nextFloat(); 
     float pSum = 0f; 
     for (float partition : partitions) { 
      pSum += partition; 
      if (random < pSum) { 
      return; 
      } 
      Node q = addNode(); 
      addEdge(p, q, 1 + gen.nextInt(maxLen)); 
      growTree(q, radius - 1); 
     } 
     } 
    } 

    /** 
    * Build a tree in the graph. Any existing nodes and edges are erased. 
    */ 
    void build() { 
     if (seed != 0) { 
     gen.setSeed(seed); 
     } 
     edges.clear(); 
     Node p = addNode(); 
     Node q = addNode(); 
     addEdge(p, q, 1 + gen.nextInt(maxLen)); 
     growTree(p, radius); 
     growTree(q, radius); 
    } 
    } 

    class TreePrinter { 

    PrintStream stream; 

    TreePrinter(PrintStream stream) { 
     this.stream = stream; 
    } 

    /** 
    * Print graph in the GraphViz DOT language. 
    */ 
    void print() { 
     stream.println("graph tree {"); 
     stream.println(" graph [layout = twopi overlap=false ranksep=1.7]"); 
     Node p = edges.keySet().iterator().next(); 
     Node q = edges.get(p).keySet().iterator().next(); 
     printEdge(p, q); 
     print(p, q); 
     print(q, p); 
     for (Node x : edges.keySet()) { 
     printNode(decorateNode(x)); 
     } 
     stream.println("}"); 
    } 

    /** 
    * Print edge {@code p--q} in the GraphViz DOT language. 
    */ 
    private void printEdge(Node p, Node q) { 
     EdgeData dq = decorateSubtree(p, q); 
     EdgeData dp = decorateSubtree(q, p); 
     stream.format(" n%d--n%d [label=\"%.0f\" fontsize=8 " 
      + "headlabel=\"%.0f:%s\" taillabel=\"%.0f:%s\"]\n", 
      p.id, q.id, dq.len, 
      dp.h, dp.next == null ? "-" : dp.next.id, 
      dq.h, dq.next == null ? "-" : dq.next.id); 
    } 

    /** 
    * Print node p in the GraphViz DOT language. 
    */ 
    private void printNode(Node p) { 
     stream.format(" n%d [ label=\"%d (%.0f:%s-%s)\" fontsize=10 ]\n", 
      p.id, p.id, p.len, 
      p.a == null ? "-" : p.a.id, p.b == null ? "-" : p.b.id); 
    } 

    /** 
    * Print the sub-tree rooted at node p, treating node q as its parent. 
    */ 
    private void print(Node p, Node q) { 
     for (Node x : edges.get(p).keySet()) { 
     if (x != q) { 
      printEdge(p, x); 
      print(x, p); 
     } 
     } 
    } 
    } 

    public static void main(String[] args) throws FileNotFoundException { 
    PrintStream stream = args.length > 0 
     ? new PrintStream(new File(args[0])) 
     : System.out; 
    Graph graph = new Graph(); 
    graph.new RandomTreeBuilder(42L, new float[]{0.3f, 0.1f, 0.3f, 0.2f}, 10, 5) 
     .build(); 
    graph.new TreePrinter(stream).print(); 
    } 
} 
+0

Является ли len (p - a) длиной ребра (p, а)? Если да, то я думаю, что «самая большая пара, выбранная из {len (p - a) + Hap, len (p - b) + Hbp, len (p - c) + Hcp}", не то, что вы ищет, так как, например,первый член, «len (p - a) + Hap», кажется, описывает длину дерева * * (а не путь), который состоит из пути длины Hap, проходящего через вершины b, p и c , плюс ребро (p, a) (так что вершина p имеет степень 3). –

+1

@j_random_hacker Hap - это высота поддерева, внедренного на (то есть самый длинный путь от a до листа, который не использует ребро a-p). Добавление len (p - a) дает общее максимальное расстояние до листа, начиная с p и используя ребро (p - a). Я напишу код, если у меня будет время. – Gene

+0

Спасибо! Теперь я вижу, что я запутался в Hpa с Хапом. Я думаю, что ваши решения работают над решением как формы проблемы, где мы хотим найти самый длинный путь *, содержащий * каждую вершину x, и просто выбрав самый большой из 3 элементов в этом наборе (вместо суммы наибольшего 2), это решило бы версию, где мы хотим, чтобы самый длинный путь * начало * в каждой вершине x (OP не ясен, какой он/она хочет). :) –

0

Вот мое решение ..

Первого проход рекурсивно перейти по всем узлам и установите M (максимальная глубина) для каждого узла в дереве.

M(X) = 0 IF X DOES NOT EXIST 
EXIST(X) = 1 IF X EXISTS, 0 OTHERWISE 

M = MAX(EXIST(LEFT) + M(LEFT), EXIST(RIGHT) + M(RIGHT)) 

Второй проход рекурсивно перейти на все узлы и установить R (максимальное расстояние через) любой корень в дереве (корень является узлом, который имеет, по меньшей мере, 1 ребенок), беря сумму из max 2 значения от детей, а затем добавление расстояния пути, если оно существует на любом ребенке.

IF SUM(EXIST(..)) = 0 THEN R = 0 
IF SUM(EXIST(..)) = 1 THEN R = X.M + 1 WHERE EXIST(X) = 1 

R = SUM(MAX2(x,y: x.M >= y.M >= ..)) + EXIST(x) + EXIST(y) 

R: the node max distance through. 
M: the node max depth. 

Final Pass рекурсивно перебрать все узлы и найти максимальное значение R в дереве.

R(NULL) = 0 
R(THIS) = R OF THE CURRENT NODE 

S = MAX(R(THIS), S(CHILD1), .. S(CHILDX)) 

Сложность

TIME = N + N + N = 3N 

TIME ~ O(N) 
+0

Вы, кажется, пытаетесь вычислить общий длинный путь в дереве, который OP делает ясно, о котором он не просит. Чтобы адаптировать это, чтобы найти самый длинный путь, начинающийся с каждого узла x (или содержащий каждый узел x, OP здесь неясно), я думаю, вам также нужно рассмотреть пути, которые возвращаются через родительский узел. –

+0

@j_random_hacker, вы правы, я неправильно понял. это будет сложнее, мне нужно больше времени, чтобы переосмыслить его и попытаться найти что-то общее между узлами, чтобы я мог поместить его в O (n). Я уже учитываю связь с родителем, выполняя '+ EXIST (x)' –