Как я могу упростить эту функцию обхода бинарного дерева? - PullRequest
3 голосов
/ 01 июля 2010
template<typename T>
void traverse_binary_tree(BinaryTreeNode<T>* root,int order = 0)// 0:pre, 1:in , 2:post
{
    if( root == NULL ) return;

    if(order == 0) cout << root->data << " ";

    traverse_binary_tree(root->left,order);

    if(order == 1) cout << root->data << " ";

    traverse_binary_tree(root->right,order);

    if(order == 2) cout << root->data << " ";

}

Есть ли лучший способ написать эту функцию?

Ответы [ 9 ]

8 голосов
/ 01 июля 2010

номер

Шучу. Я думаю, что это выглядит довольно эффективно.

Я бы перечислил значения порядка для удобства чтения.

...
enum TOrder {ORDER_PRE, ORDER_IN, ORDER_POST};

template<typename T>
void traverse_binary_tree(BinaryTreeNode<T>* root,TOrder order = ORDER_PRE) {
...
4 голосов
/ 01 июля 2010

«Простой» - это вопрос мнения.Помимо некоторых стилистических вопросов (в частности, использование магических чисел, а не именованных констант), это примерно так же просто, как вы можете получить, учитывая специфику «пройтись по дереву и напечатать его содержимое, давая выбор порядка».Однако вы можете получить больше гибкости, разделив две проблемы: обход дерева и выполнение некоторых операций с данными.Вы можете анализировать данные и манипулировать ими различными способами, а также распечатывать их, и лучше не дублировать логику обхода для каждой операции.Вместо этого вы можете добавить дополнительные параметры шаблона, чтобы разрешить произвольные комбинации операций до, после или по порядку, в соответствии с этим:

// PRE, IN and POST operations are unary function objects which can 
// take Node<T>* as their argument
template <typename T, typename PRE, typename IN, typename POST>
void traverse(Node<T>* root, PRE& pre, IN& in, POST& post)
{
    if (!root) return;

    pre(root); 
    traverse(root->left, pre, in, post);
    in(root);
    traverse(root->right, pre, in, post);
    post(root);
}

// This can be used as a template argument to denote "do nothing".
struct Nothing { void operator()(void*) {} } nothing;

// Usage example - print the nodes, pre-ordered
void print(Node<int>* node) {std::cout << node->data << " ";}
traverse(root, print, nothing, nothing);

// Usage example - find the sum of all the nodes
struct Accumulator
{
    Accumulator() : sum(0) {}
    void operator()(Node<int>* node) {sum += node->data;}
    int sum;
};

Accumulator a;
traverse(root, a, nothing, nothing);
std::cout << a.sum << std::endl;
3 голосов
/ 01 июля 2010

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

enum order_t { PREORDER, INORDER, POSTORDER };

template<typename T, order_t order, typename F>
void traverse_binary_tree(BinaryTreeNode<T>* root, F f)
{
    if( root == NULL ) return;

    if(order == PREORDER) f(root->data);

    traverse_binary_tree<T,order>(root->left,f);

    if(order == INORDER) f(root->data);

    traverse_binary_tree<T,order>(root->right,f);

    if(order == POSTORDER) f(root->data);
}

template<typename T, typename F>
void traverse_binary_tree(BinaryTreeNode<T>* root, F f, order_t order = PREORDER)
{
    switch (order) {
    case PREORDER:
    traverse_binary_tree<T,PREORDER>(root,f);
    break;

    case POSTORDER:
    traverse_binary_tree<T,POSTORDER>(root,f);
    break;

    case INORDER:
    traverse_binary_tree<T,INORDER>(root,f);
    break;
    }
}

(вы можете также захотеть использовать константные версии F & и F & вместо простого копирования, параметр функции передачи по значению, который позволит вам передавать функторы, которые могут изменяться и создаватьрезультат; по значению все в порядке, пока ваш функтор не имеет переменных-членов или конструктора)

Или вы можете создать итераторы, которые представляют три обхода, позволяя записывать вывод, используя std :: copy;однако, это будет намного больше кода и не будет сделано только для этой цели.Однако создание итератора позволит обрабатывать большие деревья без переполнения стека, так как вам придется либо иметь указатель 'parent' в каждом узле, либо иметь итератор для поддержки явного стека посещенных узлов.

Хотя сама функция становится очень простой:

std::ostream_iterator<std::string>(std::cout, " ");
std::copy(d.begin(order), d.end(), out);

Использование итератора не упрощает реализацию ни с точки зрения loc, ни с точки зрения возможности следить за происходящим:

#include<string>
#include<iostream>
#include<functional>
#include<algorithm>
#include<iterator>
#include<vector>
#include<stack>

enum order_t { PREORDER, INORDER, POSTORDER };

template <typename T>

class BinaryTreeNode {
public:
    BinaryTreeNode(const T& data) : data(data), left(0), right(0) { }
    BinaryTreeNode(const T& data, BinaryTreeNode<T>* left, BinaryTreeNode<T>* right) : data(data), left(left), right(right) { }
public:
    BinaryTreeNode<T>* left;
    BinaryTreeNode<T>* right;
    T data;

    class BinaryTreeIterator {
        BinaryTreeNode <T>* current;
        std::stack<BinaryTreeNode <T>*> stack;
        order_t order;
        bool descending;
    public:
        typedef T value_type;
        typedef std::input_iterator_tag iterator_category;
        typedef void difference_type;
        typedef BinaryTreeIterator* pointer;
        typedef BinaryTreeIterator& reference;

        BinaryTreeIterator (BinaryTreeNode <T>* current, order_t order) : current(current), order(order), descending(true)
        {
            if (order != PREORDER) 
                descend();
        }

        BinaryTreeIterator () : current(0), order(PREORDER), descending(false)
        {
        }

    private:
        void descend() {
            do {
                if (current->left) {
                    stack.push(current);
                    current = current -> left;
                    descending = true;
                } else if ((order!=INORDER) && current->right) {
                    stack.push(current);
                    current = current -> right;
                    descending = true;
                } else {
                    descending = false; 
                    break;
                }
            } while (order != PREORDER);
        }

    public:
        bool operator== (const BinaryTreeIterator& other) { 
            if (current)
                return current == other.current && order == other.order; 
            else
                return other.current == 0;
        }

        bool operator!= (const BinaryTreeIterator& other) { 
            return !((*this)==other);
        }

        const T& operator * () const {
            return current -> data;
        }

        BinaryTreeIterator& operator++ () {
            // if the stack is empty, then go to the left then right
            // if the descending flag is set, then try to descending first
            if (descending) 
                descend();

            // not descending - either there are no children, or we are already going up
            // if the stack is not empty, then this node's parent is the top of the stack
            // so go right if this is the left child, and up if this is the right child
            if (!descending) {
                do {
                    if (stack.size()) {
                        BinaryTreeNode <T>* parent = stack.top();

                        // for inorder traversal, return the parent if coming from the left
                        if ((order == INORDER) && (current == parent->left)) {
                            current = parent;
                            break;
                        }

                        // if this is the left child and there is a right child, descending into the right
                        // or if this is the parent (inorder) 
                        if ((current == parent) && (parent -> right)) {
                            current = parent->right;
                            descend();

                            // simulate return from descent into left if only the right child exists
                            if ((current->left == 0)&&(current->right))
                                stack.push(current);

                            break;
                        }

                        // if this is the left child and there is a right child, descending into the right
                        if ((current == parent->left) && (parent -> right)) {
                            descending = true;
                            current = parent->right;

                            if (order != PREORDER)
                                descend();

                            break;
                        }

                        // either has come up from the right, or there is no right, so go up
                        stack.pop();

                        current = parent;
                    } else {
                        // no more stack; quit
                        current = 0;
                        break;
                    }
                } while (order != POSTORDER);
            }

            return *this;
        }
    };

    BinaryTreeIterator begin(order_t order) { return BinaryTreeIterator(this, order); }
    BinaryTreeIterator end() { return BinaryTreeIterator(); }
};

int main () {
    typedef BinaryTreeNode<std::string> Node;

    std::ostream_iterator<std::string> out( std::cout, " " );

    Node a("a");    
    Node c("c");
    Node b("b", &a, &c);
    Node e("e");    
    Node h("h");    
    Node i("i", &h, 0);    
    Node g("g", 0, &i);
    Node f("f", &e, &g);
    Node d("d", &b, &f);

    std::copy(d.begin(INORDER), d.end(), out);
    std::cout << std::endl;

    std::copy(d.begin(PREORDER), d.end(), out);
    std::cout << std::endl;

    std::copy(d.begin(POSTORDER), d.end(), out);
    std::cout << std::endl;

    return 0;
}
2 голосов
/ 01 июля 2010

Мы можем «переупорядочить петли»

enum {post = 0x0101, in = 0x1001, pre = 0x1010};

template<typename T>
void traverse_binary_tree(BinaryTreeNode<T>* root,int order = pre)
{
    if( root == NULL ) return;

    if(order & 0x0001) traverse_binary_tree(root->left,order);
    if(order & 0x0100) traverse_binary_tree(root->right,order);

    cout << root->data << " ";

    if(order & 0x0010) traverse_binary_tree(root->left,order);
    if(order & 0x1000) traverse_binary_tree(root->right,order);

}

Но это скорее делает его смешнее, чем проще. :-) Однако здесь код дублируется два раза вместо трех.

Чтобы избежать "магических чисел", вы можете написать это так:

enum {
  left_before  = 1 << 0,
  left_after   = 1 << 1,
  right_before = 1 << 2,
  right_after  = 1 << 3,
};
int const pre  = left_after  | right_after ;
int const in   = left_before | right_after ;
int const post = left_before | right_before;

/* The function body is fixed in the same way */
1 голос
/ 01 июля 2010

Я бы, наверное, так что-то вроде этого: enum TraversalOrder {PreOrder, InOrder, PostOrder};

template<typename T>
void traverse_binary_tree_preorder(BinaryTreeNode<T>* root)
{
    if( !root ) return;

    cout << root->data << " ";

    traverse_binary_tree_preorder(root->left,order);
    traverse_binary_tree_preorder(root->right,order);

}

template<typename T>
void traverse_binary_tree_inorder(BinaryTreeNode<T>* root)
{
    if( !root ) return;

    traverse_binary_tree_inorder(root->left,order);
    cout << root->data << " ";
    traverse_binary_tree_inorder(root->right,order);

}

template<typename T>
void traverse_binary_tree_postorder(BinaryTreeNode<T>* root)
{
    if( !root ) return;


    traverse_binary_tree_postorder(root->left,order);
    traverse_binary_tree_postorder(root->right,order);
    cout << root->data << " ";

}


template<typename T>
void traverse_binary_tree(BinaryTreeNode<T>* root,TraversalOrder order = InOrder)
{
    switch(order)
    {
        case PreOrder:
            return traverse_binary_tree_preorder(root);
        case PostOrder:
            return traverse_binary_tree_postorder(root);
        default:
            return traverse_binary_tree_inorder(root);
    }
}

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

1 голос
/ 01 июля 2010

Вы можете переместить order в аргумент шаблона.

template<typename T, int order> // 0:pre, 1:in , 2:post
void traverse_binary_tree(BinaryTreeNode<T>* root)
{
    if( root == NULL ) return;    

    if(order == 0) cout << root->data << " ";    

    traverse_binary_tree<T,order> (root->left);    

    if(order == 1) cout << root->data << " ";    

    traverse_binary_tree<T,order> (root->right);    

    if(order == 2) cout << root->data << " ";    
}

Два из каждого if(order == будут компилироваться в каждом экземпляре функции.

0 голосов
/ 01 июля 2010

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

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

0 голосов
/ 01 июля 2010

Есть ли лучший способ написать это функционировать?

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

Вы можете переписать функцию таким образом, чтобы она совпала с однократным, однократным выходом (пытаясь придерживаться вашего стиля):

template<typename T>
void traverse_binary_tree(BinaryTreeNode<T>* root,int order = 0)// 0:pre, 1:in , 2:post
{
    if( root != NULL )
    {
        if(order == 0) cout << root->data << " ";

        traverse_binary_tree(root->left,order);

        if(order == 1) cout << root->data << " ";

        traverse_binary_tree(root->right,order);

        if(order == 2) cout << root->data << " ";
    }
}

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

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

Вы также можете избежать использования магических чисел (0, 1, 2) для обхода перед заказом, по порядку и после заказа и использовать вместо этого именованные константы.

0 голосов
/ 01 июля 2010

@ Vi, вы будете в углу со специализацией функции, см. https://stackoverflow.com/search?q=function+partial+specialization

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

...