Реализация итератора над двоичным деревом поиска

голоса
30

Я кодирование кучи различных реализаций двоичного дерева поиска в последнее время (AVL, расставленный, декартово дерево) и любопытно, если есть особенно «хорошо» способ написания итератора для обхода этих структур. Решение я использовал прямо сейчас, чтобы каждый узел в BST хранить указатели на следующие и предыдущие элементы в дереве, что уменьшает итерацию к стандартной связному списку итерации. Тем не менее, я не очень доволен этим ответом. Это увеличивает использование пространства каждого узла с помощью двух указателей (следующих и предыдущих), и в каком-то смысле это просто обман.

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

Мой вопрос заключается в следующем - есть способ проектировать итератор бинарного дерева поиска со следующими свойствами?

  1. Элементы посещаются в порядке возрастания (т.е. симметричного обхода)
  2. next()и hasNext()запросы выполняются в O (1) время.
  3. Использование памяти представляет собой О (1)

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

Задан 03/01/2011 в 02:54
источник пользователем
На других языках...                            


8 ответов

голоса
1

Дерево обхода , из Википедии:

Все реализации выборки потребует вызова стека пространство, пропорциональное высоте дерева. В плохо сбалансированном дереве, это может быть весьма значительным.

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

В статье есть некоторые псевдокоды для итерации с O (1) состоянием, которое может быть легко адаптировано к итератору.

Ответил 03/01/2011 в 03:09
источник пользователем

голоса
30

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

Некоторые дерева используют именно реализацию вы уже используете, потому что это важно для их конкретного использования, что сканирование очень быстро. Если количество ключей в каждом узле велико, то штраф хранения указателей родного брата не слишком обременительный. Большинство B-дерева используют этот метод.

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

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

Ответил 03/01/2011 в 03:25
источник пользователем

голоса
3

Хорошо, я знаю, что это старый, но меня попросили об этом в интервью с Microsoft некоторое время назад, и я решил работать на нем немного. Я испытал это, и она работает достаточно хорошо.

template <typename E>
class BSTIterator
{  
  BSTNode<E> * m_curNode;
  std::stack<BSTNode<E>*> m_recurseIter;

public:
    BSTIterator( BSTNode<E> * binTree )
    {       
        BSTNode<E>* root = binTree;

        while(root != NULL)
        {
            m_recurseIter.push(root);
            root = root->GetLeft();
        }

        if(m_recurseIter.size() > 0)
        {
            m_curNode = m_recurseIter.top();
            m_recurseIter.pop();
        }
        else
            m_curNode = NULL;
    }

    BSTNode<E> & operator*() { return *m_curNode; }

    bool operator==(const BSTIterator<E>& other)
    {
        return m_curNode == other.m_curNode;
    }

    bool operator!=(const BSTIterator<E>& other)
    {
        return !(*this == other);
    }

    BSTIterator<E> & operator++() 
    { 
        if(m_curNode->GetRight())
        {
            m_recurseIter.push(m_curNode->GetRight());

            if(m_curNode->GetRight()->GetLeft())
                m_recurseIter.push(m_curNode->GetRight()->GetLeft());
        }

        if( m_recurseIter.size() == 0)
        {
            m_curNode = NULL;
            return *this;
        }       

        m_curNode = m_recurseIter.top();
        m_recurseIter.pop();

        return *this;       
    }

    BSTIterator<E> operator++ ( int )
    {
        BSTIterator<E> cpy = *this;     

        if(m_curNode->GetRight())
        {
            m_recurseIter.push(m_curNode->GetRight());

            if(m_curNode->GetRight()->GetLeft())
                m_recurseIter.push(m_curNode->GetRight()->GetLeft());
        }

        if( m_recurseIter.size() == 0)
        {
            m_curNode = NULL;
            return *this;
        }       

        m_curNode = m_recurseIter.top();
        m_recurseIter.pop();

        return cpy;
    }

};
Ответил 20/10/2012 в 05:53
источник пользователем

голоса
15

Как уже упоминалось TokenMacGuy вы можете использовать стек, хранящийся в итераторе. Вот краткое тестирование реализации этого в Java:

/**
 * An iterator that iterates through a tree using in-order tree traversal
 * allowing a sorted sequence.
 *
 */
public class Iterator {

    private Stack<Node> stack = new Stack<>();
    private Node current;

    private Iterator(Node argRoot) {
        current = argRoot;
    }

    public Node next() {
        while (current != null) {
            stack.push(current);
            current = current.left;
        }

        current = stack.pop();
        Node node = current;
        current = current.right;

        return node;
    }

    public boolean hasNext() {
        return (!stack.isEmpty() || current != null);
    }

    public static Iterator iterator(Node root) {
        return new Iterator(root);
    }
}

Другое изменение будет перемещаться по дереву во время строительства и сохранить в обход списка. Вы можете использовать список итератор впоследствии.

Ответил 31/07/2013 в 00:21
источник пользователем

голоса
0

Что об использовании глубины первой методики поиска. Объект итератора просто должен иметь стек уже посещенные узлы.

Ответил 21/05/2014 в 22:02
источник пользователем

голоса
0

Если вы используете стек, вы только достичь «Extra использование памяти O (ч), ч высота дерева». Тем не менее, если вы хотите использовать только O (1) дополнительную память, вам нужно записать Вот анализ: - Если текущий узел имеет правильный ребенок: найти мин правой суб дерева - это текущий узел не имеет права ребенка, вам нужно искать его от корня, и постоянно обновлять это самый низкий предок, который является его низкий следующий узел

public class Solution {
           //@param root: The root of binary tree.

           TreeNode current;
           TreeNode root;
           TreeNode rightMost;
           public Solution(TreeNode root) {

               if(root==null) return;
                this.root = root;
                current = findMin(root);
                rightMost = findMax(root);
           }

           //@return: True if there has next node, or false
           public boolean hasNext() {

           if(current!=null && rightMost!=null && current.val<=rightMost.val)    return true; 
        else return false;
           }
           //O(1) memory.
           public TreeNode next() {
                //1. if current has right child: find min of right sub tree
                TreeNode tep = current;
                current = updateNext();
                return tep;
            }
            public TreeNode updateNext(){
                if(!hasNext()) return null;
                 if(current.right!=null) return findMin(current.right);
                //2. current has no right child
                //if cur < root , go left; otherwise, go right

                    int curVal = current.val;
                    TreeNode post = null;
                    TreeNode tepRoot = root;
                    while(tepRoot!=null){
                      if(curVal<tepRoot.val){
                          post = tepRoot;
                          tepRoot = tepRoot.left;
                      }else if(curVal>tepRoot.val){
                          tepRoot = tepRoot.right;
                      }else {
                          current = post;
                          break;
                      }
                    }
                    return post;

            }

           public TreeNode findMin(TreeNode node){
               while(node.left!=null){
                   node = node.left;
               }
               return node;
           }

            public TreeNode findMax(TreeNode node){
               while(node.right!=null){
                   node = node.right;
               }
               return node;
           }
       }
Ответил 24/04/2015 в 23:41
источник пользователем

голоса
0

Используйте O (1) пространство, которое означает, что мы не будем использовать O (h) стек.

Начать:

  1. hasNext ()? current.val <= endNode.val, чтобы проверить, если дерево будет полностью пройден.

  2. Найти мин через самое левый: Мы можем alwasy найти крайнюю слева, чтобы найти следующее минимальное значение.

  3. После того, как левое большинство мин проверяются (имя его current). Далее мин будет в 2 случаях: Если current.right = нуль, мы можем продолжать поиски крайнего левого ребенка current.right, поскольку следующий мин!. Или мы должны смотреть назад для родителей. Используйте дерево двоичного поиска , чтобы найти текущий родительский узел.

Примечание : при выполнении двоичного поиска для родителей, убедитесь , что он удовлетворяет parent.left = ток.

Потому что: Если parent.right == тока, что родитель должен был посещен прежде. В бинарном дереве поиска, мы знаем, что parent.val <parent.right.val. Нам нужно, чтобы пропустить этот особый случай, так как это приводит к ifinite петли.

public class BSTIterator {
    public TreeNode root;
    public TreeNode current;
    public TreeNode endNode;
    //@param root: The root of binary tree.
    public BSTIterator(TreeNode root) {
        if (root == null) {
            return;
        }
        this.root = root;
        this.current = root;
        this.endNode = root;

        while (endNode != null && endNode.right != null) {
            endNode = endNode.right;
        }
        while (current != null && current.left != null) {
            current = current.left;
        }
    }

    //@return: True if there has next node, or false
    public boolean hasNext() {
        return current != null && current.val <= endNode.val;
    }

    //@return: return next node
    public TreeNode next() {
        TreeNode rst = current;
        //current node has right child
        if (current.right != null) {
            current = current.right;
            while (current.left != null) {
                current = current.left;
            }
        } else {//Current node does not have right child.
            current = findParent();
        }
        return rst;
    }

    //Find current's parent, where parent.left == current.
    public TreeNode findParent(){
        TreeNode node = root;
        TreeNode parent = null;
        int val = current.val;
        if (val == endNode.val) {
            return null;
        }
        while (node != null) {
            if (val < node.val) {
                parent = node;
                node = node.left;
            } else if (val > node.val) {
                node = node.right;
            } else {//node.val == current.val
                break;
            }
        }
        return parent;
    }
}
Ответил 27/01/2016 в 16:42
источник пользователем

голоса
0

По определению, это не представляется возможным для следующего () и hasNext () работать в O (1) время. Когда вы смотрите на определенном узле в BST, вы понятия не имеете, высота и структура других узлов, поэтому вы не можете просто «прыгать» на правильный следующий узел.

Однако сложность пространства может быть сведена к O (1) (для памяти самого BST), за исключением. Вот как я хотел бы сделать это в C:

struct node{
    int value;
    struct node *left, *right, *parent;
    int visited;
};

struct node* iter_next(struct node* node){
    struct node* rightResult = NULL;

    if(node==NULL)
        return NULL;

    while(node->left && !(node->left->visited))
        node = node->left;

    if(!(node->visited))
        return node;

    //move right
    rightResult = iter_next(node->right);

    if(rightResult)
        return rightResult;

    while(node && node->visited)
        node = node->parent;

    return node;
}

Хитрость заключается в том, чтобы иметь как родительскую ссылку, и посещаемый флаг для каждого узла. На мой взгляд, мы можем утверждать, что это не дополнительное использование пространства, это просто часть структуры узла. И очевидно, iter_next () должен быть вызван без состояния древовидной структуры меняющегося (конечно), но и о том, что «посетил» флаги не меняют значения.

Вот функция тестера, который вызывает iter_next () и выводит значение каждый раз, когда для этого дерева:

                  27
               /      \
              20      62
             /  \    /  \
            15  25  40  71
             \  /
             16 21

int main(){

    //right root subtree
    struct node node40 = {40, NULL, NULL, NULL, 0};
    struct node node71 = {71, NULL, NULL, NULL, 0};
    struct node node62 = {62, &node40, &node71, NULL, 0};

    //left root subtree
    struct node node16 = {16, NULL, NULL, NULL, 0};
    struct node node21 = {21, NULL, NULL, NULL, 0};
    struct node node15 = {15, NULL, &node16, NULL, 0};
    struct node node25 = {25, &node21, NULL, NULL, 0};
    struct node node20 = {20, &node15, &node25, NULL, 0};

    //root
    struct node node27 = {27, &node20, &node62, NULL, 0};

    //set parents
    node16.parent = &node15;
    node21.parent = &node25;
    node15.parent = &node20;
    node25.parent = &node20;
    node20.parent = &node27;
    node40.parent = &node62;
    node71.parent = &node62;
    node62.parent = &node27;

    struct node *iter_node = &node27;

    while((iter_node = iter_next(iter_node)) != NULL){
        printf("%d ", iter_node->value);
        iter_node->visited = 1;
    }
    printf("\n");
    return 1;
}

Что будет печатать значения в отсортированном порядке:

15 16 20 21 25 27 40 62 71 
Ответил 13/02/2016 в 06:56
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more