長さが基準としてリストされているので、これは1681文字のゴルフバージョンです(おそらく10%改善される可能性があります)。
import java.io.*;import java.util.*;public class W{public static void main(String[]
a)throws Exception{int n=a.length<1?5:a[0].length(),p,q;String f,t,l;S w=new S();Scanner
s=new Scanner(new
File("sowpods"));while(s.hasNext()){f=s.next();if(f.length()==n)w.add(f);}if(a.length<1){String[]x=w.toArray(new
String[0]);Random
r=new Random();q=x.length;p=r.nextInt(q);q=r.nextInt(q-1);f=x[p];t=x[p>q?q:q+1];}else{f=a[0];t=a[1];}H<S>
A=new H(),B=new H(),C=new H();for(String W:w){A.put(W,new
S());for(p=0;p<n;p++){char[]c=W.toCharArray();c[p]='.';l=new
String(c);A.get(W).add(l);S z=B.get(l);if(z==null)B.put(l,z=new
S());z.add(W);}}for(String W:A.keySet()){C.put(W,w=new S());for(String
L:A.get(W))for(String b:B.get(L))if(b!=W)w.add(b);}N m,o,ñ;H<N> N=new H();N.put(f,m=new
N(f,t));N.put(t,o=new N(t,t));m.k=0;N[]H=new
N[3];H[0]=m;p=H[0].h;while(0<1){if(H[0]==null){if(H[1]==H[2])break;H[0]=H[1];H[1]=H[2];H[2]=null;p++;continue;}if(p>=o.k-1)break;m=H[0];H[0]=m.x();if(H[0]==m)H[0]=null;for(String
v:C.get(m.s)){ñ=N.get(v);if(ñ==null)N.put(v,ñ=new N(v,t));if(m.k+1<ñ.k){if(ñ.k<ñ.I){q=ñ.k+ñ.h-p;N
Ñ=ñ.x();if(H[q]==ñ)H[q]=Ñ==ñ?null:Ñ;}ñ.b=m;ñ.k=m.k+1;q=ñ.k+ñ.h-p;if(H[q]==null)H[q]=ñ;else{ñ.n=H[q];ñ.p=ñ.n.p;ñ.n.p=ñ.p.n=ñ;}}}}if(o.b==null)System.out.println(f+"\n"+t+"\nOY");else{String[]P=new
String[o.k+2];P[o.k+1]=o.k-1+"";m=o;for(q=m.k;q>=0;q--){P[q]=m.s;m=m.b;}for(String
W:P)System.out.println(W);}}}class N{String s;int k,h,I=(1<<30)-1;N b,p,n;N(String S,String
d){s=S;for(k=0;k<d.length();k++)if(d.charAt(k)!=S.charAt(k))h++;k=I;p=n=this;}N
x(){N r=n;n.p=p;p.n=n;n=p=this;return r;}}class S extends HashSet<String>{}class H<V>extends
HashMap<String,V>{}
パッケージ名とメソッドを使用し、警告を表示したり、エイリアスを作成するためだけにクラスを拡張したりしないバージョンは、次のとおりです。
package com.akshor.pjt33;
import java.io.*;
import java.util.*;
// WordLadder partially golfed and with reduced dependencies
//
// Variables used in complexity analysis:
// n is the word length
// V is the number of words (vertex count of the graph)
// E is the number of edges
// hash is the cost of a hash insert / lookup - I will assume it's constant, but without completely brushing it under the carpet
public class WordLadder2
{
private Map<String, Set<String>> wordsToWords = new HashMap<String, Set<String>>();
// Initialisation cost: O(V * n * (n + hash) + E * hash)
private WordLadder2(Set<String> words)
{
Map<String, Set<String>> wordsToLinks = new HashMap<String, Set<String>>();
Map<String, Set<String>> linksToWords = new HashMap<String, Set<String>>();
// Cost: O(Vn * (n + hash))
for (String word : words)
{
// Cost: O(n*(n + hash))
for (int i = 0; i < word.length(); i++)
{
// Cost: O(n + hash)
char[] ch = word.toCharArray();
ch[i] = '.';
String link = new String(ch).intern();
add(wordsToLinks, word, link);
add(linksToWords, link, word);
}
}
// Cost: O(V * n * hash + E * hash)
for (Map.Entry<String, Set<String>> from : wordsToLinks.entrySet()) {
String src = from.getKey();
wordsToWords.put(src, new HashSet<String>());
for (String link : from.getValue()) {
Set<String> to = linksToWords.get(link);
for (String snk : to) {
// Note: equality test is safe here. Cost is O(hash)
if (snk != src) add(wordsToWords, src, snk);
}
}
}
}
public static void main(String[] args) throws IOException
{
// Cost: O(filelength + num_words * hash)
Map<Integer, Set<String>> wordsByLength = new HashMap<Integer, Set<String>>();
BufferedReader br = new BufferedReader(new FileReader("sowpods"), 8192);
String line;
while ((line = br.readLine()) != null) add(wordsByLength, line.length(), line);
if (args.length == 2) {
String from = args[0].toUpperCase();
String to = args[1].toUpperCase();
new WordLadder2(wordsByLength.get(from.length())).findPath(from, to);
}
else {
// 5-letter words are the most interesting.
String[] _5 = wordsByLength.get(5).toArray(new String[0]);
Random rnd = new Random();
int f = rnd.nextInt(_5.length), g = rnd.nextInt(_5.length - 1);
if (g >= f) g++;
new WordLadder2(wordsByLength.get(5)).findPath(_5[f], _5[g]);
}
}
// O(E * hash)
private void findPath(String start, String dest) {
Node startNode = new Node(start, dest);
startNode.cost = 0; startNode.backpointer = startNode;
Node endNode = new Node(dest, dest);
// Node lookup
Map<String, Node> nodes = new HashMap<String, Node>();
nodes.put(start, startNode);
nodes.put(dest, endNode);
// Heap
Node[] heap = new Node[3];
heap[0] = startNode;
int base = heap[0].heuristic;
// O(E * hash)
while (true) {
if (heap[0] == null) {
if (heap[1] == heap[2]) break;
heap[0] = heap[1]; heap[1] = heap[2]; heap[2] = null; base++;
continue;
}
// If the lowest cost isn't at least 1 less than the current cost for the destination,
// it can't improve the best path to the destination.
if (base >= endNode.cost - 1) break;
// Get the cheapest node from the heap.
Node v0 = heap[0];
heap[0] = v0.remove();
if (heap[0] == v0) heap[0] = null;
// Relax the edges from v0.
int g_v0 = v0.cost;
// O(hash * #neighbours)
for (String v1Str : wordsToWords.get(v0.key))
{
Node v1 = nodes.get(v1Str);
if (v1 == null) {
v1 = new Node(v1Str, dest);
nodes.put(v1Str, v1);
}
// If it's an improvement, use it.
if (g_v0 + 1 < v1.cost)
{
// Update the heap.
if (v1.cost < Node.INFINITY)
{
int bucket = v1.cost + v1.heuristic - base;
Node t = v1.remove();
if (heap[bucket] == v1) heap[bucket] = t == v1 ? null : t;
}
// Next update the backpointer and the costs map.
v1.backpointer = v0;
v1.cost = g_v0 + 1;
int bucket = v1.cost + v1.heuristic - base;
if (heap[bucket] == null) {
heap[bucket] = v1;
}
else {
v1.next = heap[bucket];
v1.prev = v1.next.prev;
v1.next.prev = v1.prev.next = v1;
}
}
}
}
if (endNode.backpointer == null) {
System.out.println(start);
System.out.println(dest);
System.out.println("OY");
}
else {
String[] path = new String[endNode.cost + 1];
Node t = endNode;
for (int i = t.cost; i >= 0; i--) {
path[i] = t.key;
t = t.backpointer;
}
for (String str : path) System.out.println(str);
System.out.println(path.length - 2);
}
}
private static <K, V> void add(Map<K, Set<V>> map, K key, V value) {
Set<V> vals = map.get(key);
if (vals == null) map.put(key, vals = new HashSet<V>());
vals.add(value);
}
private static class Node
{
public static int INFINITY = Integer.MAX_VALUE >> 1;
public String key;
public int cost;
public int heuristic;
public Node backpointer;
public Node prev = this;
public Node next = this;
public Node(String key, String dest) {
this.key = key;
cost = INFINITY;
for (int i = 0; i < dest.length(); i++) if (dest.charAt(i) != key.charAt(i)) heuristic++;
}
public Node remove() {
Node rv = next;
next.prev = prev;
prev.next = next;
next = prev = this;
return rv;
}
}
}
ご覧のとおり、ランニングコスト分析はO(filelength + num_words * hash + V * n * (n + hash) + E * hash)
です。ハッシュテーブルの挿入/ルックアップが一定時間であるという私の仮定を受け入れるなら、それはO(filelength + V n^2 + E)
です。SOWPODSのグラフの特定の統計は、ほとんどの場合、それがO(V n^2)
実際に支配的であることを意味します。O(E)
n
サンプル出力:
IDOLA、IDOLS、IDYLS、ODYLS、ODALS、OVALS、OVELS、OVENS、EVENS、ETENS、STENS、SKENS、SKINS、SPINS、SPINE、13
ウィッカ、プロシー、オイ
BRINY、BRINS、TRINS、TAINS、TARNS、YARNS、YAWNS、YAWPS、YAPPS、7
GALES、GASES、GASTS、GESTS、GESTE、GESSE、DESSE、5
SURES、DURES、DUNES、DINES、DINGS、DINGY、4
LICHT、LIGHT、BIGHT、BIGOT、BIGOS、BIROS、GIROS、GIRNS、GURNS、GUANS、GUANA、RUANA、10
12
KEIRS、SEIRS、SEERS、BEERS、BRERS、BRERE、BREME、CREME、CREPE、7
これは、最長の最短パスを持つ6つのペアの1つです。
GAINEST、FAINEST、FAIREST、SAIREST、SAIDEST、SADDEST、MADDEST、MIDDEST、MILDEST、WILDEST、WILIEST、WALIEST、WANIEST、CANIEST、CANTEST、CONTEST、CONFEST、CONFESS、CONFERS、CONKERS、COOKERS、COOPERS、COPPERS、POPPERS、POPPERS、POPPERS、POPPERSポピーズ、ポピー、ポピー、モッピー、マウジー、ムース、ポース、プラス、プリシス、プリシス、プレス、プリーゼ、ウレアーゼ、ウーゼ、アンキャス、アンキャス、アンベース、アンベート、アンメート、アンメッド、アンメッド、エド、エド、エド、エド、エド、エド、エド、エド、エド、エド、エド、エド、エドインデックス、インデン、インデント、インセンツ、インセスト、インフェスト、インフェクト、インジェクト、56
そして、最悪の場合の可溶性8文字ペアの1つ:
エンロービング、UNROBING、UNROPING、UNCOPING、UNCAPING、アンケージング、ENCAGING、ENRAGING、ENRACING、ENLACING、UNLACING、UNLAYING、UPLAYING、広がり、噴霧、逸脱、STROYING、なでる、STOOKING、前かがみ、踏み、遊説、スランピング、凝集、CRUMPING、圧着、圧着、圧着、圧着、圧着、圧着、圧着、クランプ、クラッパー、クラッシャー、スラッシャー、スラッサー、スリザー、スミザー、スマザー、サザーズ、サワーズ、マザーズ、ムーサーズ、カウチャーズ、ポーチャーズ、ポーチャーズ、ポーチャーズ、ポーチャーズ昼食、リンチェ、リンチェット、リンチ、52
これで、質問のすべての要件が不要になったと思うので、議論します。
CompSciの場合、頂点が単語であり、エッジが1文字が異なる単語をつなぐグラフGで、質問は明らかに最短経路になります。グラフを効率的に生成することは簡単ではありません。実際、複雑さをO(V n hash + E)に減らすために再検討する必要があるアイデアがあります。私がそれを行う方法は、余分な頂点(1つのワイルドカード文字を含む単語に対応する)を挿入し、問題のグラフと同相であるグラフを作成することを含みます。Gに減らすのではなく、そのグラフを使用することを検討しました。ゴルフの観点からは、3つ以上のエッジを持つワイルドカードノードがグラフ内のエッジの数を減らすことに基づいて行うべきでした。最短経路アルゴリズムの標準的な最悪の場合の実行時間はO(V heap-op + E)
です。
しかし、私が最初にしたことは、さまざまな語長についてグラフGの分析を実行することでした。5文字以上の語については非常にまばらであることを発見しました。5文字のグラフには、12478の頂点と40759のエッジがあります。リンクノードを追加すると、グラフが悪化します。最大8文字になるまでに、ノードよりもエッジが少なくなり、3/7の単語が「アローフ」になります。そのため、最適化のアイデアはあまり役に立たないと断りました。
役立つと判明したアイデアは、ヒープを調べることでした。正直に言って、過去に中程度のエキゾチックなヒープを実装したことがありますが、これほどエキゾチックなものはありません。ターゲットとは異なる文字数のヒューリスティックでA-starを使用します(Cは使用しているヒープを考慮するとメリットがありません)。ヒープ内。優先度が(コスト+ヒューリスティック)であるノードをポップし、その隣接ノードを見ると、3つのケースが考えられます。1)隣接ノードのコストはコスト+1です。neighbourのヒューリスティックはheuristic-1です(変更される文字が「正しい」ため)。2)cost + 1およびheuristic + 0(変更する文字が「間違った」から「まだ間違っている」に変わるため。3)cost + 1とheuristic + 1(変更する文字が「正しい」から「間違った」に変わるため)。そのため、隣人をリラックスさせる場合、同じ優先度、priority + 1、またはpriority + 2で挿入します。その結果、ヒープにリンクリストの3要素配列を使用できます。
ハッシュルックアップは一定であるという仮定についてのメモを追加する必要があります。あなたは言うかもしれませんが、ハッシュ計算はどうですか?その答えは、私はそれらを償却しているということです:java.lang.String
キャッシュするhashCode()
ので、ハッシュの計算に費やされる合計時間はO(V n^2)
(グラフの生成に)あります。
複雑さに影響する別の変更がありますが、それが最適化であるかどうかの問題は、統計に関する仮定に依存します。(「最高のBig Oソリューション」を基準として置くIMOは、単純な理由で、最高の複雑さがないため、間違いです。単一の変数がないためです)。この変更は、グラフ生成手順に影響します。上記のコードでは、次のとおりです。
Map<String, Set<String>> wordsToLinks = new HashMap<String, Set<String>>();
Map<String, Set<String>> linksToWords = new HashMap<String, Set<String>>();
// Cost: O(Vn * (n + hash))
for (String word : words)
{
// Cost: O(n*(n + hash))
for (int i = 0; i < word.length(); i++)
{
// Cost: O(n + hash)
char[] ch = word.toCharArray();
ch[i] = '.';
String link = new String(ch).intern();
add(wordsToLinks, word, link);
add(linksToWords, link, word);
}
}
// Cost: O(V * n * hash + E * hash)
for (Map.Entry<String, Set<String>> from : wordsToLinks.entrySet()) {
String src = from.getKey();
wordsToWords.put(src, new HashSet<String>());
for (String link : from.getValue()) {
Set<String> to = linksToWords.get(link);
for (String snk : to) {
// Note: equality test is safe here. Cost is O(hash)
if (snk != src) add(wordsToWords, src, snk);
}
}
}
それがありますO(V * n * (n + hash) + E * hash)
。しかし、そのO(V * n^2)
部分は、各リンクに対して新しいn文字の文字列を生成し、そのハッシュコードを計算することから来ています。これは、ヘルパークラスを使用して回避できます。
private static class Link
{
private String str;
private int hash;
private int missingIdx;
public Link(String str, int hash, int missingIdx) {
this.str = str;
this.hash = hash;
this.missingIdx = missingIdx;
}
@Override
public int hashCode() { return hash; }
@Override
public boolean equals(Object obj) {
Link l = (Link)obj; // Unsafe, but I know the contexts where I'm using this class...
if (this == l) return true; // Essential
if (hash != l.hash || missingIdx != l.missingIdx) return false;
for (int i = 0; i < str.length(); i++) {
if (i != missingIdx && str.charAt(i) != l.str.charAt(i)) return false;
}
return true;
}
}
次に、グラフ生成の前半は
Map<String, Set<Link>> wordsToLinks = new HashMap<String, Set<Link>>();
Map<Link, Set<String>> linksToWords = new HashMap<Link, Set<String>>();
// Cost: O(V * n * hash)
for (String word : words)
{
// apidoc: The hash code for a String object is computed as
// s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
// Cost: O(n * hash)
int hashCode = word.hashCode();
int pow = 1;
for (int j = word.length() - 1; j >= 0; j--) {
Link link = new Link(word, hashCode - word.charAt(j) * pow, j);
add(wordsToLinks, word, link);
add(linksToWords, link, word);
pow *= 31;
}
}
ハッシュコードの構造を使用して、でリンクを生成できますO(V * n)
。ただし、これにはノックオン効果があります。ハッシュルックアップが一定時間であるという私の仮定に内在するのは、オブジェクトが等しいかどうかを比較するのが安価であるという仮定です。ただし、Linkの等価性テストはO(n)
最悪の場合です。最悪のケースは、異なる単語から生成された2つの等しいリンク間でハッシュ衝突が発生した場合です。つまりO(E)
、グラフ生成の後半に発生します。それ以外は、等しくないリンク間でハッシュ衝突が起こる可能性が低い場合を除き、私たちは大丈夫です。だから私たちは下取りしO(V * n^2)
ましたO(E * n * hash)
。統計に関する以前のポイントを参照してください。
HOUSE
するGORGE
と2として報告されるのは奇妙に思えます。中間語が2つあることに気付きました。