📄 ladders.w
字号:
del_min=del_128; enqueue=enq_128; requeue=req_128;}@ The frequency has been computed with the default weights explained in thedocumentation of |words|; it is usually less than $2^{16}$.A word whose frequency is 0 costs~16; a word whose frequency is 1 costs~15;a word whose frequency is 2 or 3 costs~14; and the costs keep decreasingby~1 as the frequency doubles, until we reach a cost of~0.@<Sub...@>=long freq_cost(v) Vertex *v;{@+register long acc=v->weight; /* the frequency, to be shifted right */ register long k=16; while (acc) k--, acc>>=1; return (k<0? 0: k);}@* Minimal ladders. The guts of this program is a routine to compute shortestpaths between two given words, |start| and |goal|.The |dijkstra| procedure does this, in any graph with nonnegative arc lengths.The only complication we need to deal with here is that |start| and |goal|might not themselves be present in the graph. In that case we want to insertthem, albeit temporarily.The conventions of {\sc GB\_\,GRAPH} allow us to do the desired augmentationby creating a new graph |gg| whose vertices are borrowed from~|g|. Thegraph~|g| has space for two more vertices (actually for four), and anynew memory blocks allocated for the additional arcs present in~|gg| willbe freed later by the operation |gb_recycle(gg)| without confusion.@<Glob...@>=Graph *gg; /* clone of |g| with possible additional words */char start[6], goal[6]; /* \.{words} dear to the user's \.{heart}, plus |'\0'| */Vertex *uu, *vv; /* start and goal vertices in |gg| */@ @<Find a minimal ladder from |start| to |goal|...@>=@<Build the amplified graph |gg|@>;@<Let |dijkstra| do the hard work@>;@<Print the answer@>;@<Remove all traces of |gg|@>;@ @<Build the amplified graph |gg|@>=gg=gb_new_graph(0L);quit_if(gg==NULL,no_room+5); /* out of memory */gg->vertices = g->vertices;gg->n = g->n;@<Put the |start| word into |gg|, and let |uu| point to it@>;@<Put the |goal| word into |gg|, and let |vv| point to it@>;if (gg->n==g->n+2) @<Check if |start| is adjacent to |goal|@>;quit_if(gb_trouble_code,no_room+6); /* out of memory */@ The |find_word| procedure returns |NULL| if it can't find the given wordin the graph just constructed by |words|. In that case it has applied itssecond argument to every adjacent word. Hence the program logic heredoes everything needed to add a new vertex to~|gg| when necessary.@<Put the |start| word into |gg|, and let |uu| point to it@>=(gg->vertices+gg->n)->name = start; /* a tentative new vertex */uu=find_word(start,plant_new_edge);if (!uu) uu = gg->vertices + gg->n++; /* recognize the new vertex and refer to it */@ @<Put the |goal|...@>=if (strncmp(start,goal,5)==0) vv=uu; /* avoid inserting a word twice */else { (gg->vertices+gg->n)->name = goal; /* a tentative new vertex */ vv=find_word(goal,plant_new_edge); if (!vv) vv = gg->vertices + gg->n++; /* recognize the new vertex and refer to it */}@ The |alph_dist| subroutine calculates the alphabetic distance betweenarbitrary five-letter words, whether they are adjacent or not.@<Sub...@>=long alph_dist(p,q) register char *p, *q;{ return a_dist(0)+a_dist(1)+a_dist(2)+a_dist(3)+a_dist(4);}@ @<Sub...@>=void plant_new_edge(v) Vertex *v;{@+Vertex *u=gg->vertices+gg->n; /* the new edge runs from |u| to |v| */ gb_new_edge(u,v,1L); /* we have $u>v$, hence |v->arcs=u->arcs-1| */ if (alph) u->arcs->len=(u->arcs-1)->len=alph_dist(u->name,v->name); else if (freq) { u->arcs->len=freq_cost(v); /* adjust the arc length from |u| to |v| */ (u->arcs-1)->len=20; /* adjust the arc length from |v| to |u| */ }}@ There's a bug in the above logic that could be embarrassing,although it will come up only when a user is trying to be clever: The|find_word| routine knows only the words of~|g|, so it will fail tomake any direct connection between |start| and |goal| if they happento be adjacent to each other yet not in the original graph. We hadbetter fix this, otherwise the computer will look stupid.@<Check if |start|...@>=if (hamm_dist(start,goal)==1) { gg->n--; /* temporarily pretend |vv| hasn't been added yet */ plant_new_edge(uu); /* make |vv| adjacent to |uu| */ gg->n++; /* and recognize it again */}@ The Hamming distance between words is the number of character positions@^Hamming, Richard Wesley, distance@>in which they differ.@d h_dist(k) (*(p+k)==*(q+k)? 0: 1)@<Sub...@>=long hamm_dist(p,q) register char *p, *q;{ return h_dist(0)+h_dist(1)+h_dist(2)+h_dist(3)+h_dist(4);}@ OK, now we've got a graph in which |dijkstra| can operate.@<Let |dijkstra| do the hard work@>=if (!heur) min_dist=dijkstra(uu,vv,gg,NULL);else if (alph) min_dist=dijkstra(uu,vv,gg,alph_heur);else min_dist=dijkstra(uu,vv,gg,hamm_heur);@ @<Sub...@>=long alph_heur(v) Vertex *v;{@+return alph_dist(v->name,goal);@+}@#long hamm_heur(v) Vertex *v;{@+return hamm_dist(v->name,goal);@+}@ @<Glob...@>=long min_dist; /* length of the shortest ladder */@ @<Print the answer@>=if (min_dist<0) printf("Sorry, there's no ladder from %s to %s.\n",start,goal);else print_dijkstra_result(vv);@ Finally, we have to clean up our tracks. It's easy to remove all arcsfrom the new vertices of~|gg| to the old vertices of~|g|; it's a bittrickier to remove the arcs from old to new. The loop here will alsoremove arcs properly between start and goal vertices, if they bothbelong to |gg| not~|g|.@<Remove all traces of |gg|@>=for (uu=g->vertices+gg->n-1; uu>=g->vertices+g->n; uu--) {@+register Arc *a; for (a=uu->arcs; a; a=a->next) { vv=a->tip; /* now |vv->arcs==a-1|, since arcs for edges come in pairs */ vv->arcs=vv->arcs->next; } uu->arcs=NULL; /* we needn't clear |uu->name| */}gb_recycle(gg); /* the |gg->data| blocks disappear, but |g->data| remains */@* Terminal interaction. We've finished doing all the interesting things.Only one minor part of the program still remains to be written.@<Prompt for...@>=putchar('\n'); /* make a blank line for visual punctuation */restart: /* if we try to avoid this label, the |break| command will be broken */if (prompt_for_five("Starting",start)!=0) break;if (prompt_for_five(" Goal",goal)!=0) goto restart;@ @<Sub...@>=long prompt_for_five(s,p) char *s; /* string used in prompt message */ register char *p; /* where to put a string typed by the user */{@+register char *q; /* current position to store characters */ register long c; /* current character of input */ while (1) { printf("%s word: ",s); fflush(stdout); /* make sure the user sees the prompt */ q=p; while (1) { c=getchar(); if (c==EOF) return -1; /* end-of-file */ if (echo) putchar(c); if (c=='\n') break; if (!islower(c)) q=p+5; else if (q<p+5) *q=c; q++; } if (q==p+5) return 0; /* got a good five-letter word */ if (q==p) return 1; /* got just \<return> */ printf("(Please type five lowercase letters and RETURN.)\n"); }}@* Index. Finally, here's a list that shows where the identifiers of thisprogram are defined and used.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -