前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >程序员修仙之路-数据结构之 CXO让我做一个计算器

程序员修仙之路-数据结构之 CXO让我做一个计算器

作者头像
架构师修行之路
发布2019-07-23 16:33:04
3860
发布2019-07-23 16:33:04
举报
文章被收录于专栏:架构师
菜菜呀,个税最近改革了,我得重新计算你的工资呀,我需要个计算器,你开发一个吧

CEO,CTO,CFO于一身的CXO

X总,咱不会买一个吗?

菜菜

那不得花钱吗,一块钱也是钱呀··这个计算器支持加减乘除运算就行,很简单

菜菜

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)

◆◆

设计要点

◆◆

那一个计算器怎么用栈来实现呢?其实很多计算器就是通过两个栈来实现的,其中一个栈保存操作的数,另一个栈保存运算符。

我们从左到右遍历表达式,当遇到数字,我们直接压入操作数栈;当遇到操作符的时候,当前操作符与操作符栈顶的元素比较优先级(先乘除后加减的原则)。如果当前运算符比栈顶运算符优先级高,那说明不需要执行栈顶运算符运算,我们直接将当前运算符也入栈;

如果当前运算符比栈顶运算符优先级低,那说明该执行栈顶运算符的运算了。然后出栈运算符栈顶元素,数据栈顶两个元素,然后进行相关运算,然后把运算结果再次压入数据栈。

◆◆

来一发吧

◆◆

golang版本

特别鸣谢公司朋友亮亮提供golang代码

代码语言:javascript
复制
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, 10, 64); 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
    }
    return i
}
//========================分割线==============================//
package main

import (
    "calculator/calculator"
    "flag"
    "fmt"
)

var (
    inputStr = flag.String("input", "", "请输入...")
)

func main() {
    flag.Parse()

    var lstAllData []string
    var tempData string

    rs := []rune(*inputStr)
    for i := 0; i < len(rs); i++ {
        if string(rs[i]) == "+" || string(rs[i]) == "-" || string(rs[i]) == "*" || string(rs[i]) == "/" {
            lstAllData = append(lstAllData, tempData)
            lstAllData = append(lstAllData, string(rs[i]))
            tempData = ""
        } else {
            tempData += string(rs[i])
        }
        if i == len(rs)-1 {
            lstAllData = append(lstAllData, tempData)
        }
    }

    ca := calculator.NewCalculator()
    for _, v := range lstAllData {
        ca.Cal(v)
    }
    ret := ca.GetResult()
    fmt.Println(ret)
}

c#版本

代码语言:javascript
复制
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 = 1; break;
                case "-": i = 1; break;
                case "*": i = 2; break;
                case "/": i = 2; break;
            }
            return i;

        }
    }

●程序猿修仙之路--数据结构之设计高性能访客记录系统

程序猿修仙之路--算法之快速排序到底有多快

程序猿修仙之路--数据结构之你是否真的懂数组?

程序猿修仙之路--算法之希尔排序

程序员修仙之路--算法之插入排序

程序员修仙之路--算法之选择排序

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-01-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 架构师修行之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档