基础入门之二叉树
2016-06-15 18:45
253 查看
学习材料:《2013年王道论坛计算机考研机试指南》
文中涉及对学习材料的摘录,以及自己的理解
1 二叉树的基本操作
1.1 二叉树结点一般结构
使用结构体表示树的一个结点
创建一个新结点:
对于一个结点的结构体变量 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
前序遍历实现:
中序遍历实现:
/* 当前结点Tree作遍历操作,比如将其字符值打印出来: */ printf("%c", T -> c); if( Tre
4000
e -> rchild != NULL) //递归遍历右子树 InOrder( Tree -> rchlid ); return;}
后续遍历实现:
给定前序遍历和中序遍历,能唯一确定一个二叉树;前序遍历的第一个元素就是根
给定后序遍历和中序遍历,能唯一确定一个二叉树;后序遍历的最后一个元素是根
但,给定前序遍历和后序遍历,则不能唯一确定一个二叉树。因为前序和后续本质上是将父节点与子节点进行分离,但并没有指明左子树和右子树的能力,因此得到这两个序列只能明确父子关系,而不能确定一个二叉树。
1.3 题目:给定一棵二叉树的前序遍历和中序遍历,求其后续遍历
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 二叉排序树的构建、前序、中序、后续遍历
2.3 题目2 判断两序列是否能构成同一二叉搜索树
2.4 二叉排序树结点的删除
可以参考的一种删除规则:
a. 利用某种遍历找到该结点
b. 若该结点为叶子结点,则直接删除它,即将其双亲结点中指向其的指针改为NULL。释放该结点的空间。
c. 若该结点仅不存在右子树(存在左子树),则直接将其左子树的根结点代替其位置后,删除该结点。即将其双亲节点指向其的指针改为指向其的左子树树根
d. 若该结点存在右子树,则找到右子树上最右下的结点,将被删除结点的数值改为右子树上最右下结点的数值后,删除最右下结点。
文中涉及对学习材料的摘录,以及自己的理解
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. 若该结点存在右子树,则找到右子树上最右下的结点,将被删除结点的数值改为右子树上最右下结点的数值后,删除最右下结点。
相关文章推荐
- Android中RelativeLayout与LinearLayout的性能分析
- Android Studio打包全攻略---从入门到精通
- 没有基础的人如何自学裁缝?
- 《软件工程》课程总结
- 《软件工程》课程总结
- MYSQL循环插入
- AIDL跨进程通讯访问远程服务
- 第十五周项目2——洗牌
- 【转载】:__I、 __O 、__IO是什么意思?怎么用?
- php实现协程,真正的异步
- [置顶] Java面试题全集(下)
- MySQL数据同步(一主多从)
- 面试心得
- php实现协程,真正的异步
- [c语言] 模拟实现c语言库函数atoi
- mongodb远程连接以及备份、还原、导出、导入
- [置顶] Java面试题全集(中)
- 继承与派生——两个程序的比较
- WebElement.getText()为空解决方法
- window apache+openssl双向认证配置