310 likes | 491 Views
题 6.33. 题 6.39. 题 6.43. 题 6.45. 题 6.47. 题 6.48. 题 6.50. 题 6.51. 题 6.54. 题 6.60. 题 6.59. 题 6.66. 6.33 已知 L[i] 和 R[i](i=0,1,…,n-1) 分别指示二叉树中第 i 个结点的左孩子和右孩子结点, 0 表示空。试写判别结点 u 是否是结点 v 的子孙的算法。. 分析:. 1 。 u 是否是结点 v 的子孙≡≡ 从结点 v 出发遍历能否到达结点 u; “ 访问 ” ≡≡判结点 u 是否是结点 v 的孩子.
E N D
题 6.33 题 6.39 题 6.43 题 6.45 题 6.47 题 6.48 题 6.50 题 6.51 题 6.54 题 6.60 题 6.59 题 6.66
6.33已知L[i]和R[i](i=0,1,…,n-1)分别指示二叉树中第i个结点的左孩子和右孩子结点,0表示空。试写判别结点u是否是结点v的子孙的算法。6.33已知L[i]和R[i](i=0,1,…,n-1)分别指示二叉树中第i个结点的左孩子和右孩子结点,0表示空。试写判别结点u是否是结点v的子孙的算法。 分析: 1。u是否是结点v的子孙≡≡ 从结点v出发遍历能否到达结点u; “访问” ≡≡判结点u是否是结点v的孩子 2。给出的存储结构实质上是一个二叉链表
Status descendent(int L[], int R[], int u, int v) { if (u && v) { if (L[v]==u || R[v]==u) returnTRUE; else if (descendent(L, R, u, L[v])) returnTRUE; else return descendent(L, R, u, R[v]); } elsereturn FALSE; } 返回
6.45编写递归算法:对于二叉树中每一个元素值为x的结点,删去以它为根的子树,并释放相应的空间。6.45编写递归算法:对于二叉树中每一个元素值为x的结点,删去以它为根的子树,并释放相应的空间。 分析: 1。在先序遍历二叉树的过程中查找每一个元素值为x的结点; 2。修改其双亲结点的相应指针; 3。释放以它为根的子树上的所有结点,则应该后序遍历以它为根的子树。
voidDelete-X( BiTree &BT, ElemType x){ if (BT) { if (BT->data==x) { disp(BT);//后序遍历释放被删子树中所有结点 BT=NULL; // 修改指针,删除子树 } else { Delete-X(BT->Lchild, x); Delete-X(BT->Rchild, x); } } // if } 返回
6.51编写一个算法,输出以二叉树表示的算术表达式,若该表达式中含有括号,则在输出时应添上。6.51编写一个算法,输出以二叉树表示的算术表达式,若该表达式中含有括号,则在输出时应添上。 分析: 1。原表达式即为带括弧的中缀表达式,则解此题应该进行中序遍历; 2。表达式求值应该进行后序遍历,则表明左、右子树的运算应该先于根结点的运算进行;因此,若左、右子树根的运算符的优先数低于根结点运算符的优先数,则应对左子树或右子树的表达式加上括弧。
voidExpression(BiTree T) { if (T) { if (!Isoprator(T->data) )printf(T->data);// 操作数 else { if ( precede(T->data, T->Lchild->data) ) // 根结点运算符的优先数 > 左子树根结点的优先数 { printf(“(”); Expression(T->Lchild); printf(“)”); } elseExpression(T->Lchild); // 遍历左子树 printf(T->data); // 访问根结点 继续
if (!precede(T -> Rchild -> data, T-> data) ) // 根结点运算符的优先数≥右子树根结点的优先数 { printf(“(”); Expression(T->Rchild); printf(“)”); } elseExpression(T->Rchild); // 遍历右子树 }//else }//if(T) }// Expression 返回 注:设操作数的优先数的级别最高。
6.59编写算法完成下列操作:无重复地输出以孩子兄弟链表存储的树T中所有的边。输出的形式为(k1,k2),…,(ki,kj),…,其中,ki和kj为树结点中的结点标识。6.59编写算法完成下列操作:无重复地输出以孩子兄弟链表存储的树T中所有的边。输出的形式为(k1,k2),…,(ki,kj),…,其中,ki和kj为树结点中的结点标识。 在孩子兄弟链表中,哪一些结点是根的孩子?
voidOutEdge(CSTree T) { if (T) { p=T->firstchild; while (p) { printf( T->data, p->data); OutEdge(p); p=p->nextsibling; } } // 先根遍历输出树中各条边 返回
6.60编写算法,对一棵以孩子兄弟链表存储的树 T ,统计树中所含叶子结点的个数。 void CountLeaf (BiTree T, int& count){ if ( T ) { if ((!T->lchild) && (!T->rchild) ) count++; // 对叶子结点计数 CountLeaf( T->lchild, count); CountLeaf( T->rchild, count); } // if } // CountLeaf CSTree T, (!T->firstchild ) T->firstchild, T->nextsibling, 问:这个算法对吗? 返回
深度优先搜索遍历的非递归形式的算法: 一般情况下,需要一个“栈”保留一些信息。 例如:遍历二叉树时,需要利用栈保留曾经路过的根结点(中序);或者保留尚未遍历的右子树的根结点(先序)以便从左子树返回时继续遍历右子树; 后序遍历二叉树的情况要复杂一些,由于根结点的访问在左、右子树的遍历之后,则不仅要保存结点的信息,还应保存“标志” 反之,如果在存储结构中加上适当信息,则可以省略栈。
6.39假设在二叉链表的结点中增设两个域:双亲域(parent)以指示其双亲结点;标志域(mark:0..2)以区分在遍历过程中到达该结点时应继续向左或向右或访问该结点。试以此存储结构编写不用栈进行后序遍历的递推形式的算法。6.39假设在二叉链表的结点中增设两个域:双亲域(parent)以指示其双亲结点;标志域(mark:0..2)以区分在遍历过程中到达该结点时应继续向左或向右或访问该结点。试以此存储结构编写不用栈进行后序遍历的递推形式的算法。 mark域的作用在于识别当前的状态: mark=0 表示当前状态是对结点作第一次访问 mark=1 表示当前状态是对结点作第二次访问 mark=2 表示当前状态是对结点作第三次访问
void postorder( BiTree T) { p=T; while (p) { switch (p->mark) { case 0: p->mark=1; if (p->Lchild) p=p->Lchild; break; case 1: p->mark=2; if (p->Rchild) p=p->Rchild; break; case 2: p->mark=0; visit (p); p=p->parent; break; }//switch }//postorder 返回
647编写按层次顺序(同一层自左至右)遍历二叉树的算法。647编写按层次顺序(同一层自左至右)遍历二叉树的算法。 分析: 按层次遍历的定义: 若树不空,则首先访问根结点, 然后,依照其双亲结点访问的顺序, 依次访问它们的左、右孩子结点; 因此, 需要一个“队列”保存已被访问的结点
void BFSTraverse(BiTree T) { InitQueue(Q); // 置空的辅助队列Q if (T) EnQueue(Q, T); // 根结点入队列 while (!QueueEmpty(Q)) { DeQueue(Q, p); // 队头元素出队并置为p Visit(p); if (p->Lchild) EnQueue(Q, p->Lchild); // 左子树根入队列 if (p->Rchild) EnQueue(Q, p->Rchild); // 右子树根入队列 } // while } 继续
若要编写按层次顺序(同一层自左至右)遍历树的算法,则应如何?若要编写按层次顺序(同一层自左至右)遍历树的算法,则应如何? 分析: 因两者层次遍历的定义相同,则算法雷同, 差别仅在于: 二叉树至多只有左、右两棵子树,而树的子树个数不定,因此,当以孩子-兄弟链表表示树时,需要顺第一个孩子结点的右指针一直往于找到所有孩子结点。
void BFSTraverse(CSTree T) { InitQueue(Q); // 置空的辅助队列Q if (T) EnQueue(Q, T); // 根结点入队列 while (!QueueEmpty(Q)) { DeQueue(Q, p); // 队头元素出队并置为p Visit(p); for (q=p->firstchild; !q; q=q->nextsibling;) EnQueue(Q, q); // 子树根入队列 } // while } 返回
6.43编写递归算法,交换二叉树中所有结点的左右子树6.43编写递归算法,交换二叉树中所有结点的左右子树 注意:此题不能依中序遍历的次序进行 void swap( BiTree BT ) { if (BT) { BT->lchild BT->rchild; swap( BT->lchild); swap( BT->rchild); } } 返回
6.66编写算法,由树的双亲表示建孩子-兄弟链表。6.66编写算法,由树的双亲表示建孩子-兄弟链表。 算法分析: 假设已建好根结点,则只要在已知双亲链表中找到它的孩子结点,递归依次建各棵子树。 CSTree CrtCSTree( Ptree T ) { // 已知树的双亲表,返回树的孩子-兄弟链表 rt = new CSNode; rt->data = T.nodes[T.r].data; rt->firstchild = rt->nextsibling = NULL; CrtTree( T, rt, T.r ); //递归建各棵子树 return rt; } // CrtCSTree
void CrtTree( Ptree T, CSTree rt, int k ) { // 已知树的双亲链表 T 和已建立的指向根的指针rt // 递归建立树的孩子-兄弟链表, k为根在T中的序号 for ( i=0; i<T.n; i++ ) if ( T.nodes[i].parent == k ) { p = new CSNode; p->data = T.nodes[i].data; p->firstchild = p->nextsibling = NULL; if ( !rt->firstchild) rt->firstchild = p; else rt->nextsibling = p; q = p; }//if }//CrtTree 返回
6.48编写算法,求二叉树中两个结点“最靠近”的共同祖先。6.48编写算法,求二叉树中两个结点“最靠近”的共同祖先。 分析: 由二叉树中结点的子孙的定义“以 t 为根的子树中所有结点均为它的子孙”可以推出共同祖先的定义,由此可得到递归算法,显然,这个递归形式的算法效率不高。 另一种分析方法是,从根到该结点路径上所有的点都是它的祖先,则比较两条“路径”上的结点便可找到最靠近的共同祖先。 如何求得路径是本题的关键,显然出要利用“栈”记下遍历到已知结点时历经的结点。
栈的数据元素类型定义: typedef struct { BiTree ptr; // 指向二叉树中结点的指针 int tag; // 标志,向左为“0”,向右为“1” } ElemType; 其它变量说明: succ :初值为“假”,找到两个结点之后设为“真”; a : 初值为空,找到第一个结点时,指向它的双亲,之 后指向最靠近它们的共同祖先; k : 指示栈中元素个数(即路径长度),初值为“0”;
BiTNode *ancestor( BiTree rt, BiTNode *p, BiTNode *q ) { // p 和q 分别指向以rt 为根指针的二叉树上两个结点, // 若它们在此二叉树中存在共同祖先,则返回指向最 // 靠近这两个结点的共同祖先,否则返回 NULL InitStack(S); w.ptr=rt; w.tag=0; a = NULL; // a 指向最靠近的共同祖先 succ=FALSE; k=0; do { // 先序遍历二叉树 } while ( StackEmpty(S) || succ); if (succ) return a ; else return NULL; }// *ancestor …… 返回
while (w.ptr && !succ) { if ((w.ptr= = p) || (w.ptr == q)) { // 找到 *p 或 *q if ( StackEmpty(S)) succ=TRUE; // 其中一个为根 else if ( a ) succ:=TRUE; //*p和 *q 都已经找到 else { //才找到其中一个,则记下它的双亲位置 GetTop(S, f); a=f.ptr; k = StackLength(S) }//else if (!succ) { // 继续向左遍历 Push(S, w); w.ptr:=w.ptr->lc }//if }//while if (! Succ) { …… // 继续向右遍历 } 返回上一页
right:=true; while( !StackEmpty(S) && right ) { GetTop(S, w); if ( w.tag == 1 ) { // 栈顶结点是其双亲的右子树 j=Stacklength(S); if ( k > j ) { GetTop(S, f); a = f.ptr; k = j ; } }//if else {// 栈顶结点是其双亲的左子树,继续遍历右子树 right=false; w.tag=1; spush(s,w); w.ptr=w.ptr^.rc; w.tag=0 }//else } 返回上一页
6.50编写算法,由按层次输入的三元组(表示二叉树中一个分支)建二叉树的二叉链表。6.50编写算法,由按层次输入的三元组(表示二叉树中一个分支)建二叉树的二叉链表。 分析: 根据二叉树结点的信息是按层次(自上而下,自左至右)次序输入,自然建立二叉链表可在“层次遍历”中进行。 除了根结点之外,在建立每个结点的同时,必须修改其双亲指针域的值,为了便于找到当前建立的结点的双亲,需要一个和输入顺序特点“先进先出”相一致的结构----“队列”保存已建好的结点的“地址”。 则此题不能递归求解。
void Crt_BT(BiTree &BT) { // 按层次输入二叉树各边,建二叉链表 cin >> e.f >> e.c >> e.tag >> endl; if (e.c == '#’) BT = NULL; else { BT = new BiTNode; BT->data = e.c; // 建根结点 BT->lchild = BT->rchild = NULL; InitQueue(Q); EnQueue(Q,BT); }//else }//Crt_BT …… // 继续输入其余结点信息 返回
cin >> e.f >> e.c >> e.tag; while ((e.f != '#') && (e.c != '#')) { p = new BiTNode; p->data = e.c; // 新建孩子结点 p->lchild = p->rchild = NULL; EnQueue(Q, p); // 新建的结点入队列 GetHead(Q, q); while ( q->data != e.f ) { // 查询双亲结点 DeQueue(Q, s); DeQueue(Q, q); }//while if ( e.tag = ’L’ ) q->lchild = p; else q->rchild = p; cin >> e.f >> e.c >> e.tag; }//while 返回上一页
6.54编写算法,由已知的完全二叉树的顺序存储表示 sa 建二叉链表。 分析: 根据完全二叉树的特性, sa.elm[1]是二叉树的根结点; sa.elem[2i]是sa.elem[i]的左孩子(2i ≤ sa.last) sa.elem[2i+1]是sa.elem[i]的右孩子(2i+1≤sa.last) 则此题可以递归求解。
BiTree Build_Bitree(SqList sa) {// 由完全二叉树的顺序存储结构建二叉链表 bt = Build(sa,1); return bt; } BiTree Build(SqList sa, int i){ if (i>sa.last) return NULL; else { b = new BiTNode; // 建根结点 b->data = sa.elem[i]; b->lchild = build(sa,2*i); //递归建左子树 b->rchild = build(sa,2*i+1); //递归建右子树 return b; } }//build 返回