設定java
程序要求:node
對帶符號整數 ,冪函數,表達式,三角函數,表達式因子組合而成的多項式進行求導運算。求導規則符合數學規則,鏈式求導,複合求導以及乘法求導。正則表達式
在本次做業中,空白字符包含且僅包含<space>和\t。編程
此外,值得注意的幾點是:數組
帶符號整數內不容許包含空白字符。
冪函數、項、表達式,在不與上一條矛盾的前提下,能夠在任意位置包含任意數量的空白字符。
若是某表達式存在不一樣的解釋方式,則只要有任意一條解釋中是合法的,該表達式即爲合法。數據結構
描述與斷定jvm
關於輸出,首先程序須要對輸入數據的合法性進行斷定。這道題在輸入處理上是一個關鍵,只有處理好輸入數據,才能進行下一步的求導工做。處理輸入在此次做業中運用到了正則表達式的匹配,以及遞歸法和正則表達式的混合使用。函數
若是是一組合法的輸入數據(即符合上述的表達式基本規則),則應當輸出一行,表示求出的導函數。求導函數則是對字符串進行加操做,在本次做業中運用了表達式樹求導,簡化了工做,至關於只對x和常數進行求導(其實常數沒有操做,直接求導爲零)。性能
解題思路:學習
1.從C語言到Java語言的思想轉化
學習面向對象思想,面向對象的三個基本特徵:繼承、封裝、多態。
用C語言寫程序的時候,咱們經常把程序寫成流程化形式,分析出解決問題所須要的步驟,而後用函數把這些步驟一步一步實現,使用的時候一個一個依次調用就能夠了(即面向過程編程);而面向對象編程是把構成問題事務分解成各個對象,創建對象的目的不是爲了完成一個步驟,而是爲了描敘某個事物在整個解決問題的步驟中的行爲。
舉個例子:(中國象棋遊戲)
(1)面向過程步驟就是:一、開始遊戲,二、紅方先下,三、判斷是否吃棋並繪製畫面,四、判斷輸贏,五、換黑方下棋,六、與步驟三、4同樣,八、返回步驟2,九、輸出最後獲勝結果。把上面每一個步驟按照順序依次用不一樣的函數來實現。
(2)面向對象則是:一、棋子對象(紅黑兩方),這兩方的行爲是如出一轍的,二、棋盤系統,負責繪製畫面,三、規則系統,判斷是否吃棋以及輸贏。面向對象是以功能來劃分問題,而不是步驟。
2.對象與類的定義
對象:是對客觀事物的抽象。
類:對對象的抽象。
或許對這兩個概念仍是很迷糊,(其實還會把本身給說繞了哈哈哈)。不過說個例子就能明白了。
例如:「女生」是一個類(class),「男生」也是一個類,但「你」和「我」是一個對象(emm我算撩你嗎?沒有對象怎麼面向對象編程)。
百度百科解釋:它們的關係是,對象是類的實例,類是對象的模板。對象是經過new className產生的,用來調用類的方法;類的構造方法 。
對象還具備屬性、方法(C語言的函數)之類的……
1 class 類名 2 { 3 public: 4 //公用的屬性和成員方法 5 protected: 6 //保護的屬性和成員方法 7 private: 8 //私有的屬性和成員方法 9 }
同一個包中公共類是能夠調用的,但私有類是不能夠調用的。本題我用到的類有主函數、表達式匹配、表達式樹、樹節點、求導類。但我並無用到繼承和接口,是我本身尚未學會使用,也以爲沒有必要用,因此繼承、多態這個特性沒有使用到。
3.Main方法的定義
public static void main(String[] args){ …… } public:表明該函數的訪問是最大的。 static:表明着主函數隨着類的加載就已經存在了。 void:主函數沒有具體的返回值。 main:不是關鍵字,是一個特殊的單從,能夠被jvm識別。 (String[] args):函數的參數,參數類型是一個數組,該數組中的元素是字符串,字符串類型的數組。
4.正則表達式的匹配
此題的關鍵就是利用正則表達式對輸入格式進行匹配,只有輸入的多項式符合格式,才能進行計算。
具體的正則表達式你們能夠百度或者博客搜索,有不少詳細的描述和講解。
正則表達式的匹配須要用到Java裏的 1 import java.util.regex.Matcher; 2 import java.util.regex.Pattern; 這兩個包。運用自帶的maches,find,lookAt等方法能夠達到很好的效果。對於最後一次嵌套表達式我是利用遞歸匹配的思想,有同窗利用了有限狀態機,這也是一個狀態轉換的過程。特別是第三次做業,我想了好幾個小時沒有思路,差點退卻了,但幾經思考,我嘗試遞歸,嘗試暴力,硬着頭皮寫出來了。(熬了兩天夜,debug到凌晨4點沒有效果也是正常的事),但值得一提的是,不少時候每一個的思考角度不一樣,因此debug的時候互相討論是很是有必要的,互相詢問應該存在的問題,這是一個頗有效的方式。
"([ \t]*[-+]?[ \t]*[-+]?((\\d+([ \t]*\\*[ \t]*x([ \t]*\\^" + "[ \t]*[-+]?\\d+)?)?)|([ \t]*x([ \t]*\\^[ \t]*[-+]?\\d+)?))" + "[ \t]*)([ \t]*[-+][ \t]*[-+]?((\\d+([ \t]*\\*[ \t]*x([ \t]*" + "\\^[ \t]*[-+]?\\d+)?)?)|([ \t]*x([ \t]*\\^[ \t]*[-+]?\\d+)?" + "))[ \t]*)*+";
5.求導以及合併同類項
求導在這個部分首先覺得會是最難的部分,但寫到後面的代碼時,發現求導並非最難的,而遞歸纔是問題,因而第三次求導做業我選擇了表達式樹,經過建樹來進行求導,大大簡化了求導的代碼,只保留了對x的求導,不過須要本身定義一個Node類,還須要用到棧的思想,這是數據結構與面向對象一塊兒使用的效果。因爲考慮到性能問題(就是求導結果的字符串長短問題),咱們須要對求導結果的字符串進行合併同類項,須要用到動態數組之類的方法,例如Arraylist與hashmap。
下面是Node類的定義:
1 public class Node { 2 private String data; 3 private Node lchild; 4 private Node rchild; 5 //private String ans; 6 7 public Node() { 8 } 9 10 public Node(String data) { 11 this.data = data; 12 this.lchild = null; 13 this.rchild = null; 14 } 15 16 public Node(String data, Node lchild, Node rchild) { 17 super(); 18 this.data = data; 19 this.lchild = lchild; 20 this.rchild = rchild; 21 } 22 23 public String getData() { 24 return data; 25 } 26 27 public Node getLchild() { 28 return lchild; 29 } 30 31 public Node getRchild() { 32 return rchild; 33 } 34 35 }
隨後就開始經過正確的表達式進行建樹,在建樹以前須要對錶達式處理一下,就是保證每一個式子都是兩個數與一個符號相鏈接,這樣才能造成表達式樹進行求解,這是求導真正部分的代碼:
1 public String Polyterm(String group) { 2 if (group.equals("x")) { 3 return "1"; 4 } else { 5 return "0"; 6 } 7 }
建樹:
1 public class Tree { 2 private String string = ""; 3 private Node root; //根節點 4 5 public String create(String str) { // create three 6 Stack<Node> poly = new Stack<Node>(); // tree stack 7 Stack<String> op = new Stack<String>(); // op stack 8 node(str, poly, op); 9 root = poly.peek(); // root node 10 //求導 11 return resovle(root); 12 } 13 }
因而就開始遍歷整個樹節點,根據符號進行相應的求導:
1 public String resovle(Node tree) { 2 Polyterm factor = new Polyterm(); 3 if (tree.getLchild() == null && tree.getRchild() == null) { 4 return factor.Polyterm(tree.getData()); 5 } 6 if (tree.getData().equals("+")) { 7 return "(" + resovle(tree.getLchild()) + "+" + 8 resovle(tree.getRchild()) + ")"; 9 } else if (tree.getData().equals("-")) { 10 return "(" + resovle(tree.getLchild()) + "-" + 11 resovle(tree.getRchild()) + ")"; 12 } else if (tree.getData().equals("*")) { 13 return "(" + resovle(tree.getLchild()) + "*(" + getTree( 14 tree.getRchild()) 15 + ")+" + resovle(tree.getRchild()) + "*(" 16 + getTree(tree.getLchild()) + "))"; 17 } else if (tree.getData().equals("^")) { 18 String a = new BigInteger(tree.getRchild().getData()). 19 subtract(BigInteger.ONE).toString(); 20 String ans = "((" + getTree(tree.getRchild()) + ")*" + getTree( 21 tree.getLchild()) + "^" + a + "*"; 22 return ans + resovle(tree.getLchild()) + ")"; 23 } else if (tree.getData().equals("s")) { 24 return "cos(" + getTree(tree.getRchild()) + ")*" 25 + resovle(tree.getRchild()); 26 } else { 27 return "(-sin(" + getTree(tree.getRchild()) + ")*" 28 + resovle(tree.getRchild()) + ")"; 29 } 30 }
6.本身遇到的問題及bug
1.空白字符比較坑,有可能你的程序運行報錯,多是匹配出現了問題;
2.因爲Matcher包的方法所限制,當「+」或「*」過多時,自測500個「+x」字符串就會致使棧溢出,此時可採用單步匹配,能夠解決問題,更建議使用獨佔模式(能夠搜索貪婪、懶惰、獨佔模式);
3.注意正則表達式的正確性,即合法格式;
4.輸出結果能夠爲最簡式,0次方不出現,1次方可省略等。
在互測的時候,我並無採起諸如評測機、對拍器等這種自動化簡單的方式,都是本身構造數據,而後人工驗證。自動化構造數據和檢驗數據確實是一個很高效而且不用花費力氣的方式,之後我還會改進,但其實本身構造數據更能達到debug的目的,能掌握對方bug所存在的點,而後進行hack。
因爲每一個人的代碼風格不一樣,在debug的時候會發現代碼很難讀下去,看不懂是很常有的事,並且對於本身的代碼寫得太少(沒有優化),別人的代碼很是長,就沒有興趣看代碼了。
首先考慮的是對方正則表達式是否匹配正確,格式有沒有考慮全,空白字符以及棧溢出的報錯信息(這個能夠運用我前面博客寫到的try...catch來捕獲);其次就是構造0次方,1次方,以及乘0,乘1,乘有前導0的表達式,但每一個人都用了大整數類型,對常數因子的處理仍是比較規範;在一個就是求導是否正確,是否多符號,少符號;最後就是合併是否正確,我我的是合併不多部分,有的地方沒有合併,因此表達式結果很長,但正確性能夠保證,有的同窗每每在合併的時候出了錯,這是不值的,寧願不要性能分,也要保證結果正確啊。
類圖:
代碼長度:
複雜度: