web-dev-qa-db-fra.com

Algorithme pour imprimer tous les chemins avec une somme donnée dans un arbre binaire

Ce qui suit est une question d'entrevue.

Vous recevez un arbre binaire (pas nécessairement BST) dans lequel chaque nœud contient une valeur . Concevez un algorithme pour imprimer tous les chemins qui totalisent cette valeur . Notez qu'il peut s'agir de n'importe quel chemin dans l'arborescence - il ne doit pas nécessairement commencer à l'origine.

Bien que je puisse trouver tous les chemins dans l’arbre qui commencent à la racine ont la somme indiquée, je ne peux pas le faire pour les chemins qui ne commencent pas à la racine. 

24
hytriutucx

Bien, ceci est un arbre, pas un graphique. Donc, vous pouvez faire quelque chose comme ça:

Pseudocode:

global ResultList

function ProcessNode(CurrentNode, CurrentSum)
    CurrentSum+=CurrentNode->Value
    if (CurrentSum==SumYouAreLookingFor) AddNodeTo ResultList
    for all Children of CurrentNode
          ProcessNode(Child,CurrentSum)

Eh bien, cela vous donne les chemins qui commencent à la racine. Cependant, vous pouvez juste faire un petit changement:

    for all Children of CurrentNode
          ProcessNode(Child,CurrentSum)
          ProcessNode(Child,0)

Vous aurez peut-être besoin d'y penser pendant une seconde (je suis occupé par d'autres choses), mais cela devrait fondamentalement utiliser le même algorithme enraciné à chaque nœud de l'arbre

EDIT: cela ne donne en réalité que le "noeud final". Cependant, comme il s'agit d'un arbre, vous pouvez simplement commencer à ces nœuds d'extrémité et remonter jusqu'à ce que vous obteniez la somme requise.

EDIT 2: et, bien sûr, si toutes les valeurs sont positives, vous pouvez interrompre la descente si votre somme actuelle est> = celle requise

15
Christian Stieber

Voici une réponse O(n + numResults) (essentiellement identique à la réponse @ Quelqu'un, mais avec tous les problèmes résolus):

  1. Effectuez une traversée de l’arbre binaire en pré-ordre, en ordre ou en post-ordre.
  2. Pendant la traversée, maintenez la somme cumulative des valeurs de nœud du nœud racine au nœud situé au-dessus du nœud actuel. Appelons cette valeur cumulativeSumBeforeNode.
  3. Lorsque vous visitez un nœud dans la traversée, ajoutez-le à une hashtable à la clé cumulativeSumBeforeNode (la valeur de cette clé sera une liste de nœuds).
  4. Calcule la différence entre cumulativeSumBeforeNode et la somme cible. Recherchez cette différence dans la table de hachage.
  5. Si la recherche dans la table de hachage réussit, elle devrait générer une liste de noeuds. Chacun de ces nœuds représente le nœud de départ d'une solution. Le noeud actuel représente le noeud final pour chaque noeud de départ correspondant. Ajoutez chaque combinaison [nœud de départ, nœud de fin] à votre liste de réponses. Si la recherche dans la table de hachage échoue, ne faites rien.
  6. Lorsque vous avez fini de visiter un nœud de la traversée, supprimez-le de la liste stockée dans la clé cumulativeSumBeforeNode dans la table de hachage.

Code:

import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.HashMap;
import Java.util.HashSet;
import Java.util.List;
import Java.util.Map;
import Java.util.Set;

public class BinaryTreePathsWithSum {
    public static void main(String[] args) {
        BinaryTreeNode a = new BinaryTreeNode(5);
        BinaryTreeNode b = new BinaryTreeNode(16);
        BinaryTreeNode c = new BinaryTreeNode(16);
        BinaryTreeNode d = new BinaryTreeNode(4);
        BinaryTreeNode e = new BinaryTreeNode(19);
        BinaryTreeNode f = new BinaryTreeNode(2);
        BinaryTreeNode g = new BinaryTreeNode(15);
        BinaryTreeNode h = new BinaryTreeNode(91);
        BinaryTreeNode i = new BinaryTreeNode(8);

        BinaryTreeNode root = a;
        a.left = b;
        a.right = c;
        b.right = e;
        c.right = d;
        e.left = f;
        f.left = g;
        f.right = h;
        h.right = i;

        /*
                5
              /   \
            16     16
              \     \
              19     4
              /
             2
            / \
           15  91
                \
                 8
        */

        List<BinaryTreePath> pathsWithSum = getBinaryTreePathsWithSum(root, 112); // 19 => 2 => 91

        System.out.println(Arrays.toString(pathsWithSum.toArray()));
    }

    public static List<BinaryTreePath> getBinaryTreePathsWithSum(BinaryTreeNode root, int sum) {
        if (root == null) {
            throw new IllegalArgumentException("Must pass non-null binary tree!");
        }

        List<BinaryTreePath> paths = new ArrayList<BinaryTreePath>();
        Map<Integer, List<BinaryTreeNode>> cumulativeSumMap = new HashMap<Integer, List<BinaryTreeNode>>();

        populateBinaryTreePathsWithSum(root, 0, cumulativeSumMap, sum, paths);

        return paths;
    }

    private static void populateBinaryTreePathsWithSum(BinaryTreeNode node, int cumulativeSumBeforeNode, Map<Integer, List<BinaryTreeNode>> cumulativeSumMap, int targetSum, List<BinaryTreePath> paths) {
        if (node == null) {
            return;
        }

        addToMap(cumulativeSumMap, cumulativeSumBeforeNode, node);

        int cumulativeSumIncludingNode = cumulativeSumBeforeNode + node.value;
        int sumToFind = cumulativeSumIncludingNode - targetSum;

        if (cumulativeSumMap.containsKey(sumToFind)) {
            List<BinaryTreeNode> candidatePathStartNodes = cumulativeSumMap.get(sumToFind);

            for (BinaryTreeNode pathStartNode : candidatePathStartNodes) {
                paths.add(new BinaryTreePath(pathStartNode, node));
            }
        }

        populateBinaryTreePathsWithSum(node.left, cumulativeSumIncludingNode, cumulativeSumMap, targetSum, paths);
        populateBinaryTreePathsWithSum(node.right, cumulativeSumIncludingNode, cumulativeSumMap, targetSum, paths);

        removeFromMap(cumulativeSumMap, cumulativeSumBeforeNode);
    }

    private static void addToMap(Map<Integer, List<BinaryTreeNode>> cumulativeSumMap, int cumulativeSumBeforeNode, BinaryTreeNode node) {
        if (cumulativeSumMap.containsKey(cumulativeSumBeforeNode)) {
            List<BinaryTreeNode> nodes = cumulativeSumMap.get(cumulativeSumBeforeNode);
            nodes.add(node);
        } else {
            List<BinaryTreeNode> nodes = new ArrayList<BinaryTreeNode>();
            nodes.add(node);
            cumulativeSumMap.put(cumulativeSumBeforeNode, nodes);
        }
    }

    private static void removeFromMap(Map<Integer, List<BinaryTreeNode>> cumulativeSumMap, int cumulativeSumBeforeNode) {
        List<BinaryTreeNode> nodes = cumulativeSumMap.get(cumulativeSumBeforeNode);
        nodes.remove(nodes.size() - 1);
    }

    private static class BinaryTreeNode {
        public int value;
        public BinaryTreeNode left;
        public BinaryTreeNode right;

        public BinaryTreeNode(int value) {
            this.value = value;
        }

        public String toString() {
            return this.value + "";
        }

        public int hashCode() {
            return Integer.valueOf(this.value).hashCode();
        }

        public boolean equals(Object other) {
            return this == other;
        }
    }

    private static class BinaryTreePath {
        public BinaryTreeNode start;
        public BinaryTreeNode end;

        public BinaryTreePath(BinaryTreeNode start, BinaryTreeNode end) {
            this.start = start;
            this.end = end;
        }

        public String toString() {
            return this.start + " to " + this.end;
        }
    }
}
10
John Kurlak

Basé sur la réponse de Christian ci-dessus:

public void printSums(Node n, int sum, int currentSum, String buffer) {
     if (n == null) {
         return;
     }
     int newSum = currentSum + n.val;
     String newBuffer = buffer + " " + n.val;
     if (newSum == sum) {
         System.out.println(newBuffer);
     }
     printSums(n.left, sum, newSum, newBuffer);
     printSums(n.right, sum, newSum, newBuffer);
     printSums(n.left, sum, 0, "");
     printSums(n.right, sum, 0, "");
} 

printSums(root, targetSum, 0, "");
9
mawaldne

Voici une approche avec une complexité nlogn.

  1. Traverser l'arbre avec inorder.
  2. Dans le même temps, conservez tous les nœuds avec la somme cumulée dans un Hashmap<CumulativeSum, reference to the corresponding node>.
  3. Maintenant, à un nœud donné, calculez la somme cumulative de la racine jusqu'à ce que le nœud dise ceci: SUM.
  4. Recherchez maintenant la valeur SUM-K dans la HashMap.
  5. Si l'entrée existe, prenez la référence de noeud correspondante dans la variable HashMap.
  6. Nous avons maintenant un chemin valide de la référence du noeud au noeud actuel. 
3
Somebody

Une solution propre en Java. Utilisation d'appels récursifs internes pour garder une trace des chemins traversés. 

private static void pathSunInternal(TreeNode root, int sum, List<List<Integer>> result, List<Integer> path){
    if(root == null)
        return;     
    path.add(root.val);
    if(sum == root.val && root.left == null && root.right == null){
        result.add(path);
    }

    else if(sum != root.val && root.left == null && root.right == null)
        return;
    else{
        List<Integer> leftPath = new ArrayList<>(path);
        List<Integer> rightPath = new ArrayList<>(path);
        pathSunInternal(root.left, sum - root.val, result, leftPath);
        pathSunInternal(root.right, sum - root.val, result, rightPath);
    }
}

public static List<List<Integer>> pathSum(TreeNode root, int sum) {
    List<List<Integer>> result = new ArrayList<>(); 
    List<Integer> path = new ArrayList<>();
    pathSunInternal(root, sum, result, path);       
    return result;
}
3
Himangshu

Mise à jour: Je vois maintenant que ma réponse ne répond pas directement à votre question. Je le laisserai ici si cela s’avère utile, mais il n’est pas nécessaire de l’avancer. Si ce n'est pas utile, je vais l'enlever. Je suis d'accord avec @nhahtdh, cependant, quand il conseille, "Réutilisez votre algorithme avec tous les autres nœuds en tant que root."

On soupçonne que l'intervieweur est en train de pêcher récursion ici. Ne le déçois pas!

Avec un nœud, votre routine devrait s’appeler elle-même contre chacun de ses nœuds enfants, le cas échéant, puis ajouter la donnée propre du nœud aux valeurs de retour, puis renvoyer la somme.

Pour obtenir un crédit supplémentaire, avertissez l'intervieweur que votre routine peut échouer, en entrant une récursion sans fond et sans fin, si elle est utilisée sur un graphique général plutôt que sur un arbre binaire.

2
thb

Nous pouvons le résoudre avec la programmation dynamique arborescente, et la complexité spatio-temporelle est égale à O (n ^ 2), où n est le nombre de tous les nœuds de l’arbre.

L'idée est la suivante:

Pour un nœud d’arbre, nous conservons un ensemble enregistrant toutes les sommes possibles, de u à tous ses descendants. Ensuite, de manière récursive, l’ensemble de nœuds peut être mis à jour par ses deux enfants, notamment en fusionnant les ensembles de deux enfants.

Le pseudocode est:

bool findPath(Node u, Set uset, int finalSum) {
    Set lset, rset;
    if (findPath(u.left, lset, finalSum) || findPath(u.right, rset, finalSum)) return true;
    for (int s1 : lset) {
        if (finalSum - u.val - s1 == 0 || rset.contains(finalSum - u.val - s1)) return true;
        // first condition means a path from u to some descendant in u's left child
        // second condition means a path from some node in u's left child to some node in u's right child

        uset.insert(s1 + u.val); // update u's set
    }
    for (int s2 : rset) {
        if (finalSum - u.val - s2 == 0) return true;
        // don't forget the path from u to some descendant in u's right child
        uset.insert(s2 + u.val); // update u's set
    }
    return false;
}

Je remarque que la question initiale est de trouver tous les chemins, mais l'algorithme ci-dessus consiste à déterminer s'il existe ou non. Je pense que l'idée est similaire, mais cette version rend le problème plus facile à expliquer :)

1
Qian Chen

On peut réduire cet arbre à un graphe pondéré G, où chaque arête Pond = somme des valeurs dans chacun de ses nœuds.

Ensuite, exécutez l’algorithme de Floyd-Warshall sur le graphique G. En inspectant les éléments de la matrice résultante, vous pouvez obtenir toutes les paires de nœuds entre lesquelles la somme totale est égale à la somme souhaitée.

Notez également que le chemin le plus court donné par l'algorithme est également le seul chemin entre 2 nœuds de cet arbre.

Ceci est juste une autre approche, pas aussi efficace qu'une approche récursive.

1
Guru Devanla
public void printPath(N n) {
    printPath(n,n.parent);
}

private void printPath(N n, N endN) {
    if (n == null)
        return;
    if (n.left == null && n.right == null) {
        do {
            System.out.print(n.value);
            System.out.print(" ");
        } while ((n = n.parent)!=endN);
        System.out.println("");
        return;
    }
    printPath(n.left, endN);
    printPath(n.right, endN);
}

Vous pouvez imprimer le chemin de l’arbre et le nœud n. comme ceci printPath (n);

0
boiledwater
void printpath(int sum,int arr[],int level,struct node * root)
{
  int tmp=sum,i;
  if(root == NULL)
  return;
  arr[level]=root->data;
  for(i=level;i>=0;i--)
  tmp-=arr[i];
  if(tmp == 0)
  print(arr,level,i+1);
  printpath(sum,arr,level+1,root->left);
  printpath(sum,arr,level+1,root->right);
}
 void print(int arr[],int end,int start)
{  

int i;
for(i=start;i<=end;i++)
printf("%d ",arr[i]);
printf("\n");
}

complexité (n logn) Complexité de l'espace (n)

0
aman_41907

Vous trouverez ci-dessous la solution utilisant la récursion . Nous effectuons une traversée dans l’ordre de l’arbre binaire, au fur et à mesure que nous descendons d’un niveau, nous additionnons le poids total du chemin en ajoutant le poids du niveau actuel aux poids des niveaux précédents du arbre, si nous atteignons notre somme, nous imprimons ensuite le chemin. Cette solution gérera les cas où nous pourrions avoir plus d'une solution le long d'un chemin d'accès donné.

Supposons que vous avez un arbre binaire enraciné à la racine.

#include <iostream>
#include <vector>
using namespace std;

class Node
{
private:
    Node* left;
    Node* right;
    int value;

public:
    Node(const int value)
    {
        left=NULL;
        right=NULL;
        this->value=value;
    }

    void setLeft(Node* left)
    {
        this->left=left;
    }

    void setRight(Node* right)
    {
        this->right = right;
    }

    Node* getLeft() const
    {
        return left;
    }

    Node* getRight() const
    {
        return right;
    }

    const int& getValue() const
    {
        return value;
    }
};

//get maximum height of the tree so we know how much space to allocate for our
//path vector

int getMaxHeight(Node* root)
{
    if (root == NULL)
        return 0;

    int leftHeight = getMaxHeight(root->getLeft());
    int rightHeight = getMaxHeight(root->getRight());

    return max(leftHeight, rightHeight) + 1;
}

//found our target sum, output the path
void printPaths(vector<int>& paths, int start, int end)
{
    for(int i = start; i<=end; i++)
        cerr<<paths[i]<< " ";

    cerr<<endl;
}

void generatePaths(Node* root, vector<int>& paths, int depth, const int sum)
{
    //base case, empty tree, no path
    if( root == NULL)
        return;

    paths[depth] = root->getValue();
    int total =0;

    //sum up the weights of the nodes in the path traversed
    //so far, if we hit our target, output the path
    for(int i = depth; i>=0; i--)
    {
        total += paths[i];
        if(total == sum)
            printPaths(paths, i, depth);
    }

    //go down 1 level where we will then sum up from that level
    //back up the tree to see if any sub path hits our target sum
    generatePaths(root->getLeft(), paths, depth+1, sum);
    generatePaths(root->getRight(), paths, depth+1, sum);
}

int main(void)
{
    vector<int> paths (getMaxHeight(&root));
    generatePaths(&root, paths, 0,0);
}

la complexité de l’espace dépend de la hauteur de l’arbre, alors qu’il s’agit d’un arbre équilibré, la complexité de l’espace est égale à 0 (log n) en fonction de la profondeur de la pile de récursion . Complexité temporelle O (n Log n) - basée sur un Arbre équilibré où il y a n nœuds à chaque niveau et à chaque niveau n travail sera fait (somme des chemins). Nous savons également que la hauteur de l’arbre est délimitée par O (log n) pour un arbre binaire équilibré. N quantité de travail effectuée pour chaque niveau sur un arbre binaire équilibré donne donc un temps d’exécution de O (n log n).

0
gilla07
# include<stdio.h>
# include <stdlib.h>
struct Node
{
    int data;
    struct Node *left, *right;
};

struct Node * newNode(int item)
{
    struct Node *temp =  (struct Node *)malloc(sizeof(struct Node));
    temp->data = item;
    temp->left =  NULL;
    temp->right = NULL;
    return temp;
}
void print(int p[], int level, int t){
    int i;
    for(i=t;i<=level;i++){
        printf("\n%d",p[i]);
    }
}
void check_paths_with_given_sum(struct Node * root, int da, int path[100], int level){

     if(root == NULL)
        return ;
    path[level]=root->data;
    int i;int temp=0;
    for(i=level;i>=0;i--){
        temp=temp+path[i];
        if(temp==da){
            print(path,level,i);
        }
    }
        check_paths_with_given_sum(root->left, da, path,level+1);
        check_paths_with_given_sum(root->right, da, path,level+1);

}
int main(){
    int par[100];
 struct Node *root = newNode(10);
    root->left = newNode(2);
    root->right = newNode(4);
    root->left->left = newNode(1);
    root->right->right = newNode(5);
    check_paths_with_given_sum(root, 9, par,0);


}

Cela marche.....

0
bulbasaur

Puisque nous avons besoin des chemins ayant la somme == k ..__, je suppose que la complexité maximale peut être O (total_paths_in_tree).

Alors pourquoi ne pas générer chaque chemin et vérifier la somme, c’est quand même un arbre avec des nombres négatifs et même pas un arbre de recherche binaire.

    struct node{
      int val;
      node *left,*right;

      node(int vl)
      {
        val = vl;
        left = NULL;
        right = NULL;
      }
   };


   vector<vector<int> > all_paths;
   vector<vector<int> > gen_paths(node* root)
   {
       if(root==NULL)
       {
          return vector<vector<int> > ();
       }

       vector<vector<int> >    left_paths = gen_paths(root->left);
       vector<vector<int> >    right_paths = gen_paths(root->right);

       left_paths.Push_back(vector<int> ()); //empty path
       right_paths.Push_back(vector<int> ());

       vector<vector<int> > paths_here;
       paths_here.clear();


       for(int i=0;i<left_paths.size();i++)
       {
           for(int j=0;j<right_paths.size();j++)
           {
              vector<int> vec;
              vec.clear();
              vec.insert(vec.end(), left_paths[i].begin(), left_paths[i].end());
             vec.Push_back(root->val);
             vec.insert(vec.end(), right_paths[j].begin(), right_paths[j].end());
             paths_here.Push_back(vec);
           }
        }

        all_paths.insert(all_paths.end(),paths_here.begin(),paths_here.end());

       vector<vector<int> > paths_to_extend;
       paths_to_extend.clear();

       for(int i=0;i<left_paths.size();i++)
       {
            paths_to_extend.Push_back(left_paths[i]);
            paths_to_extend[i].Push_back(root->val);
       }

       for(int i=0;i<right_paths.size();i++)
       {
           paths_to_extend.Push_back(right_paths[i]);
           paths_to_extend[paths_to_extend.size()-1].Push_back(root->val);
       }

       return paths_to_extend;
    }

Pour générer des chemins, j’ai généré tous les chemins de gauche et tous les chemins de droite Et j’ai ajouté les chemins left_paths + node-> val + right_paths à all_paths à chaque noeud. Et ont envoyé les chemins qui peuvent encore être étendus .i.e tous les chemins des deux côtés + noeud.

0
dhruvsharma

J'ai amélioré la logique de codage de la réponse d'Arvind Upadhyay. Une fois la boucle if effectuée, vous ne pouvez plus utiliser la même liste. Donc, besoin de créer la nouvelle liste. En outre, il est nécessaire de conserver le nombre de niveaux pour lesquels la logique passe du nœud actuel au chemin de recherche. Si nous ne trouvons pas de chemin, alors avant d'aller voir ses enfants, nous devons sortir de l'appel récursif pour qu'il soit égal au nombre de fois.

int count =0;
public void printAllPathWithSum(Node node, int sum, ArrayList<Integer> list)
{   
    if(node == null)
        return;
    if(node.data<=sum)
    {
        list.add(node.data);
        if(node.data == sum)
            print(list);
        else
        {
            count ++;
            printAllPathWithSum(node.left, sum-node.data, list);
            printAllPathWithSum(node.right, sum-node.data, list);
            count --;
        }
    }
    if(count != 0)
        return ;


    printAllPathWithSum(node.left, this.sum, new ArrayList());
    if(count != 0)
        return;
    printAllPathWithSum(node.right, this.sum, new ArrayList());

}
public void print(List list)
{
    System.out.println("Next path");
    for(int i=0; i<list.size(); i++)
        System.out.print(Integer.toString((Integer)list.get(i)) + " ");
    System.out.println();
}

Vérifiez le code complet à l’adresse: https://github.com/ganeshzilpe/Java/blob/master/Tree/BinarySearchTree.Java

0
GaNeSh
// assumption node have integer value other than zero
void printAllPaths(Node root, int sum , ArrayList<Integer> path) {

   if(sum == 0) {
      print(path); // simply print the arraylist
    }

   if(root ==null) {
     //traversed one end of the tree...just return
      return;
  }
    int data = root.data;
    //this node can be at the start, end or in middle of path only if it is       //less than the sum
    if(data<=sum) {
     list.add(data);
     //go left and right
    printAllPaths(root.left, sum-data ,  path);
    printAllPaths(root.right, sum-data ,  path);

    }
   //note it is not else condition to ensure root can start from anywhere
   printAllPaths(root.left, sum ,  path);
   printAllPaths(root.right, sum ,  path);
}
0
Arvind Upadhyay

https://codereview.stackexchange.com/questions/74957/find-all-the-paths-of-tree-that-add-to-a-input-value

J'ai tenté une réponse, dans l'attente de la révision du code. Mon code ainsi que les relecteurs devraient être une source utile.

0
JavaDeveloper

Chercher:

Recursively traverse the tree, comparing with the input key, as in binary search tree.

If the key is found, move the target node (where the key was found) to the root position using splaysteps.

Pseudocode:


Algorithm: search (key)
Input: a search-key
1.   found = false;
2.   node = recursiveSearch (root, key)
3.   if found
4.     Move node to root-position using splaysteps;
5.     return value
6.   else
7.     return null
8.   endif
Output: value corresponding to key, if found.



Algorithm: recursiveSearch (node, key)
Input: tree node, search-key
1.   if key = node.key
2.     found = true
3.     return node
4.   endif
     // Otherwise, traverse further 
5.   if key < node.key
6.     if node.left is null
7.       return node
8.     else
9.       return recursiveSearch (node.left, key)
10.    endif
11.  else
12.    if node.right is null
13.      return node
14.    else
15.      return recursiveSearch (node.right, key)
16.    endif
17.  endif
Output: pointer to node where found; if not found, pointer to node for insertion.
0
JULIUS MWANGI