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

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

一、定义 

  二叉树,顾名思义,只有两个分叉的树,他的特点就是每个节点至多只有两颗子树(即二叉树中不存在度大于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、二叉树中两结点的最低公共祖先  

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

 

你可能感兴趣的:(数据结构)