1. 程式人生 > >程式設計師修仙之路- CXO讓我做一個計算器!!

程式設計師修仙之路- CXO讓我做一個計算器!!

菜菜呀,個稅最近改革了,我得重新計算你的工資呀,我需要個計算器,你開發一個吧

CEO,CTO,CFO於一身的CXO

X總,咱不會買一個嗎?

菜菜

那不得花錢嗎,一塊錢也是錢呀··這個計算器支援加減乘除運算就行,很簡單

CEO,CTO,CFO於一身的CXO

(尼瑪)那能不能給我漲點工資呀?

菜菜

公司現在很困難,你這個計算器關係到公司的存亡,你要注意呀!!

CEO,CTO,CFO於一身的CXO

(關於撇開話題佩服的五體投地)好吧X總,我儘快做

菜菜

給你一天時間,我這裡著急要用

CEO,CTO,CFO於一身的CXO

.........

菜菜
CXO的需求果然還在繼續,深呼吸,深呼吸 .......

有人說資料結構是為演算法服務的,我還要在加一句:資料結構和演算法都是為業務服務的!!


CXO的需求果然不同凡響,又讓菜菜想到了新的資料結構:


◆◆
棧的特性
◆◆


定義

棧(stack)又名堆疊,它是一種運算受限的線性表。其限制是僅允許在表的一端進行插入和刪除運算。這一端被稱為棧頂,相對地,把另一端稱為棧底。向一個棧插入新元素又稱作進棧、入棧或壓棧,它是把新元素放到棧頂元素的上面,使之成為新的棧頂元素;從一個棧刪除元素又稱作出棧或退棧,它是把棧頂元素刪除掉,使其相鄰的元素成為新的棧頂元素。

棧作為一種資料結構,其中有幾個特性需要提起大家注意:


1.  操作受限:何為操作受限?在棧的操作中,一般語言中針對棧的操作只有兩種:入棧和出棧。並且操作只發生在棧的頂部。 有的同學會問,我用其他資料結構也一樣能實現棧的效果。不錯,但是每種資料結構都有自己的使用場景,沒有一種絕對無用的資料結構。

2.  棧在資料結構上屬於一種線性表,滿足後進先出的原則。這也是棧的最大特性,幾乎大部分後進先出的場景都可以使用棧這個容器。比如一個函式的呼叫過程中,區域性變數的儲存就是棧原理。當執行一個函式結束的時候,區域性變數其實最先釋放的是最後的區域性變數。



◆◆
實現
◆◆


        在記憶體分佈上棧是用是實現的呢?既然棧是一種線性結構,也就說可以用線性的記憶體分佈資料結構來實現。


1. 陣列實現棧(順序棧):陣列是在記憶體分佈上連續的一種資料結構。經過以前的學習,我們知道陣列的容量是不變的。如果業務上可以知道一個棧的元素的最大數量,我們完全可以用陣列來實現。為什麼這麼說?因為陣列的擴容在某些時候效能是比較低的。因為需要開闢新空間,併發生複製過程。

class MyStack

    {

        //陣列容器

        int[] container = new int[100];

        //棧頂元素的索引

        int TopIndex = -1;


        //入棧操作

        public void Push(int newValue)

        {

            if (TopIndex >= 99)

            {

                return ;

            }

            TopIndex++;

            container[TopIndex] = newValue;

        }

        //出棧操作

        public int Pop()

        {

            if (TopIndex < 0)

            {

                return 0;

            }

            var topValue = container[TopIndex];

            TopIndex--;

            return topValue;

        }

    }


2. 連結串列實現棧(鏈式棧):為了應對陣列的擴容問題,我們可以用連結串列來實現棧。棧的頂部元素永遠指向連結串列的頭元素即可。具體程式碼有興趣的同學可以實現一下


由以上可以看出,棧其實是基於基礎資料結構之上的一個具體業務形式的封裝即:先進後出。


◆◆
效能
◆◆


        基於陣列的棧我們暫且只討論未發生陣列重建的場景下。無論是陣列實現還是連結串列實現,我們發現棧的內部其實是有一個指向棧頂元素的指標,不會發生遍歷陣列或者連結串列的情形,所以棧的出棧操作時間複雜度為O(1)。

        至於入棧,如果你看過我以前介紹陣列和連結串列的文章,你可以知道,給一個數組下標元素賦值的操作時間複雜度為O(1),在連結串列頭部新增一個元素的操作時間複雜度也是O(1)。所以無論是陣列還是連結串列實現棧,入棧操作時間複雜度也是O(1)。並且棧只有入棧出棧兩種操作,比其他資料結構有N個操作方法要簡單很多,也不容易出錯。

        至於發生陣列重建,copy全部資料的過程其實是一個順序棧最壞的時間複雜度,因為和原陣列的元素個數n有關,所以時間複雜度為O(n)


◆◆
設計要點
◆◆


        那一個計算器怎麼用棧來實現呢?其實很多編譯器就是通過兩個棧來實現的,其中一個棧儲存操作的數,另一個棧儲存運算子。

        我們從左到右遍歷表示式,當遇到數字,我們直接壓入運算元棧;當遇到操作符的時候,當前操作符與操作符棧頂的元素比較優先順序(先乘除後加減的原則)。如果當前運算子比棧頂運算子優先順序高,那說明不需要執行棧頂運算子運算,我們直接將當前運算子也入棧;

        如果當前運算子比棧頂運算子優先順序低,那說明該執行棧頂運算子的運算了。然後出棧運算子棧頂元素,資料棧頂兩個元素,然後進行相關運算,然後把運算結果再次壓入資料棧。


◆◆
來一發吧
◆◆


 class Program
    {
        static void Main(string[] args)
        
{
            List<string> lstAllData = new List<string>();
            //讀取輸入的表示式,並整理
            string inputStr = Console.ReadLine();
            string tempData = "";
            for (int i = 0; i < inputStr.Length; i++)
            {
                if (inputStr[i] == '+' || inputStr[i] == '-' || inputStr[i] == '*' || inputStr[i] == '/')
                {
                    lstAllData.Add(tempData);
                    lstAllData.Add(inputStr[i].ToString());
                    tempData = "";
                }
                else
                {
                    tempData += inputStr[i];
                }
                if(i== inputStr.Length - 1)
                {
                    lstAllData.Add(tempData);
                }
            }
            foreach (var item in lstAllData)
            {
                Calculator.Cal(item.ToString());
            }
            var ret = Calculator.GetResult();
            Console.WriteLine(ret);
            Console.Read();
        }

    }
    //計算器
    class Calculator
    {
        //存放計算資料的棧
        static Stack<int> DataStack = new Stack<int>();
        //存放操作符的棧
        static Stack<string> OperatorStack = new Stack<string>();
        public static int Cal(string dataOrOperator)
        
{
            int data;
            bool isData = int.TryParse(dataOrOperator, out data);
            if (isData)
            {
                //如果是資料直接入資料棧
                DataStack.Push(data);
            }
            else
            {
                //如果是操作符,和棧頂操作符比較優先順序,如果大於棧頂,則直接入棧,否則棧頂元素出棧 進行操作
                if (OperatorStack.Count <= 0)
                {
                    OperatorStack.Push(dataOrOperator);
                }
                else
                {
                    //當前運算子的優先順序
                    var currentOpePrecedence = OperatorPrecedence(dataOrOperator);
                    //當前運算子棧頂元素的優先順序
                    var stackTopOpePrecedence = OperatorPrecedence(OperatorStack.Peek());
                    if (currentOpePrecedence > stackTopOpePrecedence)
                    {
                        //如果當前運算子的優先順序大於棧頂元素的優先順序,則入棧
                        OperatorStack.Push(dataOrOperator);
                    }
                    else
                    {
                        //運算子棧頂元素出棧,資料棧出棧兩個元素,然後進行運算
                        var stackOpe = OperatorStack.Pop();
                        var data2 = DataStack.Pop();
                        var data1 = DataStack.Pop();
                        var ret = CalculateData(stackOpe, data1, data2);
                        DataStack.Push(ret);
                        OperatorStack.Push(dataOrOperator);
                    }
                }
            }
            return 0;
        }
        //獲取表示式最後的計算結果
        public static int GetResult()
        
{
            var ret = 0;
            while (OperatorStack.Count > 0)
            {
                var stackOpe = OperatorStack.Pop();
                var data2 = DataStack.Pop();
                var data1 = DataStack.Pop();
                ret = CalculateData(stackOpe, data1, data2);
                DataStack.Push(ret);
            }
            return ret;
        }
        //根據操作符進行運算,這裡可以抽象出介面,請自行實現
        static int CalculateData(string operatorString, int data1, int data2)
        
{
            switch (operatorString)
            {
                case "+":
                    return data1 + data2;
                case "-":
                    return data1 - data2;
                case "*":
                    return data1 * data2;
                case "/":
                    return data1 + data2;
                default:
                    return 0;
            }
        }
        //獲取運算子優先順序
        public static int OperatorPrecedence(string a)    //操作符優先順序
        
{
            int i = 0;
            switch (a)
            {
                case "+": i = 1break;
                case "-": i = 1break;
                case "*": i = 2break;
                case "/": i = 2break;
            }
            return i;

        }
    }

執行結果:

10+20*3+10-10+20-20+60*2
190
golang版本
package stack

import (
    "errors"
    "fmt"
)

type Stack struct {
    Element []interface{} //Element
}

func NewStack() *Stack {
    return &Stack{}
}

func (stack *Stack) Push(value ...interface{}) {
    stack.Element = append(stack.Element, value...)
}

//返回下一個元素
func (stack *Stack) Top() (value interface{}) {
    if stack.Size() > 0 {
        return stack.Element[stack.Size()-1]
    }
    return nil //read empty stack
}

//返回下一個元素,並從Stack移除元素
func (stack *Stack) Pop() (value interface{}) {
    if stack.Size() > 0 {
        d := stack.Element[stack.Size()-1]
        stack.Element = stack.Element[:stack.Size()-1]
        return d
    }
    return nil
}

//交換值
func (stack *Stack) Swap(other *Stack) {
    switch {
    case stack.Size() == 0 && other.Size() == 0:
        return
    case other.Size() == 0:
        other.Element = stack.Element[:stack.Size()]
        stack.Element = nil
    case stack.Size() == 0:
        stack.Element = other.Element
        other.Element = nil
    default:
        stack.Element, other.Element = other.Element, stack.Element
    }
    return
}

//修改指定索引的元素
func (stack *Stack) Set(idx int, value interface{}) (err error) {
    if idx >= 0 && stack.Size() > 0 && stack.Size() > idx {
        stack.Element[idx] = value
        return nil
    }
    return errors.New("Set失敗!")
}

//返回指定索引的元素
func (stack *Stack) Get(idx int) (value interface{}) {
    if idx >= 0 && stack.Size() > 0 && stack.Size() > idx {
        return stack.Element[idx]
    }
    return nil //read empty stack
}

//Stack的size
func (stack *Stack) Size() int {
    return len(stack.Element)
}

//是否為空
func (stack *Stack) Empty() bool {
    if stack.Element == nil || stack.Size() == 0 {
        return true
    }
    return false
}

//列印
func (stack *Stack) Print() {
    for i := len(stack.Element) - 1; i >= 0; i-- {
        fmt.Println(i, "=>", stack.Element[i])
    }
}

package calculator

import (
    "calculator/stack"
    "strconv"
)

type Calculator struct{}

var DataStack *stack.Stack
var OperatorStack *stack.Stack

func NewCalculator() *Calculator {
    DataStack = stack.NewStack()
    OperatorStack = stack.NewStack()
    return &Calculator{}
}

func (c *Calculator) Cal(dataOrOperator string) int {

    if data, ok := strconv.ParseInt(dataOrOperator, 1064); ok == nil {
        //如果是資料直接入資料棧
        // fmt.Println(dataOrOperator)
        DataStack.Push(data)
    } else {

        //如果是操作符,和棧頂操作符比較優先順序,如果大於棧頂,則直接入棧,否則棧頂元素出棧 進行操作
        if OperatorStack.Size() <= 0 {
            OperatorStack.Push(dataOrOperator)
        } else {
            //當前運算子的優先順序
            currentOpePrecedence := operatorPrecedence(dataOrOperator)
            //當前運算子棧頂元素的優先順序
            stackTopOpePrecedence := operatorPrecedence(OperatorStack.Top().(string))
            if currentOpePrecedence > stackTopOpePrecedence {
                //如果當前運算子的優先順序大於棧頂元素的優先順序,則入棧
                OperatorStack.Push(dataOrOperator)
            } else {
                //運算子棧頂元素出棧,資料棧出棧兩個元素,然後進行運算
                stackOpe := OperatorStack.Pop()
                data2 := DataStack.Pop()
                data1 := DataStack.Pop()

                ret := calculateData(stackOpe.(string), data1.(int64), data2.(int64))
                DataStack.Push(ret)
                OperatorStack.Push(dataOrOperator)
            }
        }
    }
    return 0
}

func (c *Calculator) GetResult() int64 {
    var ret int64
    for {

        if OperatorStack.Size() > 0 {
            stackOpe := OperatorStack.Pop()
            data2 := DataStack.Pop()
            data1 := DataStack.Pop()

            ret = calculateData(stackOpe.(string), data1.(int64), data2.(int64))

            DataStack.Push(ret)
        } else {
            break
        }
    }

    return ret
}

func calculateData(operatorString string, data1, data2 int64) int64 {
    switch operatorString {
    case "+":
        return data1 + data2
    case "-":
        return data1 - data2
    case "*":
        return data1 * data2
    case "/":
        return data1 + data2
    default:
        return 0
    }
}

func operatorPrecedence(a string) int {
    i := 0
    switch a {
    case "+":
        i = 1
    case "-":
        i = 1
    case "*":
        i = 2
    case "/":
        i = 2
   &n