数据结构的大型实验,和之前写的计算器不同的是,少了词法解析,多了链表指针操作,写完感觉,指针操作能力上了一个台阶,有时间有兴趣的亲,也可以尝试一下,下附实验报告及源码。
1.实验内容
1.1实验目的
实验通过实现基于链表类实现的大数类,从而锻炼对链表的使用和有关大数的四则运算等操作,并在此过程中加深对类的理解和运用。
1.2类框架图
1.2.1 Node类
Node类:
1.pre和next都是Node* 类型的指针,分别指向该节点的前驱和后继。
2.num存该节点的数值,大数类实现中采取四位存一个节点的方式,考虑到short的范围,故将数值设置为short类型。
3.Node()是此类的无参构造。
1.2.2 myList类
myList类:
1.此类有first,tail,mySize三个数据成员,分别代表链表类的的头指针,尾指针和节点数(此链表带一个哨兵,哨兵不计节点数)。
2.myList()和myList(const&myList r)分别为无参构造函数和拷贝构造函数。
3.getSize()返回mySize的值。
4.add_back()和add_front()分别向链表后面和前面插入节点,向前插时,插在哨兵之后。
5.erase()删除尾指针指向的节点,当只剩哨兵时,不做操作。
6.display()因为是逆向存储的,故从后开始输出节点的值,特殊处理,第一个节点需去掉前置0。
7.~myList()析构函数,逐节点移动指针并释放内存。
1.2.3 BigInt类
BigInt类:
1.BigInt类共3个数据成员,sign,data,digit。sign仅共一次减法运算的符号判断,data即用于存储的myList类。Digit是该大数的位数。
2.该类包含BigInt(),BigInt(strings),BigInt(const &BigInt r)3个构造函数。
3.display(ostream &out)函数用于输出,可以通过改变参数,灵活地进行控制台输出或文件输出。
4.operator系列,其它都没什么特殊,只是因为‘^’操作不宜过大,故将次数值限定在了long long int范围。
5.operator- 因为需要判断正负,故在类的设计上加上了sign,来表示正负。
6.结构图中遗漏了析构函数,~BigInt()通过显示调用data的析构函数进行析构。
1.3程序流程图
2.实验验证
2.1输入形式
程序开始,需输入指令选择文件输入或键盘输入,其后选择是文件输出,还是屏幕显示。之后,便可输入表达式,表达式的形式需为 a ? b ,‘?’代表运算符,a,b分别为两大数,两两之间以空格分隔。当全部运算结束,最后输入Ctrl+Z表示结束。
2.2输出形式
示例:
输入: 1 + 2
输出: Digit:1 BigInt:3
2.3程序功能
可以较快计算大整数的加减乘除模,同时可以较快计算大整数的long long int型幂次方。支持文件/键盘读入,文件输出/屏幕显示。
3.调试分析
3.1技术难点
1.采用四位为一节点逆序存储,虽然能很大程度上加快运算速率,但实现上也颇为复杂。
示例: 123456789 6789->2345->1
2.因为采用的是四位存储模式,需要分别更新大数的位数,和链表的节点数。虽然看似简单,但在实际调试中,因为需要及时更新位数,稍有遗漏就会对大数之间的大小比较产生影响,从而导致整个程序出错。
3.因为不能直接在链表基础上进行四则运算,需要在大数类上对链表进行操作,需通过增加节点,删除节点等操作间接运算,又因体系规模略显庞大,调试过程很容易出错。
4.在函数内生成对象,函数一结束就会被析构,因此需要定义全局变量,或动态生成。
5.因为要提高除法的效率,但又受四位存储的限制,不能直接通过增加节点来使除数有效地逼近被除数,只能先通过增加节点的方式最大限度使除数逼近被除数,然后再用除数乘以一个int型整数的方法逼近被除数,同时将以上过程实现为循环。
3.2技术问题及解决方案
Problem1:
乘法效率过低。
Solution1:
采用四位存储模式。
Problem2:
除法效率过低。
Solution2:
通过先不断给除数D的拷贝对象A增加0节点,并同时给初始值为1的大整数B增加0节点,再将A乘以一个小整数的方式,找到一个大致接近被除数C的值A,不断循环减去A,并同时在答案上加上B,直至C<A。在外层循环不断重复该过程,直至被除数C小于除数D。
Problem3:
求幂效率过低。
Solution3:
类比矩阵多次幂的思想,利用已计算好的较低次幂的乘积直接求得高次幂。具体实现:以十进制为单位,计算一个数的1次,10次,100次,并循环该位上对应的数值次,将答案乘以现乘积……直至与幂的最高位等位。最后乘积即为所求。
示例: 2^421
ans=1
//对应个位1
tmp=2^1=2 ans*=tmp
//对应十位2
tmp=2^10=(2^1)^10ans*=tmp ans*=tmp
//对应百位4
tmp=2^100=(2^10)^10ans*=tmp ans*=tmp ans*=tmp ans*=tmp
//最后ans即为所求。
Problem4:对象当函数调用完时,会被析构。
Solution4:使用全局变量,或者动态生成变量。
3.3调试错误及修正方案
1.写链表类erase()函数时,应把最后一个节点的后继置为NULL,但却因失误多打了一个‘=’,导致链表遍历时,始终无法终止。一直以为是端点处理有问题,最后将‘=’去掉,就顺利通过了。
2.写除法时,始终无法跳出循环,以为是>=重载出了问题,仔细调试后发现是因为>=是基于大数位数判断的,在计算过程中未能及时修正位数,导致>=判断始终成立。随后,在减法操作中不断更新位数,最后顺利跳出循环。
3.局部变量被析构,采取全局变量或者堆区的方法。
4.测试示例
4.1输入输出示例
1.文件输入,文件输出