注:代码中使用的"ObjArrayList.h"头文件参看之前博文:数据结构之顺序列表。
//文件名:"ThBiTree.h"
#pragma once
#ifndef THBITREE_H_
#define THBITREE_H_
#include "ObjArrayList.h"
/*
. 线索二叉树 类模板定义
.
. 存储结构:线索链表
. 在二叉链表基础上增加标识位,对结点空域(左右子树指针域)增加遍历线索的存储
. 例如:
. ltag lchild | rtag rchild
. 0 左子树域 | 0 右子树域
. 1 遍历线索前驱域 | 1 遍历线索后继域
. 优点:
. 1.增加空间利用率:
. 解析:n 个结点的二叉树(二叉链表存储),在其 2n 个指针域中,就会有 n+1 个空域,只用了 n-1 个域,造成空间浪费,
. 故将这 n+1 个空域用来存放其遍历序列(先序、中序、后序)的前驱、后继结点。
. 2.加快遍历速度:
. 解析:可以直接以线索遍历,无需递归,相比二叉链表遍历更加快。
. 3.解决了二叉链表只能查找左右孩子,不能查找其在某种序列中的前驱后继的问题。
.
. 注:另外增加线索树头结点(非根结点),并作如下规定:
. 1.头结点的 lchild 域存放指向线索链表的根结点;
. 2.头结点的 rchild 域存放指向遍历序列的最后一个结点;
. 3.(中序、先序、后序)序列的第一个结点的 lchild 域指向头结点;
. 4.(中序、先序、后序)序列的最后一个结点的 rchild 域指向头结点。
*/
//线索二叉树结点
template <typename ElemType>
struct ThNode
{
ElemType * data; //数据域
ThNode<ElemType> *lchild, *rchild; //左右子树针指域
int ltag, rtag; //左右标识域 0|子树域 1|线索域
};
template <typename ElemType>
class ThBiTree
{
private:
ThNode<ElemType> * bt; //树根结点
ThNode<ElemType> * head; //线索链表头结点
ThNode<ElemType> * pre; //前驱结点临时变量
int tag = -1; //线索树类型 -1|空树 0|中序树 1|先序树 2|后序树
ThNode<ElemType> * _PreOrderCreate_R(ObjArrayList<ElemType> *list, int &index); //先序遍历递归构造二叉树(对象型结点)
void _InOrderThreading_R(ThNode<ElemType> *p); //中序遍历 递归 创建线索
void _PreOrderThreading_R(ThNode<ElemType> *p); //先序遍历 递归 创建线索
void _PostOrderThreading_R(ThNode<ElemType> *p); //后序遍历 递归 创建线索
public:
ThBiTree(ObjArrayList<ElemType> *list); //有参构造
/*
. 中序线索(既可向后,又可向前遍历)
. 优点:即可向前查找前驱,又可向后查找后继;
. 缺点:
. 1.前驱算法,需要对中序序列最后一个结点进行查找,没有后序线索前驱查找快;
. 2.后继算法,需要对中序序列第一个结点进行查找,没有先序线索后继查找快。
. 注:整体比较中和。
*/
void InOrderThreading(); //中序遍历 创建二叉树线索
void InOrderThreadingDisplay(); //中序序列 遍历显示
void InOrderThreadingDisplay_Reverse(); //中序序列 反向遍历显示
ThNode<ElemType> * GetInOrderPrecursor(ThNode<ElemType> *p); //获取中序序列 前驱结点
ThNode<ElemType> * GetInOrderSuccessor(ThNode<ElemType> *p); //获取中序序列 后继结点
/*
. 先序线索(只可向后,不可向前遍历)
. 优点:后继查找算法 比 中序线索的后继查找 更快;
. 缺点:前驱查找 需查找双亲结点,使整个算法又复杂了。
*/
void PreOrderThreading(); //先序遍历 创建二叉树线索
void PreOrderThreadingDisplay(); //先序序列 遍历显示
ThNode<ElemType> * GetPreOrderPrecursor(ThNode<ElemType> *p); //获取先序序列 前驱结点(需寻找双亲结点,效果不好,不实现)
ThNode<ElemType> * GetPreOrderSuccessor(ThNode<ElemType> *p); //获取先序序列 后继结点
/*
. 后序线索(只可向前,不可向后遍历)
. 优点:前驱查找算法 比 中序线索的前驱查找 更快;
. 缺点:后继查找 需查找双亲结点,使整个算法又复杂了。
*/
void PostOrderThreading(); //后序遍历 创建二叉树线索
void PostOrderThreadingDisplay_Reverse(); //后序序列 反向遍历显示
ThNode<ElemType> * GetPostOrderPrecursor(ThNode<ElemType> *p); //获取后序序列 前驱结点
ThNode<ElemType> * GetPostOrderSuccessor(ThNode<ElemType> *p); //获取后序序列 后继结点(需寻找双亲结点,效果不好,不实现)
};
template <typename ElemType>
ThBiTree<ElemType>::ThBiTree(ObjArrayList<ElemType> *list)
{
/*
. 有参构造:默认采用先序遍历顺序表构造二叉树
*/
int index = 0;
this->bt = _PreOrderCreate_R(list, index);
}
template <typename ElemType>
ThNode<ElemType> *ThBiTree<ElemType>::_PreOrderCreate_R(ObjArrayList<ElemType> *list, int &index)
{
/*
. 先序遍历递归创建二叉树
. 入参:
. ObjArrayList<ElemType> *list : 先序遍历组合的对象结点顺序表
. int &index: 顺序表遍历索引
. int seq: 以完全二叉树,对每个结点排序
. 出参:
. ThNode<ElemType> * : 树根结点指针
. 注:以顺序表(对象元素结点)创建
*/
ThNode<ElemType> *p = NULL;
//1.超出顺序表时返回,结束
if (index >= list->Length())
{
return p;
}
//2.元素为 NULL 时,意为空结点
else if (list->Get(index) == NULL)
{
return p;
}
//3.创建根结点,并递归创建其左右子树
else
{
p = new ThNode<ElemType>;
p->data = list->Get(index);
p->ltag = 0; //初始化 标志 为 0, 即子树指针域
p->lchild = _PreOrderCreate_R(list, ++index);
p->rtag = 0;
p->rchild = _PreOrderCreate_R(list, ++index);
}
return p;
}
template <typename ElemType>
void ThBiTree<ElemType>::InOrderThreading()
{
/*
. 中序遍历 创建二叉树线索
*/
//标记树为 中序线索树 0
this->tag = 0;
//1.初始化 线索链表 头结点
this->head = new ThNode<ElemType>;
this->head->data = NULL;
this->head->ltag = 0;
this->head->rtag = 1;
//二叉树为空
if (this->bt == NULL) {
//1.线索链表头结点
this->head->lchild = this->head;
this->head->rchild = this->head;
}
else
{
//1.头结点 lchild 指向 树根
this->head->lchild = this->bt;
//2.初始化 前驱结点 临时变量
this->pre = this->head;
//3.中序遍历 递归 创建线索
_InOrderThreading_R(this->bt);
//4.中序序列最后一个元素 rchild 指向头结点
this->pre->rtag = 1;
this->pre->rchild = this->head;
//5.头结点 rchild 指向中序序列最后一个元素
this->head->rchild = this->pre;
}
}
template <typename ElemType>
void ThBiTree<ElemType>::_InOrderThreading_R(ThNode<ElemType> *p)
{
/*
. 中序遍历 递归 创建线索
. 算法:左根右
. 访问根时的操作:可能创建当前结点的前驱线索,可能创建前驱结点的后继线索,两个操作并列
*/
if (p != NULL)
{
//1.左子树
_InOrderThreading_R(p->lchild);
//2.根
//2.1.若左子树指针域为空,创建序列前驱线索
//注:这一步中已经将中序序列的第一个结点 lchild 指向 头结点
if (p->lchild == NULL)
{
//1.置标识域为 1,并创建序列前驱线索
p->ltag = 1;
p->lchild = this->pre;
}
//2.2.若前驱右子树指针域为空,创建其后继 为当前结点
//注:这一步中已经将头结点 rchild 指向 中序序列的第一个结点
if (this->pre->rchild == NULL)
{
//1.置标识域为 1
this->pre->rtag = 1;
this->pre->rchild = p;
}
//2.3.将全局前驱结点指针移至当前结点
this->pre = p;
//3.右子树
_InOrderThreading_R(p->rchild);
}
}
template <typename ElemType>
void ThBiTree<ElemType>::InOrderThreadingDisplay()
{
/*
. 中序序列遍历显示
. 算法:
. 1.树根非空时,寻找树的第一个中序遍历结点(左下)
. 2.结点非空且不是头结点时,访问结点,并判断右指针域类型:
. 2.1.若 rchild 为右子树,寻找右子树第一个中序遍历结点(左下)
. 2.2.若 rchild 为后继结点,指针移向后继结点
. 3.循环 2 ...
*/
ThNode<ElemType> *h = this->head; //头结点(作为遍历结束标识)
ThNode<ElemType> *p = h->lchild; //根结点:头结点 lchild 指向根结点
int tag = 0; //右子树指针域类型标识 0|子树 1|线索
//树根为空
if (p == NULL)
return;
//结点不是头结点时,访问结点(无需判断非空)
while (p != h)
{
//1.若 标识记录为子树 且 结点非空,寻找子树的第一个中序遍历结点(左下)
while (tag == 0 && p->ltag == 0)
p = p->lchild;
//2.访问结点
cout << p->data << " ";
//3.标识记录:若 rchild 类型为子树,tag = 0;若 rchild 类型为线索,tag = 1。
//注:使用 tag 标识的原因:在 p = p->rchild; 后,无法得知前驱结点的 rchild 类型
tag = p->rtag == 0 ? 0 : 1;
//4.指针 p 移至 rchild 指向结点
p = p->rchild;
}
}
template <typename ElemType>
void ThBiTree<ElemType>::InOrderThreadingDisplay_Reverse()
{
/*
. 中序序列 反向遍历显示
*/
ThNode<ElemType> *h = this->head; //头结点
ThNode<ElemType> *p = h->rchild; //中序序列最后一个结点
//遍历到头结点时,结束
while (p != h)
{
cout << p->data << " ";
p = GetInOrderPrecursor(p);
}
}
template <typename ElemType>
ThNode<ElemType> * ThBiTree<ElemType>::GetInOrderPrecursor(ThNode<ElemType> *p)
{
/*
. 获取中序序列前驱结点
. 算法:
. 1.p->ltag == 1: 直接按线索取前驱;
. 2.p->ltag == 0: 取左子树中序序列的最后一个元素(右下)
*/
if (p == NULL)
return NULL;
if (p->ltag == 1)
p = p->lchild;
else
{
p = p->lchild;
while (p->rtag == 0)
p = p->rchild;
}
return p;
}
template <typename ElemType>
ThNode<ElemType> * ThBiTree<ElemType>::GetInOrderSuccessor(ThNode<ElemType> *p)
{
/*
. 获取中序序列后继结点
. 算法:
. 1.p->rtag == 1: 直接按线索取前驱;
. 2.p->rtag == 0: 取右子树中序序列的第一个元素(左下)
*/
if (p == NULL)
return NULL;
if (p->rtag == 1)
p = p->rchild;
else
{
p = p->rchild;
while (p->ltag == 0)
p = p->lchild;
}
return p;
}
template <typename ElemType>
void ThBiTree<ElemType>::PreOrderThreading()
{
/*
. 先序遍历 创建二叉树线索
*/
//标记树为 先序线索树 1
this->tag = 1;
//1.初始化 线索链表 头结点
this->head = new ThNode<ElemType>;
this->head->data = NULL;
this->head->ltag = 0;
this->head->rtag = 1;
//二叉树为空
if (this->bt == NULL) {
//1.线索链表头结点
this->head->lchild = this->head;
this->head->rchild = this->head;
}
else
{
//1.头结点 lchild 指向 树根
this->head->lchild = this->bt;
//2.初始化 前驱结点 临时变量
this->pre = this->head;
//3.先序遍历 递归 创建线索
_PreOrderThreading_R(this->bt);
//4.先序序列最后一个元素 rchild 指向头结点
this->pre->rtag = 1;
this->pre->rchild = this->head;
//5.头结点 rchild 指向先序序列最后一个元素
this->head->rchild = this->pre;
}
}
template <typename ElemType>
void ThBiTree<ElemType>::_PreOrderThreading_R(ThNode<ElemType> *p)
{
/*
. 先序遍历 递归 创建线索
*/
if (p != NULL)
{
//1.p->lchild 为空,增加前驱索引
if (p->lchild == NULL)
{
p->ltag = 1;
p->lchild = this->pre;
}
//2.前驱 rchild 为空,增加后继索引
if (this->pre->rchild == NULL)
{
this->pre->rtag = 1;
this->pre->rchild = p;
}
//3.前驱临时变量指向当前结点
this->pre = p;
//若 lchild 和 rchild 被更改为线索,则不做递归
if (p->ltag == 0)
_PreOrderThreading_R(p->lchild);
if (p->rtag == 0)
_PreOrderThreading_R(p->rchild);
}
}
template <typename ElemType>
void ThBiTree<ElemType>::PreOrderThreadingDisplay()
{
/*
. 先序序列遍历显示
. 算法:
. 1.p->ltag == 0: 直接取左子树根结点;
. 2.1.p->rtag == 0: 直接取右子树根结点;
. 2.2.p->rtag == 1: 直接按线索取后继。
*/
ThNode<ElemType> *h = this->head; //头结点
ThNode<ElemType> *p = h->lchild; //根结点
//遍历到头结点时,结束
while (p != h)
{
//1.输出结点
cout << p->data << " ";
//2.寻找后继结点
//2.1.(ltag == 0),后继为左子树根结点
if (p->ltag == 0)
p = p->lchild;
//2.2.(rtag == 0 || rtag == 1),后继为右子树根结点或线索
else
p = p->rchild;
}
}
template <typename ElemType>
ThNode<ElemType> * ThBiTree<ElemType>::GetPreOrderPrecursor(ThNode<ElemType> *p)
{
/*
. 获取先序序列前驱结点
. 算法:
. 1.根结点时:前驱为空;
. 2.ltag == 1 时:直接取前驱线索;
. 3.ltag == 0 时:需先找到其双亲结点,这一步只能通过从根开始先序遍历找,效果并不好
. 3.1.若是双亲的左子树:则前驱为其双亲结点;
. 3.2.若是双亲的右子树:则前驱为其双亲左子树的先序遍历最后一个结点:
. 算法:从子树根开始搜索
. 3.2.1.若有右子树,则指针移至右子树根结点;
. 3.2.2.若无右子树、有左子树,则指针移至左子树根结点;
. 3.2.3.若无左子树、也无右子树,则返回该结点,即为前驱。
. 如此循环 3.2.1 、3.2.2、3.2.3.....
*/
}
template <typename ElemType>
ThNode<ElemType> * ThBiTree<ElemType>::GetPreOrderSuccessor(ThNode<ElemType> *p)
{
/*
. 获取先序序列后继结点
. 算法:
. 1.p->ltag == 0: 直接取左子树根结点;
. 2.1.p->rtag == 0: 直接取右子树根结点;
. 2.2.p->rtag == 1: 直接按线索取后继。
*/
if (p == NULL)
return NULL;
if (p->ltag == 0)
p = p->lchild;
else
p = p->rchild;
return p;
}
template <typename ElemType>
void ThBiTree<ElemType>::PostOrderThreading()
{
/*
. 后序遍历 创建二叉树线索
*/
//标记树为 后序线索树 2
this->tag = 2;
//1.初始化 线索链表 头结点
this->head = new ThNode<ElemType>;
this->head->data = NULL;
this->head->ltag = 0;
this->head->rtag = 1;
//二叉树为空
if (this->bt == NULL) {
//1.线索链表头结点
this->head->lchild = this->head;
this->head->rchild = this->head;
}
else
{
//1.头结点 lchild 指向 树根
this->head->lchild = this->bt;
//2.初始化 前驱结点 临时变量
this->pre = this->head;
//3.后序遍历 递归 创建线索
_PostOrderThreading_R(this->bt);
//4.后序序列最后一个元素 rchild 指向头结点
this->pre->rtag = 1;
this->pre->rchild = this->head;
//5.头结点 rchild 指向后序序列最后一个元素
this->head->rchild = this->pre;
}
}
template <typename ElemType>
void ThBiTree<ElemType>::_PostOrderThreading_R(ThNode<ElemType> *p)
{
/*
. 后序遍历 递归 创建线索
*/
if (p != NULL)
{
_PostOrderThreading_R(p->lchild);
_PostOrderThreading_R(p->rchild);
//1.p->lchild 为空,增加前驱索引
if (p->lchild == NULL)
{
p->ltag = 1;
p->lchild = this->pre;
}
//2.前驱 rchild 为空,增加后继索引
if (this->pre->rchild == NULL)
{
this->pre->rtag = 1;
this->pre->rchild = p;
}
//3.前驱临时变量指向当前结点
this->pre = p;
}
}
template <typename ElemType>
void ThBiTree<ElemType>::PostOrderThreadingDisplay_Reverse()
{
/*
. 后序序列 反向遍历显示
. 算法:从根结点(后序序列最后一个结点)开始,向前(查找前驱)遍历
. 注:正向遍历显示,可在此基础上加个栈
*/
ThNode<ElemType> *h = this->head; //头结点
ThNode<ElemType> *p = h->rchild; //根结点(后序序列最后一个结点)
//遍历到头结点时,结束
while (p != h)
{
cout << p->data << " ";
p = GetPostOrderPrecursor(p);
}
}
template <typename ElemType>
ThNode<ElemType> * ThBiTree<ElemType>::GetPostOrderPrecursor(ThNode<ElemType> *p)
{
/*
. 获取后序序列前驱结点
. 算法:
. 1.若结点 ltag == 1:直接取前驱线索结点;
. 2.若结点 ltag == 0:
. 2.1.若其 rtag == 0(此时存在左右子树):则前驱为其右子树根结点;
. 2.2.若其 rtag == 1(此时只存在左子树):则前驱为其左子树根结点;
*/
if (p == NULL)
return NULL;
if (p->ltag == 1 || p->rtag == 1)
p = p->lchild;
else
p = p->rchild;
return p;
}
template <typename ElemType>
ThNode<ElemType> * ThBiTree<ElemType>::GetPostOrderSuccessor(ThNode<ElemType> *p)
{
/*
. 获取后序序列后继结点
. 算法:
. 1.若结点 rtag == 1:直接取后继线索结点;
. 2.若结点 rtag == 0:需先获取其双亲结点,用递归或栈从头遍历实现
. 2.1.若其为双亲的左子树,则后继为其双亲右子树后序遍历第一个结点;
. 2.2.若其为双亲的左子树,且其双亲无右子树,则后继为其双亲结点;
. 2.3.若其为双亲的右子树,则后继为其双亲结点。
*/
}
#endif // !THBITREE_H_
//文件名:"ThBiTree_Test.cpp"
#include "stdafx.h"
#include <iostream>
#include "ThBiTree.h"
#include "ObjArrayList.h"
using namespace std;
//树结点元素
struct Element
{
char a;
int b;
public:
friend ostream & operator <<(ostream& out, Element *p)
{
/*
. 友元函数重载输出操作符,实现对象输出
*/
out << p->a;
return out;
}
};
int main()
{
ObjArrayList<Element> *list = new ObjArrayList<Element>(15);
list->Add(0, new Element{ 'A',1 });
list->Add(1, new Element{ 'B',2 });
list->Add(2, new Element{ 'C',3 });
list->Add(3, NULL);
list->Add(4, NULL);
list->Add(5, new Element{ 'D',4 });
list->Add(6, new Element{ 'E',5 });
list->Add(7, NULL);
list->Add(8, new Element{ 'G',7 });
list->Add(9, NULL);
list->Add(10, NULL);
list->Add(11, new Element{ 'F',6 });
list->Add(12, NULL);
list->Add(13, NULL);
list->Add(14, NULL);
cout << endl << "----------------------中序线索树----------------------------" << endl;
ThBiTree<Element> *t_in = new ThBiTree<Element>(list);
t_in->InOrderThreading();
cout << endl << "正向遍历测试:查找后继" << endl;
t_in->InOrderThreadingDisplay();
cout << endl << "反向遍历测试:查找前驱" << endl;
t_in->InOrderThreadingDisplay_Reverse();
cout << endl << "----------------------先序线索树----------------------------" << endl;
ThBiTree<Element> *t_pre = new ThBiTree<Element>(list);
t_pre->PreOrderThreading();
cout << endl << "正向遍历测试:查找后继" << endl;
t_pre->PreOrderThreadingDisplay();
cout << endl << "----------------------后序线索树----------------------------" << endl;
ThBiTree<Element> *t_post = new ThBiTree<Element>(list);
t_post->PostOrderThreading();
cout << endl << "反向遍历测试:查找前驱" << endl;
t_post->PostOrderThreadingDisplay_Reverse();
return 0;
}