线索二叉树
来源:互联网 发布:阿里云盒子root 编辑:程序博客网 时间:2024/06/07 00:15
定义
由于一般二叉树的链式表示会有很多空闲的指针域,假设一课有n结点的二叉树,用二叉链表表示存储结构,则它的空闲链域有n+1个。因为每个结点有左右孩子链域,所以总链域为2n。又由于除了根节点,必定有它的父亲结点的某个链域指向它,因此使用的链域为n-1;所以空闲链域为2n-(n-1) = n+1个。为了充分利用这些链域,我们将这些链域设置为线索。线索的定义是,一棵树按照某种遍历方式将存储的数据线性化过程中,当一个元素保存了它的前驱或者后继信息,则那些信息称为在该树在某种遍历方式下的线索。线索树,就是充分利用空闲链域,使得某个结点在没有对应的左右孩子的时候,将它的前驱或者后继信息保存在该结点的左右孩子链域中。将一般二叉树按照某种遍历方式变成线索二叉树的过程,叫做线索化。
用中序线索二叉树举例(下面的所有讲解包括实现代码将会围绕中序围绕中序线索二叉树讲): 在上图(c)中我们可以发现,二叉树的中序遍历方式为BDAECF,关于D结点,D的前驱为B,后继为A。观察D的左孩子链域发现并没有指向左孩子,而是指向B结点。因为D结点没有左孩子,为了充分利用这个空闲的链域,左孩子链域保存了D结点的前驱线索。观察D的右孩子链域,同理发现它保存的是D的后继结点线索。
相关操作和时间复杂度分析
线索化
线索二叉树,最核心的部分就是如何将一颗一般二叉树,通过某种遍历方式,将其线索化成对应的满足需求的线索二叉树。现在就以中序线索化二叉树为例,讲解如何线索化二叉树。与在定义里不同的是,我们通常要引入一个头结点来保存线索二叉树的开始结点和结束结点信息。如下图所示:
头结点thrt的左孩子链域指向二叉树的根节点,右孩子链域指向二叉树中序遍历的最后一个。这样只要知道头结点,那么既可以正向“中序遍历”二叉树,又可以逆向“中序遍历“二叉树了。因为每个结点都保存了前驱和后继的关系。
现在还有一个问题,由于结点和线索都是结点指针变量,那么怎么区分左右孩子链域保存的是左右孩子结点还是前驱后继线索呢?这里对结点结构体还要引入两个变量就是LTag和RTag;当LTag为LINK时,则表示左孩子链域保存的是左孩子结点信息。如果LTag为THREAD时,则表示左孩子链域保存的是前驱线索。RTag同理可推,只不过保存的是右孩子信息或者是后继线索。
中序线索化二叉树也可以用递归来考虑,用全局变量pre记录上次我们走过的结点,假设我们立足于结点A,考虑一下三种情况
- 结点A是否有左孩子,如果没有则left = pre, LTag = THREAD 即记录它的前驱线索;否则, LTag = LINK;
- pre结点是否有右孩子,如果没有则pre->right = A, pre->RTag = THREAD 即记录A结点前驱结点的后继线索;否则, pre->RTag = LINK;
- 更新走过的结点,准备进入下一个递归 pre = A;
如果将中序线索化二叉树的函数名字叫做threading(Tree *tree), 则伪代码可以描述成如下方案:
Tree *pre; // 全局变量threading(Tree *tree){ if(tree){ threading(tree->left) if(tree->left){ tree->LTag = LINK; }else{ tree->LTag = THREAD; tree->left = pre; } if(pre->right){ pre->RTag = LINK; }else{ pre->RTag = THREAD; pre->right = tree; } pre = tree; threading(tree->right); }}Tree *A;threading(A);
时间复杂度和遍历二叉树一样也是O(n);
遍历线索树
遍历线索树,要从头结点出发,但不是用递归的方式遍历了,因为线索化之后的树从某种意义上来说已经是线性表的关系了。因此可以利用循环迭代的方式遍历,以下以中序线索树为例主要步骤如下:
- 判断当前结点是否为空,若不为空进行以三个下步骤
- 当结点的LTag=LINK的时候, 进入当前结点的左孩子结点,重复该步骤;
- 当结点的RTag=THREAD的时候并且结点不是头结点的时候,进入该结点的后继结点,访问后继结点数据, 重复该步骤;
- 直到当前结点RTag=LINK,则表示无法找到后继,当然这也就说明结点的右孩子链域保存了右孩子,则进入右孩子结点;
时间复杂度显然为结点总数,即O(n);
实现代码
#include<stdio.h>#include<stdlib.h>#define END -1#define LINK 0#define THREAD 1typedef struct TNode *TNodePtr;TNodePtr pre;typedef struct TNode { int data; TNodePtr left,right; int LTag,RTag; // 如果LTag 为LINK表示该结点的left指针域为左孩子,如果为THREAD则表示为前驱线索 // 如果RTag 为LINK表示该结点的right指针域为右孩子,如果为THREAD则表示为后继线索}TNode, *TNodePtr;/* * 先序建树 */void create_tree(TNodePtr *node){ int input; scanf("%d", &input); if(input==END) (*node)=NULL; else{ (*node) = (TNodePtr) malloc(sizeof(TNode)); (*node)->data = input; create_tree(&((*node)->left)); create_tree(&((*node)->right)); }}/* * 中序线索化二叉树 */void threading(TNodePtr node){ if(node){ threading(node->left); // 左子树线索化 // 如果当前左孩子为空,则进行left指针域记录前驱线索 // 前驱线索保留在pre指针变量里面 if(node->left) { node->LTag = LINK; } else { node->LTag = THREAD; node->left = pre; } // 如果前驱结点的right指针域为空,则right指针域记录后继线索 // 而后继线索为当前结点,因为hreading本身就是一个中序遍历过程 if(pre->right){ pre->RTag = LINK; } else { pre->RTag = THREAD; pre->right = node; } pre = node; threading(node->right); // 右子树线索化 }}/* * 建立中序线索 */void inOrderThreading(TNodePtr *head, TNodePtr root){ (*head) = (TNodePtr)malloc(sizeof(TNode)); (*head)->LTag = LINK; (*head)->RTag = THREAD; (*head)->right = (*head); // 默认先把头结点的right线索回指,因为如果待线索化的树为空,right指针应该指向自己 if(!root) (*head)->left = (*head); // 如果待线索化的树为空,那么头结点的左孩子指针也得回指自己 else { (*head)->left = root; // 待线索化的树root不为空,则头结点的left孩子指针指向root结点 pre = (*head); // 记录当前前驱 threading(root); pre->right = (*head); pre->RTag = THREAD; (*head)->right = pre; }}void inorder_thread_visit(TNodePtr head){ TNodePtr p = head->left; // 中序遍历的最后一个结点的right指针域指向head while(p != head){ while(p->LTag==LINK) p=p->left; // 先定位到最左子树叶结点 printf("%d ",p->data); while(p->RTag == THREAD && p->right != head){ p = p->right; printf("%d ", p->data); } p = p->right; }}int main(){ TNodePtr root; create_tree(&root); TNodePtr head; inOrderThreading(&head, root); printf("in order thread three: "); inorder_thread_visit(head);}
- 线索二叉树 --->树
- 线索二叉树算法
- C#线索二叉树
- 线索二叉树
- C#线索二叉树
- 线索二叉树
- 线索二叉树
- 线索化二叉树
- 线索二叉树
- C#线索二叉树
- C#线索二叉树
- 线索二叉树
- 线索二叉树实例
- 线索二叉树
- 线索二叉树算法
- C++线索二叉树
- 线索二叉树
- 线索二叉树实现
- Beaglebone Black更新系统的方法
- 当Java代码遇上抽象、重载加重写,一切都不美好了
- 64位Ubuntu12.04下安装arm-linux-gdb,以及解决no termcap library found的方法
- 关于assetbundle的详解等
- 链接
- 线索二叉树
- 八皇后问题
- Gradle构建控制Log开关——BuildConfig\自定义
- Android Battery一些信息获取方法
- is not allowed to connect to this MySQL server解决办法
- verilog-for 语句实例
- git 命令(1)
- 欢迎使用CSDN-markdown编辑器
- 滑动返回上一个界面的实现