一.知识与技能
1.二元树来表示表达式
2.二叉链表
3.二元树的遍历
4.中缀表达式的特点
5.合理选用STL容器
二.功能描述
用二元树来表示函数表达式,通过对二元树进行递归处理,对表达式求值,并得到带必要括号的中缀表达式。
三.运行示例
四.知识和技能详解
1.二元树来表示表达式
可以用树表示一个二元的运算表达式,例如下面这棵树:
2.二叉链表
3.二元树的遍历
文章索引:二叉链表的存储结构和基本操作(各种遍历、求树深度、求树叶个数)_二叉链表存储结构_黑面宝宝的博客-CSDN博客4.中缀表达式的特点
5.STL容器的选择使用
一.什么是容器?
所谓容器,就是可以承载,包含元素的一个器件,它是STL六大组件之一,是容器、算法、迭代器中最重要也是最核心的一部分。
二、STL中各大容器的结构与分类
2.1 顺序性容器
2.1.1 什么是顺序性容器?
顺序性容器就是将一组具有相同类型的元素以严格的线性形式组织起来
2.1.2 有哪些顺序性容器?
这里给大家整理成了一个表格的形式,如下表所示
容器 | 简介说明 |
---|---|
vector | 可变大小数组。相当于数组,可动态构建,支持随机访问,无头插和尾插,仅支持inset插入,除尾部外的元素删除比较麻烦。但使用最为广泛 |
deque | 双端队列。支持头插、删,尾插、删,随机访问较vector容器来说慢,但对于首尾的数据操作比较方便 |
list | 双向循环链表。使用起来很高效,对于任意位置的插入和删除都很快,在操作过后,以后指针、迭代器、引用都不会失效 |
forward_list | 单向链表。只支持单向访问,在链表的任何位置进行插入/删除操作都非常快 |
array | 固定数组。vector的底层即为array数组,它保存了一个以严格顺序排列的特定数量的元素 |
2.1.3 顺序性容器在什么场合使用?
一般大多数的题目都可以使用vector容器,除非有特定需求使用其他容器更加合理方便;
如果需要在一串数字的头尾进行操作,偏向deque,对于较中间的元素操作,不推荐 ;
对于中间的元素插入或删除,可采用forward_list(单向链表)或list(双向链表),不需要移动元素,只需改变相关结点的指针域即可;
2.2 关联式容器
2.2.1 什么是关联式容器?
关联式容器每一个元素都有一个键值(key),对于二元关联容器,还拥有实值(value)容器中的元素顺序不能由程序员来决定,有set(集合)和map(映射)这两大类,它们均是以RB-Tree(red-black tree,红黑树)为底层架构。
2.2.2 有哪些关联式容器?
同样,以表格的形式呈现,如下表所示
容器 | 简介说明 |
---|---|
set/mutliset | 集合/多重集合。对于set,在使用insert插入元素时,已插入过的元素不可重复插入,这正好符合了集合的互异性,在插入完成显示后,会默认按照升序进行排序,对于multiset,可插入多个重复的元素 |
map/mutlimap | 映射/多重映射。二者均为二元关联容器(在构造时需要写两个参数类型,前者对key值,后者对应value值),因为有两个参数,因此在插入元素的时候需要配合对组pair进行插入,具体见深入详解 |
2.2.3 关联式容器在什么场合使用?
如果只负责查找内容,具体到某个单位,使用场景比如对手机游戏的个人的计分的存储,可以使用set或mutliset
如果需要同时放入容器的数据不止一个,并且是不同类型,比如一个为整型int,一个为string字符串型,就可以考虑使用map或mutlimap
2.3 容器适配器
2.3.1 什么是容器适配器?
容器适配器是一个封装了序列容器的一个类模板=,它在一般的序列容器的基础上提供了一些不同的功能。之所以称为容器适配器,是因为它是适配容器来提供其它不一样的功能。通过对应的容器和成员函数来实现我们需要的功能
2.3.2 有哪些容器适配器?
不必多说,看表
容器 | 简介说明 |
---|---|
stack | 堆栈。其原理是先进后出(FILO),其底层容器可以是任何标准的容器适配器,默认为deque双端队列 |
queue | 队列。其原理是先进先出(FIFO),只有队头和队尾可以被访问,故不可有遍历行为,默认也为deque双端队列 |
pirority_queue | 优先队列。它的第一个元素总是它所包含的元素中优先级最高的,就像数据结构里的堆,会默认形成大堆,还可以使用仿函数来控制生成大根堆还是生成小根堆,若没定义,默认使用vector容器 |
2.3.3 容器适配器在什么场合使用?
- 对于 stack 堆栈,在我们日常生活中类似于坐地铁、电梯;
- 对于 deque 队列,在我们日常生活中类似于排队打饭;
- 对于 pirority_queue,因为其本质是堆,可以考虑解决一些贪心问题;
本题代码展示:
#include <iostream>
#include <vector>
#include <fstream>
#include <map>
#include <stack>
#include <sstream>
#include <cmath>
using namespace std;
struct TreeNode {
string data; //数据域
TreeNode* left; //左子树
TreeNode* right; //右子树
TreeNode(const string& val) :
data(val), left(nullptr), right(nullptr) {
}
};
map< string, int > PriMap = {
{ "+", 3 },
{ "-", 3 },
{ "*", 7 },
{ "/", 7 }
};
int stringToInt(const string& str) {
stringstream ss(str);
int result;
ss >> result;
return result;
}
TreeNode* buildBinaryTree(vector<string>& tokens, int& index) {
if (index >= tokens.size()) {
return nullptr;
}
string token = tokens[index++];
if (token == "+" || token == "-" || token == "*" || token == "/" || token == "~" || token == "^") {
if (token == "~")
{
TreeNode* node = new TreeNode("-");
node->left = buildBinaryTree(tokens, index);
--index;
tokens[index] = to_string(stoi(tokens[index]) * 2);
node->right = buildBinaryTree(tokens, index);
return node;
}
else if (token == "^")
{
return new TreeNode(to_string((int)pow(stoi(tokens[index++]), 2)));
}
else
{
TreeNode* node = new TreeNode(token);
node->left = buildBinaryTree(tokens, index);
node->right = buildBinaryTree(tokens, index);
return node;
}
}
else {
return new TreeNode(token);
}
}
vector<string> splitString(const string& str) {
vector<string> tokens;
string token;
for (char c : str) {
if (c == ' ') {
if (!token.empty()) {
tokens.push_back(token);
token.clear();
}
}
else {
token += c;
}
}
if (!token.empty())
tokens.push_back(token);
return tokens;
}
string toInfix(TreeNode* root) {
if (root == nullptr) {
return "";
}
string infix = "";
string leftExpr = toInfix(root->left);
string rightExpr = toInfix(root->right);
// 根据操作符优先级判断是否需要添加左括号
if (root->left != nullptr &&
PriMap.count(root->left->data) != 0 &&
PriMap[root->data] >= PriMap[root->left->data]) {
infix += "(" + leftExpr + ")";
}
else
infix += leftExpr;
// 添加根节点
infix += root->data;
// 根据操作符优先级判断是否需要添加右括号
if (root->right != nullptr &&
PriMap.count(root->right->data) != 0 &&
PriMap[root->data] >= PriMap[root->right->data]) {
infix += "(" + rightExpr + ")";
}
else
infix += rightExpr;
return infix;
}
int ValueInfix(const vector<string>& tokens) {
stack<string> operators;
stack<int> operands;
for (const string& token : tokens) {
if (isdigit(token[0])) {
operands.push(stoi(token));
}
else if (token == "(") {
operators.push(token);
}
else if (token == ")") {
while (!operators.empty() && operators.top() != "(") {
int operand2 = operands.top();
operands.pop();
int operand1 = operands.top();
operands.pop();
string op = operators.top();
operators.pop();
int result;
if (op == "+")
result = operand1 + operand2;
else if (op == "-")
result = operand1 - operand2;
else if (op == "*")
result = operand1 * operand2;
else if (op == "/")
result = operand1 / operand2;
operands.push(result);
}
operators.pop(); //弹出"("
}
else {
while (!operators.empty() && PriMap[operators.top()] >= PriMap[token]) {
int operand2 = operands.top();
operands.pop();
int operand1 = operands.top();
operands.pop();
string op = operators.top();
operators.pop();
int result;
if (op == "+")
result = operand1 + operand2;
else if (op == "-")
result = operand1 - operand2;
else if (op == "*")
result = operand1 * operand2;
else if (op == "/")
result = operand1 / operand2;
operands.push(result);
}
operators.push(token);
}
}
while (!operators.empty()) {
int operand2 = operands.top();
operands.pop();
int operand1 = operands.top();
operands.pop();
string op = operators.top();
operators.pop();
int result;
if (op == "+")
result = operand1 + operand2;
else if (op == "-")
result = operand1 - operand2;
else if (op == "*")
result = operand1 * operand2;
else if (op == "/")
result = operand1 / operand2;
operands.push(result);
}
return operands.top();
}
int evaluate(istream& in) {
string token;
in >> token;
if (token == "+") {
return evaluate(in) + evaluate(in);
}
else if (token == "-") {
return evaluate(in) - evaluate(in);
}
else if (token == "*") {
return evaluate(in) * evaluate(in);
}
else if (token == "/") {
int a = evaluate(in);
int b = evaluate(in);
if (b == 0)
throw runtime_error("Divide by zero");
return a / b;
}
else if (token == "~")
return -evaluate(in);
else if (token == "^")
return (int)pow(evaluate(in), 2);
else {
return stoi(token);
//stoi 是一个 C++ 标准库函数,它用于将字符串转换为整数。它的名称是 “string to int” 的缩写。
//该函数接受一个字符串作为参数,并尝试将其转换为整数。如果转换成功,
//则返回转换后的整数值。如果转换失败(例如,如果字符串不是有效的整数表示形式),则抛出一个异常。
}
}
// Function to check if a character is an operator
bool isOperator(string c) {
return (c == "add" || c == "sub" || c == "muti" || c == "doubleMe" || c == "neg" || c == "div");
}
// Function to convert a prefix expression to an infix expression
string convertToPrefix(string input) {
map<string, string> convert;
convert["add"] = "+";
convert["sub"] = "-";
convert["muti"] = "*";
convert["doubleMe"] = "^";
convert["neg"] = "~";
convert["div"] = "/";
string result;
string temp;
for (int i = 0; i < input.length(); i++) {
if (input[i] != ' ') {
temp += input[i];
}
else {
if (isOperator(temp)) {
result += convert[temp] + " ";
}
else {
result += temp + " ";
}
temp = "";
}
}
if (isOperator(temp)) {
result += convert[temp];
}
else {
result += temp;
}
return result;
}
int main() {
ifstream inFile("question.txt");
ofstream outFile("answer.txt");
string prefix;
while (getline(inFile, prefix))
{
string convertedLine;
for (char c : prefix) //将)(,全都替换成空格
{
if (c == '(' || c == ')' || c == ',') {
convertedLine += ' ';
}
else {
convertedLine += c;
}
}
string prefix = convertToPrefix(convertedLine);//名称都替换成对应符号
//cout << prefix << endl;
vector<string> tokens;
tokens = splitString(prefix);//拆解字符串
int index = 0;
TreeNode* root = buildBinaryTree(tokens, index);
string infix = toInfix(root);
cout << "中缀表达式:" << infix << endl;
outFile << infix;
istringstream iss(prefix);
int result = evaluate(iss);
cout << "表达式的值:" << result << endl;
outFile << " = " << result << endl;
}
return 0;
}
五.代码分析
第一个是对二叉树的一个创建,当然这里我们用结构体来创建它,结构体中包含三个域,一个是数据域,一个是左子树域,一个是右子树域。
第二个就是对map容器的了解,这里map容器中的值是成对存在,第一个值是起到索引作用,第二个值是真实值。
第三个就是将字符类型整数化,这个操作比较容易理解,从输入流中读取信息,然后用整形进行转换即可;
第四个是将前缀表达式转换储存到二叉树中的代码;
第五个是字符串的分割;
第六个是对优先级的处理;
第七个是对二叉树的数据进行求值运算;
结束任务;