什么应该是二叉搜索树节点的结构

时间:2013-11-07 14:27:42

标签: c++ algorithm data-structures recursion binary-search-tree

我正在尝试为二进制搜索树制作c ++程序,其中包含以下功能(实际上这是我大学作业的一部分):

A)创建二进制搜索树。

B)顺序,预订,后序遍历。 (非递归)

C)在树中搜索Val。

D)广度优先遍历。

E)深度优先遍历

F)计算叶节点,非叶节点。

G)数量没有。等级

我怀疑是: -

1。通常树节点具有以下结构:

class node{

 private:
   node *lChild;
   int info;
   node *rChild;
}

因此,如果我想执行深度优先或广度优先遍历,我可以更改节点结构并添加一个指向父节点的指针,以便我可以轻松地在层次结构中向后移动

class node{

 private:
   node *parent //pointer to parent node
   node *lChild;
   int info;
   node *rChild;
}

这被认为是编程二叉树的正常做法还是坏形式?如果它不被认为是编程树的好方法,那么还有其他任何方式,或者我必须使用堆栈(对于深度优先)和队列(对于广度优先)的书籍中给出的方法来存储节点(访问或因此未访问)

2。这是我第一次学习数据结构,所以如果有人能用简单的词语解释使用二叉树进行递归遍历和非递归遍历之间的区别,这将是一个很大的帮助考虑到

5 个答案:

答案 0 :(得分:3)

  

我更改节点结构并添加一个指向父节点的指针[...]这被认为是正常练习还是编写二叉树的错误形式?

这不是一种正常的做法(但不是很糟糕的形式)。每个节点都是数据和两个指针的集合。如果你为每个节点添加第三个指针,你将每个节点的开销增加50%(每个节点三个指针的两个指针),对于一个大的二叉树来说,这将是非常多的。

  

这是我第一次学习数据结构,所以如果有人能用简单的词语解释递归遍历和非递归遍历之间的差异,那将是一个很大的帮助

递归实现是一种仅适用于节点的函数,然后为后续节点调用自身。这使用应用程序调用堆栈来处理树的节点。

非递归实现使用本地堆栈来推送未处理的节点;只要堆栈上有数据并处理每个条目,它就会循环。

以下是打印到控制台的示例,它显示了递归和非递归之间的区别(示例不完整,因为这是作业:]):

void recursive_print(node* n) {
    std::cout << n->info << "\n";
    if(n->lChild)
        recursive_print(n->lChild); // recursive call
    // n->rChild is processed the same
}
void non_recursive_print(node* n) {
    std::stack<node*> stack;
    stack.push(n);
    while(!stack.empty()) { // behaves (more or less) the same as 
                            // the call-stack in the recursive call
        node* x = stack.top();
        stack.pop();
        std::cout << x->info << "\n";
        if(x->lChild)
            stack.push(x->lChild); // non-recursive: push to the stack
        // x->rChild is processed the same way
    }
}
// client code:
node *root; // initialized elsewhere
if(root) {
    recursive_print(root);
    non_recursive_print(root);
}

答案 1 :(得分:1)

  1. 您不需要指向父节点的指针。想想你何时会使用它的情况。您可以通过其父节点访问节点的唯一方法是,您已经访问了父节点。

  2. 你知道递归是什么意思吗?

答案 2 :(得分:0)

如果你愿意,没有什么能阻止你添加父指针。但是,它通常不是必需的,并且会略微增加尺寸和复杂性。

遍历树的常规方法是某种递归函数。首先调用该函数并传入树的根节点。然后该函数调用自身,传递子指针(一次一个)。这种情况一直递归地发生在树下,直到没有子节点为止。

在返回递归调用后,该函数会在自己的节点上执行任何处理。这意味着你基本上是在每次调用时遍历树(使调用堆栈逐渐变深),然后在每个函数返回的过程中进行备份处理。

该函数应该从不尝试以与它下来相同的方式返回树(即传入父指针),否则你最终会得到无限递归。

答案 3 :(得分:0)

通常,如果需要支持迭代,则只需要父指针 想象一下,您找到了一个叶子节点,然后想要找到下一个节点(最大键大于当前键),例如:

mytree::iterator it1=mytree_local.find(7);
if (it1 != mytree_local.end())
{
    mytree::iterator it2=it1.next();  // it1 is a leaf node and next() needs to go up
}

从这里开始你是从底部开始而不是从顶部开始,你需要上去

但是你的作业只需要从根节点开始的操作,你不应该有一个向上指针,按照其他答案的方法来避免需要上升。

答案 4 :(得分:0)

我建议你研究访客模式 - 因为它的味道,不是专门针对它的结构(它非常复杂)。

本质上,它是一种设计模式,它以一种只有一组代码进行树遍历的方式断开树的遍历,并使用该组代码在每个节点上执行各种功能。遍历代码通常不是Node类的一部分。

具体来说,它将允许您不必多次编写遍历代码 - 例如,utnapistims的答案将强制您为所需的每个功能编写遍历代码;该示例涵盖打印 - ouputXML()需要另一个遍历代码副本。最终,你的Node类变成了一个巨大的笨拙的野兽。

使用Visitor,您将拥有Tree和Node类,一个单独的Traversal类,以及许多功能类,如PrintNode,NodeToXML和可能的DeleteNode,以与Traversal类一起使用。

至于添加一个Parent指针,只有在打算在树的调用之间驻留在给定节点上时才会有用 - 即你打算在预先选择的任意节点上进行相对搜索。这可能意味着您最好不要使用所述树进行任何多线程工作。由于红/黑树可以轻松地在当前节点与其“父”之间插入新节点,因此父指针也很难更新。

我建议使用BinaryTree类,使用实例化单个Visitor类的方法,并且访问者类接受Traversal接口的实现,该接口可以是广度,宽度或二进制之一。基本上,当Visitor准备移动到下一个节点时,它会调用Traversal接口实现来获取它(下一个节点)。