在二叉树的结点上加上线索的二叉树称为线索二叉树(Threaded BinaryTree),对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程称为对二叉树进行线索化,从而使得每个结点(除了第一个和最后一个结点以外)都有一个直接前驱和直接后继。
原理
ni表示度数为i的结点个数,很明显,i=0,1,2
首先对于一个含有n个结点的二叉树,空指针的总数为2n0+n1,
又因为n=n0+n1+n2=n1+2n2+1,从而得到n0=n2+1,
因此空指针的总数为2n0+n1=n0+n1+n2+1=n+1。
而总指针的数量有2n个,很明显造成了很大的浪费,利用率非常低,因此为了提高利用率,也为了遍历的方便,我们可以利用这些空指针将二叉树线索化,使得每个结点的前驱后继都非常清晰明了,容易查找。
准备工作
这里使用的是C++实现。
首先这里使用的树结构如下所示,可以看到,这是一颗二叉排序树:
其次,因为每个结点的左右指针可能指向前驱后继,也可能指向左右子树,因此需要加上标志位来确定:
l
t
a
g
=
{
0
,
l
C
h
i
l
d
指
向
结
点
的
左
孩
子
1
,
l
C
h
i
l
d
指
向
结
点
的
前
驱
ltag=\begin{cases} 0,lChild指向结点的左孩子\\ 1, lChild指向结点的前驱\end{cases}
ltag={0,lChild指向结点的左孩子1,lChild指向结点的前驱
r t a g = { 0 , r C h i l d 指 向 结 点 的 右 孩 子 1 , r C h i l d 指 向 结 点 的 后 继 rtag=\begin{cases} 0,rChild指向结点的右孩子\\ 1, rChild指向结点的后继\end{cases} rtag={0,rChild指向结点的右孩子1,rChild指向结点的后继
相应的代码如下所示:
typedef struct BiTNode {
int data;
struct BiTNode* lChild, * rChild; //左右孩子指针
int ltag, rtag; //左右线索标志
}BiTNode;
typedef struct {
BiTNode* root;
}BiTree;
void insert(BiTree& tree, int data) { //根据二叉排序树的思想插入结点
BiTNode* node = new(BiTNode);
node->data = data;
node->lChild = node->rChild = nullptr;
node->ltag = node->rtag = 0;
if (tree.root == nullptr) {
tree.root = node;
} else {
BiTNode* temp = tree.root;
while (temp != nullptr) {
if (node->data < temp->data) {
if (temp->lChild == nullptr) {
temp->lChild = node;
return;
} else {
temp = temp->lChild;
}
} else {
if (temp->rChild == nullptr) {
temp->rChild = node;
return;
} else {
temp = temp->rChild;
}
}
}
}
}
然后在main()函数中添加上这段代码:
int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
BiTree tree;
tree.root = nullptr;
for (int i = 0; i < 10; i++) insert(tree, data[i]);
那么接下来就进入到正题了。
代码实现
中序遍历线索化二叉树:
void inThread(BiTNode* &p, BiTNode* &pre) {
if (p) {
inThread(p->lChild, pre); //递归,线索化左子树
if (p->lChild == nullptr) { //p左子树为空则指向前驱
p->lChild = pre;
p->ltag = 1;
}
if (pre && pre->rChild == nullptr) { //pre右子树为空则指向后继
pre->rChild = p;
pre->rtag = 1;
}
pre = p; //标记当前结点为刚刚访问过的结点
inThread(p->rChild, pre); //递归线索化右子树
}
}
void createInThread(BiTree& tree) {
BiTNode* pre = nullptr;
BiTNode* p = tree.root;
if (tree.root) { //对非空二叉树线索化
inThread(p, pre);
pre->rChild = nullptr; //遍历的最后一个结点的右孩子指向nullptr,没有后继
pre->rtag = 1;
}
}
这里注意inThread()函数的参数要用引用类型,否则很可能pre和p会一不小心就指向nullptr,亲测,血的教训!
线索化之后的中序遍历:
BiTNode* firstNode(BiTNode* p) {
while (p->ltag == 0) p = p->lChild; //找到最左下结点,不一定是叶子结点
return p;
}
BiTNode* nextNode(BiTNode* p) {
if (p->rtag == 0) return firstNode(p->rChild);
else return p->rChild; //若rtag为1,直接返回后继线索
}
void inOrder(BiTNode* root) {
for (BiTNode* p = firstNode(root); p; p = nextNode(p)) {
cout << p->data << " ";
}
}
运行结果如下:
可以看到,输出的结果为中序遍历的结果。
前序遍历和后序遍历的线索化也和中序遍历线索化差不多,这里就不再赘述了。
完整代码
#include<iostream>
using namespace std;
typedef struct BiTNode {
int data;
struct BiTNode* lChild, * rChild; //左右孩子指针
int ltag, rtag; //左右线索标志
}BiTNode;
typedef struct {
BiTNode* root;
}BiTree;
void insert(BiTree& tree, int data) { //根据二叉排序树的思想插入结点
BiTNode* node = new(BiTNode);
node->data = data;
node->lChild = node->rChild = nullptr;
node->ltag = node->rtag = 0;
if (tree.root == nullptr) {
tree.root = node;
} else {
BiTNode* temp = tree.root;
while (temp != nullptr) {
if (node->data < temp->data) {
if (temp->lChild == nullptr) {
temp->lChild = node;
return;
} else {
temp = temp->lChild;
}
} else {
if (temp->rChild == nullptr) {
temp->rChild = node;
return;
} else {
temp = temp->rChild;
}
}
}
}
}
void inThread(BiTNode* &p, BiTNode* &pre) {
if (p) {
inThread(p->lChild, pre); //递归,线索化左子树
if (p->lChild == nullptr) { //p左子树为空则指向前驱
p->lChild = pre;
p->ltag = 1;
}
if (pre && pre->rChild == nullptr) { //pre右子树为空则指向后继
pre->rChild = p;
pre->rtag = 1;
}
pre = p; //标记当前结点为刚刚访问过的结点
inThread(p->rChild, pre); //递归线索化右子树
}
}
void createInThread(BiTree& tree) {
BiTNode* pre = nullptr;
BiTNode* p = tree.root;
if (tree.root) { //对非空二叉树线索化
inThread(p, pre);
pre->rChild = nullptr; //遍历的最后一个结点的右孩子指向nullptr,没有后继
pre->rtag = 1;
}
}
BiTNode* firstNode(BiTNode* p) {
while (p->ltag == 0) p = p->lChild; //找到最左下结点,不一定是叶子结点
return p;
}
BiTNode* nextNode(BiTNode* p) {
if (p->rtag == 0) return firstNode(p->rChild);
else return p->rChild; //若rtag为1,直接返回后继线索
}
void inOrder(BiTNode* root) {
for (BiTNode* p = firstNode(root); p; p = nextNode(p)) {
cout << p->data << " ";
}cout << endl;
}
void delTree(BiTNode* root) {
BiTNode* pre = nullptr;
for (BiTNode* p = firstNode(root); p; ) {
pre = p;
p = nextNode(p);
delete(pre);
}
}
int main() {
int data[10] = { 5, 3, 9, 2, 4, 1, 7, 10, 6, 8 };
BiTree tree;
tree.root = nullptr;
for (int i = 0; i < 10; i++) insert(tree, data[i]);
createInThread(tree);
inOrder(tree.root);
delTree(tree.root);
cout << "释放成功!" << endl;
return 0;
}