您的位置:首页 > 其它

基础入门之二叉树

2016-06-15 18:45 253 查看
学习材料:《2013年王道论坛计算机考研机试指南》

文中涉及对学习材料的摘录,以及自己的理解

1 二叉树的基本操作
1.1 二叉树结点一般结构
使用结构体表示树的一个结点
<span style="font-size:12px;">
struct Node
{
//指向左儿子节点的指针(结构体类型的指针), 当其不存在左儿子时为Null
Node *lchild;
//指向右儿子节点的指针(结构体类型的指针), 当其不存在右儿子时为Null
Node *rchild;

/* 其他结点信息,比如结点代表的字符: */
char c;
}</span>

创建一个新结点:
<span style="font-size:12px;">
Node T;

/* 申请一个结点空间,返回指向该结点的指针 */
Node *creat()
{
T.lchild = T.rchild = NULL;         //初始化左右儿子为空
return &T;                          //返回指向新结点的指针
}</span>

对于一个结点的结构体变量  Node T,  它的元素表达为: T.lchild    T.rchild   T.c
对于一个指向结点的结构体类型指针  Node *T   它的元素表达为:T -> lchild   T -> rchild    T -> c

1.2 二叉树的遍历方法
前序遍历(pre):  对任一子树,先访问根,再遍历其左子树,最后遍历其右子树;   如下二叉树:ABDECFG
中序遍历(in):    对任一子树,先遍历其左子树,再访问根,最后遍历其右子树;  
如下二叉树:DBEAFCG
后序遍历(post):对任一子树,先遍历其左子树,再遍历其右子树,最后访问根。   如下二叉树:DEBFGCA
                        A
                     /      \
                  B         C
                /    \      /    \
             D     E   F      G

前序遍历实现:
<span style="font-size:12px;">
void PreOrder( Node *Tree )
{
/* 遍历当前结点,输出其字符信息 */
printf( "%c", Tree -> c );

/* 若当前结点左子树不为空,递归遍历其左子树 */
if( Tree -> lchild != NULL )
PreOrder( Tree -> lchild );

/* 若当前结点右子树不为空,递归遍历其右子树 */
if( Tree -> rchild != NULL )
PreOrder( Tree -> rchild );
}</span>


中序遍历实现:
<span style="font-size:12px;">
void InOrder(Node *Tree)
{
if( Tree -> lchild != NULL )    //递归遍历左子树
InOrder( Tree -> lchild );
</span>
<span style="font-size:12px;">    /* 遍历当前结点,输出其字符信息 */
printf( "%c", Tree -> c );

if( Tree -> rchild != NULL )    //递归遍历右子树
PreOrder( Tree -> rchild );
}</span>


/* 当前结点Tree作遍历操作,比如将其字符值打印出来: */ printf("%c", T -> c); if( Tre
4000
e -> rchild != NULL) //递归遍历右子树 InOrder( Tree -> rchlid ); return;}


后续遍历实现:
<span style="font-size:12px;">
void PostOrder( Node *Tree )
{
if( Tree -> lchild != NULL )  //若左子树不为空,递归遍历其左子树
PostOrder( Tree -> lchild  );

if( Tree -> rchild != NULL )  //若右子树不为空,递归遍历其右子树
PostOrder( Tree -> rchild );

/* 遍历当前结点,输出其字符信息 */
printf( "%c", Tree -> c );
}</span>


给定前序遍历和中序遍历,能唯一确定一个二叉树;前序遍历的第一个元素就是根
给定后序遍历和中序遍历,能唯一确定一个二叉树;后序遍历的最后一个元素是根
但,给定前序遍历和后序遍历,则不能唯一确定一个二叉树。因为前序和后续本质上是将父节点与子节点进行分离,但并没有指明左子树和右子树的能力,因此得到这两个序列只能明确父子关系,而不能确定一个二叉树。

1.3 题目:给定一棵二叉树的前序遍历和中序遍历,求其后续遍历

/*
* 知识点:二叉树基本操作:建树、遍历、还原
* 题目: 给定一棵二叉树的前序遍历和中序遍历,求其后序遍历(唯一确定)
* 输入: 两个字符串,结点名称用大写英文字母表示,长度均小于26;  第一行为前序遍历,第二行为中序遍历
* 输出: 后序遍历字符串
* 测试用例:
input:  ABC
BAC
FDXEAG
XDEFAG
output: BCA
XEDGAF
* 程序思路:
1)还原二叉树。
给定前序遍历FDXEAG,第一个结点F为根节点;
找到”根节点F“在中序遍历中的位置,F左边XDE为F左子树的内容,右边AG为右子树的内容;
对于前序遍历中的DXE,中序遍历中的XDE再按上述方法,确定”D为根节点“,X为左儿子,E为右儿子;
对于前序遍历中的X,中序遍历中的X,X相当于没有左右儿子的”根节点“,于是将X存下来。
上述3步可用递归实现,从根节点出发,先递归还原它的左子树,直到某结点没有左儿子,再递归还原它的右子树,直到某节点没有右儿子,从而还原出整棵树。
2)保存被还原的二叉树。
树结点用结构体类型来描述,包括左右结点和自身信息。
使用静态数组存储被还原的二叉树结点,即根据需要存放的节点数,定义一个适当大小的数组,数组元素为树结点结构体类型
3)实现后序遍历,输出后序遍历字符串。
二叉树已被还原且被保存在内存中,从该二叉树”根节点“出发,对树中的每个节点,先递归遍历其左子树,再递归遍历其右子树,最后遍历该结点本身。
对于遍历每个结点本身,由于每个结点由结构体存储,遍历结点本身只要将其字符信息输出即可
*/

#include
#include   //程序中用到strlen(..)

struct Node          //树结点结构体
{
Node *lchild;    //左儿子指针
Node *rchild;    //右儿子指针
char c;          //该结点字符信息
};
Node Tree[50];       //静态内存分配数组,用来存储二叉树的结点

int loc = 0;         //二叉树数组元素下标,表示已经分配(存储)的结点个数,loc有坐标的意思   全局变量,在还原二叉树、存储结点的过程中不断累加
char preStr[30],inStr[30]; //保存输入的前序遍历和中序遍历字符串

/*
* 函数名称:*CreatNode
* 功能:申请一个结点空间,返回指向该结点的指针
*/
Node *CreatNode()
{
Tree[loc].lchild = Tree[loc].rchild = NULL;        //初始化左右儿子为空
return &Tree[loc++];
}

/*
* 函数名称:*BuildTree
功能:由前序遍历和中序遍历还原二叉树,将其各节点保存在静态数组中,通过递归还原实现,最终返回二叉树的根节点
输入:preS:前序遍历首结点_下标,preE:前序遍历尾结点_下标,inS:中序遍历首结点_下标,inE:中序遍历尾结点_下标
即前序遍历字符串 从preStr[preS]到preStr[preE],  中序遍历字符串 从inStr[inS]到inStr[inE],
下标位置是相对概念,再递归还原过程中,实际的前序遍历字符串和中序遍历字符串不断改变,其下标位置也是相应改变的
*/
Node *BuildTree(int preS, int preE, int inS, int inE)
{
int rootIdx;                                       //根节点下标,表示当前根节点在当前中序遍历字符串中的位置

/* 1)创建新的结点 即申请空间以存放当前根结点  */
Node *ret = CreatNode();
ret ->c =  preStr[preS];                           //当前根结点就是前序遍历中的首字符
/* 2)查找当前根节点在中序遍历中的位置 */
for( int i=inS; i<=inE; i++ )
{
if( inStr[i] == ret->c )
{
rootIdx = i;
break;
}
}
/* 3)递归还原二叉树,依次还原当前结点的做右子树,并将其存储在新创建的结点空间中 */
if( rootIdx != inS )                               //若左子树不为空
ret ->lchild = BuildTree( preS+1, preS+(rootIdx-inS), inS, rootIdx-1 );//递归还原当前结点的左子树(根据当前结点左子树字符串,截取新的前序遍历、中序遍历字符串,重复上述过程)
if( rootIdx != inE )                               //若右子树不为空
ret ->rchild = BuildTree( preS+(rootIdx-inS)+1, preE, rootIdx+1, inE );//递归还原当前结点的右子树
/* 4)返回当前根节点指针,经过递归还原,就依次存储了所有节点,最终返回的是所还原二叉树的根指针 */
return ret;
}

/*
* 函数名称:PostOrder
* 功能:后序遍历实现。再遍历任意一个结点Tree时,首先递归遍历其左儿子极其子树,再遍历其右儿子及其子树,最后遍历该节点本身
* 输入:指向二叉树根节点的指针
*/
void PostOrder(Node *T)
{
if( T ->lchild != NULL )                           //若左子树不为空
PostOrder(T ->lchild);                         //递归遍历其左子树
if( T ->rchild != NULL )                           //若右子树不为空
PostOrder(T ->rchild);                         //递归遍历其右子树
printf("%c",T ->c);                                //遍历该节点本身,输出字符信息
}

int main()
{
while( scanf("%s",preStr) != EOF )
{
scanf("%s",inStr);
Node *T = BuildTree(0, (int)(strlen(preStr)-1), 0, (int)(strlen(inStr)-1) );  //还原二叉树,并将其根节点保存在T中
PostOrder(T);
printf("\n");                                  //输出换行
}
return 0;
}


2 二叉排序树
2.1 概念
二叉排序树,又称二叉搜索树,二叉查找树。特点:对于树上任意一个结点,其上的数值必大于等于其左子树上任意结点数值,必小于等于其右子树上任意结点的数值。

二叉排序树构造——对二叉排序树依次插入数字x:
a. 若当前树为空,则x为其根结点
b. 若当前结点大于x,则x插入其左子树;
    若当前结点小于x,则x插入其右子树;
    若当前结点等于x,则根据情况选择插入右子树或者直接忽略。
以插入 4、2、6、1、3为例
                        4
                     /      \
                  2          6
               /      \
             1         3
插入1,2,6,4,3:
                          1
                        /     \
                      2       6
                              /
                            4
                           /
                         3
可以发现, 数字插入的顺序不同,得到的二叉排序树形态就不同。但是只要数字相同,那么数字序列构成的二叉排序树的中序遍历相同,都是这一数字序列从小到大的递增序列。如上两个二叉排序树的中序遍历:1 2 3 4 6.
这也是二叉排序树名字的由来。

2.2 题目1   二叉排序树的构建、前序、中序、后续遍历

/*
* 知识点:二叉排序树
* 题目:给定n个整数,构造二叉排序树,输出其前序、中序、后序遍历, 忽略n个数中的重复数字
* 输入:第一行:一个整数n(1<=n<=100)   第二行:n个整数   有多组输入
* 输出:每种遍历输出一行,每行最后一个数据之后有一个空格
* 测试用例:
input:
6
1 1 6 5 9 8
output:
1 6 5 9 8
1 5 6 8 9
5 8 9 6 1
* 程序思路:
1)定义结点结构体类型的静态数组,用来存放构建的二叉排序树结点
2)依次将输入的整数插入二叉排序树,若当前结点为空,则在当前位置插入,若不空,
若插入元素比当前结点小,则插入其左子树,即以当前结点的左儿子结点为当前结点,重复上述判断;
若插入元素比当前结点大,则插入其右子树,即以当前结点的右儿子结点为当前结点,重复上述判断;
以上步骤通过递归实现,且对于每一个要插入的数字,每次都从二叉排序树的根节点开始判断,即一开始都以根节点为当前结点
最终返回指向二叉排序树根节点的指针
3)实现前序、中序、后序遍历
*/

#include

struct Node                   //二叉树结构体
{
Node *lchild;
Node *rchild;
int c;
};
Node Tree[110];               //静态数组,用于存放二叉排序树的结点

int loc;                      //静态数组元素下标,表示已存储的结点个数

/*
* 函数名称:*CreatNode
* 功能:申请一个结点空间,返回指向该结点的指针
*/
Node *CreatNode()
{
Tree[loc].lchild = Tree[loc].rchild = NULL;
return &Tree[loc++];
}

/*
* 函数名称:*Insert
功能:对二叉排序树插入数字x
* 输入:(Node *)T:二叉排序树当前(根)结点  (int)x:要插入的整数
* 输出:无
* 返回:当前(根)结点的指针
*/
Node *Insert(Node *T, int x)
{
if( T == NULL )            //若T为空指针,即当前树为空
{
T = CreatNode();       //先建立结点
T ->c = x;             //数字直接插入当前根结点
return T;
}
else if( x < T ->c  )      //若x小于当前根结点数值,插入到当前结点左子树上
{
T ->lchild = Insert( T->lchild , x );
}
else if( x > T->c )        //若x大于当前根结点数值,插入到当前结点右子树上
{
T ->rchild = Insert( T->rchild , x );
}
return T;
}

/*
* 函数名称:PreOrder()
* 功能:前序遍历
* 输入:(Node *T)指向二叉树根节点的指针
* 输出:前序遍历序列
* 返回:无
*/
void PreOrder(Node *T)
{
printf("%d ", T->c);
if( T->lchild != NULL )
PreOrder( T->lchild );
if( T->rchild != NULL )
PreOrder( T->rchild );
}

/*
* 函数名称:InOrder()
* 功能:中序遍历
* 输入:(Node *T)指向二叉树根节点的指针
* 输出:中序遍历序列
* 返回:无
*/
void InOrder(Node *T)
{
if( T->lchild != NULL )
InOrder( T->lchild );
printf("%d ", T->c);
if( T->rchild != NULL )
InOrder( T->rchild );
}

/*
* 函数名称:PostOrder()
* 功能:后序遍历
* 输入:(Node *T)指向二叉树根节点的指针
* 输出:后序遍历序列
* 返回:无
*/
void PostOrder(Node *T)
{
if( T->lchild != NULL )
PostOrder( T->lchild );
if( T->rchild != NULL )
PostOrder( T->rchild );
printf("%d ", T->c);
}

int main()
{
int n;
int i;
Node *T = NULL;
loc = 0;

while( scanf("%d",&n) != EOF )
{
for(i=0; i


2.3 题目2  判断两序列是否能构成同一二叉搜索树

/*
* 知识点:二叉排序树/二叉搜索树/二叉查找树   判断两棵树是否相同
* 题目:判断两个整数序列是否为同一二叉搜索树
* 输入:第一行:一个整数n(1<=n<=20),表示有多少个序列要分别与给出的第一个序列了比较;
第二行:一个整数序列,后面的序列都与它做比较
接下来的n行:n个整数序列,每一行都与第二行比较,是否与第二行为同一个二叉搜索树
整数序列长度<10,包含(0-9)的数字,无重复数字
当第一个整数n=0时,输入结束
* 输出:对于n对序列的比较,如果序列相同则输出YES,否则输出NO,  应该有n个答案,每个答案占一行
* 测试样例:
input:
2
567432
543267
576342
output:
YES
NO
* 程序思路:
1)如何确定两棵树相同?
a.就二叉树而言,给出一种遍历无法唯一确定二叉树。(中序遍历+前序遍历)、或(中序遍历+后序遍历)可唯一确定二叉树;即如果两棵树的中序遍历+另一种遍历相同,那么这两棵树完全相同
b.对于二叉排序树,只要数字相同,插入顺序不同而构造的二叉排序树,它们的中序遍历一定是一样的,测试样例中,第三行序列和前两行序列数字相同,构造的二叉排序树不同,但它们的中序遍历是相同的
c.如果要比较的n行序列数字都是一样的,那么已经可以确定其中序遍历是一样的,只要再比较另一种遍历(前序或后序),就可确定是否一样。
题目中没有明确说明要比较的n行序列数字是否一样,所以这里要依次比较两棵树的中序遍历和另一种遍历(选择前序遍历)是否相同
2)对于输入的序列,先分别构造二叉排序树,然后对其进行中序遍历、前序遍历。
3)如何比较两棵二叉树的前序遍历+中序遍历相同? 将每棵树的前序遍历字符串、中序遍历字符串存储在数组中,为了方便比较,可将两种遍历的字
a722
符串连接起来,放在同一个数组中
*/

#include
#include

struct Node                   //树结点结构体
{
Node *lchild;
Node *rchild;
int c;
};
Node Tree[25];               //静态数组,用于存放二叉排序树的结点

int loc;                      //静态数组元素下标,表示已存储的结点个数
char str1[25],str2[25];       //保存二叉排序树的遍历结果,将每一棵树的前序遍历字符串与中序遍历字符串连接,得到遍历结果字符串    每棵树最多10个结点,遍历字符串长度约20
int Idx;                      //字符串元素下标,表示当前存储在字符串中的字符个数
char *str;                    //当前正在保存的字符串

/*
* 函数名称:*CreatNode
* 功能:申请一个结点空间,返回指向该结点的指针
*/
Node *CreatNode()
{
Tree[loc].lchild = Tree[loc].rchild = NULL;
return &Tree[loc++];
}

/*
* 函数名称:*Insert
功能:构造二叉排序树,对二叉排序树插入数字x
* 输入:(Node *)T:二叉排序树当前(根)结点  (int)x:要插入的整数
* 输出:无
* 返回:当前(根)结点的指针
*/
Node *Insert(Node *T, int x)
{
if( T == NULL )
{
T = CreatNode();
T ->c = x;
return T;
}
else if( x < T->c )
T -> lchild = Insert( T->lchild, x );
else if( x > T->c )
T -> rchild = Insert( T->rchild, x );
return T;
}

/*
* 函数名称:PreOrder()
* 功能:前序遍历
* 输入:(Node *T)指向二叉树根节点的指针  (char *)str 指向要保存在其中的字符串  比如要将元素存在str1[]中  则输入str1
* 输出:前序遍历序列
* 返回:无
*/
void PreOrder(Node *T)
{
str[Idx++] = T->c + '0';
if( T->lchild != NULL )
PreOrder( T->lchild);
if( T->rchild != NULL )
PreOrder( T->rchild );
}

/*
* 函数名称:InOrder()
* 功能:中序遍历
* 输入:(Node *T)指向二叉树根节点的指针  (char *)str 指向要保存在其中的字符串  比如要将元素存在str1[]中  则输入str1
* 输出:中序遍历序列
* 返回:无
*/
void InOrder(Node *T)
{
if( T->lchild != NULL )
InOrder( T->lchild );
str[Idx++] = T->c + '0';
if( T->rchild != NULL )
InOrder( T->rchild );
}

int main()
{
int n;
int i,line;
char inStr[12];                                  //不知道输入的数字序列有多长,但是不大于10,用字符串的形式保存下来
//loc = 0;
//Node *T = NULL;
char ans[25][5];

while( scanf("%d",&n) != EOF && n != 0 )         //对于n=0则结束输入 可以在这里处理
{
/* 1)每组输入 输入第一行对照序列前,清空输入字符串缓存,清空上一组对照序列str1[12] */
for(i=0;i<12;i++)                            //清空输入字符串数组 inStr[12]
inStr[i] = 0;
for(i=0;i<25;i++)
str1[i] = 0;                             //清空用来存放遍历字符串的数组str1
/* 2)输入第一行对照序列 */
scanf("%s", inStr);
/* 3)对每组输入,构造新的二叉排序树 */
loc = 0;
Node *T=NULL;
for(i=0; i


2.4 二叉排序树结点的删除
可以参考的一种删除规则:
a. 利用某种遍历找到该结点
b. 若该结点为叶子结点,则直接删除它,即将其双亲结点中指向其的指针改为NULL。释放该结点的空间。
c. 若该结点仅不存在右子树(存在左子树),则直接将其左子树的根结点代替其位置后,删除该结点。即将其双亲节点指向其的指针改为指向其的左子树树根
d. 若该结点存在右子树,则找到右子树上最右下的结点,将被删除结点的数值改为右子树上最右下结点的数值后,删除最右下结点。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: