著名的王牌间谍 007 需要执行一次任务,获取敌方的机密情报。已知情报藏在一个地下迷宫里,迷宫只有一个入口,里面有很多条通路,每条路通向一扇门。每一扇门背后或者是一个房间,或者又有很多条路,同样是每条路通向一扇门…… 他的手里有一张表格,是其他间谍帮他收集到的情报,他们记下了每扇门的编号,以及这扇门背后的每一条通路所到达的门的编号。007 发现不存在两条路通向同一扇门。
内线告诉他,情报就藏在迷宫的最深处。但是这个迷宫太大了,他需要你的帮助 —— 请编程帮他找出距离入口最远的那扇门。
输入格式:
输入首先在一行中给出正整数 N(<),是门的数量。最后 N 行,第 i 行(1)按以下格式描述编号为 i 的那扇门背后能通向的门:
K D[1] D[2] ... D[K]
其中 K
是通道的数量,其后是每扇门的编号。
输出格式:
在一行中输出距离入口最远的那扇门的编号。题目保证这样的结果是唯一的。
输入样例:
13
3 2 3 4
2 5 6
1 7
1 8
1 9
0
2 11 10
1 13
0
0
1 12
0
0
输出样例:
12
看到这道题想到的数据结构是树,但是,怎么去存储这棵树呢?第一个想到的就是二维数组,将二维数组初始化为0,根据门的编号,在对应的下标处把数值改为1。但是这存在一个问题,就是当数据量太大的时候会造成大量的空间被浪费。由二维数组中的大多数是0,少部分是1,想到了稀疏矩阵,这道题
可以通过稀疏矩阵十字链表来实现,打包一个一维数组,一栏为通道数,一栏为指针。判断通道数是否为0,如果不是,则遍历链表。再将此方法进行优化,则优化成指针后带数组
typedef struct { int doors; //通道的数量 int *p; //指向通道的编号序列的指针 }node;
先从主函数开始,首先定义一个结构体数组,用于存储从题目中得出的树,根据题目,首先输入的是门的数量。由于数据量比较大,因此需要动态申请结构体数组的空间。构造一个调用函数input(),用于输入数据。由于这道题需要用到层次遍历,因此在input()这个函数中需要返回根结点的值。
int main() { node *a; //用于存储整棵树 int root; int n; //代表门的数量 cin>>n; a=new node[n+1]; //需要多少开多少空间 root=input(a,n); cout<<level(a,root)<<endl; return 0; }
在写input()函数时,需要定义一个布尔类型的数组,初始化为false,在通道数不为零的情况下,输入会经过的门的编号,这些门的编号一定不会是根结点,最后找出根结点,返回根结点的编号。
int input(node *a,int n) {//读入n扇门的信息给a数组,同时返回根所在的门牌号(下标) int i,j; bool *vi; vi=new bool[n+1]; //后面必须要初始化,因为没有输入 for(i=1;i<=n;i++) { vi[i]=false; //初始化vi数组的全部元素为false }//for for(i=1;i<=n;i++) { cin>>a[i].doors; if(a[i].doors!=0) //当通道数不为零时 { a[i].p=new int[a[i].doors]; //为a[i].p申请空间 for(j=1;j<=a[i].doors;j++) {//输入后面的通道编号 cin>>a[i].p[j-1]; vi[a[i].p[j-1]]=true; //会经过的门牌号一定不是根结点 }//for }//if else {//doors为零的情况 a[i].p=NULL; } } //for for(i=1;i<=n;i++) {//找出根结点,并返回根结点的下标 if(!vi[i]) return i; } }//input
最后对整棵树进行层次遍历,返回遍历最后的一个结点的编号。
int level(node *a,int r) {//从a[r]开始对a数组进行层次遍历,并返回遍历的最后一个结点的编号 queue<int> q; int t; q.push(r); while(!q.empty()) { t=q.front(); q.pop(); if(a[t].doors!=0) {//t号门后面还有通道,后面的通道入队 for(int i=0;i<a[t].doors;i++) { q.push(a[t].p[i]); }//for }//if }//while return t; }//level
#include <iostream> #include <queue> using namespace std; typedef struct { int doors; //通道的数量 int *p; //指向通道的编号序列的指针 }node; int input(node *a,int n); int level(node *a,int r); int main() { node *a; //用于存储整棵树 int root; int n; //代表门的数量 cin>>n; a=new node[n+1]; //需要多少开多少空间 root=input(a,n); cout<<level(a,root)<<endl; return 0; } int input(node *a,int n) {//读入n扇门的信息给a数组,同时返回根所在的门牌号(下标) int i,j; bool *vi; vi=new bool[n+1]; //后面必须要初始化,因为没有输入 for(i=1;i<=n;i++) { vi[i]=false; //初始化vi数组的全部元素为false }//for for(i=1;i<=n;i++) { cin>>a[i].doors; if(a[i].doors!=0) //当通道数不为零时 { a[i].p=new int[a[i].doors]; //为a[i].p申请空间 for(j=1;j<=a[i].doors;j++) {//输入后面的通道编号 cin>>a[i].p[j-1]; vi[a[i].p[j-1]]=true; //会经过的门牌号一定不是根结点 }//for }//if else {//doors为零的情况 a[i].p=NULL; } } //for for(i=1;i<=n;i++) {//找出根结点,并返回根结点的下标 if(!vi[i]) return i; } }//input int level(node *a,int r) {//从a[r]开始对a数组进行层次遍历,并返回遍历的最后一个结点的编号 queue<int> q; int t; q.push(r); while(!q.empty()) { t=q.front(); q.pop(); if(a[t].doors!=0) {//t号门后面还有通道,后面的通道入队 for(int i=0;i<a[t].doors;i++) { q.push(a[t].p[i]); }//for }//if }//while return t; }//level
这道题学到了三种表示一棵树的方法,二维数组是最直接简单的,但是要考虑到数据量的问题,而且在数据量大的情况下采用二维数组会导致大量空间被浪费。由于二维数组中都是0和1,且0比1多,因此采用稀疏矩阵的方法。这道题不需要考虑到双亲,因此只需要开辟空间存放指向孩子编号的指针。
这道题看上去比上一次的AI代码要难很多,但实际上把结构弄清楚,这道题需要解决的问题比上一次的AI代码要少,也比上一次的简单。
本章作业完成的作业中,leave list中,一直输出的结果不对,和别人对比一下代码也没有什么多大的区别,后来找了很久才发现是把y输入成了x。这种错误真的要少犯!!而树的同构,在处理左右子树还不是很理解,要好好地去理解一下。
接下来的目标:小测考好点;在学习新的一章时也要考虑是否需要使用前面学习的结构。不要学了一章忘一章。