Vì độ dài được liệt kê là một tiêu chí, đây là phiên bản chơi gôn với 1681 ký tự (có thể vẫn có thể được cải thiện 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>{}
Phiên bản không được sử dụng, sử dụng tên và phương thức gói và không đưa ra cảnh báo hoặc mở rộng các lớp chỉ để bí danh chúng là:
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;
}
}
}
Như bạn có thể thấy, phân tích chi phí hoạt động là O(filelength + num_words * hash + V * n * (n + hash) + E * hash)
. Nếu bạn chấp nhận giả định của tôi rằng việc chèn / tra cứu bảng băm là thời gian không đổi, thì đó là O(filelength + V n^2 + E)
. Số liệu thống kê cụ thể của các biểu đồ trong SOWPODS có nghĩa là O(V n^2)
thực sự chiếm ưu thế O(E)
đối với hầu hết n
.
Đầu ra mẫu:
IDola, IDOLS, IDYLS, ODYLS, ODALS, OVALS, OVELS, OVENS, EVENS, ETENS, STENS, SKENS, SKIN, SPIN, SPINE, 13
WICCA, PROSY, OY
BRINY, BRIN, TRIN, TAIN, TARNS, YARNS, YAWNS, YAWPS, YAPPS, 7
GALES, GASES, GASTS, GEST, GESTE, GESSE, DESSE, 5
SOUND, DOUND, DUNES, DINES, DINGY, DINGY, 4
GIẤY PHÉP, ÁNH SÁNG, ĐÊM, BIGOT, BIGOS, BIROS, GIROS, GIRNS, GACKS, GUans, GUANA, RUANA, 10
SARGE, SERGE, SERRE, SERRS, SEERS, DEERS, DYERS, OYERS, OVERS, OVELS, OVALS, ODALS, ODYLS, IDYLS, 12
KEIRS, SEIRS, SEERS, BEERS, BRERS, BRERE, BREME, CREME, CREPE, 7
Đây là một trong 6 cặp có con đường ngắn nhất dài nhất:
GAINEST, FAINEST, FAIREST, SAIREST, SAIDEST, SADDEST, MADDEST, MIDDEST, SADDEST, MADDEST, MIDDEST, MILDEST, WILEST, WILIEST, CONFEST, CANIEST, WILIEST, CONFEST, CONFEST, CONFEST, CONFEST, CONFEST, CONFEST DÂN TỘC, DÂN TỘC INDEXES, INDENES, INDENT, INCENT, INCESTS, INFESTS, INFECTS, IN DỰ ÁN, 56
Và một trong những cặp 8 chữ cái hòa tan trong trường hợp xấu nhất:
TĂNG TRƯỞNG KHAI THÁC, KHAI THÁC, KHAI THÁC LUNCHERS, LYNCHERS, LYNCHET, LINCHETS, 52
Bây giờ tôi nghĩ rằng tôi đã có tất cả các yêu cầu của câu hỏi ngoài lề, cuộc thảo luận của tôi.
Đối với một CompSci, câu hỏi rõ ràng giảm xuống đường đi ngắn nhất trong đồ thị G có các đỉnh là các từ và các cạnh của nó kết nối các từ khác nhau trong một chữ cái. Tạo đồ thị hiệu quả không phải là chuyện nhỏ - Tôi thực sự có một ý tưởng tôi cần xem lại để giảm độ phức tạp thành O (V n hash + E). Cách tôi thực hiện liên quan đến việc tạo một biểu đồ chèn thêm các đỉnh (tương ứng với các từ có một ký tự đại diện) và tương đồng với biểu đồ được đề cập. Tôi đã cân nhắc sử dụng biểu đồ đó thay vì giảm xuống G - và tôi cho rằng từ quan điểm chơi gôn tôi nên làm - trên cơ sở một nút ký tự đại diện có nhiều hơn 3 cạnh làm giảm số cạnh trong biểu đồ và trường hợp xấu nhất tiêu chuẩn thời gian chạy của thuật toán đường dẫn ngắn nhất là O(V heap-op + E)
.
Tuy nhiên, điều đầu tiên tôi làm là chạy một số phân tích biểu đồ G cho các độ dài từ khác nhau và tôi phát hiện ra rằng chúng cực kỳ thưa thớt đối với các từ có 5 chữ cái trở lên. Biểu đồ 5 chữ cái có 12478 đỉnh và 40759 cạnh; thêm các nút liên kết làm cho biểu đồ tồi tệ hơn. Vào thời điểm bạn có tới 8 chữ cái, có ít cạnh hơn các nút và 3/7 từ là "cách ly". Vì vậy, tôi đã từ chối ý tưởng tối ưu hóa đó là không thực sự hữu ích.
Ý tưởng đã chứng minh hữu ích là kiểm tra đống. Tôi có thể thành thật nói rằng tôi đã thực hiện một số đống kỳ lạ vừa phải trong quá khứ, nhưng không có gì kỳ lạ như thế này. Tôi sử dụng sao A (vì C không mang lại lợi ích gì cho heap tôi đang sử dụng) với số lượng chữ cái rõ ràng khác với mục tiêu và một chút phân tích cho thấy rằng bất cứ lúc nào cũng có không quá 3 ưu tiên khác nhau trong đống. Khi tôi bật một nút có mức độ ưu tiên là (chi phí + heuristic) và xem xét các hàng xóm của nó, có ba trường hợp tôi đang xem xét: 1) chi phí của neighbour là chi phí + 1; heuristic của neighbour là heuristic-1 (vì chữ cái nó thay đổi trở thành "chính xác"); 2) chi phí + 1 và heuristic + 0 (vì chữ cái nó thay đổi chuyển từ "sai" sang "vẫn sai"; 3) chi phí + 1 và heuristic + 1 (vì chữ cái nó thay đổi đi từ "chính xác" thành "sai"). Vì vậy, nếu tôi thư giãn hàng xóm, tôi sẽ chèn nó ở cùng mức độ ưu tiên, mức độ ưu tiên + 1 hoặc mức độ ưu tiên + 2. Kết quả là tôi có thể sử dụng một mảng gồm 3 phần tử của các danh sách được liên kết cho heap.
Tôi nên thêm một lưu ý về giả định của tôi rằng tra cứu băm là không đổi. Rất tốt, bạn có thể nói, nhưng còn tính toán băm thì sao? Câu trả lời là tôi sẽ loại bỏ chúng: java.lang.String
lưu trữ nó hashCode()
, vì vậy tổng thời gian sử dụng băm tính toán là O(V n^2)
(trong việc tạo biểu đồ).
Có một sự thay đổi khác ảnh hưởng đến sự phức tạp, nhưng câu hỏi liệu nó có phải là tối ưu hóa hay không phụ thuộc vào giả định của bạn về thống kê. (IMO đặt "giải pháp Big O tốt nhất" làm tiêu chí là một sai lầm vì không có độ phức tạp tốt nhất, vì một lý do đơn giản: không có một biến duy nhất). Sự thay đổi này ảnh hưởng đến bước tạo đồ thị. Trong đoạn mã trên, đó là:
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);
}
}
}
Đó là O(V * n * (n + hash) + E * hash)
. Nhưng O(V * n^2)
phần này đến từ việc tạo ra một chuỗi ký tự n mới cho mỗi liên kết và sau đó tính toán mã băm của nó. Điều này có thể tránh được với một lớp người trợ giúp:
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;
}
}
Sau đó, nửa đầu của thế hệ đồ thị trở thành
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;
}
}
Bằng cách sử dụng cấu trúc của mã băm, chúng tôi có thể tạo các liên kết trong O(V * n)
. Tuy nhiên, điều này có tác dụng gõ cửa. Kế thừa trong giả định của tôi rằng tra cứu băm là thời gian không đổi là một giả định rằng so sánh các đối tượng cho sự bình đẳng là rẻ. Tuy nhiên, bài kiểm tra bình đẳng của Link là O(n)
trong trường hợp xấu nhất. Trường hợp xấu nhất là khi chúng ta có xung đột băm giữa hai liên kết bằng nhau được tạo từ các từ khác nhau - tức là nó xảy ra O(E)
lần trong nửa sau của thế hệ đồ thị. Ngoài ra, ngoại trừ trường hợp không thể xảy ra xung đột băm giữa các liên kết không bằng nhau, chúng tôi đều tốt. Vì vậy, chúng tôi đã được giao dịch trong O(V * n^2)
cho O(E * n * hash)
. Xem điểm trước của tôi về số liệu thống kê.
HOUSE
đểGORGE
được báo cáo là 2. Tôi nhận thấy có 2 chữ trung gian, vì vậy nó không có ý nghĩa, nhưng # hoạt động sẽ trực quan hơn.