数据结构学习笔记 --- 线索二叉树
2012-02-26 21:25
453 查看
1. 引言
为了更方便、快捷地遍历二叉树,最好在二叉树的结点上增加2个指针,它们分别指向遍历二叉树时该结点的前驱和后继。这
样,从二叉树的任一结点都可以方便的找到其他结点。但这样做大大降低了结构的存储密度。
另外,根据二叉树的性质,有:n0 = n2 + 1。空链域=2n0 + n1(叶子结点有两个空链域,度为1的结点有一个空链域)=n0+n2+1+n1
=n+1。也就是说,在由n个结点组成的二叉树中,有n+1个指针是空指针。如果能利用这n+1个空指针,使它们指向结点的前驱(当左孩子指针空)或后继(当有孩子指针空),则即可以不降低结点的存储密度,又可以方便的遍历二叉树。不过,这样就无法区别左右孩子指针所指的到底是结点的左右孩子,还是结点的前驱后继了。为了有所区别,另增两个域(LTag和RTag)。
2. 线索二叉树
为了更方便、快捷地遍历二叉树,最好在二叉树的结点上增加2个指针,它们分别指向遍历二叉树时该结点的前驱和后继。这
样,从二叉树的任一结点都可以方便的找到其他结点。但这样做大大降低了结构的存储密度。
另外,根据二叉树的性质,有:n0 = n2 + 1。空链域=2n0 + n1(叶子结点有两个空链域,度为1的结点有一个空链域)=n0+n2+1+n1
=n+1。也就是说,在由n个结点组成的二叉树中,有n+1个指针是空指针。如果能利用这n+1个空指针,使它们指向结点的前驱(当左孩子指针空)或后继(当有孩子指针空),则即可以不降低结点的存储密度,又可以方便的遍历二叉树。不过,这样就无法区别左右孩子指针所指的到底是结点的左右孩子,还是结点的前驱后继了。为了有所区别,另增两个域(LTag和RTag)。
2. 线索二叉树
#include "ds.h" #define CHAR 1 // 字符型 // #define CHAR 0 // 整型(二者选一) #if CHAR typedef char TElemType; TElemType Nil=' '; // 字符型以空格符为空 #define form "%c" // 输入输出的格式为%c #else typedef int TElemType; TElemType Nil=0; // 整型以0为空 #define form "%d" // 输入输出的格式为%d #endif #define DEBUG #ifdef DEBUG int count = 1; #endif enum PointerTag // 枚举 { Link, // 指针 Thread // 线索 }; struct BiThrNode { TElemType data; BiThrNode *lchild, *rchild; // 左右孩子指针 PointerTag LTag, RTag; // 左右标志 }; typedef BiThrNode* BiThrTree; BiThrTree pre; // 全局变量,始终指向刚刚访问过的结点 void CreateBiThrTree(BiThrTree &T); void InThreading(BiThrTree p); void InOrderThreading(BiThrTree &Thrt,BiThrTree T); void InOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType)); void PreThreading(BiThrTree p); void PreOrderThreading(BiThrTree &Thrt,BiThrTree T); void PreOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType)); void PostThreading(BiThrTree p); void PostOrderThreading(BiThrTree &Thrt,BiThrTree T); void PostOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType)); void DestroyBiTree(BiThrTree &T); void DestroyBiThrTree(BiThrTree &Thrt); // 二叉树的二叉线索存储的基本操作,包括算法 // 按先序输入线索二叉树中结点的值,构造线索二叉树T。0(整型)/空格(字符型)表 void CreateBiThrTree(BiThrTree &T) { TElemType ch; scanf(form, &ch); if (ch == Nil) { T = NULL; } else { T = (BiThrTree)malloc(sizeof(BiThrNode)); // 生成根结点(先序) if (!T) exit(OVERFLOW); T->data = ch; #ifdef DEBUG printf(form, ch); printf("------ %d ------\n", count++); #endif CreateBiThrTree(T->lchild); // 递归构造左子树 if (T->lchild) // 有左孩子 T->LTag = Link; // 给左标志赋值(指针) CreateBiThrTree(T->rchild); // 递归构造右子树 if (T->rchild) // 有右孩子 T->RTag = Link; // 给右标志赋值(指针) } } // 通过中序遍历进行中序线索化,线索化之后pre指向最后一个结点。算法6.7 void InThreading(BiThrTree p) { if (p) // 线索二叉树不空 { InThreading(p->lchild); // 递归左子树线索化 if (!p->lchild) // 没有左孩子 { p->LTag = Thread; // 左标志为线索(前驱) p->lchild = pre; // 左孩子指针指向前驱 } if (!pre->rchild) // 前驱没有右孩子 { pre->RTag = Thread; // 前驱的右标志为线索(后继) pre->rchild = p; // 前驱右孩子指针指向其后继(当前结点p) } pre = p; // 保持pre指向p的前驱 InThreading(p->rchild); // 递归右子树线索化 } } // 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点。 void InOrderThreading(BiThrTree &Thrt,BiThrTree T) { if(!(Thrt=(BiThrTree)malloc(sizeof(BiThrNode)))) // 生成头结点不成功 exit(OVERFLOW); Thrt->LTag = Link; // 建头结点,左标志为指针 Thrt->RTag = Thread; // 右标志为线索 Thrt->rchild = Thrt; // 右指针回指 if (!T) // 若二叉树空,则左指针回指 { Thrt->lchild=Thrt; } else { Thrt->lchild = T; // 头结点的左指针指向根结点 pre = Thrt; // pre(前驱)的初值指向头结点 InThreading(T); // 中序遍历进行中序线索化,pre指向中序遍历的最后一个结点 pre->rchild = Thrt; // 最后一个结点的右指针指向头结点 pre->RTag = Thread; // 最后一个结点的右标志为线索 Thrt->rchild = pre; // 头结点的右指针指向中序遍历的最后一个结点 } } // 中序遍历线索二叉树T(头结点)的非递归算法。算法6.5 void InOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType)) { BiThrTree p; p = T->lchild; // // 空树或遍历结束时,p==T while (p != T) { while(p->LTag==Link) // 由根结点一直找到二叉树的最左结点 p=p->lchild; Visit(p->data); // 访问此结点 while(p->RTag==Thread&&p->rchild!=T) // p->rchild是线索(后继),且不是遍历的最后一个结点 { p = p->rchild; Visit(p->data); // 访问后继结点 } p = p->rchild; // 若p->rchild不是线索(是右孩子),p指向右孩子,返回循环, } // 找这棵子树中序遍历的第1个结点 } // PreOrderThreading()调用的递归函数 void PreThreading(BiThrTree p) { if (p) // 非空树 { if (!pre->rchild) // p的前驱没有右孩子 { pre->rchild = p; // p前驱的后继指向p pre->RTag = Thread; // pre的右孩子为线索 } if (!p->lchild) // p没有左孩子 { p->LTag = Thread; // p的左孩子为线索 p->lchild = pre; // p的左孩子指向前驱 } pre = p; // 移动前驱 if (p->LTag == Link) // p有左孩子 PreThreading(p->lchild); // 对p的左孩子递归调用preThreading() if (p->RTag == Link) // p有右孩子 PreThreading(p->rchild); // 对p的右孩子递归调用preThreading() } } // 先序线索化二叉树T,头结点的右指针指向先序遍历的最后1个结点 void PreOrderThreading(BiThrTree &Thrt,BiThrTree T) { if (!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode)))) // 生成头结点 exit(OVERFLOW); Thrt->LTag = Link; // 头结点的左指针为孩子 Thrt->RTag = Thread; // 头结点的右指针为线索 Thrt->rchild = Thrt; // 头结点的右指针指向自身 if (!T) // 空树 Thrt->lchild = Thrt;// 头结点的左指针也指向自身 else // 非空树 { Thrt->lchild = T; // 头结点的左指针指向根结点 pre = Thrt; // 前驱为头结点 PreThreading(T); // 从头结点开始先序递归线索化 pre->rchild = Thrt; // 最后一个结点的后继指向头结点 pre->RTag = Thread; Thrt->rchild = pre; // 头结点的后继指向最后一个结点 } } // 先序遍历线索二叉树T(头结点)的非递归算法 void PreOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType)) { BiThrTree p = T->lchild; // p指向根结点 while (p != T) // p没指向头结点(遍历的最后1个结点的后继指向头结点) { Visit(p->data); // 访问根结点 if (p->LTag == Link) // p有左孩子 p = p->lchild; // p指向左孩子(后继) else // p无左孩子 p = p->rchild; // p指向右孩子或后继 } } // PostOrderThreading()调用的递归函数 void PostThreading(BiThrTree p) { if (p) { PostThreading(p->lchild);// 对p的左孩子递归调用PostThreading() PostThreading(p->rchild);// 对p的右孩子递归调用PostThreading() if (!p->lchild) // p没有左孩子 { p->LTag = Thread; // p的左孩子为线索 p->lchild = pre; // p的左孩子指向前驱 } if (!pre->rchild) // p的前驱没有右孩子 { pre->rchild = p; // p前驱的后继指向p pre->RTag = Thread; // pre的右孩子为线索 } pre = p; // 移动前驱 } } // 后序递归线索化二叉树 void PostOrderThreading(BiThrTree &Thrt,BiThrTree T) { if (!(Thrt = (BiThrTree)malloc(sizeof(BiThrNode)))) // 生成头结点 exit(OVERFLOW); Thrt->LTag = Link; // 头结点的左指针为孩子 Thrt->RTag = Thread; // 头结点的右指针为线索 if (!T) // 空树 Thrt->lchild = Thrt->rchild = Thrt;// 头结点的左右指针指向自身 else // 非空树 { Thrt->lchild = Thrt->rchild = Thrt;// 头结点的左右指针指向根结点(最后一个结点) pre = Thrt; // 前驱为头结点 PostThreading(T); // 从头结点开始后序递归线索化 if (pre->RTag != Link); // 最后一个结点没有右孩子 { pre->rchild = Thrt; // 最后一个结点的后继指向头结点 pre->RTag = Thread; } } } // DestroyBiThrTree调用的递归函数,T指向根结点 void PostOrderTraverse_Thr(BiThrTree T,void(*Visit)(TElemType)) { // 对于后续线索化二叉树的后序遍历算法比较复杂,因为根结点是在最后访问,所以要采用带有双亲的三叉链表结构才行。本文没有给出其算法 } // DestroyBiThrTree调用的递归函数,T指向根结点 void DestroyBiTree(BiThrTree &T) { if (T) // 非空树 { if (T->LTag == 0) // 有左孩子 DestroyBiTree(T->lchild); // 销毁左孩子子树 if (T->RTag == 0) // 有右孩子 DestroyBiTree(T->rchild); // 销毁右孩子子树 free(T); // 释放根结点 T = NULL; // 空指针赋0 } } // 初始条件:线索二叉树Thrt存在。操作结果:销毁线索二叉树Thrt void DestroyBiThrTree(BiThrTree &Thrt) { if (Thrt) // 头结点存在 { if (Thrt->lchild) // 根结点存在 { DestroyBiTree(Thrt->lchild); // 递归销毁头结点lchild所指二叉树 } free(Thrt); // 释放头结点 Thrt = NULL; // 线索二叉树Thrt指针赋0 } } void vi(TElemType c) { printf(form" ",c); } int main() { BiThrTree H,T; #if CHAR printf("请按先序输入二叉树(如:ab三个空格表示a为根结点,b为左子树的二叉树)\n"); #else printf("请按先序输入二叉树(如:1 2 0 0 0表示1为根结点,2为左子树的二叉树)\n"); #endif CreateBiThrTree(T); // 按先序产生二叉树 #if 0 PreOrderThreading(H,T); // 在先序遍历的过程中,先序线索化二叉树 printf("先序遍历(输出)线索二叉树:\n"); PreOrderTraverse_Thr(H,vi); #endif #if 0 InOrderThreading(H, T); printf("中序遍历(输出)线索二叉树:\n"); InOrderTraverse_Thr(H,vi); // 中序遍历(输出)线索二叉树 #endif PostOrderThreading(H,T); // 在后序遍历的过程中,后序线索化二叉树 printf("program over\n"); DestroyBiThrTree(H); // 销毁线索二叉树 return 0; }
相关文章推荐
- 数据结构学习笔记-线索二叉树
- 数据结构学习笔记 --- 线索二叉树
- 数据结构学习笔记:顺序表
- 算法导论学习笔记-第二十一章-用于不相交集合的数据结构
- python数据结构学习笔记-2016-11-30-01-堆
- 【学习笔记----数据结构17-图的十字链表】
- 严蔚敏数据结构学习笔记四.串
- 数据结构学习笔记-双向链表(C语言实现)
- 数据结构学习笔记 --- 图(十字链表)
- 数据结构学习笔记 --- 查找(哈希表)
- 数据结构学习笔记
- 数据结构与基本算法的学习笔记-数组-3
- nginx 学习笔记(三)基本数据结构
- OpenCV学习笔记(四)——新版本的数据结构core
- ECMAScript 6 学习笔记----set和map数据结构
- 严蔚敏版数据结构学习笔记(2):线性表的链式表示和实现
- 数据结构学习笔记8--n个元素进栈,共有多少种出栈顺序?
- C#数据结构学习笔记--线性表[转]
- 数据结构学习笔记
- 算法导论学习笔记——第10章 基本数据结构