线索二叉树的实现

来源:互联网 发布:七五事件汉人反击 知乎 编辑:程序博客网 时间:2024/05/14 06:45

前言:

大家好,我是一大二学生狗,最近一直在学数据结构,感到有很多的疑惑,相信有很多同学跟我一样快被逼疯了。前段时间在论坛读了weiers的博客,感觉同样是学生,差距咋这么大,所以我也试着来一发,因为是第一次发,所以不可能有大神们的风范,有很多地方我也理解的模棱两可,希望各位大神可以多指出错误,更正解释。由于大一学的是C++,但是数据结构是C语言版的,所以代码看上去可能有些不伦不类,希望大家多担待!

线索二叉树:

学到二叉树链表的同学都知道,二叉树遍历的实现实际上是将二叉树这种非线性结构转换成了一种线性结构,由此可以轻松实现遍历。但正如单链表只能从前往后查找一样,二叉链表这种存储结构也存在着一些问题,即二叉链表只能找到左、右孩子的信息,而不能找到结点在任一序列中的前驱和后继信息,如何解决这个问题呢?我们想到在有n个结点的二叉链表中存在n+1个空链域,我们可以利用这些空链域存放结点的前驱后继信息,为此我们需要将结点类型做一些改进:

lchildltagdatartagrchild在原来的基础上新增两个标志域,并规定:

ltag=0lchild指示域指示结点的左孩子

ltag=1lchild指示域指示结点的前驱

rtag=0rtag指示域指示结点的右孩子

rtag=1rtag指示域指示结点的后继

以这种结点结构构成的二叉链表作为二叉树的存储结构,叫做线索链表,其中指向结点前驱和后继的指针叫做线索。加上线索后的二叉树成线索为二叉树。

线索二叉树的实现:

这里的思想是创建出一个二叉链表,然后经过一次遍历,在遍历的过程中添加线索,并将二叉树转换成线索二叉树,即将二叉树线索化。

首先建立二叉树结点结构,并定义枚举,存放两个标志,枚举默认值从0开始,下面的Link和Thread的值分别为0和1

typedef int ElemType;//设定两个标志,当标志域的值为Link(0)时,表示当前节点有孩子,//child指针存放的是孩子信息,当标志域的值为Thread(1),标志没有孩子//没有左孩子,则将lchild指针指向前驱,没有右孩子,则将rchild指向后继typedef enum{Link, Thread}PoniterTag;typedef struct BiThrNode{ElemType data;BiThrNode *lchild;BiThrNode *rchild;PoniterTag ltag;PoniterTag rtag;} *BiThrTree;
然后按照先序遍历的方式创建二叉链表

//注意此处传递的参数类型是二级指针,因为值传递并不会为实参添加结点,此处应采用地值传递。void CreatBiThrTree(BiThrTree *root){ElemType e;cin >> e;//这里规定e为零时表示结点为空if (e == 0){*root = NULL;}else{*root = (BiThrNode *)malloc(sizeof(BiThrNode));(*root)->data = e;//首先将标志全设置为Link,在线索化二叉树的时候进行修改(*root)->ltag = Link;(*root)->rtag = Link;CreatBiThrTree(&(*root)->lchild);CreatBiThrTree(&(*root)->rchild);}}

然后我们要在遍历过程中线索化二叉树,首先我们看下面的简图:

        1

      /     \

    2        5

  /     \        \ 

3       4         6

我们首先需要弄清楚到底应采用那种遍历方式进行线索化二叉树

先序遍历:123456

中序遍历:324156

后序遍历:342561

左孩子或右孩子为空的结点,我用不同的颜色表示了出来,我们看,先序遍历和中序遍历左右孩子为空的结点位置没有规律,而中序遍历出的结果,基本上是每隔一个结点就会有一个左右孩子为空的结点,这样,2结点的lchild指示前驱3结点,其rchild指针指向4结点,其余结点亦是如此。因此我们采用中序遍历线索化的方式。

我们看,当遍历到结点3的时候3结点的左孩子为空,我们将ltag的值改为Thread,并将左孩子指示3结点的前驱,但是此时出现了问题,3结点的前驱找不到了,因此我们必须定义一个pre指针,使它指向3结点的前驱结点,然后将3结点的左孩子指向pre即可,

//定义全局变量pre,用于表示前驱结点BiThrNode *pre;void InThreading(BiThrTree root){if (root != NULL){//只要结点的左孩子不为空,就一直遍历InThreading(root->lchild);//处理节点//当左孩子为空,修改线索if (root->lchild == NULL){//修改标志,并将lchild指向前驱root->ltag = Thread;root->lchild = pre;}//寻找后继if (pre->rchild == NULL){pre->rtag = Thread;pre->rchild = root;}//前驱后继都已寻找,改变pre指向pre = root;//左孩子遍历完,遍历右孩子InThreading(root->rchild);}}
正如单链表转换成双链表,实现了链表的首尾相接,我们在将二叉链表转换为线索链表时,也应该实现头结点和最后一个结点的连接,在遍历结束时,我们可以得到最后一个结点,但是我们却找不到头结点了,以为没有指针可以指示头结点(pre指针是在不断的移动的),为此,我们可以另设一个头指针,使其指向头结点,在遍历结束时,将最后一个结点回指头指针。设立头指针的另一个作用是在实现线索二叉树遍历的时候,可以将头指针作为参数传入函数。
//此处的header头指针是二级指针,因为后面还要用头指针遍历void InOrderThreading(BiThrTree *header, BiThrTree root){*header = (BiThrNode *)malloc(sizeof(BiThrNode));//假设头指针有左孩子(*header)->ltag = Link;//假设头指针没有右孩子(*header)->rtag = Thread;//rchild指向自身(*header)->rchild = *header;//如果书为空树,将头指针的ltag修改,并将lchild指向自身if (root == NULL){(*header)->ltag = Thread;(*header)->lchild = *header;}else{//头指针指向头结点(*header)->lchild = root;//pre指向头结点pre = *header;//遍历线索化InThreading(root);//遍历结束,要将最后一个结点回指头指针pre->rchild = *header;pre->rtag = Thread;//头指针无右孩子,指向pre,实现首尾相接(*header)->rchild = pre;}}

//中序非递归遍历线索二叉树void InOrderTraveres(BiThrTree header){//定义一个用于遍历的指针,从头结点开始BiThrNode *p = header->lchild;//p指针只有在树为空后者遍历结束时才会等于header,如果不等,说明遍历未结束while (p != header){//如果ltag一直为Link,则一直遍历知道某结点无左孩子while (p->ltag == Link){p = p->lchild;}//输出节点信息visit(p->data);//当结点也没有右孩子,并且该结点不是最后一个结点(只有最后一个结点的rchild指向header),继续遍历while (p->rtag == Thread && p->rchild != header){//输出前驱结点信息p = p->rchild;visit(p->data);}//左子树遍历后,开始遍历右子树p = p->rchild;}}

int main(){//声明头指针BiThrTree header = NULL;//声明一棵空树BiThrTree root = NULL;//创建二叉树CreatBiThrTree(&root);//中序遍历线索化InOrderThreading(&header, root);//中序非递归遍历cout << "中序非递归遍历\n";InOrderTraveres(header);cout << endl;system("pause");return 0;}

打印结果:

1 2 3 0 0 4 0 0 5 0 6 0 0中序非递归遍历:3 2 4 1 5 6
1 0
原创粉丝点击