C++实现化学方程式配平

  • 0 引言
    • 1.1 设计思路
    • 1.2 程序功能介绍
    • 1.3 例子
    • 1.4 程序原理(流程图)
    • 2 结论
    • 3 核心源代码

0 引言

化学方程式是化学反应简明的表达形式,它从“质”和“量”两个方面表达了化学反应的意义。故化学方程式的书写是我们学习化学的过程中不可或缺的一个重要环节。当我们遇到简单的化学方程式例如:2H2 + O2 = 2H2O 时,配平则是毫无压力,但是若遇到类似Fe36Si5 + H3PO4 + K2CrO7 = FePO4 + SiO2 + K3PO4+ CrPO4 + H2O 这种元素种类繁多,化学计量数复杂的化学方程式,若仅依靠人力去配平可能是一件极为困难的事情。而计算机具有庞大的计算能力,故我们想到编写程序来解决复杂化学方程的配平。本程序将实现化学方程式和离子方程式的配平,并支持检测输入的方程式是否配平。

1.1 设计思路

对于我们来说,配平化学方程式主要利用元素守恒、化合价升降。而对于每一个化合物,其中元素的化合价都不尽相同,缺少一个标准,并且当我们自己去判断化合价时,也常会出现错误,故计算机难以利用化合价去配平方程式,所以元素守恒对于计算机是最佳的选择。而元素守恒配平方程式其本质就是解线性方程组,故我们只需将配平化学方程式转化成求n元线性方程组的解即可。
    首先我们需要知道化学方程式中有哪几种元素,并按ASCII码由上到下升序 排成一列,再读取每个反应物(生成物)中拥有每个元素的个数,最终形成一个元素矩阵 。
    其次,便是求解方程组。我们知道,求解n元线性方程组无非就是消元,先得到只含一个未知数的方程,再通过上述求解出的未知数逐步解出剩下的未知数。但是该方法存在一个问题,就是会有浮点数出现,而浮点数在计算机存储时会出现浮点错误 ,故我们需要避开浮点数。这里便用到解n元线性方程的另一种方法——克莱姆法则1 。但是使用克莱姆法则必须满足n个方程对应n个未知数,若未知数的个数比方程组的个数多1,我们便可以使用待定系数法,设其中一个未知数为1,达到减少未知数的效果;若是未知数的个数小于方程组的个数,我们便需要考虑这些多出来的方程组是否存在重复的,若无重复,则去掉多余的方程组仍然能求解出来;若存在重复,则需要进行去重 ,再去求解方程组;若未知数的个数比方程组的个数多2及以上,则该方程组有无穷组解,即无法配平。虽然克莱姆法则求出的解也是分数形式,但观察这些分数形式,不难得出这些分数的分母均为D,所以我们只需提前给所有解都乘上D,以防止分数的出现,再对结果进行约分即可。
    接下来我们要处理的是如何将这些系数正确地输出,在化学方程式中,若某个反应物(生成物)前面的化学计量数为1的话,则可以省略不写。故我们需要判断计算出的值是否为1,若为1则不输出,反之输出。
    最后,我们输出的反应物(生成物)的位置存在三种情况:1、等号左边 2、化学方程式的末尾 3、除1,2以外的情况。我们需要分别对上述三种情况进行不同的输出,若为情况1,则输出“反应物”+ “=”;若为情况2,则输出“生成物”;若为情况3,则输出“生成物(反应物)”+“+”。

1.2 程序功能介绍

该程序根据巴科斯范式2将化学方程式键入进计算机,若输入方程已配平,则程序会输出“已配平!”:

若未配平且输入正确,则会输出已配平的方程式并以空格隔开:
若方程式错误或无法求解,则会输出“ERROR: 无法求解!”:
按钮功能如下:

① :点击青蒿素的结构简式程序将自动清空输入、输出框的所有内容。
② :最小化窗口。
③ :弹出help窗口,如图

④ :关闭窗口,若HelpHelpHelp窗口此时处于打开状态,则会一并关闭。
⑤ :输入框。
⑥ :输出框。

1.3 例子

我们以下列方程式为例:KMnO4 + HCl = KCl + MnCl2 + Cl2 + H2O,归边写成KMnO4 + HCl - KCl - MnCl2 - Cl2 = H2O,接下来得到元素矩阵:

KMnO4 HCl KCl MnCl2 Cl2 H2O
K 1 0 1 0 0 0
Mn 1 0 0 -1 0 0
O 4 0 0 0 0 1
H 0 1 0 0 0 2
Cl 0 1 -1 -2 -2 0

然后通过待定系数使最后一列的系数为1,并利用高斯消元对行列式进行去重,得到矩阵如下:

再利用克莱姆法则计算出上述方程的解,再经过通分、约分得到最简整数解,最终完成配平方程式。

1.4 程序原理(流程图)

Created with Raphaël 2.3.0输入化学方程式(String)对字符串进行处理(去空格、读取系数、元素)转换为元素矩阵利用高斯消元去重,克莱姆法则求解是否有解?是否配平?输出"已配平!"输出配平结果输出"ERROR: 无法求解!"yesnoyesno

2 结论

本程序可以支持绝大多数的化学、离子方程配平和检测是否配平,其算法效率较高,并且通过我们的不断DebugDebugDebug使该程序趋于完善。但是仍存在一个问题,也是所有矩阵法配平的通病,就是对于(元素种类数-反应物数-生成物数)大于222的方程式是无法求解的,用方程表示就是(未知数个数-方程组个数)大于222,显然这样的方程有无穷多组解,对应化学方程式便是无法求出其具体系数。这个问题的本质是没有结合元素化合价升降去配平,导致条件不足,无法求出方程组的唯一解。

3 核心源代码

完整源代码地址

/*
* Author:AMT
* Date:2022.1.28
*/
#include<bits/stdc++.h>
#include<windows.h>
#define Get GetStdHandle(STD_OUTPUT_HANDLE)
#define White  FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
using namespace std;
const int N = 100;
int d1[N], d2[N];                                   // d1为最终系数,d2为原方程式的系数
int ma[N][N], ma1[N][N];                            //ma为增广矩阵,ma1为判重矩阵
map<string, int> elem;                                //用来存放对应元素的数量
map<string, int>::iterator  iter;                 //迭代器
string s;
string s1[N];                                       //用来储存每一个反应物和生成物
int left_sum = 0, right_sum = 0, iloc;                //分别记录反应物、生成物种类和等号的下标
int sum, D;                                     //sum=left_sum+right_sum,D为行列式的值
int flag = 1;                                      //1代表有解
inline int gcd(int a, int b) {return b == 0 ? a : gcd(b, a % b);
}
void ReadElem() {                                   //读取所有的物质for (register int i = 0; i < iloc; i++) {    //因为原子守恒所以只读取左边的string temp = "";if (isupper(s[i])) {if (islower(s[i + 1])) {temp = string("") + char(s[i]) + char(s[i + 1]);}elsetemp = string("") + char(s[i]);elem[temp] = 0;}else if (s[i] == '<')elem["e"] = 0;}
}
inline int E_GetInt(string temp, int pos) {pos++; int x = 0;if (isdigit(temp[pos])) {while (isdigit(temp[pos])) {x = (x << 1) + (x << 3) + int(char(temp[pos]) - '0');pos++;}}else x = 1;if (temp[pos] == '+') return x;else return ~(x - 1);
}
int check(int i) {                                  //判断正负return i < left_sum ? 1 : -1;
}
string check1(int k) {                              //判断系数是否为1return k == 1 ? "" : to_string(k);
}
void InitElem() {for (register int i = 0; i < s.length(); i++)if (s[i] == '=') {iloc = i;break;}int head = 0, count = -1;for (register int i = 0; i < s.length(); i++) {if (s[i] == '+' && s[i + 1] != '>') {if (i < iloc)left_sum++;else right_sum++;count += 1;s1[count] = s.substr(head, i - head);head = i + 1;}if (i == iloc) {                          //读到等号count += 1;s1[count] = s.substr(head, i - head);head = i + 1;}if (i == s.length() - 1) {                   //最后一个count += 1;s1[count] = s.substr(head, i + 1 - head);}}left_sum++, right_sum++;sum = left_sum + right_sum;//上述将化学式分为左式和右式,并将每一个反应物和生成物切片。s1[0,left_sum-1]为左式,s1[left_sum,sum-1]为右式
}
inline int GetInt(string temp, int pos) {            //读数pos++;if (islower(temp[pos]))pos++;                     //小写if (!isdigit(temp[pos]))return 1;               //非数字就是1else {int x = 0;while (isdigit(temp[pos])) {               //读元素后面的数字x = (x << 1) + (x << 3) + int(char(temp[pos]) - '0');pos++;}return x;}
}
void ToMatr() {for (register int i = 0; i < sum; i++) {       //遍历每一个反应(生成)物for (iter = elem.begin(); iter != elem.end(); iter++)iter->second = 0;                        //初始化mapstring temp = s1[i];                       //临时存贮int k1 = 1, k2 = 1;                         //分别代表()后的数字,[]后的数字。for (register int j = 0; j < temp.length(); j++) {if (temp[j] == '(') {int m = j + 1;while (temp[m] != ')')m++;k1 = GetInt(temp, m);}if (temp[j] == ')')k1 = 1;if (temp[j] == '[') {int m = j + 1;while (temp[m] != ']')m++;k2 = GetInt(temp, m);}if (temp[j] == ']')k2 = 1;if (isupper(temp[j])) {if (islower(temp[j + 1])) {string temp1 = string("") + char(temp[j]) + char(temp[j + 1]);elem[temp1] += GetInt(temp, j) * k1 * k2 * check(i);}else {string temp1 = string("") + char(temp[j]);elem[temp1] += GetInt(temp, j) * k1 * k2 * check(i);}}else if (temp[j] == '<')elem["e"] = E_GetInt(temp, j) * check(i);}iter = elem.begin();int x = 0;while (iter != elem.end()) {ma[x][i] = iter->second;x++;iter++;}}
}
int determinant(int n) {int ans = 1, v = 1;for (int i = 0; i < n; i++)              //列for (int j = i + 1; j < n; j++) { //行while (ma1[i][i]) {              //辗转相除消元int x = ma1[j][i] / ma1[i][i];for (int k = i; k < n; k++)            //转换成下三角行列式ma1[j][k] -= x * ma1[i][k];swap(ma1[i], ma1[j]), v = -v;       //性质 4}swap(ma1[i], ma1[j]), v = -v;}for (int i = 0; i < n; i++)ans *= ma1[i][i];           //对角线求值return ans * v;
}
void solve() {flag = 1;int ele = elem.size();for (register int i = 0; i < ele; i++) //默认最后一个生成物系数为1ma[i][sum - 1] *= -1;for (int i = 0; i < sum - 1; i++)                //列for (int j = i + 1; j < ele; j++) {   //行while (ma[i][i]) {int x = ma[j][i] / ma[i][i];for (int k = i; k < sum; k++)ma[j][k] -= x * ma[i][k];swap(ma[i], ma[j]);}swap(ma[i], ma[j]);}if (sum - ele >= 2) {flag = 0;return;}else {ele = min(ele, sum - 1);for (register int i = 0; i < ele; i++)for (register int j = 0; j < ele; j++)ma1[i][j] = ma[i][j];D = determinant(ele);                           //求行列式for (register int i = 0; i < ele; i++) {        //分别对sum-1个行列式求值for (register int j = 0; j < ele; j++)    //行for (register int k = 0; k < ele; k++) {//列if (k == i)   ma1[j][k] = ma[j][ele];else ma1[j][k] = ma[j][k];}d1[i] = determinant(ele);}d1[sum - 1] = D;int Gcd = d1[0];for (register int i = 1; i < sum; i++)Gcd = gcd(Gcd, d1[i]);if (Gcd == 0) {flag = 0;return;}for (register int i = 0; i < sum; i++) {d1[i] /= Gcd;if (d1[i] <= 0) {flag = 0;return;}}}
}
void InitAll() {s = "";elem.clear();left_sum = right_sum = iloc = D = 0;for (register int i = 0; i < N; i++) {d1[i] = 0;d2[i] = 1;s1[i] = "";for (register int j = 0; j < N; j++)ma1[i][j] = ma[i][j] = 0;}
}
void print(int i) {SetConsoleTextAttribute(Get, FOREGROUND_INTENSITY | FOREGROUND_RED);cout << check1(d1[i]);SetConsoleTextAttribute(Get, White);
}
string DelSpace(string s) {string ans = "";for (register int i = 0; i < s.length(); i++) {if (char(s[i]) != ' ')ans.push_back(char(s[i]));}return ans;
}
int main() {system("mode con cols=120 lines=30");ios::sync_with_stdio(false);cin.tie(0);system("color 0F");cout << "              ********************************************************************************************" << endl;cout << "              *                  简单化学(离子)方程式配平,支持检测是否配平                               *" << endl;cout << "              *       区分大小写,暂时不支持极少数氧化还原反应,有机方程请省略化学键,离子方程请用<>       *" << endl;cout << "              ********************************************************************************************" << endl;while (1) {InitAll();cout << "请输入正确的化学方程式: " << endl;getline(cin, s);s = DelSpace(s);InitElem();for (register int i = 0; i < sum; i++) {if (!isdigit(s1[i][0]))d2[i] = 1;else {int x = 0, pos = 0;while (isdigit(s1[i][pos])) {x = (x << 1) + (x << 3) + int(char(s1[i][pos]) - '0');pos++;}d2[i] = x;string m = to_string(x);s1[i] = s1[i].substr(m.length(), s1[i].length() - m.length());}}ReadElem();ToMatr();solve();if (flag) {int cnt = 0;for (cnt = 0; cnt < sum; cnt++)if (d1[cnt] != d2[cnt]) break;if (cnt < sum) {cout << "配平结果: ";for (register int i = 0; i < sum; i++) {if (i == left_sum - 1) {print(i);cout << s1[i] << '=';}else if (i == sum - 1) {print(i);cout << s1[i];}else {print(i);cout << s1[i] << '+';}}cout << endl;}elsecout << "已配平!" << endl;}else {SetConsoleTextAttribute(Get, FOREGROUND_INTENSITY | FOREGROUND_RED);cout << "ERROR: 无法求解!" << endl;SetConsoleTextAttribute(Get, White);}cout << endl;}return 0;
}

  1. nnn元线性方程式中:
    系数构成的行列式称为该方程组的系数行列式DDD,即:
    若线性方程组的系数矩阵可逆(非奇异),即系数行列式D≠0D\not =0D​=0,则线性方程组有唯一解,其解为:
    其中DjD_jDj​ (j=1,2,3…n)(j=1,2,3…n)(j=1,2,3…n)是把DDD中第jjj列元素对应地换成常数项而其余各列保持不变所得到的行列式。如: ↩︎

  2. 巴科斯范式(Backus−NaurformBackus-Naur formBackus−Naurform,BNFBNFBNF)给出的形式化定义如下:
    <equation> ::= <expr> "=" <expr>
    <expr> ::= <coef> <formula> | <expr> "+" <coef> <formula>
    <coef> ::= <digits> | "" | <electron>
    <electron>::= "<"<digits>"+>" | "<"<digits>"->" | ""
    <digits> ::= {<digit>}
    <digit> ::= "0" | "1" | ... | "9"
    <formula> ::= <term> <coef> | <formula> <term> <coef>
    <term> ::= <element> | "(" <formula> ")" | "[" <formula> "]"
    <element> ::= <uppercase> | <uppercase> <lowercase>
    <uppercase> ::= "A" | "B" | ... | "Z"
    <lowercase> ::= "a" | "b" | ... | "z" ↩︎

C++实现化学方程式配平相关推荐

  1. 用python实现化学方程式配平

    化学方程式配平是指调整化学反应的化学方程式,使得反应中所有的化学元素的原子数目在反应开始和反应结束时相等. 下面是一个简单的 Python 代码,用于配并化学方程式: ``` python 输入化学方 ...

  2. 【自己的小玩具程序】化学方程式配平【测试中】【未完成】

    化学元素周期表(Element.txt): 1    H    氢    1 2    He    氦    4 3    Li    锂    7 4    Be    铍    9 5    B  ...

  3. 有机物燃烧的化学方程式配平(洛谷P1994题题解,Java语言描述)

    题目要求 P1994题目链接 分析 我太菜了,亏得我高中时期还虐(or被虐)生化,如今多年过去竟然没分析出来坑点,我太菜了. 我开始盲目认为"元素守恒",所以对HHH原子数求和, ...

  4. CCF 2019-12 第三题 化学方程式配平(100分)

    题解: 字符串模拟   题目意思很明确就是判断化学方程式是否配平. 基本思路:就是对化学方程式左边和右边的元素进行计数,然后判断所有的元素的数量是否相等即可.   第一步:对字符串进行拆分       ...

  5. ccf化学方程式配平检验

    样例输入: 11 H2+O2=H2O 2H2+O2=2H2O H2+Cl2=2NaCl H2+Cl2=2HCl CH4+2O2=CO2+2H2O CaCl2+2AgNO3=Ca(NO3)2+2AgCl ...

  6. Day9 化学方程式配平

    该题的难点在于括号的嵌套,如Na6((OH)(OH)2)3,比如OH在跳出嵌套之后需乘以紧接着其外的嵌套下标,我的解法是先一遍处理以下找到每个左括号和与之配置的右括号的位置,然后使用递归,每遇到左括号 ...

  7. Python趣用—配平化学方程式

    不知不觉已经毕业多年了,不知道大家是否还记得怎么配平化学方程式呢?反正小编我是已经记不太清了,所以今天的文章除了分享如何用python配平化学方程式,顺带着还会复习一些化学方程式的知识,希望广大化学爱 ...

  8. 配平化学方程式的C++代码实现

    配平化学方程式的C++代码实现 纪念一下我今天写过了 20171006. (去年的这个时候我就有了这个大胆的想法, 当时的思路是:字符串处理->暴力搜系数,可是太年轻写不对,我那会还是个只会模拟 ...

  9. 用Python配平化学方程式

    可以使用 Python 的第三方库 Pybel 来配平化学方程式. 首先,需要安装 Pybel,可以使用以下命令进行安装: pipinstall pybel

  10. 不使用第三方库,用Python配平化学方程式

    非常好!我们可以使用 Python 内置的正则表达式库来帮助我们匹配化学方程式. 首先,我们可以使用正则表达式来匹配化学方程式的左右两边. 在这里,我们假设化学方程式的左边是一个或多个化学物质的名称, ...

最新文章

  1. UVA11384 Help is needed for Dexter (规律、思维)
  2. MySQL的btree索引和hash索引的区别
  3. 程序员面试题精选100题(40)-扑克牌的顺子[算法]
  4. OpenCV 击中或没打中 Hit-or-Miss/Hit-and-Miss
  5. Java web中使用JQuery加载某页面后,自动调用Servlet(GET方法,POST方法)
  6. 利用for...in...遍历js数组与Python异同
  7. C# 在数组中判断是否存在某个数组值
  8. 杰控连接mysql_杰控FameView软件首秦ERP系统L2级中的应用
  9. windows 游戏编程 一windows 编程
  10. python 东方财富接口_东方财富开放交易api,我只想要东方财富软件交易功能
  11. xp系统dns服务器有问题,xp系统dns错误的恢复方法
  12. 【验证码识别】极验滑动拼图验证码识别方案
  13. 用python做一个车牌识别_python利用百度云接口实现车牌识别
  14. C盘扩容(步骤记录)
  15. 【统计】时间序列预测之 Holt-Winters 指数平滑模型
  16. 【入门篇】ESP8266直连智能音箱(天猫精灵)控制智能灯
  17. 使用Cookies登录网站--登录豆瓣网站
  18. [MRCTF2020]Ez_bypass
  19. java河南口腔医疗机构线上服务系统计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
  20. 第三方支付结算周期T1,T0,D1,D0区别

热门文章

  1. html画表盘 随时间转动,利用css3+js实现的可拖动简易表盘时钟
  2. java 鸡尾酒排序_十大经典排序【Java实现,手工作坊式】
  3. 2018 亚马逊Awsome Day 见闻
  4. 上行带宽,下行带宽;上传速度,下载速度
  5. html编辑唐诗的格式,古诗竖着写的格式
  6. 希尔伯特:数学世界的亚历山大(2009-06-11 21:33:43)
  7. 网络共享计算机网络无法访问,局域网电脑无法访问共享文件网络共享失败怎么解决...
  8. 物联网现状及未来发展趋势
  9. 字节跳动实习生转正工资_日常实习 | 字节跳动西瓜视频招募运营实习生啦!
  10. windows系统部署docker文档