深入虎穴
著名的王牌间谍 007 需要执行一次任务,获取敌方的机密情报。已知情报藏在一个地下迷宫里,迷宫只有一个入口,里面有很多条通路,每条路通向一扇门。每一扇门背后或者是一个房间,或者又有很多条路,同样是每条路通向一扇门…… 他的手里有一张表格,是其他间谍帮他收集到的情报,他们记下了每扇门的编号,以及这扇门背后的每一条通路所到达的门的编号。007 发现不存在两条路通向同一扇门。
内线告诉他,情报就藏在迷宫的最深处。但是这个迷宫太大了,他需要你的帮助 —— 请编程帮他找出距离入口最远的那扇门。
输入:首先在一行中给出正整数 N(<),是门的数量。最后 N 行,第 i 行(1)按以下格式描述编号为 i 的那扇门背后能通向的门:
K D[1] D[2] ... D[K]
其中K
是通道的数量,其后是每扇门的编号。
输出:距离入口最远的那扇门的编号。题目保证这样的结果是唯一的。
讲讲老师分析思路的过程吧(老师说这个比较重要),嗯嗯……
1. 首先拿到题目这就像是一棵树(不是二叉树哦),每个结点就是每一扇门(有自己的门牌号),每一扇门(结点)后面或者通向其他的门(有多个孩子)或者没有(没有孩子),数据之间的关系是一对多,所以可以确定用树的知识来解题。
2. 数据之间的关系要在选择的大框架下体现出来,这道题就是每一扇门都要能找到他所通向的门的信息(门牌号),就是每个结点都要可以找到他的孩子结点,这道题是不需要知道他的双亲结点的啦。
3. 确定下我们要用一课树来存储数据并且要可以找到他的孩子结点那我们具体要用一个怎样的存储结构呢(数组?链表?)由于我们输入的时候是对应从1开始输入,所以不妨用数组的结构。老师刚开始使用一个二维数组来存储输入的数据(行为对应顺序输入的门牌号(1-n),列为对应门牌号后面通往的门牌号),但是这个显然是个稀疏数组,空间的耗费太大所以不好。接着我们可以试试用一个一维数组,每个(门)元素存储该门的信息(后面有几扇门,编号分别是什么),那就设为结构体类型吧,(孩子)通向的门的个数直接用整型,通向的门(孩子)编号可以用个指针类型来指向(因为我们刚开始是不知道这里需要多少的空间,所以用静态的空间不太实际,用指针实现动态分配空间比较实用),那这个指针指向什么呢,考虑用链表(这个涉及链表的一系列操作,还要移动指针啊判断非空之类的),发现直接用一个数组就可以实现(可以知道数组的长度了)。
4. 所以现在我们可以确定存储的结构就为一个结构体类型(设为一个个结点)的一维数组,然后就开始写代码哈
首先是大概的框架(主函数)
方法:首先先输入总的门的数目,然后存入我们确定好的存储结构中(调用input函数),输出最后一个结点的数据(调用level函数)
int main() { int n,root; //输入n个编号 cin>>n; node *t; //定义一棵元素类型为结点的数组(树) t=new node[n+1]; //给树分配一个n+1的空间,因为空出来0号没用 root=input(t,n); //输入数据并返回根的下标 cout<<level(t,root); //输出层次遍历的结果(最后一个结点) return 0; }
细化input函数
方法:用一个for循环逐个(从1到n)输入门牌号,存下它后面所通往的门的个数,如果后面有通往的门的话就再次逐个读入(从0开始也可以)后面的门牌号。另外我们要定义一个数组来记录每个编号是否有通向它的门(双亲)。最后逐个判断布尔数组来返回根的门牌号(下标)。
int input(node *t,int n) { //输入从1到n门牌号的门的信息并返回根结点是哪个门牌(下标) bool *test; //定义一个布尔型的数组记录每个门牌是否有通向他的门(双亲) test=new bool[n+1]; //分配足够的空间 for(int i=1;i<=n;i++) { cin>>t[i].doors; //输入当前门后面的门的数目 if(t[i].doors!=0) { //如果后面还有门 t[i].p=new int[t[i].doors]; //为当前的门的指针分配一个存放(通向门的门牌号)的数组空间 for(int j=0;j<t[i].doors;j++) { //从0开始储存通向的门的门牌号 cin>>t[i].p[j]; test[ t[i].p[j] ]=true; //并记录下输出的(子)门牌是有通向的(双亲) } } else t[i].p=NULL; //若不加这句p指针一直存放一个野地址,有点危险 } for(int k=1;k<=n;k++) { //如果某个结点没有通向他的门(双亲)那就返回他的下标(门牌) if(!test[k]) return k; } }
细化level函数
方法:其实就是树结构的层次遍历(这里就省了吧,在那个找叶子结点的博客中写过了),刚好最后一个队头元素就是要找的最后一个结点,返回就可以了。
int level(node *t,int r) { //层次遍历树并输出最后一次遍历的结点下标(门牌) int a; queue<int> q; q.push(r); //先把根的编号入队 while(!q.empty()) { //如果队非空则出队,最后一个遍历(目标)的就是最后一个输出的 a=q.front(); //先存起出队的队头的门牌 ,恰好最后一个队头元素的门牌用a存了起来 q.pop(); //出队 if(t[a].doors!=0) { //如果队头的门后面还有门 for(int j=0;j<t[a].doors;j++) { //逐一把后面的门牌号入队 q.push(t[a].p[j]); } } } return a; }
最后就是不补上结点的定义
typedef struct { //定义一个结构体存储顺序输入的每个门的信息 int doors; //该门后面通向的门的数目 int *p; //一个指向后面的门的信息的指针 }node;
最后pou上完整代码
#include <iostream> #include <queue> using namespace std; typedef struct { //定义一个结构体存储顺序输入的每个门的信息 int doors; //该门后面通向的门的数目 int *p; //一个指向后面的门的信息的指针 }node; int input(node *t,int n) { //输入从1到n门牌号的门的信息并返回根结点是哪个门牌(下标) bool *test; //定义一个布尔型的数组记录每个门牌是否有通向他的门(双亲) test=new bool[n+1]; //分配足够的空间 for(int i=1;i<=n;i++) { cin>>t[i].doors; //输入当前门后面的门的数目 if(t[i].doors!=0) { //如果后面还有门 t[i].p=new int[t[i].doors]; //为当前的门的指针分配一个存放(通向门的门牌号)的数组空间 for(int j=0;j<t[i].doors;j++) { //从0开始储存通向的门的门牌号 cin>>t[i].p[j]; test[ t[i].p[j] ]=true; //并记录下输出的(子)门牌是有通向的(双亲) } } else t[i].p=NULL; //若不加这句p指针一直存放一个野地址,有点危险 } for(int k=1;k<=n;k++) { //如果某个结点没有通向他的门(双亲)那就返回他的下标(门牌) if(!test[k]) return k; } } int level(node *t,int r) { //层次遍历树并输出最后一次遍历的结点下标(门牌) int a; queue<int> q; q.push(r); //先把根的编号入队 while(!q.empty()) { //如果队非空则出队,最后一个遍历(目标)的就是最后一个输出的 a=q.front(); //先存起出队的队头的门牌 ,恰好最后一个队头元素的门牌用a存了起来 q.pop(); //出队 if(t[a].doors!=0) { //如果队头的门后面还有门 for(int j=0;j<t[a].doors;j++) { //逐一把后面的门牌号入队 q.push(t[a].p[j]); } } } return a; } int main() { int n,root; //输入n个编号 cin>>n; node *t; //定义一棵元素类型为结点的数组(树) t=new node[n+1]; //给树分配一个n+1的空间,因为空出来0号没用 root=input(t,n); //输入数据并返回根的下标 cout<<level(t,root); //输出层次遍历的结果(最后一个结点) return 0; }