길이가 기준으로 표시되므로 다음은 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>{}
패키지 이름과 메소드를 사용하고 별명으로 경고하거나 클래스를 확장하지 않는 ungolfed 버전은 다음과 같습니다.
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
WICCA, PROSY, 오우
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
SARGE, SERGE, SERRE, SERRS, SEERS, DEERS, DYERS, OYERS, OVERS, OVELS, OVALS, ODALS, ODYLS, IDYLS, 12
키 어즈, 시어즈, 시어스, 맥주, 맥주, 브 르레, 브림, 크림, CREPE, 7
가장 짧은 경로를 가진 6 쌍 중 하나입니다.
GAINEST, FAINEST, FAIREST, SAIREST, SAIDEST, SADDEST, MADDEST, MIDDEST, MILDEST, WILDEST, WIREEST, WANIEST, CANIEST, CANTEST, CONTEST, CONFEST, CONFESS, CONFERS, CONKERS, COOKERS, 구리, 구리, 구리 POPPITS, POPPIES, POPSIES, MOPSIES, MOUSIES, MOUSSES, POISSE, PLUSSES, PLISSES, PRISSES, PRESSES, REFARES, UREASES, UNEASES, UNCASES, UNCASED, UNBASED, UNBATED, UNMATED, UNMEED, WEEDEDEDED, EDWEDED, EDWEDED, EDWEDED, EDWEDED, EDWEDED, EDWEDED, EDWEDED, EDWEDED, EDWEDED,웨어 하우스 INDEXES, INDENES, INDENTS, INCENTS, INCESTS, INFESTS, INFECTS, INJECTS 56
그리고 최악의 가용성 8 문자 쌍 중 하나 :
ENROBING, UNROBING, UNROPING, UNCOPING, UNCAPING, UNCAGING, ENCAGING, ENRAGING, ENRACING, UNLACING, UNLAYING, UPLAYING, SPLAYING, 스프레이, 스트레이 핑, 쓰다듬 기, 쓰다듬 기, 스톰 핑, 스톰 핑, 스톰 핑, 스톰 핑 크림 핑, 크림 핑, 크림, 크림, 크림 퍼, 크림 퍼, 크림 퍼, 클램퍼, 클래퍼, 클래퍼, 슬래셔, 슬래 더, 슬리퍼, 스터 더 런치, 린치, 린치, 린치, 52
이제 질문에 대한 모든 요구 사항을 해결해야한다고 생각합니다.
CompSci의 경우, 질문은 꼭짓점이 단어이고 하나의 문자가 다른 단어를 연결하는 모서리가있는 그래프 G에서 최단 경로로 분명히 줄어 듭니다. 그래프를 효율적으로 생성하는 것은 쉬운 일이 아닙니다. 실제로 복잡성을 O (V n hash + E)로 줄이기 위해 다시 방문해야한다는 생각이 있습니다. 내가하는 방법은 하나의 와일드 카드 문자가있는 단어에 해당하는 여분의 정점을 삽입하고 문제의 그래프에 동종 변형되는 그래프를 만드는 것입니다. 나는 G로 줄이지 않고 그래프를 사용하는 것을 고려했다. 골프 관점에서 내가해야 할 일은 3 개의 모서리를 가진 와일드 카드 노드가 그래프의 모서리 수를 줄이고 최단 경로 알고리즘의 표준 최악의 경우 실행 시간은입니다 O(V heap-op + E)
.
그러나 내가 한 첫 번째 일은 다른 단어 길이에 대한 그래프 G에 대한 분석을 실행하는 것이었고 5 개 이상의 글자에 대해서는 매우 희박하다는 것을 알았습니다. 5 자 그래프에는 12478 개의 정점과 40759 개의 모서리가 있습니다. 링크 노드를 추가하면 그래프가 더 나빠집니다. 최대 8 글자까지는 노드보다 가장자리가 적으며 3/7 단어는 "aloof"입니다. 그래서 나는 그 최적화 아이디어가 실제로 도움이되지 않는다는 것을 거부했습니다.
도움이 된 아이디어는 힙을 검사하는 것이 었습니다. 나는 솔직히 과거에 약간의 이국적인 힙을 구현했지만 이만큼 이국적인 것은 아니라고 말할 수 있습니다. 대상과 다른 문자 수의 명백한 휴리스틱으로 A-star (C는 사용중인 힙을 제공하지 않기 때문에)를 사용하며 약간의 분석에 따르면 언제든지 3 가지 이상의 우선 순위가 없음을 보여줍니다 힙에. 우선 순위가 (비용 + 휴리스틱) 인 노드를 팝하고 해당 이웃을 볼 때 고려중인 세 가지 경우가 있습니다. 1) 이웃의 비용은 비용 +1입니다. 이웃의 휴리스틱은 휴리스틱 -1 (변경된 문자가 "올바른"이되기 때문에); 2) 비용 +1 및 휴리스틱 +0 3) 비용 +1 및 휴리스틱 +1 (변경되는 문자가 "정확한"에서 "잘못된"으로 변경됨). 따라서 이웃을 이완 시키면 동일한 우선 순위, 우선 순위 +1 또는 우선 순위 +2로 삽입합니다. 결과적으로 힙에 대해 링크 된 목록의 3 요소 배열을 사용할 수 있습니다.
해시 조회가 일정하다는 가정에 대한 메모를 추가해야합니다. 잘 말하지만 해시 계산은 어떻습니까? 대답은 내가 그들을 상각하고 있다는 것입니다 : java.lang.String
캐시합니다 hashCode()
. 그래서 해시 계산에 소요 된 총 시간은 O(V n^2)
(그래프 생성)입니다.
복잡성에 영향을 미치는 또 다른 변경 사항이 있지만 최적화 여부에 대한 질문은 통계에 대한 가정에 따라 다릅니다. (IMO는 "최고의 Big O 솔루션"을 기준으로 삼는 것은 실수가 아닙니다. 단순한 이유가 없기 때문에 최선의 복잡성이 없기 때문입니다. 단일 변수가 없습니다). 이 변경은 그래프 생성 단계에 영향을줍니다. 위의 코드에서 다음과 같습니다.
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로는 메이크업 감각을한다, 그래서보고 있지만, 작업 # 더 직관적 인 것입니다.