代码改变世界

[基础数据结构]二叉树完全总结

2013-09-04 11:40  庸男勿扰  阅读(1007)  评论(0编辑  收藏  举报

  树形结构是数据结构中一种非常常用的非线性结构。通常用来表示具备分支关系的层次结构。其中二叉树又是树形结构中最简单最常见的一种。

一、定义 

  二叉树,顾名思义,只有两个分叉的树,他的特点就是每个节点至多只有两颗子树(即二叉树中不存在度大于2的节点),通常情况下,我们称二叉树的两颗子树为左子树和右子树。二叉树又可递归定义为:①一个空树;②左子树和右子树均为二叉树。换句话说,二叉树有三个部分组成,根节点和同为二叉树的左右子树。这种递归定义非常有趣,它将帮助我们解决很多二叉树的操作问题。

  二叉树中有两种特殊的形态,分别是完全二叉树和满二叉树。

  满二叉树是指,除叶子节点外,每个节点都有两个子节点,从视觉上讲,就是这颗二叉树被完全填充。

  完全二叉树形象上讲是指“最右下角”的节点可能空缺,即只能是最右边且是最大层次的节点空缺。他有两个重要的特点:

  ①、叶子节点只可能出现在最大的两层上。

  ②、对任意节点,若其右子树的最大层次为l,那么其左子树的最大层次必定为l或l+1。

二、性质

  性质1:在二叉树的第i层至多有2i-1个节点(i>=1),这点有数学归纳法很容易证得。

  性质2:深度为k的二叉树至多有2k-1个节点(k>=1),显然,节点最多的情形即为满二叉树。

  性质3:对任意一颗二叉树,若其叶子节点(其度为0)数为n0,度为2的节点数为n2,则有n0=n2+1。

  证明:设度为1的节点数为n1,那么总结点数n = n0+n1+n2;

      设二叉树的分支数为B,由于除了根节点外,每个节点都由一个分支射入,即对应一个分支,即n = B+1。

      而每个分支都是有度为1或2的节点射出的,即B = n1+2*n2。

      则有n = n1+2*n2+1

  综上,有

      n0 = n2+1

三、二叉树的遍历

  遍历操作是二叉树最重要的操作,其他的操作大都围绕遍历来完成。根据二叉树的递归定义我们可以知道,只要分别完成对二叉树的三个部分的遍历,即可完成对整个二叉树的遍历。

  二叉树的遍历通常根据对根节点的访问顺序,分为先序遍历、中序遍历和后续遍历,即先(根)序遍历、中(根)序遍历和后(根)序遍历。以下将详细阐述三种遍历方式。

  PreOder(T) = T+PreOder(T的左子树)+PreOder(T的右子树)

  InOder(T)=InOder(T的左子树)+T+InOder(T的右子树)

       PostOder(T) = PostOder(T的左子树)+PostOder(T的右子树)+T

  1、二叉树的链式存储结构:

  由二叉树的定义可知,每个节点至少要包含三个部分,即数据、左节点指针、右节点指针。当然,在一些特殊的场合,可能还会有一些其他数据域,如父节点指针、层次、访问标记等。目前暂时考虑最简单的情形。  

1 typedef char TElemType;
2 /****二叉树的节点的存储结构****/
3 typedef struct BiTNode
4 {
5     TElemType data;
6     struct BiTNode *lchild, *rchild;
7 }BiTNode,*BiTree;
View Code

  2、先序遍历:

    递归式先序遍历:

    根据先序遍历的定义,先访问根节点,再依次访问左右子树,不难得出先序遍历的递归代码如下:    

 1 bool PreOrderTraverse_rec( BiTree T ,bool (* PrintElem)(TElemType e)) 
 2 {
 3     if(T)
 4     {
 5         if (PrintElem(T->data))    
 6         {    
 7             if (PreOrderTraverse_rec( T->lchild,PrintElem))
 8             {
 9                 if (PreOrderTraverse_rec( T->rchild ,PrintElem)) 
10                     return true;
11             }
12         }
13         return false;
14     }
15     else 
16         return true;
17 }
View Code

    PrintElement是每个元素的操作函数,最简单的操作就是打印了。    

1 bool PrintElem( TElemType e)
2 {
3     cout<<e<<" ";
4     return true;
5 }
View Code

    非递归式先序遍历:

    采用栈模拟递归过程实现非递归。对栈中的每一个节点,执行操作,并压栈,沿着左路走到底,再弹出,将右节点压栈,重复上述操作。

       算法步骤:

      a.从根开始,访问栈顶元素,不断将左子树指针压栈,直到指针为空为止;

       b.弹栈,对栈顶元素。

       如果,该节点的右结点不为空,则转到a;

       否则,转到b。

      c.栈空,则结束

版本一:   

 1 ///////////////////////////////////////////////////////////////
 2 //用非递归的方式先序遍历二叉树,若失败则返回false
 3 ///////////////////////////////////////////////////////////////
 4 bool PreOrderTraverse( BiTree T ,bool (*PrintElem)(TElemType e)) 
 5 {
 6     SqStack S; BiTree p;
 7     InitStack(S); Push(S,T);
 8     while (!StackEmpty(S))
 9     {
10         while(GetTop(S,p)&&p)
11         {
12             if (!PrintElem(p->data)) return false;
13             Push(S,p->lchild);
14         }
15         Pop(S,p);//弹出压入的空字符
16         if(!StackEmpty(S))
17         {
18             Pop(S,p);
19             Push(S,p->rchild);
20         }
21     }
22     cout<<endl;
23     return true;
24 }
View Code 

版本二:

 1 ///////////////////////////////////////////////////////////////
 2 //用非递归的方式先序遍历二叉树第二种方法,若失败则返回false
 3 ///////////////////////////////////////////////////////////////
 4 bool PreOrderTraverse2( BiTree T ,bool (*PrintElem)(TElemType e)) 
 5 {
 6     SqStack S; BiTree p;
 7     InitStack(S); 
 8     p = T;
 9     while(p||!StackEmpty(S))
10     {
11         if(p)
12         {
13             Push(S,p);
14             if (!PrintElem(p->data)) return false;
15             p = p->lchild;
16         }
17         else
18         {
19             Pop(S,p);
20             p = p->rchild;
21         }
22     }
23     
24     cout<<endl;
25     return true;
26 }
View Code

  3、中序遍历:

    递归式中序遍历:

 1 ///////////////////////////////////////////////////////////////
 2 //用递归的方式中序遍历二叉树,若失败则返回false
 3 ///////////////////////////////////////////////////////////////
 4 bool InOrderTraverse_rec( BiTree T,bool (*PrintElem)(TElemType e)) //递归
 5 {
 6     if(T)
 7     {
 8         if (InOrderTraverse_rec( T->lchild ,PrintElem))
 9             if (PrintElem(T->data))
10                 if (InOrderTraverse_rec( T->rchild, PrintElem)) 
11                     return true;
12         return false;
13     }
14     else 
15         return true;
16 }
View Code

    非递归式中序遍历:

    与先序遍历类似。先将左子树节点压栈,直到尽头,弹出时,执行操作,再将右子树节点压栈,重复上述操作。

    算法步骤:

      a.从根开始,不断将左子树指针压栈,直到指针为空为止。 

       b.访问栈顶元素。

       如果,该节点的右结点不为空,则转到a;

       否则,转到b。

      c.栈空,则结束。

版本一:

 1 ///////////////////////////////////////////////////////////////
 2 //用非递归的方式中序遍历二叉树,若失败则返回false
 3 ///////////////////////////////////////////////////////////////
 4 bool InOrderTraverse( BiTree T ,bool(*PrintElem)(TElemType e)) //非递归1
 5 {
 6     SqStack S; BiTree p;
 7     InitStack(S); Push(S,T);
 8     while(!StackEmpty(S))
 9     {
10         while(GetTop(S,p)&&p)
11         {
12             Push(S,p->lchild);
13             p=p->lchild;
14         }
15         Pop(S,p);
16         if(!StackEmpty(S))
17         {
18             Pop(S,p);
19             if (!PrintElem(p->data)) return false;
20             Push(S,p->rchild);
21         }
22     }
23     cout<<endl;
24     return true;
25 }
View Code

版本二:

 1 ///////////////////////////////////////////////////////////////
 2 //用非递归的方式中序遍历二叉树,若失败则返回false
 3 ///////////////////////////////////////////////////////////////
 4 bool InOrderTraverse2( BiTree T ,bool(*PrintElem)(TElemType e)) //非递归1
 5 {
 6     SqStack S; BiTree p;
 7     InitStack(S); 
 8     p = T;
 9     while(p||!StackEmpty(S))
10     {
11         if(p)
12         {
13             Push(S,p);
14             p = p->lchild;
15         }
16         else
17         {
18             Pop(S,p);
19             if (!PrintElem(p->data)) return false;
20             p = p->rchild;
21         }
22     }
23     
24     cout<<endl;
25     return true;
26 }
View Code

  可以发现,非递归式的先序遍历和中序遍历代码非常相似,仅是改变了操作函数的位置。

  4、后序遍历:

    递归式后序遍历:

 1 ///////////////////////////////////////////////////////////////
 2 //用递归的方式后序遍历二叉树,若失败则返回false
 3 ///////////////////////////////////////////////////////////////
 4 bool PostOrderTraverse_rec( BiTree T ,bool (* PrintElem)(TElemType e))
 5 {
 6     if(T)
 7     {
 8         if (PostOrderTraverse_rec( T->lchild ,PrintElem))
 9             if (PostOrderTraverse_rec( T->rchild, PrintElem))
10                 if (PrintElem(T->data))
11                     return true;
12         return false;
13     }
14     else 
15         return true;
16 }
View Code

    非递归式后序遍历:

    后续遍历的非递归方式相比前面两种有点复杂。主要原因在于前面两种遍历,每个结点仅有一次出入栈,而后序遍历中,每个节点会有两次出入栈.

    算法步骤:

      a.从根开始,不断将左子树指针压栈,直到指针为空为止;

       b.看栈顶元素

        如果:它没有节点,或者它的右结点被访问过,访问该结点,弹栈;转到b;

        否则:它的节点不空且未被访问过,则必须将它的右结点继续压栈;因为可能他的右结点是另一颗子树的根节点。转到a;

      c.栈空,则结束。

版本一:

根据算法步骤,在原结点结构体中加入一个字段代表是否访问过。

1 typedef struct BiTNode
2 {
3     TElemType data;
4     struct BiTNode *lchild, *rchild;
5     int mark ; //初始化为未被访问过,0
6 }BiTNode,*BiTree;
 1 ///////////////////////////////////////////////////////////////
 2 //用非递归的方式后序遍历二叉树,若失败则返回false
 3 ///////////////////////////////////////////////////////////////
 4 bool PostOrderTraverse( BiTree T ,bool (* PrintElem)(TElemType e))
 5 {
 6     BiTree p,q;
 7     SqStack S;
 8 
 9     p = T;
10     q = NULL;
11     InitStack(S);
12 
13     while (p || !StackEmpty(S))
14     {
15         if (p && p->mark==0)
16         {
17             Push(S,p);
18             p = p->lchild;
19         }
20         else
21         {
22             Pop(S,p);
23 
24             if (p->rchild && p->rchild->mark==0)  //存在右孩子,则还把该节点压栈,顺着其左子树继续
25             {
26                 q = p;
27                 p = p->rchild;//讨论p的右结点,现在右结点还没有被压栈
28                 Push(S,q);//把原来的p重新压回栈
29                 continue;
30             }
31 
32             if (!PrintElem(p->data)) return false;
33             p->mark = 1;//标志为已访问过
34             if (p == T) break;//已经访问到了头结点
35         }//else
36     }//while
37     cout<<endl;
38     return true;
39 }
View Code

版本二:

算法步骤中,要访问某节点,必须确认其右结点已经访问过,即右结点必须是上一个访问的节点,所以版本二中通过保存上次访问节点来实现。不需要添加额外字段。

 1 ///////////////////////////////////////////////////////////////
 2 //用非递归的方式后序遍历二叉树,若失败则返回false
 3 ///////////////////////////////////////////////////////////////
 4 bool PostOrderTraverse2( BiTree T ,bool (* PrintElem)(TElemType e))
 5 {
 6     BiTree p,q;
 7     SqStack S;
 8 
 9     p = T;
10     q = NULL;
11     InitStack(S);
12     Push(S,p);
13     while(!StackEmpty(S))
14     {
15         while(GetTop(S,p) && p)
16         {
17             Push(S,p->lchild);
18             p=p->lchild;
19         }
20         Pop(S,p);
21         q = NULL;//代表刚刚访问的节点
22         while(!StackEmpty(S)) 
23         {
24             GetTop(S,p);
25             if (p->rchild==NULL || p->rchild==q)//q表示刚访问过的结点
26             {
27                 if (!PrintElem(p->data)) return false;  
28                 q=p; //记录访问过的结点
29                 Pop(S,p);
30             }
31             else
32             {
33                 p=p->rchild;
34                 Push(S,p);
35                 break;
36             }
37         }
38 
39     }
40     cout<<endl;
41     return true;
42 }
View Code

版本三:

在原结构体中加入字段标记左右子树,当为右子树时,说明右子树访问过,可以访问该节点。

1 typedef enum{L,R} TagType;
2 
3 typedef struct BiTNode
4 {
5     TElemType data;
6     struct BiTNode *lchild, *rchild;
7     TagType tag;
8 }BiTNode,*BiTree;
 1 ///////////////////////////////////////////////////////////////
 2 //用非递归的方式后序遍历二叉树,若失败则返回false
 3 ///////////////////////////////////////////////////////////////
 4 bool PostOrderTraverse3( BiTree T ,bool (* PrintElem)(TElemType e))
 5 {
 6     BiTree p;
 7     SqStack S;
 8 
 9     p = T;
10     InitStack(S);
11     
12     do
13     {
14         while(p!=NULL)
15         {
16             p->tag = L;//标记为左子树
17             Push(S,p);
18             p = p->lchild;
19         }
20         while(!StackEmpty(S) && GetTop(S,p) && p->tag==R)//表示右子树访问结束,可以访问该节点
21         {
22             Pop(S,p);
23             if (!PrintElem(p->data)) return false;  
24         }
25 
26         if(!StackEmpty(S))
27         {
28             GetTop(S,p);
29             p->tag = R;//标记为右子树
30             p = p->rchild;
31         }
32     }while(!StackEmpty(S));
33     cout<<endl;
34     return true;
35 }
View Code

版本四:

前面所有的方式,本质上都是通过栈记录历史信息来模拟递归,版本四提供了一种巧妙的方法,可以不用栈,实现非递归式后序遍历。具体的实现方法是在节点中加入一个状态域来保存当前状态,是该访问左子树、右子树,还是访问节点。

1 typedef enum{L,R,V} TagType;
2 
3 typedef struct BiTNode
4 {
5     TElemType data;
6     struct BiTNode *lchild, *rchild;
7     TagType tag;
8 }BiTNode,*BiTree;
 1 ///////////////////////////////////////////////////////////////
 2 //用非递归的方式后序遍历二叉树,若失败则返回false
 3 ///////////////////////////////////////////////////////////////
 4 bool PostOrderTraverse4( BiTree T ,bool (* PrintElem)(TElemType e))
 5 {
 6     BiTree p,q;
 7     p = T;
 8     
 9     while(p)
10     {
11         switch(p->tag)
12         {
13         case L:
14             p->tag = R;//接下来要访问右子树
15             if(p->lchild)
16                 p = p->lchild;
17             break;
18         case R:
19             p->tag = V;//右子树访问完毕,接下来可以访问根了
20             if(p->rchild)
21                 p = p->rchild;
22             break;
23         case V:
24             if (!PrintElem(p->data)) return false;
25             p->tag = L;
26             p = GetParent(T,p);//指向父节点
27             break;
28         }
29     }
30     cout<<endl;
31     return true;
32 }
View Code

以上代码中有一部分是指向某节点的父节点,一种做法是节点数据域中加入一个指针指向父节点,在创建二叉树时就做好这项工作。另一种是每次都从二叉树中找某节点的父节点。上述代码用的是第二种方法:编写了从二叉树T中找到节点child的父节点的函数GetParent。

 1 BiTree GetParent(BiTree T,BiTree child)
 2 {
 3     LinkQueue Q; 
 4     BiTree p;
 5     Q.EnQueue(T);
 6     while(!Q.QueueEmpty())
 7     {
 8         Q.DeQueue(p);
 9         if (p->lchild == child || p->rchild == child)
10             return p;
11 
12         if(p->lchild)
13         {
14             Q.EnQueue(p->lchild);
15         }
16 
17         if(p->rchild)
18         {
19             Q.EnQueue(p->rchild);
20         }
21     }
22     return NULL;
23 }
View Code 

  5、层序遍历:

  层序遍历形象的就是按层次访问二叉树。上面的节点永远都是先访问的,很容易联想到“先进先出”的队列。层序遍历就是借助队列来实现的。

  算法步骤:

      a.将根节点移进队列;

        b.访问队列头节点,若其有左右节点,分别将其左右节点也移进队列,转到b;

      c.队列空,则操作结束。

 1 ///////////////////////////////////////////////////////////////
 2 //层序遍历二叉树,若失败则返回false
 3 ///////////////////////////////////////////////////////////////
 4 bool LevelOrderTraverse( BiTree T, bool (* PrintElem)(TElemType e) )
 5 {
 6     LinkQueue Q; 
 7     BiTree p;
 8     Q.EnQueue(T);
 9     while(!Q.QueueEmpty())
10     {
11         Q.DeQueue(p);
12         if (!PrintElem(p->data)) return false;
13 
14         if(p->lchild)
15         {
16             Q.EnQueue(p->lchild);
17         }
18 
19         if(p->rchild)
20         {
21             Q.EnQueue(p->rchild);
22         }
23     }
24     cout<<endl;
25     return true;
26 }
View Code

练习题:

1、Binary Tree Preorder Traversal

四、二叉树的其他操作

  1、递归创建二叉树

 1 bool CreateBiTreeFromStdin( BiTree &T )
 2 {
 3     TElemType ch;
 4     
 5     ch = getchar();
 6     if( ch=='#' ) T = NULL;
 7     else
 8     {
 9         if( !(T = (BiTNode *)malloc(sizeof(BiTNode))) ) 
10             exit(OVERFLOW);
11         T->data = ch;
12         CreateBiTreeFromStdin( T->lchild);
13         CreateBiTreeFromStdin( T->rchild);
14     }
15     return OK;
16 }
View Code

   2、递归复制二叉树

 1 /////////////////////////////////////////////////////////////////////////////
 2 //递归复制二叉树,若成功则返回true
 3 //////////////////////////////////////////////////////////////////////////////
 4 bool CopyTree(BiTree ST, BiTree &DT)
 5 {
 6     if (!ST) 
 7         DT = NULL;
 8     else
 9     {
10         DT = (BiTree)malloc(sizeof(BiTNode));
11         DT->data = ST->data;
12 
13         CopyTree(ST->lchild,DT->lchild);
14         CopyTree(ST->rchild,DT->rchild);
15     }
16     return true;
17 }
View Code

     3、判断一个二叉树是否为完全二叉树

    利用层序遍历的思想

 1 ////////////////////////////////////////////////////////////////////////////
 2 //判断一个二叉树是否为完全二叉树,若是则返回true,否则返回false
 3 ////////////////////////////////////////////////////////////////////////////
 4 bool IsFullBiTree( BiTree T )
 5 {
 6     LinkQueue Q;
 7     BiTree p;
 8         int flag=0;
 9         Q.EnQueue(T); 
10 
11     while(!Q.QueueEmpty())
12     {
13         Q.DeQueue(p);
14         if(!p) 
15             flag=1;
16         else 
17             if(flag) 
18             {
19                 cout<<"该树不是完全二叉树!\n";
20                 return false;
21             }
22             else
23             {
24                 Q.EnQueue(p->lchild);
25                 Q.EnQueue(p->rchild); 
26             }
27      }
28     cout<<"该树是完全二叉树!\n";
29     return true;
30 }
View Code

   4、交换左右子树

 1 /////////////////////////////////////////////////////////////////////////////
 2 //递归交换二叉树的左右子树,若成功则返回true
 3 //////////////////////////////////////////////////////////////////////////////
 4 bool Revolute_BT( BiTree &T )
 5 {
 6     
 7     BiTree temp;
 8     //交换
 9     temp = T->lchild;
10     T->lchild = T->rchild;
11     T->rchild = temp;
12 
13     if(T->lchild) 
14         Revolute_BT(T->lchild);
15     if(T->rchild) 
16         Revolute_BT(T->rchild);
17 
18     return true;
19 }
View Code

     5、求二叉树叶子节点数

 1 /////////////////////////////////////////////////////////////////////////////
 2 //递归求二叉树的叶子节点的个数,参数为二叉树的头结点指针和个数count的引用,若成功返回true,
 3 //若为空树,返回false
 4 ///////////////////////////////////////////////////////////////////////////
 5 bool CountLeaf( BiTree T ,int &count)
 6 {
 7     if (!T) return false;
 8 
 9     if ( (!T->lchild)&&(!T->rchild) )//既无左孩子,也无右孩子
10         count++;
11     CountLeaf( T->lchild,count );
12     CountLeaf( T->rchild,count );
13 
14     return true;
15 }
View Code

    6、求二叉树的繁茂度(高度*单层最多节点数)

 1 ///////////////////////////////////////////////
 2 //求二叉树的繁茂度(高度*单层最多节点数),参数为二叉树的头节点,返回繁茂度值
 3 //////////////////////////////////////////////
 4 int GetFanMao(BiTree T)
 5 {
 6   int count[100];  //用来存储每一层的节点数,0号单元未用
 7   memset(count,0,sizeof(count));
 8   int h; //树的高度
 9   int maxn; //单层最大节点数
10   int i;
11   int fm;//繁茂度
12   BiTree p;
13   if (!T) return 0;
14   LinkQueue Q; 
15   Q.EnQueue(T);
16   while(!Q.QueueEmpty())
17   {
18     Q.DeQueue(p);
19     count[p->layer]++;
20 
21     if(p->lchild) 
22     {
23         Q.EnQueue(p->lchild);
24         p->lchild->layer = p->layer+1;  //可以求得该节点的层数
25     }
26     if(p->rchild) 
27     {    
28         Q.EnQueue(p->rchild);
29         p->rchild->layer = p->layer+1;
30     }
31   } 
32   h=p->layer;//高度
33 
34   for(maxn=count[1],i=1;count[i];i++)//求层最大结点数
35     if(count[i]>maxn) 
36         maxn=count[i]; 
37 
38   fm = h*maxn; //计算繁茂度
39   return fm;
40 }
View Code

    7、求二叉树的高度

 1 ///////////////////////////////////////////////////////////////
 2 //求二叉树的高度,参数为二叉树的头节点,返回高度值
 3 ///////////////////////////////////////////////////////////////
 4 int GetHeight( BiTree T )
 5 {
 6     int lheight,rheight,max,h;
 7 
 8     if ( !T )     return 0;
 9     else
10     {
11         lheight = GetHeight( T->lchild );
12         rheight = GetHeight( T->rchild );
13         max = lheight > rheight ? lheight : rheight;
14         h = max+1;
15         return h;
16     }
17 }
View Code

    8、 求完全二叉树的节点数(递归版本和非递归版本)  

     http://www.cnblogs.com/codershell/p/3306676.html

     9、递归释放一颗二叉树 

1 void Remove(BiTNode* u)
2 {
3       if(u==NULL) return;
4       Remove(u->left);
5       Remove(u-right);
6       free(u);        
7 }
View Code 

    10、二叉树的重建

        11、二叉树的旋转

  12、二叉树中两结点的最低公共祖先  

后续将会补充更多与二叉树相关的操作,如果文中有任何错误或表述不清楚的,欢迎大家及时指出。