前面已经对二叉树的基本知识和创建,以及二叉树的前序、中序、后序遍历做出了描述和实现。
本篇博客主要讲述:二叉树的线索化以及对线索二叉树遍历实现。
首先,什么是二叉树的线索化,为什么要对二叉树线索化?
二叉树是一种非线性结构,遍历二叉树几乎都是通过递归或者用栈辅助实现非递归的遍历。用二叉树作为存储结构时,取到一个节点,只能获取节点的左孩子和右孩子,不能直接得到节点的任一遍历序列的前驱或者后继。
为了保存这种在遍历中需要的信息,我们利用二叉树中指向左右子树的空指针来存放节点的前驱和后继信息
n个节点的二叉树中含有n+1个空指针域。利用二叉树中的空指针域 来存放在某种遍历次序下的前驱和后继 ,这种指针叫“线索”。这种加上了线索的二叉树称为线索二叉树。
根据线索的性质的不同,线索二叉树分为:前序线索二叉树 , 中序线索二叉树 , 后序线索二叉树(本篇博客主要讲述前面两种 , 后面会专门对后序线索二叉树分析 )
线索二叉树节点:
typedef enum
{
Link,
Thread
} PointTag;
typedef struct BinaryTreeNode
{
BinaryTreeNode(const char data)
:_data(data)
, pLeft(NULL)
, pRight(NULL)
, Ltag(Link)
, Rtag(Link)
{}
char _data;
struct BinaryTreeNode *pLeft , * pRight;
PointTag Ltag, Rtag;
}BiTreeNode;
Ltag 标记是否有左子树 , Ltag 是Link 则表示有左子树 ; Ltag是Thread表示没有左子树(有前驱)
Rtag 标记是否有右子树 , Rtag是Link 则表示有右子树 ; Rtag是Thread 表示没有右子树(有后继)
首先,在线索化二叉树之前,你得有二叉树吧
还是 按之前的方式:先序创建二叉树
void _CreatTree(BiTreeNode*& Root, const char* arr, size_t size, size_t& index)
{
if (arr == NULL || size == 0)
{
cout << "输入有误 " << endl;
return;
}
if (index < size && arr[index] != '#')
{
Root = new BiTreeNode(arr[index]);
_CreatTree(Root->pLeft, arr, size, ++index);
_CreatTree(Root->pRight, arr, size, ++index);
}
}
以下面的二叉树为例:
先序遍历的顺序:0 1 2 3 4
先序线索化二叉树
分析:首先对于左子树存在的节点,就不会有前驱;同样,节点的右子树存在,那么不存在后继。那么我就先一直找寻节的左子树,判断左右子树是否为空 , 为空了才会考虑前驱和后继的问题。后继倒是很好找(反正要遍历二叉树),那么前驱的位置呢?如果遇到左子树不存在的节点,怎么才能找到已经已经扫面过得节点呢?所以我们需要创建一个节点指针,每次记住上一次扫描的节点 。 对于右子树就很好办了 , 如果当前节点的前一个节点不为空且右子树不存在,那么我们就放心大胆的链上吧 。然后循环得了。
假设 记录前一个节点的指针为:
BiTreeNode* Prev =NULL;
没听懂描述么? 没关系,如果所示:
上代码:
void _PreOrderThreading(BiTreeNode*& Root)
{
if (Root == NULL)
{
return;
}
if (Root->pLeft == NULL) //没有左子树
{
Root->pLeft = Prev; //前驱
Root->Ltag = Thread;
}
if (Prev != NULL && Prev->pRight == NULL) // 上一个节点有没有 右子树
{ //Prev第一次进来 为空
Prev->pRight = Root; //后继
Prev->Rtag = Thread;
}
Prev = Root;//前驱 , 每次记住上次的节点
//判断Root是否有左右孩子
if (Root->Ltag == Link)
_PreOrderThreading(Root->pLeft);
if (Root->Rtag == Link)
_PreOrderThreading(Root->pRight);
}
对于先序线索二叉树,我想提醒的是: 每次只会把当前节点的左子树前驱链上,这一次的 后继 不会在本次链上,当pCur指向下一个节点的时候,才会把上一次的后继链上
结果:
那么 怎么 写先序遍历线索二叉树呢
void _PreOrder(BiTreeNode* Root)
{
if (Root == NULL)
{
return;
}
BiTreeNode* pCur = Root;
while (pCur != NULL)
{
while (pCur->pLeft != NULL && pCur->Ltag == Link)//找到最左边的节点,左标记一直为Link
{
cout << pCur->_data << ' ';
pCur = pCur->pLeft;
}
//到这来,左边的的节点还没有访问
cout << pCur->_data << ' ';
if (pCur->Ltag == Thread)//遇到线索 就看右节点
{
pCur = pCur->pRight;
}
while (pCur != NULL)//循环右节点
{
if (pCur->pLeft != NULL && pCur->Ltag == Link)//遇到左节点存在 , 则访问
{
break;
}
cout << pCur->_data << ' ';
pCur = pCur->pRight;
}
}
}
代码解释:
2、中序线索化二叉树和遍历
中序遍历的顺序:213 0 4
中序遍历线索化二叉树
分析:还是和先序很像的 ,中序的顺序是左-根-右,我们同样可以使用上面的递归方式;
话不多说,上代码:
void _InOrderThreading(BiTreeNode*& Root)
{
if (Root == NULL)
{
return;
}
_InOrderThreading(Root->pLeft); // 左
if (Root->pLeft == NULL) //根
{
Root->Ltag = Thread;
Root->pLeft = Prev;
}
if (Prev != NULL && Prev->pRight == NULL)
{
Prev->pRight = Root;
Prev->Rtag = Thread;
}
Prev = Root;
_InOrderThreading(Root->pRight); //右
}
中序遍历线索二叉树
void _InOrder(BiTreeNode* Root)
{
if (Root == NULL)
{
return;
}
BiTreeNode* pCur = Root;
while (pCur )
{
while (pCur->Ltag == Link) //找最左边的节点
{
pCur = pCur->pLeft;
}
cout << pCur->_data << ' ';
while ( pCur && pCur->Rtag == Thread )//找中序后继节点
{
pCur = pCur->pRight;
cout << pCur->_data << ' ';
}
//没有后继,有右子树
pCur = pCur->pRight;
}
}
其实,中序遍历和先序遍历二叉树还是很像的,首先按照中序遍历的顺序,找到二叉树的最左边的节点,判断是否有前驱,有则遍历访问,没有则看右子树和后继的情况。
此处,可以按照之前的画图过程继续一步步来。
全部代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include<assert.h>
//线索二叉树
typedef enum
{
Link,
Thread
} PointTag;
typedef struct BinaryTreeNode
{
BinaryTreeNode(const char data)
:_data(data)
, pLeft(NULL)
, pRight(NULL)
, Ltag(Link)
, Rtag(Link)
{}
char _data;
struct BinaryTreeNode *pLeft , * pRight;
PointTag Ltag, Rtag;
}BiTreeNode;
class Thread_BiTree
{
public:
//先序 --创建树
Thread_BiTree(const char* arr, size_t Size)
:_pRoot(NULL)
, Prev(NULL)
{
size_t index = 0;
_CreatTree(_pRoot, arr, Size , index);//创建二叉树
}
protected:
void _CreatTree(BiTreeNode*& Root, const char* arr, size_t size, size_t& index)
{
if (arr == NULL || size == 0)
{
cout << "输入有误 " << endl;
return;
}
if (index < size && arr[index] != '#')
{
Root = new BiTreeNode(arr[index]);
_CreatTree(Root->pLeft, arr, size, ++index);
_CreatTree(Root->pRight, arr, size, ++index);
}
}
public:
//先序--线索化二叉树
void PreOrderThreading()
{
_PreOrderThreading(this->_pRoot);
}
//先序--遍历 线索二叉树
void PreOrder()
{
_PreOrder(this->_pRoot);
}
protected:
//先序--线索化二叉树--C
//思路:先看左子树, 找下一个节点的时候,在检测上一个节点的右节点
void _PreOrderThreading(BiTreeNode*& Root)
{
if (Root == NULL)
{
return;
}
if (Root->pLeft == NULL) //没有左子树
{
Root->pLeft = Prev; //前驱
Root->Ltag = Thread;
}
if (Prev != NULL && Prev->pRight == NULL) // 上一个节点有没有 右子树
{ //Prev第一次进来 为空
Prev->pRight = Root; //后继
Prev->Rtag = Thread;
}
Prev = Root;//前驱 , 每次记住上次的节点
//判断Root是否有左右孩子
if (Root->Ltag == Link)
_PreOrderThreading(Root->pLeft);
if (Root->Rtag == Link)
_PreOrderThreading(Root->pRight);
}
//先序--遍历 线索二叉树--C
void _PreOrder(BiTreeNode* Root)
{
if (Root == NULL)
{
return;
}
BiTreeNode* pCur = Root;
while (pCur != NULL)
{
while (pCur->pLeft != NULL && pCur->Ltag == Link)//找到最左边的节点,左标记一直为Link
{
cout << pCur->_data << ' ';
pCur = pCur->pLeft;
}
//到这来,左边的的节点还没有访问
cout << pCur->_data << ' ';
if (pCur->Ltag == Thread)//遇到线索 就看右节点
{
pCur = pCur->pRight;
}
while (pCur != NULL)//循环右节点
{
if (pCur->pLeft != NULL && pCur->Ltag == Link)//遇到左节点存在 , 则访问
{
break;
}
cout << pCur->_data << ' ';
pCur = pCur->pRight;
}
}
}
public:
//中序--线索化二叉树
void InOrderThreading()
{
_InOrderThreading(_pRoot);
}
//中序--遍历线索二叉树
void InOrder()
{
_InOrder(this->_pRoot);
}
protected:
//中序--线索化二叉树--C
//思路:按 左-根-右的顺序 先找到最左边的节点-> 和先序线索一样 ,先链接左子树,执行到下一个节点在看上次节点的右子树 -> 右子树
void _InOrderThreading(BiTreeNode*& Root)
{
if (Root == NULL)
{
return;
}
_InOrderThreading(Root->pLeft); // 左
if (Root->pLeft == NULL) //根
{
Root->Ltag = Thread;
Root->pLeft = Prev;
}
if (Prev != NULL && Prev->pRight == NULL)
{
Prev->pRight = Root;
Prev->Rtag = Thread;
}
Prev = Root;
_InOrderThreading(Root->pRight); //右
}
//中序--遍历二叉树--C
//思路:找到中序开始的节点(最左边的节点)-> (后继 )它的根节点,若没有则找右节点
void _InOrder(BiTreeNode* Root)
{
if (Root == NULL)
{
return;
}
BiTreeNode* pCur = Root;
while (pCur )
{
while (pCur->Ltag == Link) //找最左边的节点
{
pCur = pCur->pLeft;
}
cout << pCur->_data << ' ';
while ( pCur && pCur->Rtag == Thread )//找中序后继节点
{
pCur = pCur->pRight;
cout << pCur->_data << ' ';
}
//没有后继,有右子树
pCur = pCur->pRight;
}
}
public:
//后序--线索二叉树
void PostOrderThreading()
{
_PostOrderThreading(_pRoot);
}
protected:
//后序--线索二叉数--C
//思路:左-右-根 和前面的一样
void _PostOrderThreading(BiTreeNode*& Root)
{
if (Root == NULL)
{
return;
}
_PostOrderThreading(Root->pLeft);
_PostOrderThreading(Root->pRight);
if (Root->pLeft == NULL)
{
Root->pLeft = Prev;
Root->Ltag = Thread;
}
if (Prev != NULL && Prev->pRight == NULL)
{
Prev->pRight = Root;
Prev->Rtag = Thread;
}
Prev = Root;
}
private:
BiTreeNode* _pRoot;
BiTreeNode* Prev; //记录
};
int main()
{
//char * arr = "013##4##25##6##";
char * arr = "012##3##4##";
Thread_BiTree tree(arr, strlen(arr));
tree.PreOrderThreading(); //先序线索化
tree.PreOrder(); //遍历先序线索二叉树
cout << endl << "------------------------" << endl;
char * arr1 = "013##4##25##6##";
Thread_BiTree tree1(arr1, strlen(arr1));
tree1.InOrderThreading(); //中序线索化
tree1.InOrder(); //遍历中序线索二叉树
cout << endl << "------------------------" << endl;
char * arr2 = "013##4##25##6##";
Thread_BiTree tree2(arr2, strlen(arr2));
tree2.PostOrderThreading();
tree2.PostOrder();
cout << endl << "------------------------" << endl;
return 0;
}
下一篇将详细的讲述,后序线索化二叉树及遍历(我也是花了很大的心思,看了好久才看明白)
PS:祝你们学习愉快,希望能学到知识的同时,分享给渴求知识的各位童鞋