เนื่องจากความยาวมีการระบุว่าเป็นเกณฑ์ต่อไปนี้เป็นรุ่น golfed ที่ 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, ETENS, STENS, SKENSS, SPINS, SPINE, 13
WICCA, PROSY, OY
BRINY, BRINS, TRINS, TAINS, TARNS, YARNS, YAWNS, YAWPS, YAPPS, 7
GALES, GASTS, GASTS, GESTS, GESTE, GESSE, DESSE, 5
SURES, DURE, DUNES, DINES, DINGS, DINGY, 4
LICHT เบา BIGHT BIGOT BIGOS BIROS GIROS GURNS GURNS กวาน GUANA RUANA 10
SARGE, SERGE, SERRE, SERRS, SEERS, DEERS, DYERS, OERS, OVERS, OVELS, OVALS, ODALS, ODYLS, IDYLS, 12
KEIRS, SEIRS, SEERS, BRERS, BRERE, BREME, CREME, CREPE, 7
นี่คือหนึ่งใน 6 คู่ที่มีเส้นทางที่สั้นที่สุดที่ยาวที่สุด:
GAINEST, FAINEST, FAIREST, SAIREST, SAADEST, SADDEST, MADDEST, MIDDEST, MILDEST, WILDEST, WILIEST, WALIEST, WANIEST, CIANEST, CONFEST, CONFESS, CONFERS, CONFERS, COPERS, POPPERS, POPPERS, POPPERS, COPERS, POPPERS, POPPERS, POPPERS POPPITS, POPPIES, POPSIES, MOUSIES, MOUSIES, MOUSSES, POUSSES, PLUSSES, PLISSES, PLISSES, PRISSES, PRESSES, PRASES, UASASES, UNAS, UNBASED, UNBASED, UNBASED ดัชนี, INDENES, เยื้อง, Incents, INCESTS, INFESTS, INFECTS, INJECTS, 56
และหนึ่งในแปดตัวอักษรที่ละลายน้ำได้กรณีที่เลวร้ายที่สุด:
ENROBING, UNROBING, UNROPING, UNCOPING, UNCAPING, UNCAGING, ENCAGING, ความล้มเหลว, ความล้มเหลว, การทำให้ล้มลง, การล้ม, การทำให้ล้มลง, การล้ม, การทำให้ล้ม CRIMPING, CRISPING, CRISPINS, CRISPENS, CRISPERS, CRIMPERS, CRCHERS, PCHERS, PCHERS, PCHERS, PERSHERS, PERSHERS, PERSHERS, PERSHERS, PERSHERS, PERSHERS, POTHERS, POTHERS, PERSHERS, PERSHERS LUNCHERS, LYNCHERS, LYNCHETS, LINCHETS, 52
ตอนนี้ฉันคิดว่าฉันมีความต้องการครบถ้วนสำหรับคำถามแล้วการอภิปรายของฉัน
สำหรับ CompSci คำถามจะลดลงอย่างเห็นได้ชัดในเส้นทางที่สั้นที่สุดในกราฟ G ซึ่งจุดยอดเป็นคำและขอบซึ่งเชื่อมต่อคำต่างกันในจดหมายฉบับหนึ่ง การสร้างกราฟได้อย่างมีประสิทธิภาพนั้นไม่สำคัญเลย - จริง ๆ แล้วฉันมีความคิดว่าฉันต้องกลับไปเพื่อลดความซับซ้อนเป็น O (V n hash + E) วิธีที่ฉันทำเกี่ยวข้องกับการสร้างกราฟที่แทรกจุดยอดพิเศษ (ตรงกับคำที่มีอักขระไวด์การ์ดหนึ่งตัว) และเป็น homeomorphic ของกราฟที่เป็นปัญหา ฉันได้พิจารณาใช้กราฟนั้นแทนที่จะลดลงเป็น G - และฉันคิดว่าจากมุมมองของการเล่นกอล์ฟที่ฉันควรทำ - บนพื้นฐานที่โหนดสัญลักษณ์ที่มีมากกว่า 3 ขอบลดจำนวนของขอบในกราฟและ O(V heap-op + E)
มาตรฐานเวลาที่เลวร้ายกรณีการทำงานของอัลกอริทึมเส้นทางที่สั้นที่สุดคือ
อย่างไรก็ตามสิ่งแรกที่ฉันทำคือเรียกใช้การวิเคราะห์บางอย่างของกราฟ G สำหรับความยาวคำต่าง ๆ และฉันค้นพบว่าพวกมันกระจัดกระจายมากสำหรับคำที่มีตัวอักษร 5 ตัวขึ้นไป กราฟ 5 ตัวอักษรมีจุดยอด 12478 และขอบ 40759; การเพิ่มลิงค์ของโหนดทำให้กราฟแย่ลง ในเวลาที่คุณมีตัวอักษรไม่เกิน 8 ตัวจะมีขอบน้อยกว่าโหนดและ 3/7 ของคำนั้น "ห่างเหิน" ดังนั้นฉันจึงปฏิเสธแนวคิดการเพิ่มประสิทธิภาพที่ไม่เป็นประโยชน์จริงๆ
แนวคิดที่พิสูจน์ว่ามีประโยชน์คือการตรวจสอบกอง ฉันสามารถพูดได้อย่างตรงไปตรงมาว่าฉันได้ติดตั้งฮีปแปลกใหม่ในระดับปานกลางในอดีต แต่ก็ไม่มีอะไรแปลกใหม่เช่นนี้ ฉันใช้ A-star (เนื่องจาก C ไม่ให้ประโยชน์กับกองที่ฉันใช้) ด้วยจำนวนฮิวริสติกที่ชัดเจนของตัวอักษรที่แตกต่างจากเป้าหมายและการวิเคราะห์แสดงให้เห็นว่าในเวลาใดก็ตามมีลำดับความสำคัญไม่เกิน 3 ครั้ง ในกอง เมื่อฉันปรากฏโหนดที่มีลำดับความสำคัญคือ (ราคา + การวิเคราะห์พฤติกรรม) และดูที่เพื่อนบ้านมีสามกรณีที่ฉันกำลังพิจารณา: 1) ค่าใช้จ่ายของเพื่อนบ้านคือราคา + 1; ฮิวริสติกของเพื่อนบ้านคือ heuristic-1 (เพราะตัวอักษรที่เปลี่ยนจะกลายเป็น "ถูกต้อง"); 2) ราคา +1 และแบบแก้ปัญหา + 0 (เนื่องจากตัวอักษรเปลี่ยนไปจาก "ผิด" เป็น "ยังผิด"; 3) ค่าใช้จ่าย +1 และการแก้ปัญหา + 1 (เนื่องจากตัวอักษรเปลี่ยนไปจาก "ถูกต้อง" เป็น "ผิด") ดังนั้นถ้าฉันผ่อนคลายเพื่อนบ้านฉันจะใส่มันด้วยความสำคัญลำดับความสำคัญ + 1 หรือลำดับความสำคัญ + 2 เป็นผลให้ฉันสามารถใช้อาร์เรย์ 3 องค์ประกอบของรายการที่เชื่อมโยงสำหรับกอง
ฉันควรเพิ่มหมายเหตุเกี่ยวกับข้อสันนิษฐานของฉันว่าการค้นหาแฮชคงที่ ดีมากคุณอาจพูดว่า แต่แล้วการคำนวณแฮชล่ะ คำตอบก็คือฉันจะตัดค่าใช้จ่ายออกไป: java.lang.String
แคชhashCode()
ดังนั้นเวลาทั้งหมดที่ใช้ในการคำนวณแฮชคือO(V n^2)
(ในการสร้างกราฟ)
มีการเปลี่ยนแปลงอีกอย่างที่ส่งผลต่อความซับซ้อน แต่คำถามที่ว่าเป็นการเพิ่มประสิทธิภาพหรือไม่นั้นขึ้นอยู่กับสมมติฐานของคุณเกี่ยวกับสถิติ (IMO วาง "ทางออก Big Big ที่ดีที่สุด" เป็นเกณฑ์เป็นข้อผิดพลาดเพราะไม่มีความซับซ้อนที่ดีที่สุดด้วยเหตุผลง่ายๆ: ไม่มีตัวแปรเดียว) การเปลี่ยนแปลงนี้มีผลต่อขั้นตอนการสร้างกราฟ ในรหัสข้างต้นมันคือ:
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)
กรณีที่เลวร้ายที่สุด กรณีที่แย่ที่สุดคือเมื่อเรามีการชนกันระหว่างสองลิงค์ที่เท่ากันซึ่งสร้างจากคำต่าง ๆ นั่นคือเกิดขึ้นO(E)
ครั้งในช่วงครึ่งหลังของการสร้างกราฟ นอกเหนือจากนั้นยกเว้นในกรณีที่มีการชนกันของแฮ็ชระหว่างลิงค์ที่ไม่เท่ากันเราน่าจะดี ดังนั้นเราจึงได้มีการซื้อขายในสำหรับO(V * n^2)
O(E * n * hash)
ดูจุดก่อนหน้าของฉันเกี่ยวกับสถิติ
HOUSE
จนGORGE
จบเป็น 2 ฉันรู้ว่ามีคำกลาง 2 คำดังนั้นมันสมเหตุสมผล แต่ # ของการดำเนินการจะง่ายขึ้น