6.3 二叉树的存储结构
一、二叉树的顺序存储表示
#define MAX_TREE_SIZE 100 //二叉树的最大结点数
typedef TElemType SqBiTree[MAX_TREE_SIZE];//0号单元存储根结点
SqBiTree bt;
二、二叉树的链式存储表示
1.二叉链表
2.三叉树
3.双亲链表
4.线索链表
1. 二叉链表
typedef struct BiTNode { TElemType data; struct BiTNode *lchild, *rchild; //左右孩子指针 }BiTNode, *BiTree; |
2.三叉链表
typedef struct TriTNode { TElemType data; struct TriTNode *lchild, *rchild; //左右孩子指针 struct TriTNode *parent; }TriTNode, *TriTree; |
3双亲链表
typedef struct BPTNode { TElemType data; int *parent; //指向双亲的指针 char LRTag; //左右孩子标识域 }BPTNode;
typedef struct BPTree { BPTNode nodes[MAX_TREE_SIZE]; int num_node; //结点数目 int root; //根结点的位置 }BPTree; |
4.线索链表
6.4 二叉树的遍历
一、问题的提出
顺着某一条搜索路径巡访二叉树中的结点,使得每个结点均被访问一次,而且仅被访问一次
“访问”的含义可以很广、如:输出结点的信息等。
“遍历”是任何类型均有的操作,对线性结构而言,只有一条搜索路径(因为每个结点均只有一个后继),故不需要另加讨论。而二叉树是非线性结构,每个结点有两个后继,则存在如何遍历即按什么样的搜索路径遍历的问题。
对“二叉树”而言,可以有三条搜索路径:
1. 先上后下的按层次遍历
2. 先左(子树)后右(子树)的遍历
3. 先右(子树)后左(子树)的遍历
二、先左(子树)后右(子树)的遍历
先(根)序的遍历算法:若二叉树为空树,则空操作;否则,1)访问根结点;2)先序遍历左子树;3)后序遍历右子树
中(根)序的遍历算法:若二叉树为空树,则空操作;否则,1)中序遍历左子树;2)访问根结点;3)后序遍历右子树
后(跟)序的变量算法:若二叉树为空树,则空操作;否则,1)后序遍历左子树;2)后续遍历右子树;3)访问根结点
三、算法的递归描述
19_001 |
void Preorder(BiTree T, void(* visit)(TElemType &c)) { //先序遍历二叉树 if(T) { visit(T->data); //访问结点 Preorder(T->lchild, visit); //遍历左子树 Preorder(T->rchild, visit); //遍历右子树 } } |
四、中序遍历算法的非递归描述
19_002 |
BiTNode *GoFarLeft(BiTree T, Stack *S) { if(! T) return NULL; while(T->lchild) { Push(S, T); T = T->lchild; } return T; } |
19_003 |
//非递归中序遍历的算法 void Inorder_l(BiTree T, void(* visit)(TelemType &e)) { Stack *S; t = GoFarLeft(T, S); //找到最左下的结点 while(t) { visit(t->data); if(t->rchild) t = GoFarLeft(t->rchild, S); else if(! StackEmpty(S)) //栈不空时退栈 t = Pop(S); else t = NULL; //栈空表明遍历结束 } } |
一、遍历算法的应用
1.统计二叉树中叶子结点的个数(先序遍历)
020_001 |
void CountLeaf(BiTree T, int &count) { if(T) { if((! T->lchild) && (!T->rchild)) count ++; CountLeaf(T->lchild); CountLeaf(T->rchild); } } |
2. 求二叉树的深度(后序遍历)
020_002 |
int Depth(BiTree T) { if(! T) depthval = 0; else { depthLeft = Depth(T->lchild); depthRight = Depth(T->rchild); depthval = 1+(depthLeft > depthRight ? depthLeft : depthRight); } return depthval; } |
3. 复制二叉树(后序遍历)
020_003 |
//生成一个二叉树 BiTNode *GetTreeNode(TElemType item, BiTNode *lptr, BiTNode *rptr) { if(! (T = (BiTNode *) malloc(sizeof(BiTNode)))) exit(1); T->data = item; //T->lchild = T->rchild = NULL; //存在问题应该改为下面2行 T->lchild = lptr; T->rchild = rptr; return T; } |
020_004 |
BiTNode *CopyTree(BiTNode *T) { if(! T) return NULL; if(T->lchild) newlptr = CopyTree(T->lchild); else newlptr = NULL; if(T->rchild) newrptr = CopyTree(T->rchild); else newrptr = NULL; newnode = GetTreeNode(T->data, newlptr, newrptr); return newnode; } |
4. 建立二叉树的存储结构
按给定的先序序列建立二叉树链表
20_005 |
Status CreateBiTree(BiTree &T) { scanf(& ch); if(ch == '') T = NULL; else { if(! (T = (BiTNode *) malloc(sizeof(BiTNode))) exit(OVERFLOW); T->data = ch; //生成根结点 CreateBiTree(T->lchild); //构造左子树 CreateBiTree(T->rchild); //构造右子树 } return OK; }// CreateBiTree |
按给定的表达式建相应二叉树
由先缀表示建树
20_006 |
vodi CrtExptree(BiTree &T, char exp[]) { InitStack(S); Push(S, '#'); InitStack(PTR); p = exp; ch = *p; while(! GetTop(S) == '#' && ch == '#')) { if(! IN(ch, OP)) CrtNode(t, ch); //建立叶子结点并入栈 else { switch(ch) { case '(': Push(S, ch); break; case ')': //... default: //default }//switch }//else if(ch != '#') { p ++; ch = *p; } }// while Pop(PTR, t); }//CrtExptree |
20_007 |
Pop(S, c); while(c != '(') { CrtSubtree(t, c); //创建二叉树并入栈 Pop(S, c); } break;
while(! Gettop(S, c) && (precede(c, ch))) { CrtSubtree(t, c); Pop(S, c); } if(ch != '#') Push(S, ch); break; |
建叶子结点的算法为:
20_008 |
void CrtNode(BiTree &T, char ch) { T = (BiTNode *) malloc(sizeof(BiTNode)); T->data = char; T->lchild = T->rchild = NULL; Push(PTR, T); } |
建子树的算法为:
20_009 |
void CrtSubtree(BiTree &T, char c) { T = (BiTNode *) malloc(sizeof(BiTNode)); T->data = c; Pop(PTR, rc); T->rchild = rc; Pop(PTR, lc); T->lchild = lc; Push(PTR, T); } |
由二叉树的先序和中序序列建树
例如:已知二叉树
先序序列:abcdefg
中序序列:cdbaegf
分析可得:根结点是a 左子树是bcd 右子树是egf
6.5 线索二叉树
何谓线索二叉树
线索链表的遍历算法
如何建立线索表?
遍历二叉树的结果是,求得结点的一个线性序列
指向该线性序列中的“前驱”和“后继”的指针,称作“线索”
包含“线索”的存储结构,称作“线索链表”
与其相应的二叉树,“线索二叉树”
对线索链表中结点的约定:
在二叉链表的结点中增加两个标识域,并作如下规定:
若该结点的左子树不空
则lchild域的指针指向其左子树,且左标志域的值为0
否则,lchild域的指针指向其“前驱”,且左标志的值为1
若该节点的右子树不空
则rchild域的指针指向其右子树,且右标志域的值为0,“指针Link”
否则,rchild域的指针指向其“后继”,且右标志的值为1“指针Thread”。
线索链表的结构描述:
21_001 |
typedef enum(Link, Thread) PointerThr; //Link == 0;指针,Thread==1; 线索 typedef struct BiThrNode { TElemType data; struct BiThrNode *lchild, *rchild; //左右指针 PointerThr LTag, RTag; //左右标志 }BiThrNode, *BiThrTree; |
二、线索链表的遍历算法
21_002 |
for(p = firstNode(T); p; p = Succ(p)) Visit(p); |
中序线索化链表的遍历算法:
中序遍历的第一个结点?
在中序线索化链表中结点的后继?
21_003 |
Status InOrderTraverse_Thr(BiTree T, Status(*Visit)(TElemType e)) { p = T->child; //p指向根结点 while(p != T) //空树或遍历结束时 p==T { while(p->LTag == Link) p = p->lchild; if(! Visit(p->data)) return ERROR; while(p->RTag == Thread && p->rchild != t) { p = p->rchild; Visit(p->data); //访问后继结点 } p = p->rchild; //p进其右子树跟 } return OK; }// InOrderTraverse_Thr |
三、如何建立线索链表
在中序遍历过程中修改结点的左、又指针域,以保存当前访问结点的“前驱”和“后继”信息。遍历过程中,附设指针pre,并始终保持指针pre指向当前访问的,指针p所指结点的前驱。
21_004 |
void InThreading(BiThrTree p) { if(p) { InThrcading(p->lchild); //左子树线索化 if(! p->lchild) { p->LTag = Thread; p->lchild - pre; //建前驱线索 } if(! pre->rchild) { Pre->RTag = Thread; pre->rchild = p; //建后继限速 } pre = p; InThreading(p->rchild); //右子树线索化 } }//InThreading |
21_005 |
Status 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; InThreading(T); pre->rchild = Thrt; pre->RTag = Thread; Thrt->rchild = pre; //处理最后一个结点 } return OK; }//InOrderThreading |