1.汉诺塔问题:
void hannoi (int n, char A, char B, char C)
{
if (n == 1)
{
cout << "Move disk " << n << " from " << A << " to " << C << endl;
}
else
{
hannoi (n-1, A, C, B);
cout << "Move disk " << n << " from " << A << " to " << C << endl;
hannoi (n-1, B, A, C);
}
}
2.二叉树的三种遍历:
(1)先序遍历
Status PreOrderTraverse (BiTree &T){
if (T){
std::cout<< T->data < PreOrderTraverse (T->lchild); PreOrderTraverse (T->rchild); } else return OK; } (2)中序遍历 Status InOrderTraverse (BiTree &T){ if (T){ InOrderTraverse (T->lchilde); std::cout<< T->data < InOrderTraverse (T->rchild); } else return OK; } (3)后序遍历 Status PostOrderTraverse (BiTree &T){ if (T){ PostOrderTraverse (T->lchild); PostOrderTraverse (T->rchild); std::cout<< T->data < } else return OK; } 个人总结:感觉递归算法是非常强大的工具,尤其用在汉诺塔,树的遍历等,这样涉及大量重复性操作的问题求解中。 比如汉诺塔问题,要想将n个盘子由A移动到C,则先将前n-1个移动到B,然后再把第n个盘子由A移动到C,然后再把剩下的盘子由B移动到C。用了递归算法,只需重复调用算法即可,将负责的问题简单化。 再比如以上的三种二叉树的遍历操作,先序遍历,必定是先遍历根节点,然后遍历根的左子树,再遍历根的右子树。中序遍历,则是先遍历根的左子树,再遍历根节点,再遍历根的右子树,而在遍历左子树的过程中,将左子树当做一个单独的树,也按照先遍历左子树,根,右子树的顺序遍历,这样层次遍历下去,完成递归,在遍历左右子树的过程中不断又调用了递归方法。可以说是很好很强大的算法。 但以上四个算法,其实都还有可以改进的地方,那就是遍历过程所涉及的操作,以上四个算法就单纯的把它写死了,就是简单的输出,这样的弊端是:比如某天想要在遍历二叉树的过程中,将原来的值输出后,把原来的值加1,那么就不得不更改三个遍历算法,每个算法都要做改动,这样其实程序的可扩展性很差劲。所以最好的方法是,在遍历的时候,指定访问函数,通过访问函数对元素值就行处理:输出,更改,删除等等,这样某天想在遍历的时候,附加某些操作的时候,只需将其绑定的访问函数做更改即可即可,其他代码不需要再变动。 改动过后的以上四个算法如下所示: 1'汉诺塔问题 int i = 0; void Move (char x, int k, char z){ std::cout << "The "<< ++i <<" step : Move " << k <<" from "<< x <<" to " << z < } void Hanoi (int n, char x, char y, char z) { if (1 == n){ Move (x, n, z); } else{ Hanoi (n-1, x, z, y); Move (x, n, z); Hanoi (n-1, y, x, z); } } 这样通过加入一个Move方法,在遍历的过程中对元素进行处理,增加了可扩展性。 如果要移动n个盘子,则总共需要2^n - 1步。 2'二叉树的先序遍历 void PrintElemType (TElemType e){ std::cout<< e < } Status PreOrderTraverse (BiTree &T. void (*Visit) (TElemType e)){ if (T){ Visit (T->data); PreOrderTraverse (T->lchild, Visit); PreOrderTraverse (T->rchild, Visit); } else return OK; } 这样通过加入visit访问函数,使得后续遍历时想对元素做附加操作的话,只需通过更改特定的绑定函数就可以方便的完成。 另外这里用到的指针函数是一个非常差方便的概念,在NS3里面大量用到,需要注意。 3'二叉树的中序遍历 Status InOrderTraverse (BiTree &T, void (*Visit)(TElemType e)){ if (T){ InOrderTraverse (T->lchild, Visit); Visit (T->data); InOrderTraverse (T->rchild, Visit); }else return OK; } 4'二叉树的后序遍历 Status PostOrderTraverse (BiTree &T, void (*Visit)(TElemType e)){ if (T){ PostOrderTraverse (T->lchild, Visit); PostOrderTraverse (T->rchild, Visit); Visit (T->data); }else return OK; } 5''二叉树中序非递归遍历:递归遍历和非递归遍历的根本区别在于,递归时系统替你管理栈,非递归要自己管理。 typedef struct{ BiTree *base; BiTree *top; int stacksize; }SqStack; Status InitSqStack (SqStack &S){ S.base = (BiTree *) malloc (sizeof (BiTree) * STACK_INIT_SIZE); if (!S.base) exit (OVERFLOW); S.top = S.base; S.stacksize = STACK_INIT_SIZE; return OK; } bool IsStackEmpty (SqStack &S){ if (S.base == S.top) return true; return false; } Status GetTop (SqStack &S, BiTree &e){ if (S.top == S.base) return ERROR; e = *(S.top - 1); return OK; } Status Pop (SqStack &S, BiTree &e){ if (S.top == S.base) return ERROR; e = *--S.top; return OK; } Status Push (SqStack &S, BiTree &e){ if (S.top >= S.base + STACK_INIT_SIZE){ S.base = (BiTree *) realloc (sizeof (BiTree) * (STACK_INIT_SIZE + STACK_INCREMENT)); if (!S.base) exit (OVERFLOW); S.top = S.base + STACK_INIT_SIZE; S.stacksize += STACK_INCREMENT; } *S.top++ = e; return OK; } //中序遍历二叉树,非递归的第一种方法。 Status InOrderTraverseNonRecursion (BiTree &T, void (*Visit) (TElemType e)){ InitSqStack (S); Push (S, T); BiTree p; while (!StackEmpty(S)){ while (GetTop (S, p) && p) Push (S, p->lchild);//入栈前不做检查,最后会入栈一个空指针 Pop (S, p); if (!StackEmpty (S)){ Pop (S, p); Visit (p->data); Push (S, p->rchild); }//if }//while return OK; } //中序遍历二叉树,非递归的第二种方法。 Status InOrderTraverseNonRecursion2 (BiTree &T, void (*Visit) (TElemType e)){ SqStack S; InitSqStack (S); BiTree p = T; while (p || !StackEmpty (S)){ if (p) {//在入栈前先做检查,如果为空,表明已经到头。 Push (S, p); p = p->lchild; } else { Pop (S, p); Visit (p->data); p = p->rchild; } }//while return OK; } 比较以上两种中序遍历二叉树的非递归方法: 1.第一种,在将遍历指针入栈前不做检查,直接入栈,所以会出现将最后空指针加入到栈的情况。 2.第二种,在将遍历指针入栈前作检查,如果为空,表明已经到头,所以不会把空指针入栈。 3.第一种,在遍历右子树的时候,将右子树入栈Push (S, p->rchild);然后再返回while循环,继续遍历,指针入栈 4.第二种,p = p->rchild;然后返回while循环,继续判断p指针,以此来遍历右子树 除了这两点,其余的情况基本一样。 6.二叉树的先序非递归算法: Status PreOrderTraverseNonRecursion (BiTree &T, void (*Visit) (TElemType e)){ if (!T) return ; SqStack S; InitSqStack (S); Push (S, T); BiTree p ; while (!StackEmpty (S)){ Pop (S, p); Visit (p->data); if (p->rchild){ Push (S, p->rchild); } if (p->lchild){ Push (S, p->lchild); } } return OK; } 先序非递归遍历二叉树需要注意的是:栈不空的时候,出栈,访问出栈节点的数据,然后如果左子树和右子树不空,分别入栈,在入栈的时候,先入栈右子树,后入栈左子树,这样保证在出栈是,左子树先出。 同样的,和前序递归遍历的区别在于:非递归的时候自己管理栈,而递归的时候,操作系统替你管理栈。 7.二叉树的后序非递归算法: Status PostTraverseNonRecursion (BiTree &T, void (*Visit) (TElemType e)){ SqStack S; InitSqStack (S); BiTree p = T; BiTree previsited = NULL; while (p || !StackEmpty (S)){ while (p) { Push (S, p); p = p->lchild; } GetTop (S, p); if (p->rchild == NULL || p->rchild == previsited){ Pop (S, p); Visit (p->data); previsited = p; p = NULL; } else{ p = p->rchild; } } return OK; } 二叉树的后续非递归遍历,核心在于,当一个节点的右子树为空,或者右子树已经访问过了的时候,才访问该节点,在访问该节点的同时,要同时做好三件事: 1.出栈当前节点(已经访问过了,必须出栈) Pop(S, p) 2.将前一个访问的指针指向该节点 3.当前指针指向空,为下一次循环的出栈做好准备。 如果右子树尚未被访问过,则转到右子树,继续入栈,找最左下边要被访问的第一个元素。 -------------------------------扩展:二叉树的线索化和线索二叉树的遍历------------------------------------------ 不管是递归还是非递归,二叉树的遍历,都是讲二叉树线性化进行遍历。除了第一个和最后一个节点之外,每个节点都有一个直接前驱和一个直接后继,所以二叉树的前序、中序、后序都是将二叉树先序、中序、后序线性化。 同时考虑到,在含有n个节点的二叉树中,空链域有n+1个,所以考虑将这n+1个链利用起来存储线索。(n+1的由来:n个节点,总共的链域有2n个。又因为n=所有分支数+1=B+1=n1+2n2+1,即n=n1+2n2+1,在这2n个链域中,使用了的有:n1+2n2 = n—1个,所以剩余的链域有2n-(n-1) = n+1个) 先来看二叉树的线索化,线索化,就是在遍历的过程中修改空域的指针,使其成为指向前驱和后继的线索。 以中序遍历线索化为例子。 void InThreading (BiThrTree p){//p--指向以p为根的树 if (p){ InThreading (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) } } //中序遍历二叉树T,并将其中序线索化,Thrt指向头结点。 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指向刚刚访问过的节点 pre->RTag = Thread;//线索化最后一个节点 pre->rchild = Thrt; Thrt->rchild = pre; } return OK; } 以上是二叉树中序线索化的过程算法。下面来看如何遍历中序线索二叉树。 //T指向头节点,头节点的左链lchild指向根节点 Status InOrderTraverse_Thr (BiThrTree T, void (*Visit) (TElemType e)){ BiThrTree p = T->lchild; while (p != T){//空树或者遍历结束时,p==T while (p->LTag == Link) p = p->lchild; //找到最左边的节点 Visit (p->data); while (p->RTag == Thread && p->rchild != T){ p = p->rchild; Visit (p->data); } p = p->rchild; } return OK; } 最后给出二叉线索树的数据结构表示: typedef enum PointTag {Link, Thread}; typedef struct BiThrNode{ TElemType data; struct BiThrNode *lchild; struct BiThrNode *rchild; PointTag LTag; PointTag RTag; }BiThrNode, *BiThrTree;