线索二叉树实现

遍历二叉树是以一定规则将二叉树中结点排列成一个线性序列,得到二叉树中结点的先序序列或中序序列或后序序列。这实际上是对一个非线性结构进行线性化操作,使得每个结点(除第一个和最后一个外)在这些线性序列中有且仅有一个直接前驱和直接后继。

当以二叉链表作为存储结构时,只能找到结点的左右孩子的信息,而不能直接得到结点在任一序列中的前驱和后继信息,这种信息只有在遍历的动态过程中才能得到。

那么,如果有一个需要大量查询前驱后继的程序,对于每次都遍历,时间成本过大,我们便需要能快速查找前驱后继的办法。最简单的方法,每个结点加两个存储前驱后继的变量,但是这样却浪费大量的空间。

另一种方法,对于n个结点的二叉链表中必定存在n+1个空链域,由此,可以利用这些空链域来存放结点的前驱和后继的信息。

规定如下:若结点有左子树,则其lchild域指示其左孩子。否则令lchild域指示其前驱;若结点有右子树,则其rchild域指示其右孩子,否则令rchild域指示其后继。(为了避免混淆,尚需改变结点结构,增加两个标志域,用来区分其左右孩子指针到底指向的是子树还是前驱后继)。

更改为这种结构的二叉链表作为二叉树的存储结构叫做线索链表,其中指向结点前驱和后继的指针叫做线索。加上线索的二叉树称之为线索二叉树。对二叉树以某种次序遍历使其变为线索二叉树的过程叫做线索化

在线索树上进行遍历,只要先找到序列中的第一个结点,然后依次找结点后继直至其后继为空时为止。

在后序线索树中招结点后继较为复杂些,可分为3种情况:

  • 若结点x是二叉树的根,则其后继为空
  • 若结点x使其双亲的右孩子或左孩子且其双亲没有右子树,则其后继即为双亲结点
  • 若结点x是其双亲的最孩子,且其双亲有右子树,则其后继为双亲的右子树上按后序遍历列出的第一个结点

为方便起见,仿照线性表的存储结构,在二叉树的线索链表上也添加一个头结点,病令其lchild域的指针指向二叉树的根结点,其rchild域的指针指向中序遍历时访问的最后一个结点;反之,灵感二叉树中序序列中的第一个结点的l迟来的域指针和最后一个结点rchild域的指针均指向头结点。

这好比为二叉树建立了一个双向线索链表,既可从第一个结点起顺后继进行遍历,也可以从最后一个结点起顺前驱进行遍历。

以双向线索链表为存储结构是对二叉树进行遍历的算法:

Status InOrderTraverse_Thr(BiThrTree T, Status (*Visit)(ElemType)) {
   // T指向头结点,头结点的左链lchild指向根结点,头结点的右链lchild指向
   // 中序遍历的最后一个结点。中序遍历二叉线索链表表示的二叉树T,
   // 对每个数据元素调用函数Visit。
   BiThrTree p;
   p = T->lchild;                            // 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始终指向刚刚访问过的结点,若指针p指向当前访问的结点,则per指向他的前驱。由此,可得中序遍历建立中序线索化链表的算法:

Status InOrderThreading(BiThrTree &Thrt, BiThrTree T) {  // 算法6.6
   // 中序遍历二叉树T,并将其中序线索化,Thrt指向头结点。
   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);  // 算法6.7:中序遍历进行中序线索化
      pre->rchild = Thrt;  pre->RTag = Thread; // 最后一个结点线索化
      Thrt->rchild = pre;
   }
   return OK;
} // InOrderThreading

更多基础介绍:线索二叉树概要

2014/11/03 15:52 pm posted in  算法&数据结构

树的遍历

遍历二叉树

在二叉树的一些应用中,常常要求在树中查找具有某种特征的结点,或者对树中全部结点逐一进行某种处理,这就提出了一个遍历二叉树的问题。即如何按某条搜索路径巡防树中每个结点,使得每个节点均被访问一次,而且仅被访问一次。 因为二叉树是由3个基本单元组成:根结点,左子树和右子树。因此,若能依次遍历这三个部分,便是遍历了整个二叉树。根据对这三个部分遍历顺序不同,那么我们就有了先序遍历,中序遍历,后序遍历。 先序遍历: 若二叉树为空则空操作(递归边界),否者: 1.访问根节点 2.先序遍历左子树 3.先序遍历右子树 中序遍历: 若二叉树为空则空操作,否者: 1.中序遍历左子树 2.访问根节点 3.中序遍历右子树 后序遍历: 若二叉树为空则空操作,否者: 1.后序遍历左子树 2.后序遍历右子树 3.访问根节点 这个过程明显是个递归的过程,《数据结构》给出了先序遍历的伪代码:

Status PreOrderTraverse( BiTree T, Status(*Visit)(ElemType) ) {
   // 采用二叉链表存储结构,Visit是对数据元素操作的应用函数,
   // 先序遍历二叉树T的递归算法,对每个数据元素调用函数Visit。
   // 最简单的Visit函数是:
   //     Status PrintElement( ElemType e ) {  // 输出元素e的值
   //        printf( e );  // 实用时,加上格式串
   //        return OK;
   //     }
   // 调用实例:PreOrderTraverse(T, PrintElement);
   if (T) {
      if (Visit(T->data))
         if (PreOrderTraverse(T->lchild, Visit))
            if (PreOrderTraverse(T->rchild, Visit)) return OK;
      return ERROR;
   } else return OK;
} // PreOrderTraverse

对于上图的树(忽略他的线索线),如果我们采用不同的遍历方式,可以得到不同的结果: 先序遍历:-+ab-cd/ef 中序遍历:a+bc-d-e/f 后序遍历:abcd-*+ef/- 其实这就是前缀表达式(波兰式),中缀表达式,后缀表达式(逆波兰式)。 其实用树便可以实现前缀、中缀、后缀表达式相互转换,但是,一般我们采用栈的方式实现,至少不用建树。 仿照递归算法执行过程中递归工作栈的状态变化状态可直接写出相应的非递归算法。例如,从中序遍历递归算法执行过程中递归工作栈的状态可见: 1.工作记录中包含两项其一是递归调用的语句编号,其二是指向左子树根的指针进栈 2.若栈顶记录中的指针值为空,则应退至上一层,若是从左子树返回,则应访问当前层即栈顶记录中指针所指的根结点 3.若是从右子树返回,则表明当前层的遍历结束,应继续退栈。从另一角度,这意味着遍历右子树时不再需要保存当前层指针,直接修改栈顶记录中的指针就好。 非递归算法执行效率要比递归算法高些,有时候可以考虑。

Status InOrderTraverse(BiTree T, Status (*Visit)(ElemType)) {
  // 采用二叉链表存储结构,Visit是对数据元素操作的应用函数。
  // 中序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit。
  stack S;
  BiTree p;
  InitStack(S);  Push(S, T);  // 根指针进栈
  while (!StackEmpty(S)) {
    while (GetTop(S, p) && p) Push(S, p->lchild);  // 向左走到尽头
    Pop(S, p);                // 空指针退栈
    if (!StackEmpty(S)) {     // 访问结点,向右一步
      Pop(S, p);
      if (!Visit(p->data)) return ERROR;
      Push(S, p->rchild);
    }
  }
  return OK;
} // InOrderTraverse

Status InOrderTraverse(BiTree T, Status (*Visit)(ElemType)) {
  // 采用二叉链表存储结构,Visit是对数据元素操作的应用函数。
  // 中序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit。
  stack S;
  BiTree p;
  InitStack(S);  p = T;
  while (p || !StackEmpty(S)) {
    if (p) { Push(S, p);  p = p->lchild; }  // 非空指针进栈,继续左进
    else {       // 上层指针退栈,访问其所指结点,再向右进
      Pop(S, p);
      if (!Visit(p->data)) return ERROR;
      p = p->rchild;
    }
  }
  return OK;
} // InOrderTraverse

建立二叉树

遍历是二叉树各种操作的基础,可以在遍历过程中对结点进行各种操作,如:对一颗已知树可求结点的双亲,求结点的孩子结点,判断结点所在的层次等。 反之,也可以在遍历过程中生成结点,建立二叉树的存储结构。

BiTree CreateBiTree(BiTree &T) {
  // 按先序次序输入二叉树中结点的值(一个字符),空格字符表示空树,
  // 构造二叉链表表示的二叉树T。
  char ch;
  scanf("%c",&ch);
  if (ch=='#') T = NULL;
  else {
    if (!(T = (BiTNode *)malloc(sizeof(BiTNode)))) return ERROR;
    T->data = ch;              // 生成根结点
    CreateBiTree(T->lchild);   // 构造左子树
    CreateBiTree(T->rchild);   // 构造右子树
  }
  return T;
} // CreateBiTree

  对二叉树的遍历的搜索路径除了已经说的先序中序后序遍历,还可以从上到下(DFS),从左到右(BFS)按层次遍历。 但是遍历二叉树的算法中的基本操作时访问结点,则无论安那一种次序进行遍历,对含n个结点的二叉树,其时间复杂度均为O(n),所需辅助空间为遍历过程中栈的最大容量,即树的深度,最坏情况下为n则空间复杂度也为O(n)。

回溯法与树的遍历

在程序设计中有一类求一组解或求全部解或求最优解的问题,比如八皇后问题,骑士周游问题,不是根据某种确定的计算法则,而是利用试探和回溯的搜索技术求解。 回溯法的实质是一个先序遍历一棵“状态树”的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中的(边建立便遍历,随之释放)。 八皇后问题以后讨论,在数据结构中有一个很好玩的例题。

求含n个元素的集合的幂集 集合A的幂集是由集合A的所有子集所组合的集合。如:A={1,2,3},则A的幂集, p(A)={{1,2,3},{1,2},{1,3},{2,3},{1},{2},{3},∅}

但是这个答案肿么解出来,树中给出了一个树的思想,因为对每个元素只有舍或者留两种情况,那么,我们可以用二叉树,左支是留,右支是舍:

没有比代码更直观的了:

void GetPowerSet(int i, List A, List &B) {
   // 线性表A表示集合A,线性表B表示幂集ρ(A)的一个元素。
   // 局部量k为进入函数时表B的当前长度。
   // 第一次调用本函数时,B为空表,i=1。
   ElemType x;
   int k;
   if (i > ListLength(A)) Output(B); // 输出当前B值,即ρ(A)的一个元素
   else {
      GetElem(A, i, x);        k = ListLength(B);
      ListInsert(B, k+1, x);   GetPowerSet(i+1, A, B);
      ListDelete(B, k+1, x);   GetPowerSet(i+1, A, B);
   }
} // GetPowerSet

其实还有优化,比如加上状态压缩什么的。当然,这只是在竞赛中的小伎俩罢了。

2014/11/03 10:56 am posted in  算法&数据结构

线索二叉树概要

当用二叉链表作为二叉树的存储结构时,因为每个结点中只有指向其左、右儿子结点的指针,所以从任一结点出发只能直接找到该结点的左、右儿子。在一般情况下靠它无法直接找到该结点在某种遍历序下的前驱和后继结点。如果在每个结点中增加指向其前驱和后继结点的指针,将降低存储空间的效率。

我们可以证明:在n个结点的二叉链表中含有n+1个空指针。因为含n个结点的二叉链表中含有个指针,除了根结点,每个结点都有一个从父结点指向该结点的指针,因此一共使用了n-1个指针,所以在n个结点的二叉链表中含有n+1个空指针。

因此可以利用这些空指针,存放指向结点在某种遍历次序下的前驱和后继结点的指针。这种附加的指针称为线索,加上了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树。为了区分一个结点的指针是指向其儿子的指针,还是指向其前驱或后继结点的线索,可在每个结点中增加两个线索标志。这样,线索二叉树结点类型定义为:

type
 TPosition=^thrNodeType;
 thrNodeType=record
              Label:LabelType;
              ltag,rtag:0..1;
              LeftChild,RightChild:TPosition;
             end;

线索二叉树

其中ltag为左线索标志,rtag为右线索标志。它们的含义是:

  • ltag=0,LeftChild是指向结点左儿子的指针;
  • ltag=1,LeftChild是指向结点前驱的左线索。
  • rtag=0,RightChild是指向结点右儿子的指针;
  • rtag=1,RihgtChild是指向结点后继的右线索。

例如图(a)是一棵中序线索二叉树,它的线索链表如图(b)所示。


(a)


(b)

图(b)中,在二叉树的线索链表上增加了一个头结点,其LeftChild指针指向二叉树的根结点,其RightChild指针指向中序遍历时的最后一个结点。另外,二叉树中依中序列表的第一个结点的LeftChild指针,和最后一个结点的RightChild指针都指向头结点。这就像为二叉树建立了一个双向线索链表,既可从第一个结点起,顺着后继进行遍历,也可从最后一个结点起顺着前驱进行遍历。

如何在线索二叉树中找结点的前驱和后继结点?

以图的中序线索二叉树为例。树中所有叶结点的右链是线索,因此叶结点的RightChild指向该结点的后继结点,如图中结点"b"的后继为结点""。当一个内部结点右线索标志为0时,其RightChild指针指向其右儿子,因此无法由RightChild得到其后继结点。然而,由中序遍历的定义可知,该结点的后继应是遍历其右子树时访问的第一个结点,即右子树中最左下的结点。例如在找结点""的后继时,首先沿右指针找到其右子树的根结点"-",然后沿其LeftChild指针往下直至其左线索标志为1的结点,即为其后继结点(在图中是结点"c")。类似地,在中序线索树中找结点的前驱结点的规律是:若该结点的左线索标志为1,则LeftChild为线索,直接指向其前驱结点,否则遍历左子树时最后访问的那个结点,即左子树中最右下的结点为其前驱结点。由此可知,若线索二叉树的高度为h,则在最坏情况下,可在O(h)时间内找到一个结点的前驱或后继结点。在对中序线索二叉树进行遍历时,无须像非线索树的遍历那样,利用递归引入栈来保存待访问的子树信息。

对一棵非线索二叉树以某种次序遍历使其变为一棵线索二叉树的过程称为二叉树的线索化。由于线索化的实质是将二叉链表中的空指针改为指向结点前驱或后继的线索,而一个结点的前驱或后继结点的信息只有在遍历时才能得到,因此线索化的过程即为在遍历过程中修改空指针的过程。为了记下遍历过程中访问结点的先后次序,可附设一个指针pre始终指向刚刚访问过的结点。当指针p指向当前访问的结点时,pre指向它的前驱。由此也可推知pre所指结点的后继为p所指的当前结点。这样就可在遍历过程中将二叉树线索化。对于找前驱和后继结点这二种运算而言,线索树优于非线索树。但线索树也有其缺点。在进行插人和删除操作时,线索树比非线索树的时间开销大。原因在于在线索树中进行插人和删除时,除了修改相应的指针外,还要修改相应的线索。

其实线索树是为了快速查询任意结点的前驱和后继而存在的。这也是它的优势。但是,如果删除插入频繁的话,反而不是好的选择。

这篇文章转载自HUST。

2014/11/01 15:48 pm posted in  算法&数据结构

二叉树

这一篇文章继续讲,二叉树是树的特殊,想要熟悉树的模型,就要从二叉树开始,而存储二叉树的二叉链表和三叉链表也是存储任意树的一般方式。

定义

二叉树是一种树型结构,它的特点是每个结点至多只有两颗子树(即二叉树中不存在度大于2的结点),并且,二叉树的子树有左右之分,其次序不能任意颠倒。

而树的抽象数据类型:

ADT BinaryTree{

 //数据对象D:D是具有相同特性的数据元素的集合。
 //数据关系R:
 //  若D=Φ,则R=Φ,称BinaryTree为空二叉树;
 //  若D≠Φ,则R={H},H是如下二元关系;
 //    (1)在D中存在惟一的称为根的数据元素root,它在关系H下无前驱;
 //    (2)若D-{root}≠Φ,则存在D-{root}={D1,Dr},且D1∩Dr =Φ;
 //    (3)若D1≠Φ,则D1中存在惟一的元素x1,<root,x1>∈H,且存在D1
 //         上的关系H1 ⊆H;若Dr≠Φ,则Dr中存在惟一的元素xr,<root,xr>∈H,且存在上的关系Hr ⊆H;H={<root,x1>,<root,xr>,H1,Hr};
 //    (4)(D1,{H1})是一棵符合本定义的二叉树,称为根的左子树;(Dr,{Hr})是一棵符合本定义的二叉树,称为根的右子树。

基本操作:

 InitBiTree( &T )
     //操作结果:构造空二叉树T。

   DestroyBiTree( &T )
   //  初始条件:二叉树T已存在。
   //  操作结果:销毁二叉树T。

   CreateBiTree( &T, definition )
   //  初始条件:definition给出二叉树T的定义。
   //  操作结果:按definiton构造二叉树T。

   ClearBiTree( &T )
   //  初始条件:二叉树T存在。
   //  操作结果:将二叉树T清为空树。

   BiTreeEmpty( T )
   //  初始条件:二叉树T存在。
   //  操作结果:若T为空二叉树,则返回TRUE,否则返回FALSE。

   BiTreeDepth( T )
   //  初始条件:二叉树T存在。
   //  操作结果:返回T的深度。

   Root( T )
   //  初始条件:二叉树T存在。
   //  操作结果:返回T的根。

   Value( T, e )
   //  初始条件:二叉树T存在,e是T中某个结点。
   //  操作结果:返回e的值。

   Assign( T, &e, value )
   //  初始条件:二叉树T存在,e是T中某个结点。
   //  操作结果:结点e赋值为value。

   Parent( T, e )
   //  初始条件:二叉树T存在,e是T中某个结点。
   //  操作结果:若e是T的非根结点,则返回它的双亲,否则返回“空”。

   LeftChild( T, e )
   //  初始条件:二叉树T存在,e是T中某个结点。
   //  操作结果:返回e的左孩子。若e无左孩子,则返回“空”。

   RightChild( T, e )
   //  初始条件:二叉树T存在,e是T中某个结点。
   //  操作结果:返回e的右孩子。若e无右孩子,则返回“空”。

   LeftSibling( T, e )
   //  初始条件:二叉树T存在,e是T中某个结点。
   //  操作结果:返回e的左兄弟。若e是T的左孩子或无左兄弟,则返回“空”。

   RightSibling( T, e )
   //  初始条件:二叉树T存在,e是T中某个结点。
   //  操作结果:返回e的右兄弟。若e是T的右孩子或无右兄弟,则返回“空”。

   InsertChild( T, p, LR, c )
   //  初始条件:二叉树T存在,p指向T中某个结点,LR为0或1,非空二叉树c与T不相交且右子树为空。
   //  操作结果:根据LR为0或1,插入c为T中p所指结点的左或右子树。p所指结点的原有左或右子树则成为c的右子树。

   DeleteChild( T, p, LR )
   //  初始条件:二叉树T存在,p指向T中某个结点,LR为0或1。
   //  操作结果:根据LR为0或1,删除T中p所指结点的左或右子树。

   PreOrderTraverse( T, visit() )
   //  初始条件:二叉树T存在,Visit是对结点操作的应用函数。
   //  操作结果:先序遍历T,对每个结点调用函数Visit一次且仅一次。一旦visit()失败,则操作失败。

   InOrderTraverse( T, visit() )
   //  初始条件:二叉树T存在,Visit是对结点操作的应用函数。
   //  操作结果:中序遍历T,对每个结点调用函数Visit一次且仅一次。一旦visit()失败,则操作失败。

   PostOrderTraverse( T, visit() )
   //  初始条件:二叉树T存在,Visit是对结点操作的应用函数。
   //  操作结果:后序遍历T,对每个结点调用函数Visit一次且仅一次。一旦visit()失败,则操作失败。

   LevelOrderTraverse( T, visit() )
   //  初始条件:二叉树T存在,Visit是对结点操作的应用函数。
   //  操作结果:层次遍历T,对每个结点调用函数Visit一次且仅一次。一旦visit()失败,则操作失败。

}ADT BinaryTree
}

基本操作的实现:

//二叉树的结点结构体:

typedef struct{

    TElemType data;

    struct BiTNode *lchild,*rchild;

}BiTNode,*BiTree;

//二叉树的创建:

 /*******************************************/
 /*          按照前序遍历建立二叉树         */
 /*******************************************/
 Status CreateBiTree(BiTree &T)
 {
     //按先序次序输入二叉树中结点的值(一个字符),
     //空格字符表示空树,构造二叉链表表示的二叉树T。
     char ch;
     ch = getchar();//scanf("%c",&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;
 }

 /************************************************************/
 /*                  按层次顺序建立一棵二叉树 :队列         */
 /************************************************************/
 Status LevelCreateBiTree(BiTree &T)
 {
     BiTree p,s;//p指向父亲结点,s指向孩子结点
     Queue BiNodeQueue;
     char ch;
     ch=getchar();
     if(ch==' ')
     {
         return NULL;
     }
     T=(BiTNode*)malloc(sizeof(BiTNode)); //生成根结点
     T->data=ch;
     EnQueue(BiNodeQueue,T); //用队列实现层次遍历
     while(!BiNodeQueue.Empty())
     {
         DeQueue(BiNodeQueue,p);
         ch=getchar(); //为了简化操作,分别对左右子结点进行赋值。
         if(ch!=' ')//子树不空则进队列进行扩充。下同
         {
             s=(BiTNode*)malloc(sizeof(BiTNode));
             s->data=ch;
             p->lchild=s;
             EnQueue(BiNodeQueue,s);
         }
         else
         {
             p->lchild=NULL;
         }
         ch=getchar();
         if(ch!=' ')
         {
             s=(BiTNode*)malloc(sizeof(BiTNode));
             s->data=ch;
             p->rchild=s;
             EnQueue(BiNodeQueue,s);
         }
         else
         {
             p->rchild=NULL;
         }
     }
     return OK;
 }

//2.二叉树的前序遍历:

 /*******************************************/
 /*          按照前序递归遍历二叉树         */
 /*******************************************/
 Status PreOrderTraverse(BiTree T)
 {
     if(T)
     {
         printf("%d",T->data);
         PreOrderTraverse(T->lchild);
         PreOrderTraverse(T->rchild);
     }
     return OK;
 }

 /*******************************************/
 /*          按照前序非递归遍历二叉树:栈   */
 /*******************************************/
 Status PreOrderTraverse(BiTree T)
 {
     stack S;
     InitStack(S);
     BiTree p=T;  //p指向当前访问的结点
     while(p||!StackEmpty(S))
     {
         if(p)
         {
             printf("%c",p->data);
             Push(S,p);
             p=p->lchild;
         }
         else
         {
             Pop(S,p);
             p=p->rchild;
         }
     }
     return OK;
 }

 //3.二叉树的中序遍历:

 /*******************************************/
 /*          按照中序递归遍历二叉树         */
 /*******************************************/
 Status InOrderTraverse(BiTree T)
 {
     if(T)
     {
         InOrderTraverse(T->lchild);
         printf("%d",T->data);
         InOrderTraverse(T->rchild);
     }
     return OK;
 }

 /*******************************************/
 /*          按照中序非递归遍历二叉树       */
 /*******************************************/
 Status InOrderTraverse(BiTree T)
 {
     stack S;
     BiTree p;
     InitStack(S);  Push(S, T);
     while (!StackEmpty(S))
     {
         while (GetTop(S, p) && p)
             Push(S, p->lchild);   // 向左走到尽头
         Pop(S, p);                // 空指针退栈(叶子的左孩子)
         if (!StackEmpty(S))
         {   // 访问结点,向右一步
             Pop(S, p);
             printf("%d",p->data);   //当前根结点
             Push(S, p->rchild);
         }
     }
     return OK;
 }

 /*******************************************/
 /*          按照中序非递归遍历二叉树       */
 /*******************************************/
 Status InOrderTraverse(BiTree T)
 {
     stack S;
     InitStack(S);
     BiTree p=T;
     while (p || !StackEmpty(S))
     {
         if (p)
         {
             Push(S, p);
             p = p->lchild;
         }  // 非空指针进栈,继续左进
         else
         {  // 上层指针退栈,访问其所指结点,再向右进
             Pop(S, p);
             printf("%d",p->data);
             p = p->rchild;
         }
     }
     return OK;
 }

 //二叉树的后序遍历:
 /*******************************************/
 /*          按照后序递归遍历二叉树         */
 /*******************************************/
 Status PostOrderTraverse(BiTree T)
 {
     if(T)
     {
         PostOrderTraverse(T->lchild);
         PostOrderTraverse(T->rchild);
         printf("%d",T->data);
     }
     return OK;
 }

 /*******************************************/
 /*        按照后序非递归遍历二叉树         */
 /*******************************************/
 Status PostOrderTraverse(BiTree T)
 {
     stack S;
     InitStack(S);
     BiTree p=T,pre=NULL;
     while ( p || !StackEmpty(S))
     {
         if (p)
         {
             Push(S,p);
             p = p->left;
         }
         else
         {
             Pop(S,p);
             if (p->right!=NULL && pre!=p->right)
             { //pre指向上次访问的右结点,避免再次访问
                 p=p->right;
             }
             else
             {
                 printf("%d",p->data);
                 pre=p;
                 p=NULL;
             }
         }
     }
}

 /*******************************************/
 /*        按照后序非递归遍历二叉树         */
 /*******************************************/
 Status PostOrderTraverse(BiTree T)
 {
     BiTree p = T,last = NULL;
     stack S;
     InitStack(S);
     Push(S,p);
     while(!StackEmpty(S))
     {
         Pop(S,p);
         if(last == p->left || last == p->right)//左右子树已经访问完了,该访问根节点了
         {
             printf("%d",p->data);
             last = p;
         }
         else if(p->left || p->right) //左右子树未访问,当前节点入栈,左右节点入栈
         {
             Push(S,p);
             if(p->right)
                 Push(S,p->right);
             if(p->left)
                 Push(S,p->left);
         }
         else //当前节点为叶节点,访问
         {
             printf("%d",p->data);
             last = p;
         }
     }
 }

 //二叉树的层次遍历:
 /*******************************************/
 /*           按照层次遍历二叉树            */
 /*******************************************/
 void LevelOrderTraverse(BiTree T)
 {
     Queue BiNodeQueue;
     BiTree p=T;
     EnQueue(BiNodeQueue,p);
     while(!BiNodeQueue.Empty())
     {
         DeQueue(BiNodeQueue,p);
         if(p)
         {
             printf("%c",p->data);
             EnQueue(BiNodeQueue,p->lchild);
             EnQueue(BiNodeQueue,p->rchild);
         }
     }
 }

 //交换左右子树:

 /*******************************************/
 /*      递归法将二叉树的左右子树互换       */
 /*******************************************/
 void Exchange(BiTree T)
 {
     BiTree temp;
     if(T)
     {
         Exchange1(T->lchild);
         Exchange1(T->rchild);
         temp=T->lchild;
         T->lchild=T->rchild;
         T->rchild=temp;
     }
 }

 /*******************************************/
 /*      非递归法将二叉树的左右子树互换     */
 /*******************************************/
 void Exchange(BiTree T)
 {
     stack S;
     InitStack(S);
     BiTree p=T,temp;
     while(p||!StackEmpty(S))
     {
         if(p)
         {
             Push(S,p);
             temp=p->lchild;
             p->lchild=p->rchild;
             p->rchild=temp;
             p=p->lchild;
         }
         else
         {
             Pop(S,p);
             p->rchild;
         }
     }
 }

 //7.统计二叉树中叶子结点的数目:

 /*******************************************/
 /*        递归法求叶子结点个数             */
 /*******************************************/
 int LeavesNum(BiTree T)
 {
     if(T)
     {
         if(T->lchild==NULL&&T->rchild==NULL)
         {
             return 1;
         }
         return LeavesNum(T->lchild)+LeavesNum(T->rchild);
     }
     return 0;
 }

 /*******************************************/
 /*        非递归法求叶子结点个数           */
 /*******************************************/
 int LeavesNum(BiTree T)
 {
     int count=0;
     stack S;
     InitStack(S);
     BiTree p=T;
     while(p||!StackEmpty(S))
     {
         if(p)
         {
             Push(S,p);
             if(p->lchild==NULL&&p->rchild==NULL)
             {
                 count++;
             }
             p=p->lchild;
         }
         else
         {
             Pop(S,p);
             p=p->rchild;
         }
     }
     return count;
 }

 //8.求二叉树的深度:
 /**********************************************/
 /*                  求一棵树的高度            */
 /**********************************************/
 int Depth(BiTree T)
 {
     int  lh = rh =0 ;
     BiTree p=T;
     if(p==NULL)
     {
         return 0 ;
     }
     else
     {
         lh = Depth( p->lchild ) ;
         rh = Depth( p->rchild ) ;
         return ( lh > rh ? lh : rh ) + 1 ;
     }
  }

   //9.判断两棵树是否等价:
   /*******************************************************/
   /*                  判断两棵是否等价                   */
   /*******************************************************/
   int Is_equal( BiTree T1 , BiTree T2 )
 {
     int t=0;
     if(NULL == T1 && NULL == T2)
     {
         t=1;
     }
     else
     {
         if(NULL !=T1 &&NULL != T2 )
         {
             if(T1->data == T2->data)
             {
                 if(Is_equal(T1->lchild,T2->lchild))
                 {
                     t=Is_equal(T1->rchild,T2->rchild);
                 }
             }
         }
     }
     return t;
 }

 //10.查找某个信息是否在这棵树中
 /****************************************************/
 /*            查找某个信息是否在这棵树中            */
 /****************************************************/
 BiTree Locate(BiTree T,char x)
 {
     BiTree p=T;
     if(P==NULL)
         return NULL;
     else
     {
         if( P -> data == x )
             return P;
         else
         {
             p = Locate(P->lchild,x);
             if(p)
                 return p;
             else
                 return Locate(P->rchild,x);
         }
     }
 }

 //11.结点个数:
 /****************************************************/
 /*                  树的结点个数                    */
 /****************************************************/
 int  Num_Of_Node(BiTree t)
 {
     if(t==NULL)
         return 0 ;
     else
         return Num_Of_Node(t->lchild)+Num_Of_Node(t->rchild)+1;
 }

二叉树,或者说树都是一个递归的概念,他们的左右孩子一样还是二叉树。

在二叉树中,还有完全二叉树满二叉树的概念

一颗深度为k而且2^k - 1个结点的二叉树称为满二叉树(下图a便是满二叉树)。满二叉树的特点是每一层上的结点数都是最大结点数。


(a)

可以对满二叉树的结点进行连续编号,约定编号从根结点起,自上而下,自左而右。就此,可以引出完全二叉树的定义:

深度为k的,由n个结点的二叉树,当且仅当其每一个结点都与深度为k的慢二叉树中编号1至n的结点一一对应时,称之为完全二叉树(如下图b)。完全二叉树的特点是:


(b)

1.叶子结点只可能在层次最大的两层上出现

2.对任一结点,若其右分支下的子孙的最大层次为L,则其左下分支下的子孙的最大层次必为l或l+1。

一定要区分好满二叉树和完全二叉树。

在性质4、5将用到满二叉树和完全二叉树。

性质

1.在二叉树的第i层上至多有2^(i-1)个结点(i>=1)

2. 深度为k的二叉树至多有2^k - 1个结点(k >= 1)

3.对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2 + 1

4.具有n个结点的完全二叉树(其深度为[log2n]+1)的结点按层序编号(从第一层到第[log2n]+1层,每层从左到右),则对任一结点i(11,则其双亲PARENT(i)是结点[i/2]

  • 如果2i>n。则结点i无左孩子(结点i为叶子结点);否者其左孩子LCHILD (i)是结点2i
  • 如果2i + 1 > n。则结点i无右孩子;否则其右孩子RCHILD(i)是结点2i + 1

(这个性质也方便了二叉树的线性存储)

存储结构

顺序存储结构

存储结构如名,按照顺序存储结构的的定义,用一组地址连续的存储单元依次自上而下,自左至右的存储完全二叉树上的结点元素,即将完全二叉树上的编号为i的结点元素存储在上述定义的一位数组中下标为i-1的分量中。

这种存储结构仅适用于完全二叉树,因为在最坏的情况下,一个深度为k ,且只有k个结点的单支树(树中不存在度为2的结点),却要使用长度为2^k - 1的一位数组。

(线段树就是用的这种存储结构,因为线段树分布比较均匀,虽然不是完全二叉树,)这种存储结构如果在树分布比较均匀的情况下,还是可以使用的,虽然特殊情况有些占用内存,但是操作十分简单,而且静态速度也快。

链式存储结构

要表示二叉树的链表中的结点至少包含3个域:数据域和左右指针域,有时为了方便还需要包含一个指向双亲的指针域。那么利用这两种结点结构所得二叉树的存储结构分别称之为二叉链表和三叉链表。(三叉链表是为了方便寻找双亲结点而存在的,因为在包含n个结点的二叉链表中有n+1个空链域,为方便查找前驱后继,又引入线索链表的概念)。

上面讲的实现就是链式存储结构,这也是二叉树最基本最常用的存储结构。

在以后写树和森林的时候,再又特殊到一般总结树的存储方式。

2014/10/31 15:51 pm posted in  算法&数据结构

树的定义和基本术语

定义

树是n个结点的有限集。在任意一棵非空树中:

1.有且只有一个特定称为根的结点

2.当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1,T2,....,Tm。其中每一个集合本身又是一棵树,并且称为根的子树

和广义表一样,树的结构定义是一个递归 的定义,即在树的定义中又用到树的概念。

 基本术语

  1. 结点的度:一个结点含有的子树的个数称为该节点的度;
  2. 叶节点终端节点:度为零的节点称为叶节点;
  3. 非终端节点分支节点:度不为零的节点;
  4. 双亲节点:在含有孩子的节点中,这个节点称为孩子节点的双亲节点;
  5. 孩子节点:一个节点子树的根节点称为孩子节点;
  6. 兄弟节点:具有相同双亲节点的节点互称为兄弟节点;
  7. 树的度:一棵树中,最大的结点的度称为树的度;
  8. 结点的层次:从根开始定义起,根为第0层,根的孩子为第1层;
  9. 树的高度深度:树中节点的最大层次;
  10. 堂兄弟:双亲在同一层的节点互为堂兄弟;
  11. 节点的祖先:从根到该节点所经分支上的所有节点;
  12. 子孙:以某节点为根的子树中任一节点都称为该节点的子孙。
  13. 有序树:树中结点的各子树看成从左至右是有次序的(既不可互换),反之为无序树
  14. 森林:由m(m>=0)棵互不相交的树的集合称为森林;、

就逻辑结构而言,任何一棵树是一个二元组Tree(root,F),其中;root是数据元素,称作树的根结点;F是m(m>=0)棵树的森林,F=(T1,T2,....,Tm),其中Ti = (r,Fi)称作为根root 的第i棵子树;当m!=0时,在树根和其子树森林之间存在下列关系:

Rf = {|i = 1,2,...,m,m>0}。

这个定义将有助于得到森林和树与二叉树之间转换的递归定义。

2014/10/29 15:50 pm posted in  算法&数据结构