我正在尝试为二进制搜索树制作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 回答
这不是一种正常的做法(但不是很糟糕的形式) . 每个节点都是数据和两个指针的集合 . 如果向每个节点添加第三个指针,则会将每个节点的开销增加50%(每个节点三个指针的两个指针),对于大型二叉树来说,这将是非常多的 .
递归实现是仅适用于节点的函数,然后为后续节点调用自身 . 这使用应用程序调用堆栈来处理树的节点 .
非递归实现使用本地堆栈来推送未处理的节点;只要堆栈上有数据并处理每个条目,它就会循环 .
这是打印到控制台的示例,它显示了递归和非递归之间的区别(示例是不完整的,因为这是作业:]):
您不需要指向父节点的指针 . 想想你何时使用它的情况 . 到达节点的唯一方法是通过其父节点,因此您已经访问过父节点 .
你知道递归是什么意思吗?
如果你愿意,没有什么可以阻止你添加父指针 . 但是,它通常不是必需的,并且稍微增加了尺寸和复杂性 .
遍历树的常规方法是某种递归函数 . 首先调用该函数并传入树的根节点 . 然后该函数调用自身,传递子指针(一次一个) . 这种情况一直递归地发生在树下,直到没有剩下子节点 .
在递归调用返回后,该函数会在自己的节点上执行任何处理 . 这意味着你基本上是在每次调用时遍历树(使你的调用栈逐渐变深),然后在每个函数返回的过程中进行备份 .
该函数应该尝试以与它下来相同的方式返回树(即传入父指针),否则最终会得到无限递归 .
通常,如果需要支持迭代,则只需要父指针想象一下,您已找到叶节点,然后想要查找下一个节点(最大键大于当前键),例如:
从这里开始你是从底部而不是从顶部开始,你需要上升
但是你的任务只需要从根节点开始的操作,你不应该有一个向上的指针,按照其他答案来避免需要上升的方法 .
我建议你研究一下访客模式 - 因为它的味道,不是专门针对它的结构(它非常复杂) .
从本质上讲,它是一种设计模式,它以一种只有一组代码进行树遍历的方式断开树的遍历,并使用该组代码在每个节点上执行各种功能 . 遍历代码通常不是Node类的一部分 .
具体来说,它将允许您不必多次编写遍历代码 - 例如,utnapistims答案将强制您为您需要的每个功能编写遍历代码;该示例涵盖打印 - ouputXML()需要另一个遍历代码副本 . 最终,你的Node类变成了一个巨大的笨拙的野兽 .
使用Visitor,您将获得树和节点类,一个单独的Traversal类,以及与Traversal类一起使用的许多函数类,如PrintNode,NodeToXML和可能的DeleteNode .
至于添加一个Parent指针,只有在打算在树的调用之间驻留在给定节点上时才会有用 - 即你打算在预先选择的任意节点上进行相对搜索 . 这可能意味着您最好不要使用所述树进行任何多线程工作 . 由于红/黑树可以轻松地在当前节点与其“父”之间插入新节点,因此父指针也很难更新 .
我建议一个BinaryTree类,使用一个实例化单个Visitor类的方法,访问者类接受Traversal接口的实现,该接口可以是宽度,宽度或二进制之一 . 基本上,当Visitor准备好移动到下一个节点时,它会调用Traversal接口实现来获取它(下一个节点) .