這兩天看到的內容是關於棧和佇列,在棧的模組發現了Dijkstra雙棧算術表示式求值演算法,可以用來實現計算器型別的app。
程式語言系統一般都內建了對算術表示式的處理,但是他們是如何在內部實現的呢?為了瞭解這個過程,我們可以自行搭建一套簡易的算術表示式處理機制,這裡就用到棧特性和本篇提到的Dijkstra演算法。
概述:
算術表示式可能是一個數、或者是由一個左括號、一個算術表示式、一個運算子、另一個算術表示式和一個右括號組成的表示式。為了簡化問題,這裡定義的是未省略括號的算術表示式,它明確地說明了所有運算子的運算元,形式如下:
(1+((2+3)*(4*5)))
思路:
表示式由括號、運算子和運算元構成,我們根據以下4中情況從左至右逐個將這些實體送入棧處理:
1.將運算元壓入運算元棧;
2.將運算子壓入運算子棧;
3.忽略左括號;
4.在遇到右括號時,彈出一個運算子,彈出所需數量的運算元,並將運算後的結果壓入運算元棧;
在處理完最後一個右括號時,運算元棧上只會剩下一個值,它就是表示式的計算結果。這種方法咋一看難理解,但要證明它能計算得到正確的值很簡單:
每當演算法遇到一個括號包圍,並由一個運算子和兩個運算元組成的子式時,他都將運算子和運算元運算結果壓入運算元棧。這樣的結果就像是在輸入中用這個值代替了該子表示式,因此用這個值代替子表示式得到的結果和原表示式相同。我們可以反覆應用這個規律並得到一個最終值。
例如:
(1+((2+3)*(4*5)))
(1+(5*(4*5)))
(1+(5*20))
(1+100)
101
程式碼實現:
這裡我採用C#來實現,最終執行效果完全符合預期,證明了此演算法的正確性,程式碼如下:
using System; using System.Collections.Generic; using System.Linq; namespace Evaluate { class Program { static void Main(string[] args) { string testExpress = "(1+((2+3)*(4*5)))"; Console.WriteLine(Evaluate(testExpress)); } //DijkStra static double Evaluate(string express) { var expressChars = express.ToArray<char>(); Stack<char> ops = new Stack<char>(); Stack<double> vals = new Stack<double>(); if (express.Length > 0) { foreach (var opt in expressChars) { switch (opt) { case '+': case '-': case '*': case '/': ops.Push(opt); break; case ')': var op = ops.Pop(); var v = vals.Pop(); switch (op) { case '+': v += vals.Pop(); break; case '-': v = vals.Pop() - v; break; case '*': v *= vals.Pop(); break; case '/': v = vals.Pop() / v; break; } vals.Push(v); break; case ' ': case '(': break; default: vals.Push(double.Parse(opt.ToString())); break; } } return vals.Pop(); } return double.MaxValue; } } }
總結:
Dijkstra演算法充分利用了棧的特性,具備較高的執行效率,經過進一步的擴充修改,就完全可以實現具備科學計算功能的複雜計算類app。如果大家還有更好的,更適用的演算法,歡迎在評論中給出地址,謝謝。