PAT 1131 Subway Map

1131 Subway Map

这题输入地铁路线图,图中没有自环,但是有环。
地铁线有交叉点,即中转站。 但是一个中转站最多只有5条线交叉
重要的信息  Each station interval belongs to a unique subway line, 也就是相邻站点的这一段线路只可能属于一条线。


输出:
最优路线站数 (站数最少,尽量换乘最少)
Take Line#3 from 1306 to 2302.
只需要起点终点中转点的 id 

这里需要找最短路,图的规模不大,100*100最多。用BFS,DFS,Dijkstra 都行。
还有一个问题是如何判断搭乘哪条线?单纯依靠站点本身是否为换乘站是不行的。但是可以通过当前站和下一站来判断目前在哪条线。


BFS的话,在搜索过程中,节点被标记为三种状态, visited, visiting, unvisit。
如果直接把访问过的节点标记为 visited的话,会阻止同层搜索这个节点。导致同样站数换乘更少的解法被略去了。
我采取更加灵活的方式,通过跟踪 level, level_last 两个变量判断现在搜索的第几层。访问过的节点保存的是在第几层搜索过。
当前搜索的 level > 节点level,说明已访问; 相等则正在访问,小于则未访问。 所以初始化的时候要把节点level设置无穷大。

在搜索到一个可行的走法的时候,为了获得路径,需要从终点向前沿着父节点找到起点。 因此在搜索过程中,需要当前节点需要保存指向父节点的下标。

#include <cstdio> 
#include <vector> 
#include <map>
#include <cstring>
#define infinite 10000000

using namespace std;

vector<int> graph[10000];
map< int, int > stop_line;

int   visited[10000];    // 记录节点被BFS的哪个层次访问过 
vector<int> ans;

struct node{   //bfs 队列中的节点,previous记录前节点的下标 
	int stop_id, previous; 
};  
// 这里获得的 path 是倒着的,终点在0, 起点在最后 
void get_path_from( const vector<node>& q, int end, vector<int>& path )
{
	while( end >= 0 )
	{
		path.push_back( q[end].stop_id );
		end = q[end].previous;
	}
}

int find_next_transfer( const vector<int> &a, int pos )
{
	int line_1 = stop_line[ (a[pos] << 16) + a[pos-1] ];
	while ( --pos > 0 )
	{
		int line_2 = stop_line[ (a[pos] << 16) + a[pos-1] ];
		if( line_1 != line_2 ) return pos;
		line_1 = line_2;
	}
	return pos; //没有transfer 
}
int count_transfer( vector<int>& path )
{
	int count = 0;
	for( int i=path.size()-1; i>0; i=find_next_transfer(path, i) )
		++count;
	return count;
}

void BFS( int st, int ed )
{
	memset( visited, 0x7f, sizeof(visited) );
	ans.clear();
	
	vector< node > q;
	int front=0; //队列头 
	int level_last=0, level=1, best_level=infinite, transfer_count=infinite; //层次遍历中某一层的最后一个 
	q.push_back( {st, -1} ); //起点的前节点标记-1 
	visited[ st ] = level;  
	
	while( front < q.size() )
	{
//		printf("front=%d  q[front]=%d level=%d last=%d\n", front, q[front].stop_id, level, level_last );

		if ( q[front].stop_id == ed ) //找到可行解, 和之前的比较一下优劣, 可能先找到换乘数较多的路线 
		{
			vector<int> possible_solution;
			get_path_from( q, front, possible_solution );
			auto tmp = count_transfer( possible_solution ) ;
			if ( tmp < transfer_count )
			{
				best_level=level;
				transfer_count=tmp;
				swap( possible_solution, ans );
			}
		}
		else //还没到终点,继续搜索 
		for( int i=0; i<graph[ q[front].stop_id ].size(); i++ )
		{
			auto& cur_stop = graph[ q[front].stop_id ];
			if ( visited[ cur_stop[i] ] < level ) continue; //当前节点被前一层的访问过,那就没必要继续搜。 如果当前节点仅被同层的搜过,还是可以搜的 
			
			visited[ cur_stop[i] ] = level;
			q.push_back( {cur_stop[i], front} );
		}
		
		if ( level_last == front ) //上一层的最后一个节点也被搜了,说明接下来就是新一层的,队列中最后那个就是新层的最后
		{
			level_last = q.size()-1;
			level++;
			if ( level > best_level ) break; //不可能还有更优解了,直接走人 
		}
		++front;
	}
}


void print( const vector<int> &a )
{
	printf("%zd\n", a.size()-1);
	// Take Line#X1 from S1 to S2.
	int pos = a.size()-1, next_pos;
	for(	next_pos = find_next_transfer( a, pos ); 
			pos>0; 
			next_pos = find_next_transfer( a, next_pos ) )
	{
		printf( "Take Line#%d from %04d to %04d.\n", stop_line[ (a[pos]<<16) + a[pos-1]] , a[pos], a[next_pos] );
		pos = next_pos;
	}
}

int main()
{
	int N;
	scanf("%d", &N);
	for ( int i=0, m, st; i<N; i++ )
	{
		scanf("%d %d", &m, &st); // 祈祷不会有 M = 0 的情况 
		
		for ( int j=1, next; j<m; j++ )
		{
			scanf("%d", &next);
			graph[ st ].push_back( next );
			graph[ next ].push_back( st );
			
			stop_line.insert( { (st<<16) + next, i+1 } ); //记录 两个相邻站点属于哪条线 
			stop_line.insert( { (next<<16) + st, i+1 } );
			st = next; 
		}
	}
	int k, st, ed;
	scanf("%d", &k);
	while( k-- )
	{
		scanf("%d %d", &st, &ed);
		BFS( st, ed );
		
		print( ans );
	}
}


/* test case
4
7 1001 3212 1003 1204 1005 1306 7797
9 9988 2333 1204 2006 2005 2004 2003 2302 2001
13 3011 3812 3013 3001 1306 3003 2333 3066 3212 3008 2302 3010 3011
4 6666 8432 4011 1306
3
6666 3212
6666 2001
9988 2001
----------------------------
2
4 1 2 3 4
6 5 2 6 7 3 8
4
5 8

----------------------

6 
3 1 2 3
3 1 4 5
3 1 6 7
3 1 8 9
4 2 4 6 8
3 2 5 7 
5
 
 
*/

网上看到了别人不同的思路:

BFS之中用一个优先队列,队列中的节点会保存到达该节点时的距离和换线数目。因此搜到的第一个解就是最优解了,比我的做法更好,无需搞这么多复杂的标记、level。

 

还有下面这种用SPFA的最短路算法,类似 BFS + Dijkstra 的混合体,有类似BFS的两层循环和队列,有Dijkstra的松弛操作。

#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
vector<int>e[10005], line[10005],s2l[10005];
int L, Q,n,u,v,l,start,ed,dis[10005],preS[10005],preL[10005],cnt[10005],tmp;
void add(int a, int b, int c)
{
	e[a].push_back(b);
	e[b].push_back(a);
	line[a].push_back(c);
	line[b].push_back(c);
}
void spfa(int s, int d)
{
	memset(dis, 0x3f, sizeof(dis));
	memset(preS, -1, sizeof(preS));
	memset(preL, -1, sizeof(preL));
	memset(cnt, 0x3f, sizeof(cnt));
	dis[s] = 0;
	cnt[s] = 0;
	queue<int>q;
	q.push(s);
	while (!q.empty())
	{
		u = q.front();
		q.pop();
		for (int i = 0; i < e[u].size(); i++)
		{
			v = e[u][i];
			l = line[u][i];
			if (dis[v] > dis[u] + 1)
			{
				dis[v] = dis[u] + 1;
				preS[v] = u;
				preL[v] = l;
				if (preL[v] != preL[u])
					cnt[v] = cnt[u] + 1;
				else
					cnt[v] = cnt[u];
				q.push(v);
			}
			else if (dis[v] == dis[u] + 1)
			{
				if (preL[u] != l)
				{
					tmp = cnt[u] + 1;
				}
				else
				{
					tmp = cnt[u];
				}
				if (tmp <=cnt[v])
				{
					dis[v] = dis[u] + 1;
					preS[v] = u;
					preL[v] = l;
					if (preL[v] != preL[u])
						cnt[v] = cnt[u] + 1;
					else
						cnt[v] = cnt[u];
					q.push(v);
				}
			}
		}
	}
	printf("%d\n", dis[d]);
	u = d;
	int curL=preL[d],tmpS=d;
	for (int i = d; i != s; i = preS[i])
	{
		l = preL[i];
		if (l != curL)
		{
			printf("Take Line#%d from %04d to %04d.\n", curL, tmpS, i);
			curL = preL[i];
			tmpS = i;
		}
	}
	printf("Take Line#%d from %04d to %04d.\n", curL, tmpS, s);
}
int main()
{
	scanf("%d", &L); //while (L ==9);//
	for (int i = 1; i <= L; i++)
	{
		scanf("%d", &n);
		scanf("%d", &u);
		//start = u;
		for (int j = 1; j < n; j++)
		{
			scanf("%d", &v);
			add(u, v, i);
			u = v;
		}
	}//printf("Q");
	scanf("%d", &Q); //while (Q < 5);
	for (int i = 0; i < Q; i++)
	{
		scanf("%d%d", &start, &ed);
		if (start != ed)
			spfa(ed, start);
		else
			printf("0\n");
	}//while (1);
}

 


BFS框架:

初始化数据{ 起点入队, 标记, 记录层次,第一层的last元素下标  }

遍历队列 {
      找到解 / 头节点拓展入队,做标记,记录每个节点被访问层次
      更新level,level_last。
      下一个队头
}

BFS的变体:
1、需要记录路径, 那么队列中保存父节点的下标。
2、不止要找一条最短路,而是找出全部最短路。 
3、双向bfs。 用两个队列,分别从起点 终点开始搜索。另外用一个数组记录到达节点步数,是哪个方向的遍历到达的。
     当两队列有交集,就是搜索到了。

4、迭代加深的搜索,避免搜索的分支太多,占空间太多。

5、A* 这种启发式搜索,本质上统一了dfs和bfs,bfs和dfs只不过是估价函数的不同而已。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值