//使用非递归栈模拟深度遍历,否则此题用递归深搜会栈溢出
//本题的题意其实就是找出一棵树中所有最长路径的端点
//只需要遍历两次即可找到长度最长的路径的所有端点
//第一次遍历从任意一点开始,记录以这个点为起点的最长路径的尾端点(可能不止一个)
//第二次遍历从第一次的尾端点中任选一个为起点,再次记录此次遍历过程中最长路径的尾端点
//两次遍历的尾端点放在一块就是整个树中,最长路径的所有端点了
//(注意,第一次遍历和第二次遍历中记录的尾端点可能有重合,当第一次遍历起点恰好是最长路径中点时,便会重合)
//ac
#define MAXN 10000
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;
struct Node{
int neighbors[MAXN]; //结点的邻居
int visIdx; //结点已访问到的邻居的下标
int size; //结点的邻居数
};
struct Stack{ //栈
int data[MAXN]; //存放nodes数组下标
int top; //指向栈顶元素的下一个,初始为0
};
struct Gather{ //记录尾端点的集合
int data[MAXN]; //存放nodes数组下标
int size; //尾端点数,初始为0
//路径长度,初始为0 当遍历到一路径的长度比dph大时,更新dph,清空data,并加入尾端点
//当遍历到一路径的长度等于dph时,直接把尾端点加入到data中
int dph;
};
Node* nodes[MAXN]; //所有结点
bool visited[MAXN]; //访问标志数组
Stack s;
Gather gat1; //存放第一次遍历的尾端点
Gather gat2; //存放第二次遍历的尾端点
void traversal(int st,Gather* gat){ //使用栈的深度遍历,从st开始的最长路径的尾端点会被记录在gat中
/*
这种遍历都是一个套路!!
先入栈一个起点,然后当栈不空时一个大循环
循环里面
第一:访问栈顶元素(visited=true),检查栈顶元素是否命中,命中了就怎么怎么样啊巴拉巴拉,
这个根据自己想做什么自己写
第二:寻找栈顶元素的下一个访问元素,就是找栈顶元素的没访问过的下一个邻居,用node->visIdx记录访问到哪一个邻居了
用visited检擦这个一邻居有没有访问过
第三:如果我的邻居全部都访问完了(node->visIdx==node->size),那就弹出栈顶元素(top--);否则入栈我找到的这个邻居
*/
s.top=0;
s.data[s.top++]=st; //起点入栈
while(s.top!=0){ //当栈不空时一直循环
//此处为查看遍历过程
// for(int i=0;i<s.top;i++){
// printf("%d ",s.data[i]);
// }
// printf("\n");
int topEmt=s.data[s.top-1]; //topEmt表示栈顶元素在nodes数组中的下标
//以下检查栈顶是否命中
visited[topEmt]=true;
if(s.top > gat->dph){ //s.top恰好可以代表当前路径的深度,如果当前路径更深,那就更新
gat->size=0;
gat->dph=s.top;
gat->data[gat->size++]=topEmt;
}else if(s.top==gat->dph){ //如果当前路径一样深,那检查有没有记录过这个尾端点,没有的话就记录它
bool tmp=false;
for(int i=0;i<gat->size;i++){
if(gat->data[i]==topEmt){
tmp=true;
break;
}
}
if(!tmp)
gat->data[gat->size++]=topEmt;
}
//寻找栈顶的下一个结点
for(;nodes[topEmt]->visIdx < nodes[topEmt]->size;nodes[topEmt]->visIdx++){
if(!visited[nodes[topEmt]->neighbors[nodes[topEmt]->visIdx]]){
break;
}
}
//如果找到了,入栈,如果没找到,弹出栈顶元素
if(nodes[topEmt]->visIdx==nodes[topEmt]->size){
s.top--;
}else{
//入栈
s.data[s.top++]=nodes[topEmt]->neighbors[nodes[topEmt]->visIdx];
nodes[topEmt]->visIdx++;
}
}
}
bool cmp(int a,int b){
return a<b;
}
int main(){
int N;
scanf("%d",&N);
//初始化nodes,nodes[0]是不使用的
for(int i=0;i<=N;i++){
nodes[i]=(Node*)malloc(sizeof(Node));
nodes[i]->size=0;
nodes[i]->visIdx=0;
}
//添加邻居
for(int i=1;i<=N-1;i++){
int ti,tj;
scanf("%d %d",&ti,&tj);
nodes[ti]->neighbors[nodes[ti]->size++]=tj;
nodes[tj]->neighbors[nodes[tj]->size++]=ti;
}
gat1.size=0;
gat1.dph=0;
gat2.size=0;
gat2.dph=0;
fill(visited,visited+MAXN,false);
int cndNum=0; //连通分支数
for(int i=1;i<=N;i++){
//检查所有结点,如果没访问过就从它为起点进行遍历
//如果只有一个连通分支,那第一次遍历就可以使所有结点都visit过
//如果有多个连通分支,那就从以下一个没有访问过的结点为起点再遍历
//有几个连通分支,就需要遍历几次
if(!visited[i]){
cndNum++;
traversal(i,&gat1);
}
}
if(cndNum>1){
printf("Error: %d components\n",cndNum);
return 0;
}
//只有一个连通分支的情况下,直接进行第二次遍历
//这里要重新初始化visited,以及所有结点的visIdx
fill(visited,visited+MAXN,false);
for(int j=1;j<=N;j++){
nodes[j]->visIdx=0;
}
//尾端点存到gat2中
traversal(gat1.data[0],&gat2);
//合并gat2和gat1到gat1中
for(int i=0;i<gat2.size;i++){
//合并前要先检查gat1中是否已经有gat2中的元素了
int j=0;
for(;j<gat1.size;j++){
if(gat1.data[j]==gat2.data[i]){
break;
}
}
if(j==gat1.size){
gat1.data[gat1.size++]=gat2.data[i];
}
}
//按序号从小到大排序
sort(gat1.data,gat1.data+gat1.size,cmp);
//输出结果
for(int i=0;i<gat1.size;i++){
printf("%d\n",gat1.data[i]);
}
}
感谢各位耐着性子看我的代码,不足之处欢迎指点。
努力努力再努力!
博客通过非递归栈模拟深度遍历解决PTA问题1021,寻找树中最长路径的端点。两次遍历确定所有最长路径,第一次记录起点最长路径尾端点,第二次从这些端点开始再次遍历。最终,两次遍历的尾端点组合即为最长路径的所有端点。

318

被折叠的 条评论
为什么被折叠?



