今天做了三道洛谷上的《并查集与树》类型的题目以及一道学校刷题网上的树的题目:
目录
题目:
相信大家看到这道题后第一想法是写出一个类似与这样的代码
当然咯,大家肯定会多多少少加些东西在这里,例如左右指针指向左右子节点啥的,其实这样的想法没啥毛病,但是可能问题就出在“用指针将各个节点连接起来后不知道怎么遍历输出”,然后就自然而然地简简单单地用一个循环去遍历、输出,结果你就发现其实代码主要使用的部分只有上图那样一部分,而你拿上面的代码一交居然也能有80分。
而上面的代码怎么错了呢?很明显的,倘若相邻序号的两个节点的子节点没有重合部分就可能出错了,不信,你拿这组样例试一试:
7
4 6
7 3
0 0
0 2
0 0
0 5
0 0
按上图代码得到的是“3”,然而实际应该是“4”
那么重新再理解一下题意,就能明白过来,这题估计是得用dfs。明确方向后就可以敲代码了
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int n,ans=0;
struct abc{
struct abc * left;
struct abc * right;
}tree[100010];
int max(int x,int y){
return (x>y?x:y);
}
void dfs(struct abc * x,int deep){
ans=max(ans,deep);
if(x->left !=NULL)
dfs(x->left,deep+1);
if(x->right != NULL)
dfs(x->right,deep+1);
}
int main()
{
int j,i;
struct abc * head;
scanf("%d",&n);
for(i=0;i<=n;i++){
tree[i].left =NULL;
tree[i].right=NULL;
}
for(i=1;i<=n;i++){//循环遍历第i个节点情况
int a,b;
scanf("%d %d",&a,&b);
if(a != 0)
tree[i].left =&tree[a];
if(b != 0)
tree[i].right=&tree[b];
if(i == 1)
head=&tree[i];
}
dfs(head,1);
printf("%d",ans);
return 0;
}
题目:
在中序遍历、后序遍历以及前序遍历三种遍历方式中,知道任意两种遍历得到的字符串都可以推知原树的模样,除了“知道前序和后序遍历的字符串”的情况,具体为什么,我们可以自己来尝试推一下:
先尝试一下复杂一些的树的遍历,这里还看不太出来其中的规律,主要目的是要确保自己真的明白三种遍历方式是怎么进行的。
接下来再看看简单树的遍历情况:
从这里我们可以明白,当确定前序遍历和后序遍历得到的字符串时,得到的树确实尚不确定,那么怎么确定凭当前已知的前序后序遍历的字符串可以找出几种不同的树呢?
继续思考,我们发现某个节点只有一个叶节点时,叶节点在左节点上还是在右节点上,它的前序遍历和后序遍历得到的字符串都是一样的,但是它的中序遍历结构却不一样。进一步思考,我们发现不仅仅是叶节点,当某个节点只有左子树或右子树时情况也是一样的。
而当只有一个子树时,其子树上的唯一节点仍需考虑左右情况。
至此,我们发现规律:可得到树的个数=二的“单一节点数”次方
(图来自《大话数据结构》)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
char s1[100010],s2[100010];
int main()
{
int i,j,k,ans=0;
scanf("%s",s1);
scanf("%s",s2);
int l1=strlen(s1),l2=strlen(s2);
for(i=0;i<l1;i++)//在前序字符串中不在首位末位
for(j=1;j<l2;j++)//在后序字符串中不在首位末位
if(s1[i] == s2[j] && s1[i+1] == s2[j-1])//在两字符串中字符相同且顺序相反
ans++;//则叶节点+1
k=pow(2,ans);
printf("%d",k);
return 0;
}
题目:
3:刻录光盘 - 洛谷
普通的并查集是双向连接的,就好比你把1、2,1,3连接后find(2)和find(3)的结果是一样的,但在这题要求的应是单项连接,当然,也好解决,用数组将各个愿意分享光盘的人用下标表示、存储起来,用0,1表示愿意分享光盘与否,逐个判断,明确“ 若i愿意给j,j愿意给k,那么可视为i愿意给k ”这一点就可以做出来了。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int parent[100010],link[1000][1000];
int n,ans;
int main()
{
int i,j,k;
scanf("%d",&n);
for(i=0;i<=n;i++)
parent[i]=i;//初始化
for(i=1;i<=n;i++){
while(scanf("%d",&k) && k){//循环输入第i个人愿意分享的人的编号,若为0则停止本次输入
link[i][k]=1;
}
}
for(i=1;i<=n;i++){//从第一个人开始遍历
for(j=1;j<=n;j++){//判断第i个人是否愿意分享给第j个人
for(k=1;k<=n;k++){//同时判断第j个人是否愿意分享给第k个人
if(link[i][j] == 1 && link[j][k] == 1)//若i愿意给j,j愿意给k,那么可视为i愿意给k
link[i][k]=1;
}
}
}
for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
if(link[i][j] == 1)//若第i个人愿意给第j个人
parent[j]=parent[i];//则令第j个人的父节点为第i个人
}
}
for(i=1;i<=n;i++) {
if(parent[i] == i)
ans++; //若i的父节点就是自己本身的话,最后需要的光盘数+1
}
printf("%d",ans);
return 0;
}
题目:
4:二叉树的建立
由于这题是学校网站上的,不是我们学校的可能看不到,我就把题目复制一下:
题目描述
给定一个序列,按先序序列建立二叉树。输出建立后的二叉树的从上到下层次遍历序列。
输入格式
一个序列
输出格式
从上之下层次序列
样例输入content_copy
ABC##DE#G##F###
样例输出content_copy
ABCDEFG
很基础的一道题,但也刚好是我当前阶段需要的。
首先设立一个结果体,包含三个成员变量:输入的数据、左指针、右指针。
随后就是输入部分,这里我是选择像建立链表一样的方式开辟新的子树节点,使用递归的方式一个一个节点输入。
输出部分,我是用的类似于队列的操作,设两个整型数值分别指向头尾,利用指针逐个将节点压入结构体数组中,完成到这一步就可以输出了。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define f (sizeof(struct abc))
int parent[100010],n,m;
struct abc {
char data;
struct abc * left ;
struct abc * right;
};
struct abc * t[100010];
struct abc * creat(){
char ch;
struct abc * tree;
scanf("%c",&ch);
if(ch == '#')
tree=NULL;//若为 ‘# ’,则表示当前子树已遍历到叶节点,返回
else{
tree=(struct abc *) malloc (f);
if( tree == NULL )
exit(0);
tree->data = ch;
tree->left = creat();
tree->right = creat();
}
return tree;
}
void printf_tree(struct abc * x)
{
if(x != NULL)
t[0]=x;
int i,head=1,number=0;
while(1)
{
//t[ number ]的左右子结点依次存入t中
if( t[ number ] -> left != NULL )
t[ head ++ ] = t[ number ] -> left;
if( t[ number ] -> right != NULL )
t[ head ++ ] = t[ number ] -> right;
number ++;
if( number == head )
{
//当number == head为真,表示所有结点均已存入t数组
t[ number ] = NULL;
break;
}
}
number = 0;
while( t[ number ] )
{
//层序输出二叉树结点
printf("%c",t[ number ] -> data);
number ++;
}
}
int main(){
struct abc * k;
int i;
k=creat();
printf_tree(k);
}