【数据结构】-线索二叉树(后序)

1.头文件及类型定义

#include<stdio.h>
#include<stdlib.h>
#define ElemType char

2.线索二叉树结点类型定义

//线索二叉树结点类型定义
typedef struct ThreadNode {
	ElemType data;							//数据元素
	struct ThreadNode* lchild, * rchild;	//左、右孩子指针
	int ltag, rtag;							//左、右线索标志
	//tag=0,表示指针指向孩子;tag=1,表示指针是“线索”,ltag指向前驱,rtag指向后继
}ThreadNode, * ThreadTree;

3.函数声明

/*函数声明*/
void CreateThTree(ThreadTree& T);			//1.先序建立线索二叉树
void InitThread(ThreadTree& T);				//2.初始化tag
void visit1(ThreadNode* q);					//3-1.访问并建立线索
void PostThread(ThreadTree T);				//3-2.遍历
void CreatePostThread(ThreadTree T);		//3-3.后序线索化主过程
ThreadNode* PreNode(ThreadNode* p);			//4.找到p的前驱结点
void visit2(ThreadNode* p);					//5-1.打印结点
void RevPostOrder(ThreadNode* T);			//5-2.利用后序前驱实现逆向后序遍历

4.基本操作

4.1 先序建立线索二叉树

/*1.先序建立线索二叉树*/
void CreateThTree(ThreadTree& T) {
	char c;
	scanf("%c", &c);
	if (c == '#')
		T = NULL;
	else {
		T = (ThreadNode*)malloc(sizeof(ThreadNode));
		T->data = c;
		CreateThTree(T->lchild);
		CreateThTree(T->rchild);
	}
}

4.2 初始化tag

/*2.初始化tag*/
void InitThread(ThreadTree& T) {
	if (T != NULL) {
		T->ltag = 0;
		T->rtag = 0;	//初始化当前树中的tag指针为0,表示还未线索化
		InitThread(T->lchild);	//递归遍历左子树
		InitThread(T->rchild);	//递归遍历右子树
	}
}

4.3 后序线索化二叉树

4.3.1 访问并建立线索

ThreadNode* pre = NULL;		//pre指向当前访问结点的前驱
//3-1.访问并建立线索
void visit1(ThreadNode* q) {
	if (q->lchild == NULL) {	//左子树为空,建立前驱线索
		q->lchild = pre;
		q->ltag = 1;
	}
	if (pre != NULL && pre->rchild == NULL) {	
		pre->rchild = q;	//建立前驱结点的后继线索
		pre->rtag = 1;
	}
	pre = q;			//标记当前结点为刚刚访问过的结点
}

4.3.2 遍历

//3-2.遍历
void PostThread(ThreadTree T) {
	if (T != NULL) {
		PostThread(T->lchild);	//后序遍历左子树
		PostThread(T->rchild);	//后序遍历右子树
		visit1(T);				//访问根节点
	}
}

4.3.3 后序线索化主过程

//3-3.主过程
void CreatePostThread(ThreadTree T) {
	pre = NULL;			//pre初始化为NULL
	if (T != NULL) {	//非空二叉树才能线索化			
		PostThread(T);	//后序线索化二叉树
		if (pre->rchild == NULL)	
			pre->rtag = 1;			//处理遍历的最后一个结点
	}
}

4.4 寻找后序前驱

//4.寻找后序前驱
ThreadNode* PreNode(ThreadNode* p) {
	if (p->ltag == 0) {		//若ltag=0,说明所找结点有左孩子
		if (p->rtag == 0)		//若rtag=0
			return p->rchild;		//说明所找结点有右孩子,根据后序遍历的特点(左-右-根),右孩子为前驱
		else                    //若rtag=1
			return	p->lchild;		//说明所找结点无右孩子,则前驱结点为其左孩子
	}
	else
		return p->lchild;	//若ltag=1,说明所找结点无左孩子,则返回前驱线索
}

4.5 逆向后序遍历

4.5.1 打印结点

//5-1.打印结点
void visit2(ThreadNode* p) {
	printf("%c\t", p->data);
}

4.5.2 利用后序前驱实现逆向后序遍历

//5-2.利用后序前驱实现逆向后序遍历:空间复杂度为O(1) 
void RevPostOrder(ThreadNode* T) {
	for (ThreadNode* p = T; p != NULL; p = PreNode(p))
		visit2(p);
}

4.6 main函数

int main() {
	ThreadTree T;

	/*1、创建二叉树*/
	printf("先序创建二叉树:");
	CreateThTree(T);

	/*2、初始化tag为0*/
	InitThread(T);

	/*3、后序线索化*/
	CreatePostThread(T);

	/*4、寻找后序前驱(用根结点测试)*/
	printf("根结点的后序前驱为:%c\n", PreNode(T)->data);

	/*6、逆向后序遍历二叉树*/
	printf("<————逆向后序遍历————>\n");
	RevPostOrder(T);

	return 0;
}

4.7 测试

4.7.1 二叉树结构

在这里插入图片描述

4.7.2 测试结果

在这里插入图片描述

5.小结

  • 对于后序线索二叉树并不会出现“转圈"现象,原因是根结点是最后被访问的,不可能再回去访问其左右孩子指向的子树。
  • 另外,同先序线索二叉树一样,后序线索二叉树也存在类似的问题:即只能找到后序前驱。具体分析同先序线索二叉树类似,已在上一篇文章小结中介绍,此处不再展开。

版权声明:本文为ZX_ZX_原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。