数据结构学习——栈

考试前复习下数据结构,把一些知识点整理在这!主要参考了殷人昆主编的《数据结构(用面向对象方法与C++语言描述)》这本书,以及中山大学刘聪老师的课件内容!


概念与性质

一些概念

栈是只允许在表的末端进行插入和删除的线性表。

  • 栈顶:允许插入和删除的一端
  • 栈底:不允许插入和删除的一端
  • 空栈:栈中没有任何元素的栈

栈的性质

由于栈是一种运算受限的线性表,只允许在表的一端进行插入删除,所以栈具有一些特殊的性质。给定栈S=(a1, a2, …, an),则最后加入栈的元素an为栈顶,按a1, a2, …, an的顺序进栈,出栈的顺序则刚好相反,即an先出栈,a1最后出栈。这样的性质可以概括为先进后出(FILO),或者叫后进先出(LIFO),两种说法都可以,但是我比较习惯FILO。

栈的操作

栈有几个主要的操作:

  • 建立一个空栈(构造函数Stack());
  • 在栈顶添加一个新元素,如果栈未满(push() );
  • 在栈顶删除一个元素,如果栈不空(pop() );
  • 检查栈是否空(isEmpty());
  • 取得栈顶元素,如果栈不空(Top())。

栈的抽象数据类型主要有两种存储表示:基于数组的存储表示(顺序栈)和基于链表的存储表示(链式栈)。


顺序栈

基于数组的存储表示实现的栈称为顺序栈。

数组实现

template <typename T>
class Stack {
  private:
    const static int MAX_ELEMENT_COUNT = 1000;
    int elementCount;
    T elements[MAX_ELEMENT_COUNT];

  public:
    Stack() : elementCount(0) {}

    bool isFull() {
        return elementCount >= MAX_ELEMENT_COUNT;
    }

    bool isEmpty() {
        return elementCount == 0;
    }

    void push(T element) {
        if (isFull()) {
            cout << "The stack is full. Can not push any element now." << endl;
            return;
        }
        elements[elementCount] = element;
        elementCount++;
    }

    void pop() {
        if (isEmpty()) {
            cout << "The stack is empty. Can not pop any element now." << endl;
            return;
        }
        elementCount--;
    }

    T top() {
        if (isEmpty()) {
            throw runtime_error("No data in stack.");
        }
        return elements[elementCount-1];
    }
};

vector实现

template <typename T>
class Stack {
  private:
    vector<T> elements;

  public:
    Stack() {}

    bool isEmpty() {
        return elements.size() == 0;
    }

    void push(T element) {
        elements.push_back(element);
    }

    void pop() {
        if (isEmpty()) {
            cout << "The stack is empty. Can not pop any element now." << endl;
            return;
        }
        elements.pop_back();
    }

    T top() {
        if (isEmpty()) {
            throw runtime_error("No data in stack.");
        }
        return elements[elements.size() - 1];
    }
};

链式栈

基于链表的存储表示实现的栈称为链式栈。

template <typename T>
class Stack {
  private:
    LinkedList<T> elements;

  public:
    Stack() {}

    bool isEmpty() {
        return elements.isEmpty();
    }

    void push(T element) {
        elements.addFirst(element);
    }

    void pop() {
        if (isEmpty()) {
            cout << "The stack is empty. Can not pop any element now." << endl;
            return;
        }
        elements.removeFirst();
    }

    T top() {
        if (isEmpty()) {
            throw runtime_error("No data in stack.");
        }
        return elements.getFirst();
    }
};

注:模板类LinkedList的实现在上一篇博客《数据结构学习——线性表》中,查看上一篇博客


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是关于应用(表达式求值)的学习笔记。 ## 1. 表达式求值的基本概念 ### 1.1 中缀表达式 中缀表达式是我们平时写的表达式,如:2 + 3 * 4 - 6 / 2。 ### 1.2 后缀表达式 后缀表达式也叫逆波兰表达式,它是一种不包含括号的表达式。在后缀表达式中,运算符在操作数的后面,因此也叫后缀表示法。例如,上面的中缀表达式的后缀表达式为:2 3 4 * + 6 2 / -。 ### 1.3 前缀表达式 前缀表达式也叫波兰式,它与后缀表达式类似,只是运算符在操作数的前面。例如,上面的中缀表达式的前缀表达式为:- + * 3 4 2 / 6 2。 ### 1.4 运算符优先级 在中缀表达式中,运算符有不同的优先级。通常,乘法和除法的优先级高于加法和减法。如果有括号,则括号内的表达式优先计算。 ### 1.5 中缀表达式转后缀表达式 将中缀表达式转换成后缀表达式的过程,也叫中缀表达式的后缀表达式化。具体的转换规则如下: - 遍历中缀表达式的每个元素。 - 如果当前元素是操作数,则将其加入后缀表达式中。 - 如果当前元素是运算符,则判断其与顶运算符的优先级,如果顶运算符优先级高于或等于当前运算符,则弹出顶运算符加入后缀表达式中,并继续比较下一个顶运算符,直到当前运算符的优先级高于顶运算符或为空时,将当前运算符入。 - 如果当前元素是左括号“(”,则直接入。 - 如果当前元素是右括号“)”,则依次弹出顶运算符加入后缀表达式中,直到遇到左括号为止,此时将左括号弹出,但不加入后缀表达式中。 ### 1.6 后缀表达式求值 将后缀表达式求值的过程,也叫后缀表达式的求值。具体的求值规则如下: - 遍历后缀表达式的每个元素。 - 如果当前元素是操作数,则将其入。 - 如果当前元素是运算符,则弹出顶的两个操作数,进行运算,并将运算结果入。 - 遍历完后缀表达式后,中只剩下一个元素,即为表达式的值。 ## 2. 表达式求值的实现 ### 2.1 中缀表达式转后缀表达式的实现 中缀表达式转后缀表达式可以使用来实现。具体的代码实现如下: ```cpp #include <iostream> #include <stack> #include <string> using namespace std; // 判断一个字符是否为操作符 bool isOperator(char c) { return c == '+' || c == '-' || c == '*' || c == '/'; } // 判断两个操作符的优先级 int getPriority(char op1, char op2) { if ((op1 == '*' || op1 == '/') && (op2 == '+' || op2 == '-')) { return -1; } else if ((op1 == '+' || op1 == '-') && (op2 == '*' || op2 == '/')) { return 1; } else { return 0; } } // 将中缀表达式转换成后缀表达式 string infixToPostfix(string infix) { stack<char> opStack; // 运算符 string postfix; // 后缀表达式 for (char c : infix) { if (isdigit(c)) { // 如果是数字,直接加入后缀表达式 postfix += c; } else if (isOperator(c)) { // 如果是操作符 while (!opStack.empty() && opStack.top() != '(' && getPriority(opStack.top(), c) >= 0) { postfix += opStack.top(); // 弹出顶操作符加入后缀表达式 opStack.pop(); } opStack.push(c); } else if (c == '(') { // 如果是左括号,直接入 opStack.push(c); } else if (c == ')') { // 如果是右括号 while (!opStack.empty() && opStack.top() != '(') { postfix += opStack.top(); // 弹出顶操作符加入后缀表达式 opStack.pop(); } opStack.pop(); // 弹出左括号 } } while (!opStack.empty()) { // 将剩余的操作符加入后缀表达式 postfix += opStack.top(); opStack.pop(); } return postfix; } int main() { string infix = "2+3*4-6/2"; string postfix = infixToPostfix(infix); cout << postfix << endl; // 输出后缀表达式:234*+62/- return 0; } ``` ### 2.2 后缀表达式求值的实现 后缀表达式求值也可以使用来实现。具体的代码实现如下: ```cpp #include <iostream> #include <stack> #include <string> using namespace std; // 判断一个字符是否为操作符 bool isOperator(char c) { return c == '+' || c == '-' || c == '*' || c == '/'; } // 计算两个操作数的运算结果 int calculate(int a, int b, char op) { if (op == '+') { return a + b; } else if (op == '-') { return a - b; } else if (op == '*') { return a * b; } else { return a / b; } } // 计算后缀表达式的值 int evaluate(string postfix) { stack<int> numStack; // 操作数 for (char c : postfix) { if (isdigit(c)) { // 如果是数字,将其转换成整数并入 int num = c - '0'; numStack.push(num); } else if (isOperator(c)) { // 如果是操作符 int b = numStack.top(); numStack.pop(); int a = numStack.top(); numStack.pop(); int result = calculate(a, b, c); numStack.push(result); } } return numStack.top(); } int main() { string postfix = "234*+62/-"; int result = evaluate(postfix); cout << result << endl; // 输出计算结果:8 return 0; } ``` ## 3. 总结 在表达式求值中的应用是很常见的,掌握了这个知识点,对于编写计算器等应用程序会有很大的帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值