这道题,一看之下就是一个多源有权图求最小距离的题目。这么说吧,它这里要求对于任意一个起始点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