07-图4 哈利·波特的考试

这道题,一看之下就是一个多源有权图求最小距离的题目。这么说吧,它这里要求对于任意一个起始点Ai,到距离它最远的点的距离Di;,然后在这K个Di中,找到一个最小的。

(起点是Ai的最短路径的定义是这样的:求Ai到其他所有节点的最短路径→就是Ai到最远节点的最短路径)。


认清了题目的本质之后,可以选择调用N次Dijkstra算法(K是Vertex数目),或者直接使用Floyd算法。因为我一开始没有认清题目本质,所用选择的是先对一个节点做出最短路径,然后对每个节点都用一遍就好(就是用N遍Dijkstra算法)。


这里主要有一个问题,对Dijkstra算法的这一句话理解错误:(下图中圈起来的部分)



最开始,我对Dijkstra算法的理解是,在BFS的基础上,每次从队列中选出权值最小的弹出。相当于对于每一层,都选择权值最小的。但这有一个问题:

在图中这种情况下,若采用我的算法,以1为源点,访问顺序是这样的,1→2→3→4→5.可以看到,虽然4到1的距离大于5到1的距离,但是仍然是4先被访问(正确的顺序是1→2→3→5→4)。这里就显示出,Dijkstra并不是BFS算法的变种,Dijkstra并不需要队列;每次以 距上一层的距离作为判定最小值的依据是不靠谱的。所以应该以table中dis作为判定依据,找到还没有访问过(T[i].know==0),并且T[i].dis的值最小的i进行访问。对于如何确保dis就是这个点到源点的距离。由于每次找到了最小值V之后,它的所有邻接点Wi的距离都被更新,更新为min{Wi.dis,V.dis+Weight(Wi,V)}(即2者中的最小值)。可以看到,从源点开始,若是每次都是执行第二个更新,Wi的点就是从源点到它的最小距离。


附程序如下:

#include <stdio.h>
#include <stdlib.h>

typedef struct _head{  //邻接表储存法,这里储存头结点 
	struct _node *firstdata;
}Head;

typedef struct _node{ //储存每个vertex 
	int data;
	int weight;
	struct _node *next;
}Node;

typedef struct _list{  //遍历的时候用于储存元素的 
	struct _node *vertex;
	struct _list *next;
}List;

typedef struct _table{
	int know;
	int dv;
	int pre;
}Table;

int Vertex,Edge;  //最大节点数以及最大边数 
Head *ArrayList; // 邻接表 

Node* creat_node();  //创造每个vertex 
void  add_node(Node *head,int data,int weight);  //往邻接表里面加节点 
void  print_node(Node *head); //打印邻接表的其中一行 
void  add_list(List *beginer,Node *node); // 遍历的时候储存 
int   delemin_sheet(Table *sheet);  //取出权重最小的 
void  print_list(List *beginer);  //打印储存。
void  Dijkstra(Table *sheet,int first);
int   max_sheet(Table *sheet);
int   con_sheet(Table *sheet);

int main(){
	scanf("%d %d",&Vertex,&Edge);
	ArrayList=(Head*)malloc((Vertex+1)*sizeof(Head));
	int k;
	int i=0,j=0,w=0;
	ArrayList[0].firstdata=NULL;
	for(k=1;k<Vertex+1;k++){
		ArrayList[k].firstdata=creat_node();
		ArrayList[k].firstdata->data=k;
	}
	
	for(k=0;k<Edge;k++){
		scanf("%d %d %d",&i,&j,&w);
		add_node(ArrayList[i].firstdata,j,w);
		add_node(ArrayList[j].firstdata,i,w);
	}

//	int kk;
//	int mm=Vertex*Vertex;
//	for(k=1;k<=Vertex;k++){   //首尾相接出现换的时候貌似真的有问题 
		scanf("%d %d %d",&i,&j,&w);
//		for(kk=k+1;kk<=Vertex;kk++){
//			add_node(ArrayList[k].firstdata,kk,mm);
//			add_node(ArrayList[kk].firstdata,k,mm);
//			mm--;
//		}
//
//	}
//	for(k=1;k<Vertex+1;k++){
//		print_node(ArrayList[k].firstdata);
//		printf("\n");
//	}
//	printf("----------------------------------\n");
	int numberofvertex=0;
	int numberofdistance=10000;
	int count=0;
	Table *sheet=(Table*)malloc((Vertex+1)*sizeof(Table));
	for(count=1;count<Vertex+1;count++){
		for(k=1;k<Vertex+1;k++){
			sheet[k].know=0;
			sheet[k].dv=20000;
			sheet[k].pre=-1;
		}
		
		Dijkstra(sheet,count);
			if(con_sheet(sheet)){
				if(max_sheet(sheet)<numberofdistance){
					numberofdistance=max_sheet(sheet);
					numberofvertex=count;
//					printf("%d\n",numberofvertex);
				}
			}
//		for(k=1;k<Vertex+1;k++){
//			printf("%d know=%d dv=%d pre=%d\n",k,sheet[k].know,sheet[k].dv,sheet[k].pre);
//		}
//		printf("\n");
	}
	
	if(numberofvertex!=0){
		printf("%d %d",numberofvertex,numberofdistance);
	}
	else
	{
		printf("0");
	}
	
	return 0;
}

void  Dijkstra(Table *sheet,int first){  //已经能够完成搜寻最小值的工作了。 
	sheet[first].dv=0;
	sheet[first].pre=0; 
	Node *subrecord;
	int result;
	while(1){
		result=delemin_sheet(sheet);   //找到表中第一个权重最小的节点 (这个地方有误,不是找到表中最小的节点,而是找到未入table中到源点距离最小的点。)
//		printf("min=%d\n",result);
		if(result==-1){  //如果表已经空了 
			break;
		}
		sheet[result].know=1;   //  表示节点已经被访问了 
		subrecord=ArrayList[result].firstdata;  //求得弹出的节点所在的行的头结点。             
		while(subrecord){   //这一行还没遍历完的话 
			if(sheet[subrecord->data].know!=1){   //如果这个节点还没有被访问过 
				if(sheet[result].dv + subrecord->weight < sheet[subrecord->data].dv){   //如果这个节点所在的值大于之前节点和他们两者之间的权重 
					sheet[subrecord->data].dv=sheet[result].dv+subrecord->weight;   //把这个节点的权重更新 
					sheet[subrecord->data].pre=result;                              //下一个节点的父节点等于上一个节点 
				}
			}
			subrecord=subrecord->next;	
		}
	}
}

void  add_node(Node *head,int data,int weight){
	Node *p=head;
	while(p->next){
		p=p->next;
	}
	Node *q=creat_node();
	p->next=q;
	q->data=data;
	q->weight=weight;
}

Node* creat_node(){
	Node *p=(Node*)malloc(sizeof(Node));
	p->data=0;
	p->next=NULL;
	p->weight=0;
	return p;
}

void  print_node(Node *head){
	Node *p=head;
	while(p){
		printf("data=%d weight=%d ",p->data,p->weight);
		p=p->next;
	}
}

void  add_list(List *beginer,Node *node){
	List *p=beginer;
	List *q=(List*)malloc(sizeof(List));
	while(p->next){
		p=p->next;
	}
	q->next=NULL;
	q->vertex=node;
	p->next=q;	
}

int delemin_sheet(Table *sheet){
	int i=0;
	int length=0;
	int location=-1;
	int total=20000;
	for(i=1;i<Vertex+1;i++){
		if(sheet[i].know==0){
			length=sheet[i].dv;
//			printf("%d length=%d\n",i,length);
			if(length<total){
				location=i;
				total=length;
			}
		}
	}
	return location;
}

void  print_list(List *beginer){
	List *p=beginer->next;
	while(p){
		printf("%d ",p->vertex->data);
		p=p->next;
	}
	printf("\n");
}

int   max_sheet(Table *sheet){
	int i;
	int result=0;
	for(i=1;i<Vertex+1;i++){
		if(result < sheet[i].dv)
		{
			result=sheet[i].dv;
		}
	}
	return result;
}

int   con_sheet(Table *sheet){
	int i;
	int flag=1;
	for(i=1;i<Vertex+1;i++){
		if(sheet[i].dv==20000){
			flag=0;
			break;
		}
	}
//	printf("flag=%d\n",flag);
	return flag;
}


07-图4 哈利·波特的考试   (25分)

哈利·波特要考试了,他需要你的帮助。这门课学的是用魔咒将一种动物变成另一种动物的本事。例如将猫变成老鼠的魔咒是haha,将老鼠变成鱼的魔咒是hehe等等。反方向变化的魔咒就是简单地将原来的魔咒倒过来念,例如ahah可以将老鼠变成猫。另外,如果想把猫变成鱼,可以通过念一个直接魔咒lalala,也可以将猫变老鼠、老鼠变鱼的魔咒连起来念:hahahehe。

现在哈利·波特的手里有一本教材,里面列出了所有的变形魔咒和能变的动物。老师允许他自己带一只动物去考场,要考察他把这只动物变成任意一只指定动物的本事。于是他来问你:带什么动物去可以让最难变的那种动物(即该动物变为哈利·波特自己带去的动物所需要的魔咒最长)需要的魔咒最短?例如:如果只有猫、鼠、鱼,则显然哈利·波特应该带鼠去,因为鼠变成另外两种动物都只需要念4个字符;而如果带猫去,则至少需要念6个字符才能把猫变成鱼;同理,带鱼去也不是最好的选择。

输入格式:

输入说明:输入第1行给出两个正整数N (100)和M,其中N是考试涉及的动物总数,M是用于直接变形的魔咒条数。为简单起见,我们将动物按1~N编号。随后M行,每行给出了3个正整数,分别是两种动物的编号、以及它们之间变形需要的魔咒的长度(100),数字之间用空格分隔。

输出格式:

输出哈利·波特应该带去考场的动物的编号、以及最长的变形魔咒的长度,中间以空格分隔。如果只带1只动物是不可能完成所有变形要求的,则输出0。如果有若干只动物都可以备选,则输出编号最小的那只。

输入样例:

6 11
3 4 70
1 2 1
5 4 50
2 6 50
5 6 60
1 3 70
4 6 60
3 6 80
5 1 100
2 4 60
5 2 80

输出样例:

4 70
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值