线索二叉树的相关算法(线索化、求前驱后继、遍历)

写在前面的话:本文不讲算法思路,主要描述的是对算法的代码实现,以及写代码过程中需要特别注意的地方。算法思路参考B站王道论坛的数据结构课程

二叉树结点的定义

线索二叉树必须具有左右孩子标志位,这里标志位的值为0表示孩子指向孩子节点。

typedef int elementType;
typedef struct TreeNode {
	elementType data = 0;
	TreeNode *lchild = nullptr, *rchild = nullptr, *pre = nullptr;//先定义为三叉链表
	int ltag = 0, rtag = 0;//线索二叉树必须具备的标志位
	TreeNode(elementType val) :data(val) {}
	TreeNode() = default;
}TreeNode, *BiTree;

二叉树的线索化

二叉树的线索化用的是递归的思想,和遍历二叉树的方法是一样的,这里不做赘述。递归的三要素是:

  • 结束递归的条件;
  • 核心算法;
  • 调用自己的函数实现递归;

其中递归结束条件是第一个写的,后面两个要素的顺序要根据实际情况,例如在本例中的先序、中序和后序线索化中它们的顺序都不一样。递归最重要的是在自调用的时候,要假装这个函数就是实现了功能,哈哈。有点绕,看代码吧。
二叉树线索化需要特别注意的是:

  1. 先序线索化函数中在递归对左子树进行线索化,必须先判断左子树是否真的存在(通过标志位ltag进行判断,如果ltag==0,那么左子树存在),否则可能会出现死循环或者可以对所有的递归都先进行判断一次,形成习惯就不会忘记。
  2. 线索化函数还没完整的对二叉树进行线索化,在调用线索化函数结束时候,需要通过pre指针把最后一个节点进行线索化,如果pre没有右孩子那么必须修改其标志位。
//中序线索化
void midThread(BiTree tree, TreeNode * &pre)
//pre指针变量传递的是引用,为了每次递归修改的是同一个前驱指针变量
{
	//递归结束的条件(要素一)
	if (!tree)return;
	//左子树线索化(要素二)
	midThread(tree->lchild, pre);
	
	//核心算法(要素三)
	//前驱节点的线索化
	if (pre&&!pre->rchild) {
		pre->rchild = tree;
		pre->rtag = 1;
	}
	//根节点线索化
	if (!tree->lchild) {
		tree->lchild = pre;
		tree->ltag = 1;
	}
	//更新前驱结点
	pre = tree;
	
	//右子树的线索化(递归,要素二)
	midThread(tree->rchild, pre);
}

//先序线索化
void preThread(BiTree tree, TreeNode * &pre)
{
	//递归结束的条件(要素一)
	if (!tree)return;
	
	//核心算法(要素三)
	//前驱节点的线索化
	if (pre&&!pre->rchild) {
		pre->rchild = tree;
		pre->rtag = 1;
	}
	//根节点线索化
	if (!tree->lchild) {
		tree->lchild = pre;
		tree->ltag = 1;
	}
	//更新前驱结点
	pre = tree;
	
	//左子树线索化(要素二)
	if(tree->ltag == 0)preThread(tree->lchild, pre);//必须保证左子树还没被线索化才能递归
	//右子树的线索化(要素二)
	preThread(tree->rchild, pre);
}

//后序线索化
void backThread(BiTree tree, TreeNode * &pre)
{
	//递归结束的条件
	if (!tree)return;
	
	//左子树线索化(要素二)
	backThread(tree->lchild, pre);
	//右子树的线索化(要素二)
	backThread(tree->rchild, pre);
	
	//核心算法(要素三)
	//前驱节点的线索化
	if (pre&&!pre->rchild) {
		pre->rchild = tree;
		pre->rtag = 1;
	}
	//根节点线索化
	if (!tree->lchild) {
		tree->lchild = pre;
		tree->ltag = 1;
	}
	//更新前驱结点
	pre = tree;
}

寻找线索二叉树的前驱、后继,通过后继进行遍历

寻找前驱后继难度不大,不过对于先序线索二叉树找前驱、后序线索二叉树找后继需要借助三叉链表完成,这里盗用王道课程的表格(若侵权,联系我删除)。

中序线索二叉树先序线索二叉树后序线索二叉树
找前驱ok借助三叉链表ok
找后继okok借助三叉链表
//寻找中序线索二叉树结点的前驱
TreeNode * findMidPre(TreeNode *p)
{
	if (p->ltag == 1)return p->lchild;
	TreeNode *temp = p->lchild;
	while (temp->rtag == 0)temp = temp->rchild;
	return temp;
}

//寻找中序线索二叉树结点的后继
TreeNode * findMidBack(TreeNode *p)
{
	if (p->rtag == 1)return p->rchild;
	TreeNode *temp = p->rchild;
	while (temp->ltag == 0)temp = temp->lchild;
	return temp;
}

//对中序线索二叉树进行中序遍历
void midRead(BiTree tree)
{
	while (tree->ltag == 0)tree = tree->lchild;
	while (tree) {
		cout << tree->data << ends;
		tree = findMidBack(tree);
	}
	cout << endl;
}

//寻找先序线索二叉树的前驱(通过三叉链表来实现)
TreeNode * findPrePre(TreeNode *p)
{
	if (p->ltag == 1)return p->lchild;
	//如果p有左孩子
	TreeNode *pre = p->pre;
	if (!pre)return pre;
	if (pre->lchild == p)return pre;
	pre = pre->lchild;
	while (pre->rtag == 0)pre = pre->rchild;
	return pre;
}

//寻找先序线索二叉树的后继
TreeNode * findPreBack(TreeNode *p)
{
	if (p->ltag == 0)return p->lchild;
	return p->rchild;
}

//对先序线索二叉树进行先序遍历
void preRead(BiTree tree)
{
	while (tree) {
		cout << tree->data << ends;
		tree = findPreBack(tree);
	}
}

//寻找后序线索二叉树的前驱
TreeNode * findBackPre(TreeNode *p)
{
	if (p->rtag == 0) return p->rchild;
	else return p->lchild;
}

//寻找后序线索二叉树的后继
TreeNode * findBackBack(TreeNode *p)
{
	if (p->rtag == 1)return p->rchild;
	TreeNode *pre = p->pre;
	if (!pre||pre->rchild == p|| pre->rtag == 1)return pre;
	pre = pre->rchild;
	while (pre->ltag == 0)pre = pre->lchild;
	while (pre->rtag == 0)pre = pre->rchild;
	return pre;
}

//对后序线索二叉树进行后序遍历
void backRead(BiTree tree)
{
	while (tree->ltag == 0)tree = tree->lchild;
	while (tree->rtag == 0)tree = tree->rchild;
	while (tree) {
		cout << tree->data << ends;
		tree = findBackBack(tree);
	}
}

完整代码(包括实例)

代码的输出可以根据自己想测试的函数适当修改。例子中的二叉树结构图如下:
在这里插入图片描述

#include<iostream>
using namespace std;

typedef int elementType;
typedef struct TreeNode {
	elementType data = 0;
	TreeNode *lchild = nullptr, *rchild = nullptr, *pre = nullptr;//先定义为三叉链表
	int ltag = 0, rtag = 0;//线索二叉树必须具备的标志位
	TreeNode(elementType val) :data(val) {}
	TreeNode() = default;
}TreeNode, *BiTree;

/****************************************二叉树的线索化****************************************/
//中序线索化
void midThread(BiTree tree, TreeNode * &pre)
{
	//递归结束的条件
	if (!tree)return;
	//左子树线索化
	midThread(tree->lchild, pre);
	//前驱节点的线索化
	if (pre&&!pre->rchild) {
		pre->rchild = tree;
		pre->rtag = 1;
	}
	//根节点线索化
	if (!tree->lchild) {
		tree->lchild = pre;
		tree->ltag = 1;
	}
	//更新前驱结点
	pre = tree;
	//右子树的线索化
	midThread(tree->rchild, pre);
}

//先序线索化
void preThread(BiTree tree, TreeNode * &pre)
{
	//递归结束的条件
	if (!tree)return;
	//前驱节点的线索化
	if (pre&&!pre->rchild) {
		pre->rchild = tree;
		pre->rtag = 1;
	}
	//根节点线索化
	if (!tree->lchild) {
		tree->lchild = pre;
		tree->ltag = 1;
	}
	//更新前驱结点
	pre = tree;
	//左子树线索化
	if(tree->ltag == 0)preThread(tree->lchild, pre);//必须保证左子树还没被线索化才能递归
	//右子树的线索化
	preThread(tree->rchild, pre);
}

//后序线索化
void backThread(BiTree tree, TreeNode * &pre)
{
	//递归结束的条件
	if (!tree)return;
	//左子树线索化
	backThread(tree->lchild, pre);
	//右子树的线索化
	backThread(tree->rchild, pre);
	//前驱节点的线索化
	if (pre&&!pre->rchild) {
		pre->rchild = tree;
		pre->rtag = 1;
	}
	//根节点线索化
	if (!tree->lchild) {
		tree->lchild = pre;
		tree->ltag = 1;
	}
	//更新前驱结点
	pre = tree;
}

/****************************************线索二叉树的遍历****************************************/
//寻找中序线索二叉树结点的前驱
TreeNode * findMidPre(TreeNode *p)
{
	if (p->ltag == 1)return p->lchild;
	TreeNode *temp = p->lchild;
	while (temp->rtag == 0)temp = temp->rchild;
	return temp;
}

//寻找中序线索二叉树结点的后继
TreeNode * findMidBack(TreeNode *p)
{
	if (p->rtag == 1)return p->rchild;
	TreeNode *temp = p->rchild;
	while (temp->ltag == 0)temp = temp->lchild;
	return temp;
}

//对中序线索二叉树进行中序遍历
void midRead(BiTree tree)
{
	while (tree->ltag == 0)tree = tree->lchild;
	while (tree) {
		cout << tree->data << ends;
		tree = findMidBack(tree);
	}
	cout << endl;
}

//寻找先序线索二叉树的前驱(通过三叉链表来实现)
TreeNode * findPrePre(TreeNode *p)
{
	if (p->ltag == 1)return p->lchild;
	//如果p有左孩子
	TreeNode *pre = p->pre;
	if (!pre)return pre;
	if (pre->lchild == p)return pre;
	pre = pre->lchild;
	while (pre->rtag == 0)pre = pre->rchild;
	return pre;
}

//寻找先序线索二叉树的后继
TreeNode * findPreBack(TreeNode *p)
{
	if (p->ltag == 0)return p->lchild;
	return p->rchild;
}

//对先序线索二叉树进行先序遍历
void preRead(BiTree tree)
{
	while (tree) {
		cout << tree->data << ends;
		tree = findPreBack(tree);
	}
}

//寻找后序线索二叉树的前驱
TreeNode * findBackPre(TreeNode *p)
{
	if (p->rtag == 0) return p->rchild;
	else return p->lchild;
}

//寻找后序线索二叉树的后继
TreeNode * findBackBack(TreeNode *p)
{
	if (p->rtag == 1)return p->rchild;
	TreeNode *pre = p->pre;
	if (!pre||pre->rchild == p|| pre->rtag == 1)return pre;
	pre = pre->rchild;
	while (pre->ltag == 0)pre = pre->lchild;
	while (pre->rtag == 0)pre = pre->rchild;
	return pre;
}

//对后序线索二叉树进行后序遍历
void backRead(BiTree tree)
{
	while (tree->ltag == 0)tree = tree->lchild;
	while (tree->rtag == 0)tree = tree->rchild;
	while (tree) {
		cout << tree->data << ends;
		tree = findBackBack(tree);
	}
}

int main()
{
	BiTree tree;

	TreeNode *node1 = new TreeNode(1);
	TreeNode *node2 = new TreeNode(2);
	TreeNode *node3 = new TreeNode(3);
	TreeNode *node4 = new TreeNode(4);
	TreeNode *node5 = new TreeNode(5);
	TreeNode *node6 = new TreeNode(6);
	TreeNode *node7 = new TreeNode(7);
	TreeNode *node8 = new TreeNode(8);
	TreeNode *node9 = new TreeNode(9);
	TreeNode *node10 = new TreeNode(10);
	TreeNode *node11 = new TreeNode(11);
	TreeNode *node12 = new TreeNode(12);
	TreeNode *node13 = new TreeNode(13);

	node1->lchild = node2;
	node2->pre = node1;
	node1->rchild = node3;
	node3->pre = node1;
	node2->lchild = node4;
	node4->pre = node2;
	node2->rchild = node5;
	node5->pre = node2;
	node3->lchild = node6;
	node6->pre = node3;
	node3->rchild = node7;
	node7->pre = node3;
	node5->lchild = node8;
	node8->pre = node5;
	node5->rchild = node9;
	node9->pre = node5;
	node9->rchild = node13;
	node13->pre = node9;
	node6->rchild = node10;
	node10->pre = node6;
	node7->lchild = node11;
	node11->pre = node7;
	node7->rchild = node12;
	node12->pre = node7;
	tree = node1;

	//线索化
	TreeNode *pre = nullptr;
	backThread(tree, pre);
	if (!pre->rchild)pre->rtag = 1;//对最后一个节点的右孩子也需要修改其线索化标志位

	//遍历输出
	backRead(tree);
	cout << endl << findBackPre(node2)->data;

	delete node1;
	delete node2;
	delete node3;
	delete node4;
	delete node5;
	delete node6;
	delete node7;
	delete node8;
	delete node9;
	delete node10;
	delete node11;
	delete node12;
	delete node13;
	return 0;
}
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值