二叉树算法二之中序线索

   在介绍线索二叉树之前,我们需要知道对于一个有n个结点的二叉树,每个结点有指向左右孩子的两个指针域,所有一共有2n个指针域。而n个结点一共有n-1个分支线数,则存在2n-(n-1)=n+1个空指针域,而这里介绍的都是指中序线索二叉树。

    对于任意的遍历二叉树,为了解决清楚地知道任意一个结点的前驱和后继的问题,引入了线索二叉树,把指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树,对应的数据结构是

typedef enum{Link,Thread}PointType;

struct BiTree{
	struct BiTree *lchild,*rchild
	PointType ltag,rtag;
	char data;
};

    而线索二叉树就是有效的利用前面计算中存在的n+1个空指针域,让所有的空指针域中的rchild指向它的后继结点,让所有空指针域的lchild指向它的前驱结点。如图,中序的遍历是HDIBJEAFCG


    其实线索二叉树就是把一颗二叉树转变 成了双向链表。一开始还觉得这个观点很新颖,但是想想确实是那么回事,如图(中序遍历HDIBJEAFCG)   


    完成线索二叉树和遍历的函数一共有四个,其中1.创建二叉树 2.线索化二叉树 3.初始化线索二叉树 4.遍历线索二叉树,首先是1,和上一节介绍的一样,只不过对于有左子树和右子树的结点的ltag和rtag都应该为Link(默认0),代码如下:

struct BiTree *createTree(){
	struct BiTree *root;
	char val;
	scanf("%c",&val);
	if(val=='#') return NULL;
	else{
		root=(struct BiTree*)malloc(sizeof(struct BiTree));
		if(!root)  return NULL;
		root->data=val;
		root->lchild=createTree();
		if(root->lchild)  root->ltag=Link;
		root->rchild=createTree();
		if(root->rchild)  root->rtag=Thread;
	}
	return root;
}
    核心的函数2,线索二叉树函数,就是对于每一个结点如果左子树为空,就让ltag=Thread,然后让ltag指向前驱结点,如果右子树为空,就让rtag=Thread,然后让rtag指向后继结点,既然存在前驱结点,那么肯定需要一个指针指向前驱,在代码中就是pre
struct BiTree *pre;

void inThreading(struct BiTree* root){
	if(root){
		inThreading(root->lchild);
		if(!root->lchild){
			root->ltag=Thread;
			root->lchild=pre;
		}
		if(!pre->rchild){
			pre->rtag=Thread;
			pre->rchild=root;
		}
		pre=root;
		inThreading(root->rchild);
	}
}
看上面的代码,其实去除两个if和pre的赋值,不就是中序遍历的代码嘛,只不过现在加上了对于左右标志的赋值,和对于前驱后继指针的修改。
   对于步骤3,由于2中用到了pre指针,但是明显没有初始化,而线索二叉树和双向链表一样,加上了一个头结点,让其lchild指向二叉树的根节点,其rchild指向二叉树的中序遍历的最后一个节点,可以参考第一个图片,pre的初始值是指向头结点,然后每次都指向中序序列的下一个结点(HDIBJEAFCG)。接下来就是初始化函数:
int initThreadTree(struct BiTree**head,struct BiTree *root){
	*head=(struct BiTree*)malloc(sizeof(struct BiTree));
	if(!*head) return 0;
	(*head)->ltag=Link;
	(*head)->rtag=Thread;
	(*head)->rchild=*head;
	if(!root){
		(*head)->lchild=*head;
	}
	else{
		(*head)->lchild=root;
		pre=(*head);  //初始化pre全局变量
		threading(root);
		pre->rtag=Thread;
		pre->rchild=*head;
		(*head)->rchild=pre;
	}
	return 1;
}
   其实步骤4,有2和3这两个函数就已经完成了对于二叉树的线索化的过程,但是最后我们怎么知道自己写的对不对呢,这样就多了一个遍历的函数,代码如下:
int threadTravderse(struct BiTree *head){
	struct BiTree *p=head->lchild;
	while(p!=head){
		while(p->ltag==Link){
			p=p->lchild;
		}
		printf("%c",p->data);
		while(p->rtag==Thread&&p->rchild!=head){    //p肯定是指向到最后节点,所以p->rchild最后指向head
			p=p->rchild;
			printf("%c",p->data);
		}
		p=p->rchild;
	}
	return 1;
}
  总的代码如下:
#include <stdio.h>
#include <stdlib.h>

typedef enum{Link,Thread}PointType;

struct BiTree{
	struct BiTree *lchild,*rchild;
	PointType ltag,rtag;
	char data;
};

struct BiTree *pre;

struct BiTree* createTree(){
	char val;
	struct BiTree *root;
	scanf("%c",&val);
	if(val=='#'){
		return NULL;
	}
	else{
		root=(struct BiTree*)malloc(sizeof(struct BiTree));
		if(!root) return NULL;
		root->data=val;
		root->lchild=createTree();
		if(root->lchild) root->ltag=Link;  //左子树不为空,则ltag=0
		root->rchild=createTree();
		if(root->rchild) root->rtag=Link;
	}
	return root;
}

int threading(struct BiTree *root){
	if(root){
		threading(root->lchild);
		if(!root->lchild){ //左孩子为空
			root->ltag=Thread;
			root->lchild=pre;
		}
		if(!pre->rchild){   //右孩子为空
			pre->rtag=Thread;
			pre->rchild=root;
		}
		pre=root;
		threading(root->rchild);
	}
	return 1;
}

int initThreadTree(struct BiTree**head,struct BiTree *root){
	*head=(struct BiTree*)malloc(sizeof(struct BiTree));
	if(!*head) return 0;
	(*head)->ltag=Link;
	(*head)->rtag=Thread;
	(*head)->rchild=*head;
	if(!root){
		(*head)->lchild=*head;
	}
	else{
		(*head)->lchild=root;
		pre=(*head);  //初始化pre全局变量
		threading(root);
		pre->rtag=Thread;
		pre->rchild=*head;
		(*head)->rchild=pre;
	}
	return 1;
}

int threadTravderse(struct BiTree *head){
	struct BiTree *p=head->lchild;
	while(p!=head){
		while(p->ltag==Link){
			p=p->lchild;
		}
		printf("%c",p->data);
		while(p->rtag==Thread&&p->rchild!=head){    //p肯定是指向到最后节点,所以p->rchild最后指向head
			p=p->rchild;
			printf("%c",p->data);
		}
		p=p->rchild;
	}
	return 1;
}

int main(){
	struct BiTree *head=NULL,*root=NULL;
	printf("先序创建二叉树(如ABDH##I##EJ###CF##G##)\n"); 
	root=createTree();
	initThreadTree(&head,root);
	threadTravderse(head);
	return 0;
}
   其中初始化二叉树的head是一个二级指针,这和我在双向链表中需要解决的问题的方式一样,不赘述。我也是看完大话之后,自己理解的情况下再敲的一遍,其实文中反复提到中序遍历的结果,写代码的时候,对照第一张图,然后想清楚现在到了哪一个结点,再思考这样写感觉就八九不离十了。


未完待续 下一节 二叉树算法三之哈弗曼树和编码


如果文章有什么错误或者有什么建议,欢迎提出,大家共同交流,一起进步

文章转载请注明出处,请尊重知识产权

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值