一.实习题目与要求
【问题描述】
设计一个一元稀疏多项式简单计算器。
【基本要求】
(1)输入并建立两个多项式;
(2)多项式a与b相加,建立和多项式c;
(3)多项式a与b相减,建立差多项式d;
(3)输出多项式a,b,c,d。输出格式:比如多项式a为:A(x)=c1xe1+ c2xe2+… + cmxem,其中,ci和ei分别为第i项的系数和指数,且各项按指数的升幂 排列,即0≤e1<e2<…<em。
【测试数据】
(1)(1+x+x2+x3+x4+x5)+(-x3-x4)=(1+x+x2+x5)
(2)(x+x100)+(x100+x200)=(x+2x100+x200)
(3)(2x+5x8-3x11)+(7-5x8+11x9)=(7+2x+11x9-3x11)
(4)(6x-3-x+4.4x2-1.2x9)-(-6x-3+4.4x2+7.8x15)
=(12x-3-x-1.2x9-7.8x15)
【实现提示】
(1)用带头结点的单链表存储多项式。
(2)三个多项式链表中都只存储非零系数项。若多项式a与b中指数相等 的两项相加后,系数为零,则在和多项式c中不存储该指数项。
【拓展内容】
计算器的仿真界面。
二.需求分析
问题描述:输入任意两个多项式,输出这两个多项式的和及差
系统环境:visual studio 2017
运行要求:手动创建一个名为“多项式测试数据”的文本文件
三.概要设计
数据结构:链表
存储结构:头结点放在类里,实例化后存储在栈里,多项式的每个项放在堆里
算法设计:顺序取源多项式的每一项,然后遍历目标多项式。
如果找到指数一样的项,便直接对目标多项式的系数进行加减;
如果找到指数更大的项,便插入到该项的前面。
模块设计:term类:对项的描述,项的属性与方法
Polynome类:对多项式的描述,多项式的头结点和操作
Main函数:从文件里取数据,输出多项式相加相减结果
四.详细设计
类设计:
Term类:
数据成员:ceof:项的系数;exp:项的指数;next:下一项的地址
函数成员:构造函数term():对类成员列表初始化
析构函数:默认
Polynome类:
数据成员:first:链表头结点,串起多项式的每一项
函数成员:
构造函数:对从文件里读来的多项式string形式进行分析,初始化整个链表
复制构造函数:复制一个新对象,且新对象的first指针 也指向一个相同的链表
析构函数:从头结点出发,delete后面的项结点
静态 convert函数:在构造函数里读到的字符数组转换为数字
其他模块:
Term类的友元函数:
<<运算符重载:输出项
<<运算符重载:输出多项式
+运算符重载:多项式相加
-运算符重载:多项式相减
Polynome类的友元函数:
<<运算符重载:输出多项式
+运算符重载:多项式相加
-运算符重载:多项式相减
五.调试分析
调试过程中遇到的问题:
1类型不匹配:把字符写成字符串数组,判断相等,把””改成‘’
2在类外部使用类的私有成员:把类外部的函数或者类设为该类的 友元函数或者友元类
3const对象赋给一个非const对象:在类的接口函数形参列表后加 上const限定符
4访问未知空间导致程序异常终止:返回的类对象在调用<<重载符 之前已被析构,用一个对象接收返回的对象,不要使用一个临时对象
5vector访问越界:vector遍历时将遍历参数的类型声明为int, 而vector的下标类型是size_t,是unsigned,这样会导致越界。改unsigned。
对设计和编码的回顾:从string对象读取有用的信息存下来是很麻烦的一 件事,在这个过程中,是很多次不断的调式与检查,也是最耗费时间的模块
算法的时间复杂度和空间复杂度:
构造函数里读字符串的算法:
时间复杂度:O(n),n表示字符串的长短,
空间复杂度:O(n),n表示字符串的长短
多项式加减算法:
时间复杂度:O(mn),m和n分别表示目标多项式和源多项式的项数
空间复杂度:O(m+n),同上
算法的进一步改进:
读字符串的算法。
1可以减少时间复杂度的常数级。可以尽量少些一些if分支,尽量让一个分支能顾及到多种情况,如只有常数项和一次项,这两者都没有指数项。这样也能使程序结构更加清晰。
2可以减少空间复杂度的常数级。通过父循环和子循环的合理调配和循环条件的改变,可以少一些flag标志。
多项式的算法:
3可以减少时间复杂度的一次项。由于输入的多项式每一项都是升幂排列,每次插入目标多项式时的后一项可以把上一次插入的位置标志下来,这样不必每次都要丛头开始遍历。
4可以减少空间复杂度到O(m).可以不用再初始化一个对象来赋值目标多项式,直接在引用上进行操作。但这样会破坏目标多项式。所以这样做也有不足之处。
六.使用说明
1要创建一个名为“多项式测试数据的文件”
2在向文件输入时按照普通的多项式格式输入就行,指数用^符号分隔
3输入时一个多项式之间要紧密输入,不能有空格。
4目标多项式放在第一行,源多项式放在第二行。然后另起一行,用#隔开,输入下一组数据。最后一行无需用#
5程序会自动输出每一组的和多项式和差多项式,组与组之间是用空格隔开的
七.测试结果
下面附上测试数据
1+x+x^2+x^3+x^4+x^5
-x^3-x^4
#
x+x^100
x^100+x^200
#
2x+5x^8-3x^11
7-5x^8+11x^9
#
6x^-3-x+4.4x^2-1.2x^9
-6x^-3+4.4x^2+7.8x^15
#
5.8x^4-7.855x^-2+2x^2+3x^-1
2x^-1-4.8x^4+x
八.源程序清单
friend.h
#pragma once
#include "term.h"
#include "polynome.h"
using namespace std;
ostream& operator <<(ostream&, const term&);
ostream& operator <<(ostream&, const polynome&);
polynome operator +(polynome, const polynome);
polynome operator -(polynome, const polynome);
friend.cpp
#include "friend.h"
ostream& operator <<(ostream& out, const term& x) {
if ((x.ceof == 1 || x.ceof == -1) && x.exp != 0) {
if (x.ceof == -1) {
out << "-";
}
if (x.exp == 1) out << "x";
else out << "x^" << x.exp;
return out;
}
if (x.ceof == 0) return out;
out << x.ceof;//注意:这里不加setflags(ios::fixed)
if (x.exp == 0) return out;
if (x.exp == 1) out << "x";
else out << "x^" << x.exp;
return out;
}
ostream& operator <<(ostream&out, const polynome&p) {
bool h = 0;
for (term* cur = p.first->next; cur != NULL; cur = cur->next) {
if (h == 1) {
if (cur->ceof > 0) out << "+";
}
h = 1;
out << *cur;
}
return out;
}
polynome operator +(polynome p1, const polynome p2) {
polynome temp;
term *x1 = p1.first->next, *x2 = p2.first->next, *last = temp.first;
while (1) {//对于顶层const,是否类型一致都可以赋值//但是对于底层const,const = 非const,
if (x1 == NULL) {
last->next = x2;
break;
}
if (x2 == NULL) {
last->next = x1;
break;
}
if (x1->exp < x2->exp) {
last->next = x1;
last = x1;
x1 = x1->next;
continue;
}
if (x1->exp > x2->exp) {
last->next = x2;
last = x2;
x2 = x2->next;
continue;
}
if (x1->exp == x2->exp) {
x1->ceof += x2->ceof;
last->next = x1;
last = x1;
term *del = x2;
x2 = x2->next;
delete del;
x1 = x1->next;
}
}
(p1.first)->next = NULL;
(p2.first)->next = NULL;//析构函数不会重复析构同一个堆空间,注意析构顺序
return temp;//明确析构顺序
}
polynome operator -(polynome p1, const polynome p2) {
for (term* i = p2.first->next; i != NULL; i = i->next) {
i->ceof = -i->ceof;
}
return (p1 + p2);
}
polynome.h
#pragma once
#include <iostream>
#include "term.h"
#include <cmath>
#include <cctype>
#include <vector>
#include <string>
using namespace std;
class polynome {
public:
polynome(const string&);
polynome() = default;
polynome(const polynome&);
~polynome();
static double convert(const vector<int>&, bool);
void sort();
friend ostream& operator <<(ostream&, const polynome&);
friend polynome operator +(polynome, const polynome);
friend polynome operator -(polynome, const polynome);
private:
term* first = new term(0, -1,NULL);
};
polynome.cpp
#include "polynome.h"
double polynome::convert(const vector<int>& v, bool sign) {
if (v.empty()) return 0;
double result = 0;
unsigned x = v.size();
for (unsigned i = 0; i < v.size(); ++i) {//写了个等号,导致vector访问越界,当然,vector访问越界还有几种情况
result += v[i] * pow(10, x - i - 1);
}
if (sign == 0) return -result;
return result;
}
void polynome::sort() {
term *p1, *p2, *p;
for (term* end = NULL; end != first; end = p) {
for (p = p1 = first; p1->next->next != end; p1 = p1->next) {
if (p1->next->exp > p1->next->next->exp) {
p2 = p1->next->next;
p1->next->next = p2->next;
p2->next = p1->next;
p1->next = p2;
p = p1->next->next;
}
}
}
}
polynome::polynome(const string& express) {
term *cur = first;//注意类内初始化和构造函数调用的先后顺序
//从文件里读出一个项来
double ceof;
int exp;
bool ceofSign = 1;
string::const_iterator p = express.begin();
while (p != express.end()) {
vector<int> s1, s2, addit;
//判断符号
if (*p == '-' || *p == '+') {
if (*p == '-') ceofSign = 0;
if (*p == '+') ceofSign = 1;
p++;
}
//读一个项的系数的整数部分
for (; isdigit(*p); ++p) {///"+"和"-"的区别,这里不能改成isdigit()
s1.push_back(*p - 48);
continue;
}
if (p != express.end() && *p == '.') {
p++;
//读一个项的系数的小数部分
while (p != express.end() && isdigit(*p)) {//若是读到一个小数点
addit.push_back(*p - 48);
p++;
continue;
}
}
//如果只读到一个常数,直接继续读.为了避免只有一个多项式只有一个常数的情况,
if (p != express.end() && (*p == '+' || *p == '-')) {//在大while循环里加上判断尾部判断
double result = convert(s1, ceofSign) + convert(addit, ceofSign) / pow(10, addit.size());
term* temp = new term(result, 0, NULL);
cur->next = temp;
cur = temp;
continue;
}
//跳过字母
p++;
//读一个项的指数,注意符号
bool expSign = 1;
if (p != express.end() && *p == '^') {
p++;
if (p != express.end() && *p == '-') {
expSign = 0;
++p;
}
for (; p != express.end() && isdigit(*p); ++p) { //操作类型不兼容,字符串和字符的区别
s2.push_back(*p - 48);
}
}
//对收集到的指数字符进行转换
if (s2.empty()) exp = 1;
else exp = (int)convert(s2, expSign);
//对收集到的系数字符进行转换
if (s1.empty()) {
if (ceofSign == 1) ceof = 1;
else ceof = -1;
}
else {
if (addit.empty()) ceof = convert(s1, ceofSign);
else {
double deci = convert(addit, ceofSign) / (int)pow(10, (int)addit.size());//unsigned可以自动转化为double,因为double是较高的类型
ceof = convert(s1, ceofSign) + deci;
}
}
//连接链表
term* temp = new term(ceof, exp, NULL);
cur->next = temp;
cur = temp;
}
sort();
}
polynome::polynome(const polynome&T) {
//first = T.first;就是这一句,害我找了两个星期
first = new term(0, -1,NULL);/
term* newNode = first;
for (term* cur = T.first->next; cur != NULL; cur = cur->next) {
term* temp = new term(*cur);
newNode->next = temp;
newNode = temp;
}
newNode->next = NULL;
}
polynome::~polynome() {
term *del, *temp;
temp = first;
while (1) {
del = temp;
temp = temp->next;
delete del;
if (temp == NULL) break;
}
}
term.h
#pragma once
#include <iostream>
using namespace std;
class polynome;//这个前向声明感觉可以用头文件来代替,先暂且不用
class term {
friend class polynome;
public:
term(double, int, term* );
~term();
friend ostream& operator <<(ostream&, const term&);
friend ostream& operator <<(ostream&, const polynome&);
friend polynome operator +(polynome, const polynome);
friend polynome operator -(polynome, const polynome);
private:
double ceof;
int exp;
term* next;
};
term.cpp
#include "term.h"
term::term(double ceof, int exp, term* next = NULL) :ceof(ceof), exp(exp), next(next) {}
term::~term() = default;
main.cpp
#include <iostream>
#include "polynome.h"
#include <fstream>
#include <string>
#include <cstdlib>
using namespace std;
int main() {
string flag;
ifstream infile("多项式测试数据.txt", ios::in);
do {
string str1, str2;
getline(infile, str1);
getline(infile, str2);
polynome pol1(str1), pol2(str2);
polynome add = pol1 + pol2;
polynome sub = pol1 - pol2;//发现两个错误
cout << add << endl;
cout << sub << endl;//这里注意,暂时值会被析构函数析构,导致重载函数访问一个未定义的空间
cout << endl;
} while (infile.good() && getline(infile, flag) && flag == "#");
system("pause");
}