写在前面的话:本文不讲算法思路,主要描述的是对算法的代码实现,以及写代码过程中需要特别注意的地方。算法思路参考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;
二叉树的线索化
二叉树的线索化用的是递归的思想,和遍历二叉树的方法是一样的,这里不做赘述。递归的三要素是:
- 结束递归的条件;
- 核心算法;
- 调用自己的函数实现递归;
其中递归结束条件是第一个写的,后面两个要素的顺序要根据实际情况,例如在本例中的先序、中序和后序线索化中它们的顺序都不一样。递归最重要的是在自调用的时候,要假装这个函数就是实现了功能,哈哈。有点绕,看代码吧。
二叉树线索化需要特别注意的是:
- 先序线索化函数中在递归对左子树进行线索化,必须先判断左子树是否真的存在(通过标志位ltag进行判断,如果ltag==0,那么左子树存在),否则可能会出现死循环或者可以对所有的递归都先进行判断一次,形成习惯就不会忘记。
- 线索化函数还没完整的对二叉树进行线索化,在调用线索化函数结束时候,需要通过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 |
找后继 | ok | ok | 借助三叉链表 |
//寻找中序线索二叉树结点的前驱
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;
}