web-dev-qa-db-fra.com

Successeur dans l'ordre dans l'arborescence de recherche binaire

Étant donné un nœud dans un BST, comment trouve-t-on la clé supérieure suivante?

25
shreyasva

La manière générale dépend du fait que vous ayez ou non un lien parent dans vos nœuds.

Si vous stockez le lien parent

Ensuite, vous choisissez:

  1. L'enfant le plus à gauche de l'enfant de droite, si votre nœud actuel a un enfant de droite. Si l'enfant droit n'a pas d'enfant gauche, l'enfant droit est votre successeur inorder.
  2. Naviguez vers le haut des nœuds ancêtres parents et lorsque vous trouvez un parent dont l'enfant gauche est le nœud où vous êtes actuellement, le parent est le successeur inorder de votre nœud d'origine.

Si vous avez un bon enfant, suivez cette approche (cas 1 ci-dessus):

inorder-when-right-child

Si vous n'avez pas un bon enfant, suivez cette approche (cas 2 ci-dessus):

inorder-when-no-right-child

Si vous ne stockez pas le lien parent

Ensuite, vous devez exécuter une analyse complète de l'arborescence, en gardant une trace des nœuds, généralement avec une pile, afin d'avoir les informations nécessaires pour faire essentiellement la même chose que la première méthode qui reposait sur le lien parent.

68
Lasse V. Karlsen

Code Python aux Lasse's réponse :

def findNext(node):
  if node.rightChild != None:
    return findMostLeft(node.rightChild)
  else:
    parent = node.parent
    while parent != None:
      if parent.leftChild == node:
        break
      node = parent
      parent = node.parent
    return parent
7
Vitalii Fedorenko

Vérifiez ici: Successeur InOrder dans un arbre de recherche binaire

Dans l'Arbre Binaire, le successeur Inorder d'un nœud est le nœud suivant dans la traversée Inorder de l'Arbre Binaire. Inorder Successor est NULL pour le dernier nœud de la traversée Inoorder. Dans l'arbre de recherche binaire, Inorder Successor d'un nœud d'entrée peut également être défini comme le nœud avec la plus petite clé supérieure à la clé du nœud d'entrée.

2
sgokhales

nous n'avons pas besoin d'un lien parent ou d'une pile pour trouver le successeur dans l'ordre dans O (log n) (en supposant un arbre équilibré). Conservez une variable temporaire avec la valeur la plus récente rencontrée dans la traversée dans l'ordre qui est supérieure à la clé. si la traversée inordonnée trouve que le nœud n'a pas un enfant droit, alors ce serait le successeur inordre. sinon, le descendant le plus à gauche de l'enfant de droite.

2
Aman

Voici une implémentation sans avoir besoin de liens parents ou de structures intermédiaires (comme une pile). Cette fonction successeur dans l'ordre est un peu différente de ce que la plupart pourraient rechercher puisqu'elle opère sur la clé par opposition au nœud. En outre, il trouvera un successeur d'une clé même si elle n'est pas présente dans l'arborescence. Pas trop difficile à changer si nécessaire, cependant.

public class Node<T extends Comparable<T>> {

private T data;
private Node<T> left;
private Node<T> right;

public Node(T data, Node<T> left, Node<T> right) {
    this.data = data;
    this.left = left;
    this.right = right;
}

/*
 * Returns the left-most node of the current node. If there is no left child, the current node is the left-most.
 */
private Node<T> getLeftMost() {
    Node<T> curr = this;
    while(curr.left != null) curr = curr.left;
    return curr;
}

/*
 * Returns the right-most node of the current node. If there is no right child, the current node is the right-most.
 */
private Node<T> getRightMost() {
    Node<T> curr = this;
    while(curr.right != null) curr = curr.right;
    return curr;
}

/**
 * Returns the in-order successor of the specified key.
 * @param key The key.
 * @return
 */
public T getSuccessor(T key) {
    Node<T> curr = this;
    T successor = null;
    while(curr != null) {
        // If this.data < key, search to the right.
        if(curr.data.compareTo(key) < 0 && curr.right != null) {
            curr = curr.right;
        }
        // If this.data > key, search to the left.
        else if(curr.data.compareTo(key) > 0) { 
            // If the right-most on the left side has bigger than the key, search left.
            if(curr.left != null && curr.left.getRightMost().data.compareTo(key) > 0) {
                curr = curr.left;
            }
            // If there's no left, or the right-most on the left branch is smaller than the key, we're at the successor.
            else {
                successor = curr.data;
                curr = null;
            }
        }
        // this.data == key...
        else {
            // so get the right-most data.
            if(curr.right != null) {
                successor = curr.right.getLeftMost().data;
            }
            // there is no successor.
            else {
                successor = null;
            }
            curr = null;
        }
    }
    return successor;
}

public static void main(String[] args) {
    Node<Integer> one, three, five, seven, two, six, four;
    one = new Node<Integer>(Integer.valueOf(1), null, null);
    three = new Node<Integer>(Integer.valueOf(3), null, null);
    five = new Node<Integer>(Integer.valueOf(5), null, null);
    seven = new Node<Integer>(Integer.valueOf(7), null, null);
    two = new Node<Integer>(Integer.valueOf(2), one, three);
    six = new Node<Integer>(Integer.valueOf(6), five, seven);
    four = new Node<Integer>(Integer.valueOf(4), two, six);
    Node<Integer> head = four;
    for(int i = 0; i <= 7; i++) {
        System.out.println(head.getSuccessor(i));
    }
}
}
2
tandoc

Avec l'arbre de recherche binaire, l'algorithme pour trouver le nœud le plus haut suivant d'un nœud donné consiste essentiellement à trouver le nœud le plus bas du sous-arbre droit de ce nœud.

L'algorithme peut simplement être simplement:

  1. Commencez avec le bon enfant du nœud donné (faites-en le nœud actuel temporaire)
  2. Si le nœud actuel n'a plus d'enfant gauche, il s'agit du nœud supérieur suivant.
  3. Si le nœud actuel a un enfant gauche, faites-en le nœud actuel.

Répétez 2 et 3 jusqu'à ce que nous trouvions le prochain nœud le plus haut.

2
vutbao

Si nous effectuons une traversée dans l'ordre, nous visitons le sous-arbre gauche, puis le nœud racine et enfin le sous-arbre droit pour chaque nœud de l'arbre. Effectuer une traversée dans l'ordre nous donnera les clés d'un arbre de recherche binaire dans l'ordre croissant, donc lorsque nous nous référons à la récupération du successeur dans l'ordre d'un nœud appartenant à un arbre de recherche binaire, nous voulons dire quel serait le nœud suivant dans la séquence de le nœud donné.

Disons que nous avons un nœud R et que nous voulons que son successeur soit dans les cas suivants.

[1] La racine R a un nœud droit, donc tout ce que nous devons faire est de traverser le nœud le plus à gauche de R-> droite.

[2] La racine R n'a pas de nœud droit, dans ce cas, nous remontons l'arborescence en suivant les liens parent jusqu'à ce que le nœud R soit un enfant gauche de son parent, lorsque cela se produit, nous avons le parent le nœud P comme successeur dans l'ordre.

[3] Nous sommes au nœud extrême droit de l'arbre, dans ce cas il n'y a pas de successeur en ordre.

L'implémentation est basée sur la définition de nœud suivante

class node
{
private:
node* left;
node* right;
node* parent
int data;

public:
//public interface not shown, these are just setters and getters
.......
};

//go up the tree until we have our root node a left child of its parent
node* getParent(node* root)
{
    if(root->parent == NULL)
        return NULL;

    if(root->parent->left == root)
        return root->parent;
    else
        return getParent(root->parent);
}

node* getLeftMostNode(node* root)
{
    if(root == NULL)
        return NULL;

    node* left = getLeftMostNode(root->left);
    if(left)
        return left;
    return root;
}

//return the in order successor if there is one.
//parameters - root, the node whose in order successor we are 'searching' for
node* getInOrderSucc(node* root)
{
    //no tree, therefore no successor
    if(root == NULL)
        return NULL;

    //if we have a right tree, get its left most node
    if(root->right)
        return getLeftMostNode(root->right);
    else
        //bubble up so the root node becomes the left child of its
        //parent, the parent will be the inorder successor.
        return getParent(root);
}
1
gilla07

Solution C++ en supposant que les nœuds ont des pointeurs gauche, droit et parent:

Ceci illustre la fonction Node* getNextNodeInOrder(Node) qui retourne la clé suivante de l'arbre de recherche binaire dans l'ordre.

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

struct Node{
    int data;
    Node *parent;
    Node *left, *right;
};

Node *createNode(int data){
    Node *node =  new Node();
    node->data = data;
    node->left = node->right = NULL;
    return node;
}

Node* getFirstRightParent(Node *node){
    if (node->parent == NULL)
        return NULL;

    while (node->parent != NULL && node->parent->left != node){
        node = node->parent;
    }
    return node->parent;
}
Node* getLeftMostRightChild(Node *node){
    node = node->right;
    while (node->left != NULL){
        node = node->left;
    }
    return node;
}
Node *getNextNodeInOrder(Node *node){
    //if you pass in the last Node this will return NULL
    if (node->right != NULL)
        return getLeftMostRightChild(node);
    else
        return getFirstRightParent(node);
}
void inOrderPrint(Node *root)
{
    if (root->left != NULL) inOrderPrint(root->left);
    cout << root->data << " ";
    if (root->right != NULL) inOrderPrint(root->right);
}

int main(int argc, char** argv) {
    //Purpose of this program is to demonstrate the function getNextNodeInOrder
    //of a binary tree in-order.  Below the tree is listed with the order
    //of the items in-order.  1 is the beginning, 11 is the end.  If you 
    //pass in the node 4, getNextNode returns the node for 5, the next in the 
    //sequence.

    //test tree:
    //
    //        4
    //      /    \
    //     2      11
    //    / \     /
    //   1  3    10
    //          /
    //         5
    //          \
    //           6 
    //            \
    //             8
    //            / \
    //           7  9


    Node *root = createNode(4);
    root->parent = NULL;

    root->left = createNode(2);
    root->left->parent = root;

    root->right = createNode(11);
    root->right->parent = root;

    root->left->left = createNode(1);
    root->left->left->parent = root->left;

    root->right->left = createNode(10);
    root->right->left->parent = root->right;

    root->left->right = createNode(3);
    root->left->right->parent = root->left;

    root->right->left->left = createNode(5);
    root->right->left->left->parent = root->right->left;

    root->right->left->left->right = createNode(6);
    root->right->left->left->right->parent = root->right->left->left;

    root->right->left->left->right->right = createNode(8);
    root->right->left->left->right->right->parent = 
            root->right->left->left->right;

    root->right->left->left->right->right->left = createNode(7);
    root->right->left->left->right->right->left->parent = 
            root->right->left->left->right->right;

    root->right->left->left->right->right->right = createNode(9);
    root->right->left->left->right->right->right->parent = 
            root->right->left->left->right->right;

    inOrderPrint(root);

    //UNIT TESTING FOLLOWS

    cout << endl << "unit tests: " << endl;

    if (getNextNodeInOrder(root)->data != 5)
        cout << "failed01" << endl;
    else
        cout << "passed01" << endl;

    if (getNextNodeInOrder(root->right) != NULL)
        cout << "failed02" << endl;
    else
        cout << "passed02" << endl;

    if (getNextNodeInOrder(root->right->left)->data != 11)
        cout << "failed03" << endl;
    else
        cout << "passed03" << endl;

    if (getNextNodeInOrder(root->left)->data != 3)
        cout << "failed04" << endl;
    else
        cout << "passed04" << endl;

    if (getNextNodeInOrder(root->left->left)->data != 2)
        cout << "failed05" << endl;
    else
        cout << "passed05" << endl;

    if (getNextNodeInOrder(root->left->right)->data != 4)
        cout << "failed06" << endl;
    else
        cout << "passed06" << endl;

    if (getNextNodeInOrder(root->right->left->left)->data != 6)
        cout << "failed07" << endl;
    else
        cout << "passed07" << endl;

    if (getNextNodeInOrder(root->right->left->left->right)->data != 7)
        cout << "failed08 it came up with: " << 
          getNextNodeInOrder(root->right->left->left->right)->data << endl;
    else
        cout << "passed08" << endl;

    if (getNextNodeInOrder(root->right->left->left->right->right)->data != 9)
        cout << "failed09 it came up with: " 
          << getNextNodeInOrder(root->right->left->left->right->right)->data 
          << endl;
    else
        cout << "passed09" << endl;

    return 0;
}

Qui imprime:

1 2 3 4 5 6 7 8 9 10 11

unit tests: 
passed01
passed02
passed03
passed04
passed05
passed06
passed07
passed08
passed09
1
Eric Leschinski

Nous pouvons trouver le successeur dans O (log n) sans utiliser de pointeurs parents (pour un arbre équilibré).

L'idée est très similaire à celle des pointeurs parents.

Nous pouvons définir une fonction récursive qui y parvient comme suit:

  • Si le nœud actuel est la cible, renvoyez le nœud le plus à gauche/le plus petit de son sous-arbre droit, s'il existe.
  • Reprenez à gauche si la cible est plus petite que le nœud actuel, et à droite si elle est plus grande.
  • Si la cible est à gauche et que nous n'avons pas encore trouvé de successeur, renvoyez le nœud actuel.

Pseudo-code:

Key successor(Node current, Key target):
   if current == null
      return null
   if target == current.key
      if current.right != null
         return leftMost(current.right).key
      else
         return specialKey
   else
      if target < current.key
         s = successor(current.left, target)
         if s == specialKey
            return current.key
         else
            return s
      else
         return successor(current.right, target)

Node leftMost(Node current):
    while current.left != null
       current = current.left
    return current

Live Java .

1
Dukeling
Node successor(int data) {
  return successor(root, data);
}

// look for the successor to data in the tree rooted at curr
private Node successor(Node curr, int data) {
  if (curr == null) {
    return null;
  } else if (data < curr.data) {
    Node suc = successor(curr.left, data);
    // if a successor is found use it otherwise we know this node
    // is the successor since the target node was in this nodes left subtree
    return suc == null ? curr : suc;
  } else if (data > curr.data) {
    return successor(curr.right, data);
  } else {
    // we found the node so the successor might be the min of the right subtree
    return findMin(curr.right);
  }
}

private Node findMin(Node curr) {
  if (curr == null) {
    return null;
  }

  while (curr.left != null) {
    curr = curr.left;
  }

  return curr;
}
0
Vincent

Implémentation C # (non récursive!) Pour trouver le nœud "suivant" d'un nœud donné dans une arborescence de recherche binaire où chaque nœud a un lien vers son parent.

    public static Node WhoIsNextInOrder(Node root, Node node)
    {
        if (node.Right != null)
        {
            return GetLeftMost(node.Right);
        }
        else
        {
            Node p = new Node(null,null,-1);
            Node Next = new Node(null, null, -1);
            bool found = false;
            p = FindParent(root, node);
            while (found == false)
                {
                    if (p.Left == node) { Next = p; return Next; }
                    node = p;
                    p = FindParent(root, node);
                }
            return Next;
        }
    }

    public static Node FindParent(Node root, Node node)
    {
        if (root == null || node == null)
        {
            return null;
        }
        else if ( (root.Right != null && root.Right.Value == node.Value) || (root.Left != null && root.Left.Value == node.Value))
        {
            return root;
        }
        else
        {
            Node found = FindParent(root.Right, node);

            if (found == null)
            {
                found = FindParent(root.Left, node);
            }

            return found;
        }
    }

    public static Node GetLeftMost (Node node)
    {
        if (node.Left == null)
        {
            return node;
        }
        return GetLeftMost(node.Left);
    }
0
Aerin

Vous pouvez lire des informations supplémentaires ici (poumon Rus)

Node next(Node x)
   if x.right != null
      return minimum(x.right)
   y = x.parent
   while y != null and x == y.right
      x = y
      y = y.parent
   return y


Node prev(Node x)
   if x.left != null
      return maximum(x.left)
   y = x.parent
   while y != null and x == y.left
      x = y
      y = y.parent
   return y
0
isxaker

Ces réponses me semblent toutes trop compliquées. Nous n'avons vraiment pas besoin de pointeurs parents ou de structures de données auxiliaires comme une pile. Tout ce que nous devons faire est de traverser l'arbre à partir de la racine dans l'ordre, de définir un indicateur dès que nous trouvons le nœud cible, et le nœud suivant dans l'arbre que nous visitons sera le nœud successeur dans l'ordre. Voici une routine rapide et sale que j'ai écrite.

Node* FindNextInorderSuccessor(Node* root, int target, bool& done)
{
    if (!root)
        return NULL;

    // go left
    Node* result = FindNextInorderSuccessor(root->left, target, done);
    if (result)
        return result;

    // visit
    if (done)
    {
        // flag is set, this must be our in-order successor node
        return root;
    }
    else
    {
        if (root->value == target)
        {
            // found target node, set flag so that we stop at next node
            done = true;
        }
    }

    // go right
    return FindNextInorderSuccessor(root->right, target, done);
}
0
Sudheer Anne

Solution JavaScript - Si le nœud donné a un nœud droit, retournez le plus petit nœud dans le sous-arbre droit - Sinon, il y a 2 possibilités: - Le nœud donné est un enfant gauche du nœud parent. Si tel est le cas, renvoyez le nœud parent. Sinon, le nœud donné est un enfant droit du nœud parent. Si c'est le cas, retournez le bon enfant du nœud parent

function nextNode(node) {
  var nextLargest = null;
  if (node.right != null) {
    // Return the smallest item in the right subtree

    nextLargest = node.right;
    while (nextLargest.left !== null) {
      nextLargest = nextLargest.left;
    }

    return nextLargest;
  } else {
    // Node is the left child of the parent
    if (node === node.parent.left) return node.parent;

    // Node is the right child of the parent
    nextLargest = node.parent;
    while (nextLargest.parent !== null && nextLargest !== nextLargest.parent.left) {
      nextLargest = nextLargest.parent
    }
    return nextLargest.parent;
  }
}
0
satnam

Chaque "tutoriel" que j'ai vérifié sur google et toutes les réponses de ce fil utilisent la logique suivante: " Si le nœud n'a pas un bon enfant, alors son successeur dans l'ordre sera l'un de ses ancêtres. L'utilisation du lien parent continue de monter jusqu'à ce que vous obteniez le nœud qui est l'enfant gauche de son parent. Ensuite, ce nœud parent sera le successeur dans l'ordre. "

Cela revient à penser " si mon parent est plus grand que moi, alors je suis l'enfant de gauche " (propriété d'un arbre de recherche binaire). Cela signifie que vous pouvez simplement remonter la chaîne parent jusqu'à ce que la propriété ci-dessus soit vraie. Ce qui à mon avis se traduit par un code plus élégant.

Je suppose que la raison pour laquelle tout le monde vérifie " suis-je l'enfant gauche " en regardant les branches au lieu des valeurs dans le chemin de code qui utilise les liens parents vient de logique "d'emprunt" de l'algorithme sans lien avec le parent.

Également à partir du code inclus ci-dessous, nous pouvons voir qu'il y a pas besoin de structure de données de pile comme suggéré par d'autres réponses.

Voici une fonction C++ simple qui fonctionne pour les deux cas d'utilisation (avec et sans utiliser le lien vers le parent).

Node* nextInOrder(const Node *node, bool useParentLink) const
{
    if (!node)
        return nullptr;

    // when has a right sub-tree
    if (node->right) {
        // get left-most node from the right sub-tree
        node = node->right;
        while (node->left)
            node = node->left;
        return node;
    }

    // when does not have a right sub-tree
    if (useParentLink) {
        Node *parent = node->parent;
        while (parent) {
            if (parent->value > node->value)
                return parent;
            parent = parent->parent;
        }
        return nullptr;
    } else {
        Node *nextInOrder = nullptr;
        // 'root' is a class member pointing to the root of the tree
        Node *current = root;
        while (current != node) {
            if (node->value < current->value) {
                nextInOrder = current;
                current = current->left;
            } else {
                current = current->right;
            }
        }
        return nextInOrder;
    }
}

Node* previousInOrder(const Node *node, bool useParentLink) const
{
    if (!node)
        return nullptr;

    // when has a left sub-tree
    if (node->left) {
        // get right-most node from the left sub-tree
        node = node->left;
        while (node->right)
            node = node->right;
        return node;
    }

    // when does not have a left sub-tree
    if (useParentLink) {
        Node *parent = node->parent;
        while (parent) {
            if (parent->value < node->value)
                return parent;
            parent = parent->parent;
        }
        return nullptr;
    } else {
        Node *prevInOrder = nullptr;
        // 'root' is a class member pointing to the root of the tree
        Node *current = root;
        while (current != node) {
            if (node->value < current->value) {
                current = current->left;
            } else {
                prevInOrder = current;
                current = current->right;
            }
        }
        return prevInOrder;
    }
}
0
gatis paeglis

On peut le diviser en 3 cas:

  1. Si le nœud est un parent: Dans ce cas, nous trouvons s'il a un nœud droit et traversons vers l'enfant le plus à gauche du nœud droit. Dans le cas où le nœud droit n'a pas d'enfants, le nœud droit est son successeur inorder. S'il n'y a pas de bon nœud, nous devons remonter dans l'arbre pour trouver le successeur inorder.

  2. Si le nœud est un enfant gauche: dans ce cas, le parent est le successeur inorder.

  3. Si le nœud (appelez-le x) est un enfant droit (de son parent immédiat): nous parcourons l'arbre jusqu'à ce que nous trouvions un nœud dont le sous-arbre gauche a x.

Cas extrême: si le nœud est le nœud le plus à droite, il n'y a pas de successeur inorder.

0
Rosy Gupta

Faire cela en Java

TreeNode getSuccessor(TreeNode treeNode) {
    if (treeNode.right != null) {
         return getLeftMostChild(treeNode.right);
    } else {
        TreeNode p = treeNode.parent;
        while (p != null && treeNode == p.right) { // traverse upwards until there is no parent (at the last node of BST and when current treeNode is still the parent's right child
            treeNode = p;
            p = p.parent; // traverse upwards
        }
        return p; // returns the parent node
    }
}

TreeNode getLeftMostChild(TreeNode treeNode) {
    if (treeNode.left == null) {
        return treeNode;
    } else {
        return getLeftMostChild(treeNode.left);
    }
}
0
Tim