二叉树性质操作遍历总结

这几天面试都碰到了二叉树,在此将问题总结下,以备今后再遇。


参考文献:
1. 数据结构与算法分析——c语言描述
2. 百度百科 http://baike.baidu.com/view/88806.htm   http://baike.baidu.com/view/127820.htm

定义:二叉树是一棵树,其中每个节点都不能有多于两个儿子节点


性质:
1、二叉树的平均深度要比N小,为O(N^(1/2));二叉查找树,其深度的平均值为O(logN)
2、二叉树分为满二叉树和完全二叉树。
满二叉树:所有非叶子节点都达到最大的二叉树
完美二叉树:叶子节点仅在层次最大的两层出现,对于任意节点,左子树的高度等于右子数的高度加1
3、第i层的节点总数不超过2^(i-1)
4、深度为h的二叉树,最多有2^h-1个节点(h>=1),最少有h个节点
5、对于任意二叉树,如果其叶子节点数为N0,而度数为2的节点总数为N2,则有N0=N2+1
6、具有n个节点的完全二叉树,其深度为int(log2n)+1
7、有N个节点的完全二叉树,如果采用顺序方式存储,则节点所在顺序表中的位置有如下关系:
若节点i不为根节点,则其父节点编号为i/2;
若2*i<=N,则其左儿子的编号为i*2,若2*i>N,则无左儿子;
若2*i+1<=N,则其右儿子的编号为2*i+1;若2*i>N,则无右儿子。
8、给定N个节点,能构成h(N)中不同的二叉树。
h(N)为卡特兰树德第N项, h(N) = C(n, 2n)/(n+1)


类型:
1、二叉查找树:对于树中的每个节点x,其左子树的所有关键字的值小于关键字x的值,右子树中所有关键字的值,大于x

下面是二叉查找树的定义及常用方法的C实现:

[cpp]   view plain copy
  1. struct TreeNode;  
  2. typedef struct TreeNode *Position;  
  3. typedef struct TreeNode *SearchTree;  
  4.   
  5. typedef int ElementType;  
  6.   
  7. SearchTree MakeEmpty(SearchTree T);  
  8. Position Find(ElementType x, SearchTree T);  
  9. SearchTree Insert(ElementType X, SearchTree T);  
  10. SearchTree Delete(ElementType X, SearchTree T);  
  11. Position findMin(SearchTree T);  
  12. Position findMax(SearchTree T);  
  13.   
  14.   
  15. struct TreeNode  
  16. {  
  17.     ElementType Element;  
  18.     SearchTree left;  
  19.     SearchTree Right;  
  20. };  
  21.   
  22. //建立空树  
  23. SearchTree MakeEmpty(SearchTree T)  
  24. {  
  25.     if(T != NULL)  
  26.     {  
  27.         MakeEmpty(T->Left);  
  28.         MakeEmpty(T->Right);  
  29.         free(T);  
  30.     }  
  31.     return NULL;  
  32. }  
  33.   
  34. Position Find(ElementType X, SearchTree T)  
  35. {  
  36.     if(T == NULL)  
  37.         return NULL;  
  38.       
  39.     if(x < T->Element)  
  40.         return Find(X, T->Left);  
  41.     else if(X > T->Element)  
  42.         return Find(X, T->Right);  
  43.     else  
  44.         return T;  
  45. }  
  46.   
  47. Position FindMin(SearchTree T)  
  48. {  
  49.     if(T == NULL)  
  50.         return NULL;  
  51.     else if(T->Left == NULL)  
  52.         return T;  
  53.     else    return FindMin(T->Left);  
  54. }  
  55.   
  56. Position FindMax(SearchTree T)  
  57. {  
  58.     if(T != NULL)  
  59.     {  
  60.         while(T->Right != NULL)  
  61.             T = T->Right;  
  62.     }  
  63.     return T;  
  64. }  
  65.   
  66. SearchTree Insert(ElementType X, SearchTree T)  
  67. {  
  68.     if(T == NULL)  
  69.     {  
  70.         //Create and return an one-node tree  
  71.         T = (SearchTree)malloc(sizeof(struct TreeNode));  
  72.         if(T == NULL)  
  73.             Error("out of space!\n");  
  74.         else  
  75.         {  
  76.             T->Element = X;  
  77.             T->Left = T->Right = NULL;  
  78.         }  
  79.     }  
  80.     else if(X < T->Element)  
  81.         T->Left = Insert(X, T->Left);  
  82.     else if(X > T->Element)  
  83.         T->Right = Insert(X, T->Right);  
  84.           
  85.     return T;  
  86. }  

关于二叉查找树的删除,存在三种情况:

1、叶子节点,直接删除即可;

2、如果只有一个儿子节点,将其父节点指向其儿子节点,然后free掉该节点,即可;

3、最复杂的情况,有两个节点,一般的删除策略是用其右子树的最小节点的数据,代替该节点的数据,并递归删除那个节点(即右子数最小节点)

代码如下:

[cpp]   view plain copy
  1. SearchTree Delete(ElementType X, SearchTree T)  
  2. {  
  3.     Position TmpCell;  
  4.       
  5.     if(T == NULL)  
  6.         Error("Element not found");  
  7.     else if(x < T->Element)  
  8.         T->left = Delete(X, T->Left);  
  9.     else if(X > T->Element)  
  10.         T->Right = Delete(X, T->Right);  
  11.     else if(T->Left&&T->Right)  
  12.     {  
  13.         TmpCell = FindMin(T->Right);  
  14.         T->Element = TmpCell->Element;  
  15.         T->Right = Delete(T->Element, T->Right);  
  16.     }  
  17.     else  
  18.     {  
  19.         TmpCell = T;  
  20.         if(T->Left == NULL)  
  21.             T = T->Right;  
  22.         else if(T->Right == NULL)  
  23.             T = T->Left;  
  24.         free(TmpCell);  
  25.     }  
  26.       
  27.     return T;  
  28. }  

2、一般二叉树
二叉树的遍历,按照当前节点的遍历顺序,分别由前序,中序和后序,此处的前中后,是根据当前节点的遍历次序来说的。比如前序,就是先处理当前的节点P,再处理其左右节点。
[cpp]   view plain copy
  1. void PreOrderTraverse(Tree T)  
  2. {  
  3.     if(T != NULL)  
  4.     {  
  5.         PrintElement(T->Element);  
  6.         PreOrderTraverse(T->Left);  
  7.         PreOrderTraverse(T->Right);  
  8.     }  
  9. }  

中序和后序的递归遍历和上面相似,就是在中间或最后打印当前节点。下面分析树的非递归遍历,二叉树前序非递归遍历:
[cpp]   view plain copy
  1. void PreOrderTraverseNew(Tree T)  
  2. {  
  3.     InitStack(S);  
  4.     Position p = T;  
  5.     while(p || !StackEmpty(S))  
  6.     {  
  7.         if(p)  
  8.         {  
  9.             PrintElement(p->Element);  
  10.             Push(S, p);  
  11.             p = p->lchild;  
  12.         }  
  13.         else  
  14.         {  
  15.             Pop(S, p);  
  16.             p = p->rchild;  
  17.         }  
  18.     }  
  19. }  
中序的非递归遍历为:
[cpp]   view plain copy
  1. void InOrderTraverseNew(Tree T)  
  2. {  
  3.     InitStack(S);  
  4.     Position p = T;  
  5.     while(p || !StackEmpty(S))  
  6.     {  
  7.         if(p)  
  8.         {  
  9.             Push(S, p);  
  10.             p = p->lchild;  
  11.         }  
  12.         else  
  13.         {  
  14.             Pop(S, p);  
  15.             PrintElement(p->Element);  
  16.             p = p->rchild;  
  17.         }  
  18.     }  
  19. }  
可见,前序和中序的非递归遍历很相近,采用栈来实现,当左子树不为空时,就不断往栈中压入节点,直到左儿子为空,则弹出栈顶元素,并且指向其右儿子,然后重复前面的过程。直到栈中节点全部出栈,且p指向空。唯一的区别在于前序在当前节点入栈前就处理(本例中为打印)该节点,然后再入栈;而中序则是在该元素出栈时打印处理。可见,在非递归的实现中,遍历指针p总会指向null,因此遍历的判断条件仅有一个,具体来讲,就是是否print一个节点,前序的判断为入栈前就要打印,而中旬的判断为其左子树为空或已打印处理,从堆栈的角度来看,左子树为空和左子树已经打印,对于当前节点来说,栈的状态都是一样的,即要打印的节点都是栈顶元素,而且不用考虑右子树的状态,因此出栈打印即可。
但对于后序遍历来说,上面的方法是不行的。因为,在后序遍历中,一个节点是否打印处理,有两种情况,一是该节点左右子树都为空,另一种情况则是该节点的左右子树都已经打印。在判断一个节点是否出栈处理时,首先要确定该节点的右子树是否已经处理,若无,则还要进入右子数处理,因为需要设置一个tag,随着每个节点进入栈中,来记录该节点的右子树是否已经处理。
因此,后序的非递归遍历为:
[cpp]   view plain copy
  1. void PostOrderTraverse(Tree T)  
  2. {  
  3.     InitStack(S);  
  4.     Position p = T;  
  5.     while(p || !StackEmpty)  
  6.     {  
  7.         while(p)  
  8.         {  
  9.             Push(S, p, 0);  
  10.             p = p->lchild;     
  11.         }  
  12.         while(!StackEmpty(S) && GetTopTag(S) == 1)  
  13.         {  
  14.             Pop(S, p);  
  15.             PrintElement(p->Element);  
  16.         }  
  17.         if(!StackEmpty(S))  
  18.         {  
  19.             setTop(S, 1);  
  20.             p = GetTop(S);  
  21.             p = p->rchild;  
  22.         }  
  23.         else  
  24.             break;  
  25.     }  
  26. }  

树的深度优先遍历即为先前提到的先序、中序和后序,而树的广度优先遍历则用队列来实现。
[cpp]   view plain copy
  1. void BreadthSearch(Tree T)  
  2. {  
  3.     Queue Q;  
  4.     InitQueue(Q);  
  5.     Position p = T;  
  6.     EnQueue(Q, p);  
  7.     while(!QueueEmpty(Q))  
  8.     {  
  9.         DeQueue(Q, p);  
  10.         PrintElement(p->Element);  
  11.         if(p->lchild)  
  12.         {  
  13.             EnQueue(Q, p->lchild);  
  14.         }  
  15.         else if(p->rchild)  
  16.         {  
  17.             EnQueue(Q, p->rchild);  
  18.         }  
  19.     }  
  20. }  

树的高度
采用递归的方式即可求取
[cpp]   view plain copy
  1. int Height(Tree T)  
  2. {  
  3.     if(T == NULL)  
  4.         return 0;  
  5.     else  
  6.     {  
  7.         return Max(Height(T->Left), Height(T->Right)) + 1;  
  8.     }  
  9. }  

3、哈夫曼树
给定n个权值作为n个叶子节点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也叫做哈夫曼树。

路径:在一棵树中,从一个节点往下可以到达的孩子或子孙节点之间的通路,成为路径;
路径长度:通路中分支的数目称为路径长度,若规定根节点的层数为1,则从根节点到第L层节点的路径长度为L-1;
节点的权:若将树中节点赋给一个有着某种含义的数值,则称该数值为该节点的权;
节点的带权路径长度:从根节点到该节点之间的路径长度与该节点的权的乘积。
树德带权路径长度:所有叶子节点的带权路径长度之和,记为WPL。

哈夫曼树的构造:
假设有n个权值,则构造出的哈夫曼树有n个叶子节点,n个权值分别设为分别设为w1、w2、...、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、...、wn看成是有n棵树的森林(每棵树仅有一个节点);
(2) 在森林中选出两个根节点的权值最小的树合并,作为一棵新树的左右子树,且新树的根节点权值为其左右子树的根节点权值之和;
(3) 从森林中删除选取的两棵树,并将新树加入森林;
(4) 重复(2) (3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

二叉树的遍历:

D:访问根结点,L:遍历根结点的左子树,R:遍历根结点的右子树。

给定一棵二叉树的前序遍历序列和中序遍历序列可以惟一确定一棵二叉树(后序遍历可以举出很多不唯一的反例)。

二叉树的深度优先遍历的非递归的通用做法是采用栈,广度优先遍历的非递归的通用做法是采用队列。

深度优先遍历二叉树。

1. 中序遍历(LDR)的递归算法:

若二叉树为空,则算法结束;否则:

    中序遍历根结点的左子树;

    访问根结点;

    中序遍历根结点的右子树。

2. 前序遍历(DLR)的递归算法:

若二叉树为空,则算法结束,否则:

    访问根结点;

    前序遍历根结点的左子树;

    前序遍历根结点的右子树。

3. 后序遍历(LRD)的递归算法:

若二叉树为空,则算法结束,否则:

    后序遍历根结点的左子树;

    后序遍历根结点的右子树;

    访问根结点。

 

广度优先遍历二叉树。

广度优先周游二叉树(层序遍历)是用队列来实现的,从二叉树的第一层(根结点)开始,自上至下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问。

按照从根结点至叶结点、从左子树至右子树的次序访问二叉树的结点。算法:

    1初始化一个队列,并把根结点入列队;

    2当队列为非空时,循环执行步骤3到步骤5,否则执行6;

    3出队列取得一个结点,访问该结点;

    4若该结点的左子树为非空,则将该结点的左子树入队列;

    5若该结点的右子树为非空,则将该结点的右子树入队列;

    6结束。

 

非递归深度优先遍历二叉树。

栈是实现递归的最常用的结构,利用一个栈来记下尚待遍历的结点或子树,以备以后访问,可以将递归的深度优先遍历改为非递归的算法。

1. 非递归前序遍历:遇到一个结点,就访问该结点,并把此结点推入栈中,然后下降去遍历它的左子树。遍历完它的左子树后,从栈顶托出这个结点,并按照它的右链接指示的地址再去遍历该结点的右子树结构。

2. 非递归中序遍历:遇到一个结点,就把它推入栈中,并去遍历它的左子树。遍历完左子树后,从栈顶托出这个结点并访问之,然后按照它的右链接指示的地址再去遍历该结点的右子树。

3. 非递归后序遍历:遇到一个结点,把它推入栈中,遍历它的左子树。遍历结束后,还不能马上访问处于栈顶的该结点,而是要再按照它的右链接结构指示的地址去遍历该结点的右子树。遍历遍右子树后才能从栈顶托出该结点并访问之。另外,需要给栈中的每个元素加上一个特征位,以便当从栈顶托出一个结点时区别是从栈顶元素左边回来的(则要继续遍历右子树),还是从右边回来的(该结点的左、右子树均已周游)。特征为Left表示已进入该结点的左子树,将从左边回来;特征为Right表示已进入该结点的右子树,将从右边回来。

4. 简洁的非递归前序遍历:遇到一个结点,就访问该结点,并把此结点的非空左结点推入栈中,然后下降去遍历它的左子树。遍历完左子树后,从栈顶托出一个结点,并按照它的右链接指示的地址再去遍历该结点的右子树结构。

----------------------------------------------------------------------

    图的深度优先搜索法是树的先根遍历的推广,它的基本思想是:从图G的某个顶点v0出发,访问v0,然后选择一个与v0相邻且没被访问过的顶点vi访问,再从vi出发选择一个与vi相邻且未被访问的顶点vj进行访问,依次继续。如果当前被访问过的顶点的所有邻接顶点都已被访问,则退回到已被访问的顶点序列中最后一个拥有未被访问的相邻顶点的顶点w,从w出发按同样的方法向前遍历,直到图中所有顶点都被访问。
    图的广度优先搜索是树的按层次遍历的推广,它的基本思想是:首先访问初始点vi,并将其标记为已访问过,接着访问vi的所有未被访问过的邻接点vi1,vi2, …, vi t,并均标记已访问过,然后再按照vi1,vi2, …, vi t的次序,访问每一个顶点的所有未被访问过的邻接点,并均标记为已访问过,依次类推,直到图中所有和初始点vi有路径相通的顶点都被访问过为止。

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值