0.二叉树相关的基本概念和性质:
定义:
1、满二叉树:一棵深度为k且有2的k次方减1个结点的二叉树称为满二叉树
2、完全二叉树:如果有深度为k的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时,称之为完全二叉树。
性质:
1、二叉树的第i层上至多有2的i-1次方个结点(i>=1)。
2、深度为k的二叉树至多有2的k次方减1个结点(k>=1)。
3、对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1。
4、具有n个结点的完全二叉树的深度为以2为底n的对数取下限加1。
5、如果对一棵有n个结点的完全二叉树的结点按层序编号,则对任一结点i(1=<i=<n)有:
(1)如果i=1,则结点i是二叉树的根,无双亲;如果i>1,则双亲PARENT(i)是结点[i/2]
(2)如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子LCHILD(i)是结点2i
(3)如果2i+1>n,则结点i无右孩子;否则其右孩子RCHILD(i)是结点2i+1.
存储结构:
顺序存储结构(数组方式),链式存储结构(二叉链表)
二叉树链表存储方式代码实现如下:
struct BiNode
{ Type data;
structBiNode *lchild,*rchild;//左右孩子指针
}BiNode,*BiTree
一.层序遍历
对二叉树层序遍历是指,同层次的节点按照从左到右的顺序依次遍历节点,当一层遍历结束后进入下一层,直到所有的节点遍历完成,其实现思想利用一个队列,存储每一层的左右孩子节点,进入下一层后依次出队遍历上一层存储的节点同时对其左右孩子进行入队,通过这种方式循环遍历直到结束。代码如下:
void levelTravel(TreeNode *root){
if(root==NULL)
return;
queue<TreeNode *> thequeue;
cout<<root->element<<endl;
if(root->left!=NULL)
thequeue.push(root->left);
if(root->right!=NULL)
thequeue.push(root->right);
while(!thequeue.empty()){
cout<<thequeue.front()->element<<endl;
thequeue.pop();
if(root->left!=NULL)
thequeue.push(root->left);
if(root->right!=NULL)
thequeue.push(root->right);
}
}
二.深度遍历
二叉树的深度遍历包括前序、中序、后序遍历,三种遍历方法根据遍历节点的顺序不同进行区分,其遍历顺序分别如下:
前序遍历:
1.访问根节点
2.前序遍历左子树
3.前序遍历右子树
代码如下:
void preorder(TreeNode*root)
{
TreeNode *p1=root;
TreeNode *p2=p1;
if(p1!=NULL)
{
cout<<p1->val<<endl;
midorder(p1->left);
midorder(p2->right);
}
}
中序遍历:
1.中序遍历左子树
2.访问根节点
3.中序遍历右子树
代码如下:
voidmidorder(TreeNode *root)
{
TreeNode *p1=root;
TreeNode *p2=p1;
if(p1!=NULL)
{
midorder(p1->left);
cout<<p1->val<<endl;
midorder(p2->right);
}
}
后序遍历:
1.后序遍历左子树
2.后序遍历右子树
3.访问根节点
代码如下:
voidmidorder(TreeNode *root)
{
TreeNode *p1=root;
TreeNode *p2=p1;
if(p1!=NULL)
{
midorder(p1->left);
cout<<p1->val<<endl;
midorder(p2->right);
}
}
对二叉树进行深度遍历采用递归的方式,代码十分整洁,十分有规律。非递归的遍历方式也可以实现,有机会可以自己敲一下,相对比较复杂。
1、已知前序、中序遍历,求后序遍历
例:
前序遍历: GDAFEMHZ
中序遍历: ADEFGHMZ
画树求法:第一步,根据前序遍历的特点,我们知道根结点为G
第二步,观察中序遍历ADEFGHMZ。其中root节点G左侧的ADEF必然是root的左子树,G右侧的HMZ必然是root的右子树。
第三步,观察左子树ADEF,左子树的中的根节点必然是大树的root的leftchild。在前序遍历中,大树的root的leftchild位于root之后,所以左子树的根节点为D。
第四步,同样的道理,root的右子树节点HMZ中的根节点也可以通过前序遍历求得。在前序遍历中,一定是先把root和root的所有左子树节点遍历完之后才会遍历右子树,并且遍历的左子树的第一个节点就是左子树的根节点。同理,遍历的右子树的第一个节点就是右子树的根节点。
第五步,观察发现,上面的过程是递归的。先找到当前树的根节点,然后划分为左子树,右子树,然后进入左子树重复上面的过程,然后进入右子树重复上面的过程。最后就可以还原一棵树了。该步递归的过程可以简洁表达如下:
1 确定根,确定左子树,确定右子树。
2 在左子树中递归。
3 在右子树中递归。
4 打印当前根。
那么,我们可以画出这个二叉树的形状:
那么,根据后序的遍历规则,我们可以知道,后序遍历顺序为:AEFDHZMG
编程求法:(依据上面的思路,写递归程序)
1 #include <iostream> 2 #include <fstream> 3 #include <string> 4 5 struct TreeNode 6 { 7 struct TreeNode* left; 8 struct TreeNode* right; 9 char elem; 10 }; 11 12 void BinaryTreeFromOrderings(char* inorder, char* preorder, int length) 13 { 14 if(length == 0) 15 { 16 //cout<<"invalid length"; 17 return; 18 } 19 TreeNode* node = new TreeNode;//Noice that [new] should be written out. 20 node->elem = *preorder; 21 int rootIndex = 0; 22 for(;rootIndex < length; rootIndex++) 23 { 24 if(inorder[rootIndex] == *preorder) 25 break; 26 } 27 //Left 28 BinaryTreeFromOrderings(inorder, preorder +1, rootIndex); 29 //Right 30 BinaryTreeFromOrderings(inorder + rootIndex + 1, preorder + rootIndex + 1, length - (rootIndex + 1)); 31 cout<<node->elem<<endl; 32 return; 33 } 34 35 36 int main(int argc, char* argv[]) 37 { 38 printf("Hello World!\n"); 39 char* pr="GDAFEMHZ"; 40 char* in="ADEFGHMZ"; 41 42 BinaryTreeFromOrderings(in, pr, 8); 43 44 printf("\n"); 45 return 0; 46 }
输出的结果为:AEFDHZMG
2、已知中序和后序遍历,求前序遍历
依然是上面的题,这次我们只给出中序和后序遍历:
中序遍历: ADEFGHMZ
后序遍历: AEFDHZMG
画树求法:第一步,根据后序遍历的特点,我们知道后序遍历最后一个结点即为根结点,即根结点为G。
第二步,观察中序遍历ADEFGHMZ。其中root节点G左侧的ADEF必然是root的左子树,G右侧的HMZ必然是root的右子树。
第三步,观察左子树ADEF,左子树的中的根节点必然是大树的root的leftchild。在前序遍历中,大树的root的leftchild位于root之后,所以左子树的根节点为D。
第四步,同样的道理,root的右子树节点HMZ中的根节点也可以通过前序遍历求得。在前后序遍历中,一定是先把root和root的所有左子树节点遍历完之后才会遍历右子树,并且遍历的左子树的第一个节点就是左子树的根节点。同理,遍历的右子树的第一个节点就是右子树的根节点。
第五步,观察发现,上面的过程是递归的。先找到当前树的根节点,然后划分为左子树,右子树,然后进入左子树重复上面的过程,然后进入右子树重复上面的过程。最后就可以还原一棵树了。该步递归的过程可以简洁表达如下:
1 确定根,确定左子树,确定右子树。
2 在左子树中递归。
3 在右子树中递归。
4 打印当前根。
这样,我们就可以画出二叉树的形状,如上图所示,这里就不再赘述。
那么,前序遍历: GDAFEMHZ
编程求法:(并且验证我们的结果是否正确)
#include <iostream> #include <fstream> #include <string> struct TreeNode { struct TreeNode* left; struct TreeNode* right; char elem; }; TreeNode* BinaryTreeFromOrderings(char* inorder, char* aftorder, int length) { if(length == 0) { return NULL; } TreeNode* node = new TreeNode;//Noice that [new] should be written out. node->elem = *(aftorder+length-1); std::cout<<node->elem<<std::endl; int rootIndex = 0; for(;rootIndex < length; rootIndex++)//a variation of the loop { if(inorder[rootIndex] == *(aftorder+length-1)) break; } node->left = BinaryTreeFromOrderings(inorder, aftorder , rootIndex); node->right = BinaryTreeFromOrderings(inorder + rootIndex + 1, aftorder + rootIndex , length - (rootIndex + 1)); return node; } int main(int argc, char** argv) { char* af="AEFDHZMG"; char* in="ADEFGHMZ"; BinaryTreeFromOrderings(in, af, 8); printf("\n"); return 0; }
输出结果:GDAFEMHZ
3、已知前序、后序遍历,求中序遍历
这种情况下得到的结果不唯一,常见的问题是判断可能的中序遍历序列是否合法。