线索二叉树
定义:
对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域,利用这些空链域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针称为线索,加上线索的二叉树称为线索二叉树。
特点:
利用线索二叉树进行遍历时,不必采用堆栈处理,可看做是对线性结构进行访问,速度比一般二叉树的遍历速度快,且节约存储空间。并且任意一个结点都能直接找到它的前驱和后继结点。 但是结点的插入和删除麻烦,且速度也较慢。
中序线索二叉树
存储结构为
lc | ltag | data | rtag | rc |
---|
typedef struct tbtNode
{
char data;//结点中的信息
struct tbtNode* lc;//指向左孩子结点
struct tbtNode* rc;//指向右孩子结点
int ltag, rtag;//左右指针标识
}tbtNode;
当某个结点的ltag等于0时,表示该结点的lc指针不为空,即它有左孩子;当ltag等于1时,表示该结点的lc为空指针,这时就将其设置为线索。指向该结点在中序遍历中的直接前驱。
同理rtag指向该结点在中序遍历中的直接后继。
当ltag/rtag为1时,lc/rc由原来的空指针被修改为指向该结点的前驱/后继。
算法思路:
1,进行中序线索化时,初始化所有ltag和rtag都为0;
2,采用p指针指向当前结点,采用pre指针指向当前结点的前一个结点。
3,初始将pre置为NULL,通过递归入栈找到中序遍历中的第一个结点p,对它的lc和ltag修改,令其ltag=1;lc=pre;然后对pre赋值为p, 而p将被赋值为它在中序遍历中的下一个结点。
第一种情况:若p没有右子树,那么p应该被赋值为它的父结点,这通过退栈操作来完成。
第二张情况:若p有右子树,则需要将p->lc递归入栈找到p->lc结点最左边的结点
4,函数执行到最后,pre指向中序遍历的最后一个结点。需要将pre->rtag=1;pre->rc=NULL;至此结束。
线索化代码实现:
在这采用两个函数来实现中序线索化,注意!inThread函数的pre参数为对指针的引用。
void createInThread(tbtNode* p)//创建中序线索二叉树
{
tbtNode* pre = NULL;
if (p != NULL)
{
inThread(p, pre);//传递pre指针的引用,否则inThread运行完毕后,pre仍为NULL
pre->rc = NULL;//此时pre为二叉树中序遍历最后一个结点,其右子树定为空
pre->rtag = 1;
}
}
void inThread(tbtNode* p, tbtNode*& pre)//对pre指针的引用
{
if (p != NULL)
{
inThread(p->lc, pre);//对p的左子树递归调用
if (p->lc == NULL )
{
p->lc = pre;
p->ltag = 1;
}
if (pre != NULL&&pre->rc == NULL)
{
pre->rc = p;
pre->rtag = 1;
}
pre = p;
p = p->rc;
inThread(p, pre);//对当前p的左子树递归调用
}
}
在中序线索二叉树中求后继的代码如下:
tbtNode* next(tbtNode* p)
{
if (p != NULL)
{
if (p->rtag == 1)
return p->rc;
tbtNode* temp = p->rc;
while (temp->ltag==0)//判定条件不能写成temp->lc != NULL
temp = temp->lc;
return temp;
}
}
附上一个完整的例子:
#include<iostream>
typedef struct tbtNode
{
char data;//结点中的信息
struct tbtNode* lc;//左孩子结点
struct tbtNode* rc;//右孩子结点
int ltag, rtag;//左右指针标识
}tbtNode;
tbtNode* initial(char* ele, int num);//用数组初始化一棵树(创建一棵完全二叉树)
void createInThread(tbtNode* p);//创建中序线索二叉树
void inThread(tbtNode* p, tbtNode*& pre);//对二叉树进行线索化
tbtNode* next(tbtNode* p);//求p结点的后继结点
void inOrder(tbtNode* p);//中序遍历(中序线索二叉树)
int main()
{
using namespace std;
char data[6] = { 'a', 'b', 'c', 'd', 'e', 'f' };
tbtNode* p = initial(data, 6);
createInThread(p);
cout << "中序线索树遍历:";
inOrder(p);
cout << endl;
return 0;
}
tbtNode* initial(char* ele, int num)
{
if (num<1)
return NULL;
tbtNode* temp = new tbtNode[num];
int i = 0;
while (i < num)//将所有结点的左右子树置为空,左右标识均置为0
{
temp[i].lc = NULL;
temp[i].rc = NULL;
temp[i].ltag = 0;
temp[i].rtag = 0;
++i;
}
i = 0;
while (i < num/2)//通过完全二叉树的顺序存储来创建树的结构
{
if (2*i+1<num)
temp[i].lc = temp + 2 * i + 1;
if (2*i+2<num)
temp[i].rc = temp + 2 * i + 2;
++i;
}
for (i = 0; i < num; i++)//对树中的每个结点赋值
temp[i].data = ele[i];
return temp;
}
void createInThread(tbtNode* p)
{
tbtNode* pre = NULL;
if (p != NULL)
{
inThread(p, pre);//传递pre指针的引用,否则inThread运行完毕后,pre仍为NULL
pre->rc = NULL;//此时pre为二叉树中序遍历最后一个结点,其右子树定为空
pre->rtag = 1;
}
}
void inThread(tbtNode* p, tbtNode*& pre)//对pre指针的引用
{
if (p != NULL)
{
inThread(p->lc, pre);//对p的左子树递归调用
if (p->lc == NULL )
{
p->lc = pre;
p->ltag = 1;
}
if (pre != NULL&&pre->rc == NULL)
{
pre->rc = p;
pre->rtag = 1;
}
pre = p;
p = p->rc;
inThread(p, pre);//对当前p的左子树递归调用
}
}
tbtNode* next(tbtNode* p)
{
if (p != NULL)
{
if (p->rtag == 1)
return p->rc;
tbtNode* temp = p->rc;
while (temp->ltag==0)//判定条件不能写成temp->lc != NULL
temp = temp->lc;
return temp;
}
}
void inOrder(tbtNode* p)
{
if (p != NULL)
{
while (p->ltag == 0)
p = p->lc;//查找中序遍历第一个结点
while (p != NULL)//类似链表的顺序访问进行中序遍历
{
std::cout << p->data << ' ';
p = next(p);
}
}
}
后序线索二叉树
关于后序线索二叉树,在这里只介绍如何求p结点的后继。
一,如果p->rtag为1,那么p的后继为p->rc
二,若p->rtag为0时:
1,若p结点是它的父结点f的右孩子,那么父结点f为p的后继。
2,若若p结点是它的父结点f的左孩子:a,若p没有右兄弟,那么父结点f为p的后继。b,若p有右兄弟,那么p的后继为在右兄弟上后序遍历得到的第一个结点。(有些累,敲不懂了…无代码~)