二叉树性质操作遍历总结

来源:互联网 发布:茂名荔枝网络品牌 编辑:程序博客网 时间:2024/06/05 20:26
这几天面试都碰到了二叉树,在此将问题总结下,以备今后再遇。


参考文献:
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实现:

struct TreeNode;typedef struct TreeNode *Position;typedef struct TreeNode *SearchTree;typedef int ElementType;SearchTree MakeEmpty(SearchTree T);Position Find(ElementType x, SearchTree T);SearchTree Insert(ElementType X, SearchTree T);SearchTree Delete(ElementType X, SearchTree T);Position findMin(SearchTree T);Position findMax(SearchTree T);struct TreeNode{ElementType Element;SearchTree left;SearchTree Right;};//建立空树SearchTree MakeEmpty(SearchTree T){if(T != NULL){MakeEmpty(T->Left);MakeEmpty(T->Right);free(T);}return NULL;}Position Find(ElementType X, SearchTree T){if(T == NULL)return NULL;if(x < T->Element)return Find(X, T->Left);else if(X > T->Element)return Find(X, T->Right);elsereturn T;}Position FindMin(SearchTree T){if(T == NULL)return NULL;else if(T->Left == NULL)return T;elsereturn FindMin(T->Left);}Position FindMax(SearchTree T){if(T != NULL){while(T->Right != NULL)T = T->Right;}return T;}SearchTree Insert(ElementType X, SearchTree T){if(T == NULL){//Create and return an one-node treeT = (SearchTree)malloc(sizeof(struct TreeNode));if(T == NULL)Error("out of space!\n");else{T->Element = X;T->Left = T->Right = NULL;}}else if(X < T->Element)T->Left = Insert(X, T->Left);else if(X > T->Element)T->Right = Insert(X, T->Right);return T;}

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

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

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

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

代码如下:

SearchTree Delete(ElementType X, SearchTree T){Position TmpCell;if(T == NULL)Error("Element not found");else if(x < T->Element)T->left = Delete(X, T->Left);else if(X > T->Element)T->Right = Delete(X, T->Right);else if(T->Left&&T->Right){TmpCell = FindMin(T->Right);T->Element = TmpCell->Element;T->Right = Delete(T->Element, T->Right);}else{TmpCell = T;if(T->Left == NULL)T = T->Right;else if(T->Right == NULL)T = T->Left;free(TmpCell);}return T;}

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

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

树的深度优先遍历即为先前提到的先序、中序和后序,而树的广度优先遍历则用队列来实现。
void BreadthSearch(Tree T){Queue Q;InitQueue(Q);Position p = T;EnQueue(Q, p);while(!QueueEmpty(Q)){DeQueue(Q, p);PrintElement(p->Element);if(p->lchild){EnQueue(Q, p->lchild);}else if(p->rchild){EnQueue(Q, p->rchild);}}}

树的高度
采用递归的方式即可求取
int Height(Tree T){if(T == NULL)return 0;else{return Max(Height(T->Left), Height(T->Right)) + 1;}}

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

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

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


原创粉丝点击