参考王道《2023年数据结构考研复习指导》
注意:王道2023年数据结构讲解里,先序线索化二叉树代码有问题(如下图),线索化右子树也必须判断下是否已经建立线索
否则,如果二叉树是这样的结构:
若不对右子树判空,会造成死循环:
分析:走到pre=7那步后t没路了,然后回退到3进行3的第二个递归栈,因为没有对3的右标志判断,t走到6,此时pre还是7不变,然后就死循环了
如图,此时执行完PreThread(7, 6),pre指向7,且3结点左子树已经建立了线索二叉树,返回去执行建立3结点右子树的线索二叉树,由于没有判断右子树标志(原本3结点没有右子树,由于建立了线索二叉树,此时3结点的右子树指向它的后继结点6),执行PreThread(6,7),由于pre指向7,结点7没有右子树,使得结点7的右子树指向6(图中未画出),这就导致对先序线索二叉树进行先序遍历(利用线索实现的非递归算法)时,由于7结点的右子树(后继结点)指向6,造成了死循环。
后序线索树王道的代码暂时没发现问题,不过,以防万一,我觉得还是判定下左右标志
完整代码:
#include <iostream>
typedef int ElemType;
// 线索二叉树(链式存储)
typedef struct ThreadNode {
ElemType data;
struct ThreadNode* lchild, * rchild;
int ltag, rtag; // 左右线索标志(有孩子结点时为0,有线索时为1)
} ThreadNode, * ThreadTree;
bool CreateTree(ThreadTree& T, ElemType* datagroup, int length);
bool PreTree(ThreadTree& T, ElemType* datagroup, int length, int& i);
void InThread(ThreadTree& T, ThreadNode*& pre);
void CreateInThread(ThreadTree T);
ThreadNode* FirstNode(ThreadNode* p);
ThreadNode* NextNode(ThreadNode* p);
void InOrder(ThreadNode* T);
void visit(ThreadNode* p);
ThreadNode* LastNode(ThreadNode* p);
ThreadNode* PreNode(ThreadNode* p);
void RevInOrder(ThreadNode* T);
void PreThread(ThreadTree& T, ThreadNode*& pre);
void CreatePreThread(ThreadTree T);
ThreadNode* NextNode2(ThreadNode* p);
void PreOrder(ThreadNode* T);
void PostThread(ThreadTree& T, ThreadNode*& pre);
void CreatePostThread(ThreadTree T);
ThreadNode* PreNode2(ThreadNode* p);
void PostOrder(ThreadNode* T);
int main() {
ThreadTree T1;
ElemType datagroup[] = { 1, 2, 4, -1, -1, 5, -1, -1, 3, 6, -1, 7, -1, -1, -1 };
CreateTree(T1, datagroup, sizeof(datagroup) / sizeof(datagroup[0]));
CreateInThread(T1);
InOrder(T1);
std::cout << std::endl;
RevInOrder(T1);
std::cout << std::endl;
ThreadTree T2;
CreateTree(T2, datagroup, sizeof(datagroup) / sizeof(datagroup[0]));
CreatePreThread(T2);
PreOrder(T2);
std::cout << std::endl;
ThreadTree T3;
CreateTree(T3, datagroup, sizeof(datagroup) / sizeof(datagroup[0]));
CreatePostThread(T3);
PostOrder(T3);
std::cout << std::endl;
system("pause");
return 0;
}
// 创建二叉树
// datagroup数组的元素的值作为树中各个节点的数据,并规定如果某个元素的值为-1,表示对应的节点是NULL节点。
bool CreateTree(ThreadTree& T, ElemType* datagroup, int length) {
int i = 0; // 数组指针
return PreTree(T, datagroup, length, i);
}
// 创建二叉树(按照先序遍历方式,组成一个棵树)
bool PreTree(ThreadTree& T, ElemType* datagroup, int length, int& i) {
if (i >= length || datagroup[i] == -1) {
T = NULL;
i++;
return true;
}
T = (ThreadNode*)malloc(sizeof(ThreadNode));
if (T == NULL)
return false;
T->data = datagroup[i++];
T->ltag = 0; // 左右线索标志初始化为0
T->rtag = 0;
return PreTree(T->lchild, datagroup, length, i) && PreTree(T->rchild, datagroup, length, i);
}
/*==================================================================*/
// 中序遍历对二叉树线索化
void InThread(ThreadTree& T, ThreadNode*& pre) {
if (T != NULL) {
if (T->ltag == 0) // 判断是否已经建立线索,以防万一
InThread(T->lchild, pre); // 递归,线索化左子树
if (T->lchild == NULL) { // 左子树为空,建立前驱线索
T->lchild = pre;
T->ltag = 1;
}
if (pre != NULL && pre->rchild == NULL) { // 因为pre一开始为NULL
pre->rchild = T; // 建立前驱结点的后继线索
pre->rtag = 1;
}
pre = T; // 标记当前结点成为刚刚访问过的结点
if (T->rtag == 0) // 判断是否已经建立线索,以防万一
InThread(T->rchild, pre); // 递归,线索化右子树
}
}
// 中序线索化二叉树T
void CreateInThread(ThreadTree T) {
ThreadNode* pre = NULL;
if (T != NULL) {
InThread(T, pre);
pre->rchild = NULL; // 处理遍历的最后一个结点
pre->rtag = 1;
}
}
/*==================================================================*/
// 中序线索二叉树正向中序序列(找中序后继)
// 找到以p为根节点的子树中,第一个被中序遍历的结点
ThreadNode* FirstNode(ThreadNode* p) {
while (p->ltag == 0)
p = p->lchild;
return p;
}
// 在中序线索二叉树中找到结点p的后继结点
ThreadNode* NextNode(ThreadNode* p) {
if (p->rtag == 1)
return p->rchild;
else
return FirstNode(p->rchild); // 返回右子树最左下结点(即p的后继节点)
}
// 对中序线索二叉树进行中序遍历(利用线索实现的非递归算法)
void InOrder(ThreadNode* T) {
for (ThreadNode* p = FirstNode(T); p != NULL; p = NextNode(p))
visit(p);
}
// 访问二叉树结点的操作
void visit(ThreadNode* p) {
std::cout << p->data << " ";
}
/*==================================================================*/
// 中序线索二叉树逆向中序序列(找中序后继)
// 找到以p为根节点的子树中,最后一个被中序遍历的结点
ThreadNode* LastNode(ThreadNode* p) {
while (p->rtag == 0)
p = p->rchild;
return p;
}
// 在中序线索二叉树中找到结点p的前驱结点
ThreadNode* PreNode(ThreadNode* p) {
if (p->ltag == 1)
return p->lchild;
else
return LastNode(p->lchild); // 返回右子树最左下结点(即p的后继节点)
}
// 对中序线索二叉树进行逆向中序遍历(利用线索实现的非递归算法)
void RevInOrder(ThreadNode* T) {
for (ThreadNode* p = LastNode(T); p != NULL; p = PreNode(p))
visit(p);
}
/*==================================================================*/
// 先序遍历对二叉树线索化
void PreThread(ThreadTree& T, ThreadNode*& pre) {
if (T != NULL) {
if (T->lchild == NULL) { // 左子树为空,建立前驱线索
T->lchild = pre;
T->ltag = 1;
}
if (pre != NULL && pre->rchild == NULL) { // 因为pre一开始为NULL
pre->rchild = T; // 建立前驱结点的后继线索
pre->rtag = 1;
}
pre = T; // 标记当前结点成为刚刚访问过的结点
if (T->ltag == 0) { // 因为左结点在递归前建立可能已经前驱线索,避免造成死循环
PreThread(T->lchild, pre); // 递归,线索化左子树
}
if (T->rtag == 0) {
PreThread(T->rchild, pre); // 递归,线索化右子树
}
}
}
// 先序线索化二叉树T
void CreatePreThread(ThreadTree T) {
ThreadNode* pre = NULL;
if (T != NULL) {
PreThread(T, pre);
pre->rchild = NULL; // 处理遍历的最后一个结点
pre->rtag = 1;
}
}
/*==================================================================*/
// 先序线索二叉树正向先序遍历(找后继结点)
// 在先序线索二叉树中找到结点p的后继结点
ThreadNode* NextNode2(ThreadNode* p) {
if (p->rtag == 1)
return p->rchild;
else { // 此时p必有右孩子
if (p->ltag == 0) // 有左孩子,左孩子为后继结点
return p->lchild;
else // 无左孩子,右孩子为后继结点
return p->rchild;
}
}
// 对先序线索二叉树进行先序遍历(利用线索实现的非递归算法)
void PreOrder(ThreadNode* T) {
for (ThreadNode* p = T; p != NULL; p = NextNode2(p))
visit(p);
}
/*==================================================================*/
// 后续遍历对二叉树线索化
void PostThread(ThreadTree& T, ThreadNode*& pre) {
if (T != NULL) {
if (T->ltag == 0)
PostThread(T->lchild, pre); // 递归,线索化左子树
if (T->rtag == 0)
PostThread(T->rchild, pre); // 递归,线索化右子树
if (T->lchild == NULL) { // 左子树为空,建立前驱线索
T->lchild = pre;
T->ltag = 1;
}
if (pre != NULL && pre->rchild == NULL) { // 因为pre一开始为NULL
pre->rchild = T; // 建立前驱结点的后继线索
pre->rtag = 1;
}
pre = T; // 标记当前结点成为刚刚访问过的结点
}
}
// 后序线索化二叉树T
void CreatePostThread(ThreadTree T) {
ThreadNode* pre = NULL;
if (T != NULL) {
PostThread(T, pre);
if (pre->rchild == NULL) // 处理遍历的最后一个结点(最后遍历的是根结点,可能存在右孩子)
pre->rtag = 1;
}
}
/*==================================================================*/
// 后续线索二叉树逆向后序遍历(找前驱结点)
// 在后序线索二叉树中找到结点p的前驱结点
ThreadNode* PreNode2(ThreadNode* p) {
if (p->ltag == 1)
return p->lchild;
else { // 此时p必有左孩子
if (p->rtag == 0) // 有右孩子,右孩子为前驱结点
return p->rchild;
else // 无右孩子,左孩子为前驱结点
return p->lchild;
}
}
// 对后序线索二叉树进行逆向后序遍历(利用线索实现的非递归算法)
void PostOrder(ThreadNode* T) {
for (ThreadNode* p = T; p != NULL; p = PreNode2(p))
visit(p);
}