本章講解的是算法的做用,「看起來很困難的問題也能夠有一個簡單的、意想不到的答案。」在任何願意在編程以前、之中和以後進行認真思考的程序員都有機會捕捉到這靈光一閃。 java
文章從三個問題展開,我獨自先思考了一下,發現解決方法都是比較低效的,既浪費空間也浪費時間。 git
a.給定一個最多包含40億個隨機排列的32位整數的順序文件,找出一個不在文件中的32位整數(在文件中至少缺乏一個這樣的數,爲何?就是232 > 40億 因此就是有至少缺乏一個)。在具備足夠內存的狀況下,如何解決該問題?若是有幾個外部的「臨時」文件可用,可是僅有幾百字節的內存,又該如何解決該問題? 程序員
b.將一個n元一維向量左旋轉i個位置。例如,當n=8且i=3時,向量abcdefgh旋轉爲defghabc。簡單的代碼使用一個n元的中間變量在n步內完成該工做。你可否僅使用數十個額外字節的存儲空間,在正比於n的時間內完成向量的旋轉? 算法
c.給定一個英語詞典,找出其中的全部變位詞集合。例如,「pots","stop"和「tops"互爲變位詞,由於每一個單詞均可以經過改變其餘單詞中字母的順序來獲得。 編程
對於a題,第一時間應該是想到「二分搜索」這個方法。在log n時間內完成對順序文件的搜索。但由於前提是要順序,因此這也是二分搜索的一個弊端。由於要對非排序的整數排序,至少是須要n的正比時間。此題所提到的「二分搜索」我會在第四章章才實現,由於那章說起到「二分搜索」的校驗。對於第二個小問,若是僅有幾百字節的內存,可使用我上一章說起到位圖存儲的方法存儲這40億個整數。 ide
對於b題,我第一時間會想到題目說起到的方法實現,但十分低效。文章中說起到三種方法(我實現了兩種,第三種比較簡單) 函數
這裏有一個抽象類是記錄每種方法的旋轉的運行時間: this
package ckj.chapter2.swap; public abstract class Rotation { public char[] m_input; public String m_rotname; public int m_length; public int m_rotdist; public Rotation(String input,int rotdist){ this.m_input = input.toCharArray(); this.m_length = input.length(); this.m_rotdist = rotdist; } public abstract void rotate(); /** * the cost time of the rotation */ public void rotCost(){ //printChar("before----->"); long t1 = System.currentTimeMillis(); rotate(); long cost = System.currentTimeMillis() - t1 ; System.out.println("The cost of "+this.m_rotname+" : " + cost); //printChar("after----->"); } /** * print out the char array with the string name before * @param str */ private void printChar(String str){ System.out.println(str); for(int i = 0 ; i < this.m_length ; i ++){ System.out.print(this.m_input[i]); } System.out.println(); } }
第一種方法,按書上的說法是頗有技巧性的:移動x[0]到臨時變量t,而後移動x[i]至x[0],x[2i]至x[i],以此類推,直至返回到取x[0]中的元素,此時改成從t取值而後終止過程。 spa
package ckj.chapter2.swap; public class MagicRotate extends Rotation { public MagicRotate(String input,int rotdist){ super(input,rotdist); this.m_rotname = "Magic Rotation"; } @Override public void rotate() { if (this.m_rotdist == 0 ) return; for ( int i = 0 ; i < gcd(this.m_rotdist,this.m_length) ; i ++){ /* move i-th values of blocks */ char t = m_input[i]; int j = i,k ; while(true){ k = j + m_rotdist ; if ( k >= m_length ) k -= m_length ; if (k == i) break; m_input[j] = m_input[k]; j = k; } m_input[j] = t; } } /** * To find the greatest common divisor between i & j * @param i * @param j * @return the greatest common divisor */ private int gcd(int i, int j) { if ( i < j) return gcd(j,i); if ( i%j == 0 ) return j; while (i != j){ return gcd(i%j,j); } return 0; } }第二種方法是塊旋轉。旋轉就是交換變量ab,獲得變量ba。假如a比b短,將b分爲b l和b r,使得 b r具備與a相同的長度,交換a與br 獲得br bl a ,而後最後交換br bl , (若是長度不同,重複上述步驟),最後就能獲得結果 bl br a 。
package ckj.chapter2.swap; public class MagicRotate extends Rotation { public MagicRotate(String input,int rotdist){ super(input,rotdist); this.m_rotname = "Magic Rotation"; } @Override public void rotate() { if (this.m_rotdist == 0 ) return; for ( int i = 0 ; i < gcd(this.m_rotdist,this.m_length) ; i ++){ /* move i-th values of blocks */ char t = m_input[i]; int j = i,k ; while(true){ k = j + m_rotdist ; if ( k >= m_length ) k -= m_length ; if (k == i) break; m_input[j] = m_input[k]; j = k; } m_input[j] = t; } } /** * To find the greatest common divisor between i & j * @param i * @param j * @return the greatest common divisor */ private int gcd(int i, int j) { if ( i < j) return gcd(j,i); if ( i%j == 0 ) return j; while (i != j){ return gcd(i%j,j); } return 0; } }
第三種方法是求逆方法。從ab開始,先對a求逆ar,再對b求逆br,最後在對arbr整個求逆(arbr)r 這個方法比較容易實現,晚一點的時候補上。 code
最後調用的主函數MainClass.java
package ckj.chapter2.swap; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; public class MainClass { private static final int _SIZE = 1000000; /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub char[] inputchar = new char[_SIZE]; /* produce a string repeated 123...789123...789123...789123... totally _SZIE digits */ for ( int i = 0 ; i < _SIZE ; i ++) inputchar[i] = (char)(i%9+49); String input = new String(inputchar); /* write the string to rotate in a file named "rotationString.txt" */ writeFile(input,false); for (int rotdist = 1; rotdist < 50; rotdist++) { System.out.println("No. " +rotdist+" ----->"); Rotation mr = new MagicRotate(input, rotdist); mr.rotCost(); //writeFile(new String(mr.m_input),true); mr = new BlockRotate(input, rotdist); mr.rotCost(); //writeFile(new String(mr.m_input),true); } } private static void writeFile(String input,boolean flag) { try { File file = new File("rotationString.txt"); FileOutputStream fos = new FileOutputStream(file,flag); OutputStreamWriter osw = new OutputStreamWriter(fos); BufferedWriter bw = new BufferedWriter(osw); bw.write(input); bw.newLine(); bw.newLine(); bw.close(); osw.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
對於c題,用到的思想就是「標識」。以標記的方式,對數據進行標記,而不是直接對內容進行搜索。例如單詞這裏,mississippi 標識能夠寫成「i4m1p2s4",分別表示字母出現的次數,字母要以升序排序。
package ckj.chapter2.words; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; public class WordManipulation { private Map<String, List<String>> result ; private String[] wordInput; public WordManipulation(String[] word){ this.wordInput = word; } /** * string sort to find a lable like (mississippi,i4m1p2s4) * @param word The string to be sorted * @return the string sorted */ private String sort(String word){ Map<Character,Integer> map = new TreeMap<Character,Integer>(); for ( int i = 0 ; i < word.length() ; i ++ ){ char temp = word.charAt(i); if ( map.get(temp) == null){ map.put(temp, 1); } else { int value = map.get(temp); map.put(temp, ++value); } } return map.toString(); } /** * squash the same label into a ArrayList * @param map */ private void squash(Map<String,String> map){ result = new TreeMap<String, List<String>>(); Set<Map.Entry<String,String>> entrySet = map.entrySet(); for (Map.Entry<String, String> entry:entrySet){ String strKey = entry.getKey(); String strValue = entry.getValue(); System.out.println(strKey+" ----> "+ strValue); List<String> resultList; if (result.get(strValue) == null){ resultList = new ArrayList<String>(); } else { resultList = result.get(strValue) ; } resultList.add(strKey); result.put(strValue, resultList); } } /** * calculate the anagram */ public void doCalculate(){ Map<String,String> temp = new TreeMap<String,String>(); for(int i = 0 ; i < this.wordInput.length ; i ++){ temp.put(this.wordInput[i], sort(this.wordInput[i])) ; } squash(temp); print(); } private void print(){ System.out.println(result.values()); } }
最後的主函數,能夠經過控制檯輸入想要的單詞中找到全部變位詞。以"over」做爲結束的單詞輸入。
文件讀寫WordsFile.java . 會將輸入的單詞中存入words.txt的文件中,之後的運行,能夠把主函數的wf.createWordsFile()語句註釋,能夠不用再輸入單詞。
package ckj.chapter2.words; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; public class WordsFile { private String fileName ; public WordsFile(){ this("words.txt"); } public WordsFile(String fileName){ this.fileName = fileName; } private void createWordFile(String fileName){ System.out.println("input your words you want to find the anagram (with typing 'over' to end):"); try { File file = new File(fileName); FileOutputStream fos = new FileOutputStream(file); OutputStreamWriter osw = new OutputStreamWriter(fos); BufferedWriter bw = new BufferedWriter(osw); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String s; while(!(s = br.readLine()).equalsIgnoreCase("over")){ bw.write(s); bw.newLine(); } bw.flush(); br.close(); bw.close(); osw.close(); fos.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void createWordFile(){ createWordFile(this.fileName); } private String[] readWordFile(String fileName){ try { File file = new File(fileName); FileInputStream fis = new FileInputStream(file); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); String s ; String result = ""; while((s=br.readLine())!=null){ result += s +" " ; } System.out.println("result---->"+result); br.close(); isr.close(); return result.split(" "); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public String[] readWordFile(){ return this.readWordFile(this.fileName); } }
主函數調用MainClass.java
package ckj.chapter2.words; public class MainClass { public static void main(String[] args) { WordsFile wf = new WordsFile(); /* barring it's the first time to run this program , you should run the next code . */ wf.createWordFile(); // WordManipulation wm = new WordManipulation(wf.readWordFile()); wm.doCalculate(); } }
本章的內容主要是兩個——「二分搜索」的高效 以及 「標識」等價關係的使用。