武汉大学/遥感信息工程学院/数据结构与算法作业(黄玉春班)/极大数链式表储存&相加
主要任务
要求: 1)参照书上多项式相加的示例算法,实现以链表形式存储的超大+进制数字的加法,要求加法计算结果正确,符合+
进制进位规则; 2) 从input.txt中读入两个超大数字, 加法计算结果追加记录到input.txt,且输出结果要求从数位高到低的顺序排列。
作业难度主要分布
1.从一个文件中读取
2.读取数字的办法
3.如何加起来
4.如何按顺序输出
申明:正文部分展示部分代码,结尾是完整代码&我是新手,欢迎批评、指正、建议或分享
1.从“一个”文件中“读取”
我首先是做的从俩文件中读取数字的版本,不用判断空格‘\ ’或者是换行’\n’,而且分别读只用写一个函数用两次就行。而从一个文件读取会考虑更多,包括函数形参设置,数值传回等等,在循环过程中的一些小点也要重改。
2.读取数字 的办法
由于数字太大,所以读取有两种:①按int用“%1d”来一个一个地读取,但是不能满足“识别空格/换行“的功能,但又要求从一个文件中读,所以不适用。②按照char型一个一个地读进数字和空格,但是读数字时注意一个数字被按char读入后并不是这个数字。
大致代码如下:
Status ReadFile(head &L1, head& L2,char* FileAdress, int& digits1, int& digits2) {
FILE* fp;
char bowl='\0';
DigandVal* needle1=0x00;
DigandVal* needle2 = 0x00;
fopen_s(&fp, FileAdress, "rt");
if (!fp)
return ERROR;
int i1 = 0, i2 = 0;
while (!feof(fp)) {
fscanf_s(fp,"%c", &bowl);
if (bowl != '\ ') {
bowl = (int)bowl;
InitialList_L(needle1);
needle1->Position = i1;
needle1->Value = bowl;
needle1->next = L1->next;
L1->next = needle1;
bowl = '\0';
}
else
break;
++i1;
}
int flag1;
for (flag1 = 1; flag1 <= i1; flag1++) {
needle1->Position = flag1;
needle1 = needle1->next;
}
digits1 = i1;
bowl = '\0';
while (!feof(fp)) {
fscanf_s(fp, "%c", &bowl);
if (bowl != '\ ') {
bowl = (int)bowl;
InitialList_L(needle2);
needle2->Position = i2;
needle2->Value = bowl;
needle2->next = L2->next;
L2->next = needle2;
bowl = '\0';
}
else
break;
++i2;
}
L2->next = needle2->next;
delete needle2;
needle2 = L2->next;
i2--;
int flag2;
for (flag2 = 1; flag2 <= i2; flag2++) {
needle2->Position = flag2;
needle2 = needle2->next;
}
digits2 = i2;
fclose(fp);
return OK;
}
下面这是先写的一个从单个文件中读取数的函数(后面在调整顺序时会用)
//读取文件,并存入链表,并返回位数
Status ReadFileSingle(head& L, char* FileAdress, int& digits) {
FILE* fp;
char bowl = '\0';
int replace = 0;
DigandVal* needle = 0x00;
fopen_s(&fp, FileAdress, "rt");
if (!fp)
return ERROR;
int i = 0;
while (!feof(fp)) {
++i;
fscanf_s(fp, "%1d", &replace);
bowl = replace + 48;
InitialList_L(needle);
needle->Position = i;
needle->Value = bowl;
needle->next = L->next;
L->next = needle;
bowl = '\0';
}
L->next = needle->next;
delete needle;
needle = L->next;
i--;
int flag;
for (flag = 1; flag <= i; flag++) {
needle->Position = flag;
needle = needle->next;
}
digits = i;
fclose(fp);
return OK;
}
这里读文件我遇到了一个以前不知道的问题:当文件以数字结尾,不管是以int类型读入还是char类型读入,只要是在未知位数的情况下用feof判断是否读完,都会多读一次(在我的实操中我选择在该过程结束后手动删除最后加入的结点内容)。
对于第一个链表的写入不会有上述注意点
3.如何“加起来”
为了图方便,我在逆读入文件,顺输出数字和顺读入文件,逆输出数字中选择了后者。因为存在进位情况,所以最好按照“最大数位加一”来完善尚未相加地两数,然后两数相加,将结果存入其中一链。最后在将结果输入文件时需要判断数字最高位是否为零。
//将数据补成相同的位数:位数最大的数的最大位前面填一个0,其他的通过加零来达到相同的位数
Status CompleteFormat(int originaldigit,int expandeddigits, head &L) {
head p;
p = L->next;//p指向第一个结点
//找到最后一个结点
for (int i = 1; i <= originaldigit; i++) {
if(i!= originaldigit)
p = p->next;
}
//开始创建新添结点
head s;
for (int howmanyleft = expandeddigits - originaldigit; howmanyleft > 0; howmanyleft--) {
InitialList_L(s);
s->Position = originaldigit + 1;
s->Value = 0+48;
p->next = s;
p = p->next;
}
return OK;
}
//将两个数从个位相加,并在每一次循环中删除其中一列的某一位,最后结果用位数大的那一个链表表示出来
Status AddUp(head &HeadNo1,head &HeadNo2, int expandeddigits) {
head p1pre,p1, p2;
p1 = HeadNo1->next;
p2 = HeadNo2->next;
int a1=0,a2=0,ultimate=0,carry=0;
for (int flag = expandeddigits; flag > 0; flag--) {
a1 = p1->Value - 48;
a2 = p2->Value - 48;
ultimate = (a1 + a2 + carry) % 10;
carry = (a1 + a2 + carry - ultimate) / 10;
p2->Value = ultimate + 48;
p1 = p1->next;
p2 = p2->next;
}
以下是我在main函数里判断我供进位的那个结点值是否为零的代码
//检查2号链表的最后有没有0
checkforzero = L2->next;
for (int flag3 = n3; flag3 > 2; flag3--) {
checkforzero = checkforzero->next;
}
checkzeropartner = checkforzero->next;
if (checkzeropartner->Value == 48) {
checkforzero->next = NULL;
delete checkzeropartner;
n3--;
}
head print;
print = L2->next;
for (int baga = 1; baga <= n3; baga++) {
if (baga != n3) {
print = print->next;
}
}
(我刚开始认为这个做法画蛇添足,但是仔细一想会发现在相加的过程中如果一直考虑进位比如说99999999+1,那么我要判断后面的进位与否实际上还是要写到一个关于最大位数加一的函数,循环不可避免,而进位在加法中存在的概率是0.45左右,鉴于这是个小程序,所以用一次性补齐位数,再加一个删一个的思路做起来,时间复杂性和判断是否需要进位的思路是在大多数机会中比起来确实要大一点,但是无所谓哈哈哈)
4.如何“按顺序输出”
由于我的读入是从最高位开始,那么如果从头结点开始输出必然是反过来的,我的解决方案是:从头结点开始输出(结果为逆向)到一个额外文件,然后再从文件开始读入(结果对于原数来说是顺着的),然后再输出一遍追加到原文件。
//写文件
//先把这个数从个位开始输入到文件里再扫描文件,把这个数构建一个链表然后再输出就是从高位开始写了
//这个操作需要再来一个链表
FILE* fp2;
fopen_s(&fp2, strfile2, "wt");
writeafile = L2->next;
for (int flag = n3; flag > 0; flag--) {
fprintf(fp2, "%c", writeafile->Value);
writeafile = writeafile->next;
}
fclose(fp2);
ReadFileSingle(LReverse, strfile2, n3);
head writeafile2;
FILE* fp3;
fopen_s(&fp3, strfile1, "at");
writeafile2 = LReverse->next;
fprintf(fp3, "\n");
for (int flag = n3; flag > 0; flag--) {
fprintf(fp3, "%c", writeafile2->Value);
writeafile2 = writeafile2->next;
}
fclose(fp3);
我的一个小TIP
1.一定要注意哪些该删没删,小心持续占用的内存日积月累最后让你关键时刻掉链子。
2.写循环时多动手,用最小的问题规模验证自己的想法。
3.在调试时不要吝惜笔墨,记下来画下来帮助理解和辨别。
结语&完整代码
谢谢你看到这里!以上就是我想与你分享的
再次声明:我是新手,欢迎批评、指正、建议或分享
以下为完整代码(文件路径因人而异)
#include"stdio.h"
#include"stdlib.h"
#include"time.h"
#include"math.h"
#define LIST_INIT_SIZE 100
#define OK 1
#define ERROR 0
typedef int ElemType;
typedef int Status;
typedef struct tagNumberandPosition {
ElemType Position;
char Value;
struct tagNumberandPosition* next;
}*head,DigandVal;
//将数从个位开始存入链表
Status InitialList_L(head& headposition) {
headposition = new DigandVal;
headposition->Position = 0;
headposition->Value = 48;
headposition->next = NULL;
return OK;
}
//读取文件,并存入链表,并返回位数
Status ReadFile(head &L1, head& L2,char* FileAdress, int& digits1, int& digits2) {
FILE* fp;
char bowl='\0';
DigandVal* needle1=0x00;
DigandVal* needle2 = 0x00;
fopen_s(&fp, FileAdress, "rt");
if (!fp)
return ERROR;
int i1 = 0, i2 = 0;
while (!feof(fp)) {
fscanf_s(fp,"%c", &bowl);
if (bowl != '\ ') {
bowl = (int)bowl;
InitialList_L(needle1);
needle1->Position = i1;
needle1->Value = bowl;
needle1->next = L1->next;
L1->next = needle1;
bowl = '\0';
}
else
break;
++i1;
}
int flag1;
for (flag1 = 1; flag1 <= i1; flag1++) {
needle1->Position = flag1;
needle1 = needle1->next;
}
digits1 = i1;
bowl = '\0';
while (!feof(fp)) {
fscanf_s(fp, "%c", &bowl);
if (bowl != '\ ') {
bowl = (int)bowl;
InitialList_L(needle2);
needle2->Position = i2;
needle2->Value = bowl;
needle2->next = L2->next;
L2->next = needle2;
bowl = '\0';
}
else
break;
++i2;
}
L2->next = needle2->next;
delete needle2;
needle2 = L2->next;
i2--;
int flag2;
for (flag2 = 1; flag2 <= i2; flag2++) {
needle2->Position = flag2;
needle2 = needle2->next;
}
digits2 = i2;
fclose(fp);
return OK;
}
//读取文件,并存入链表,并返回位数
Status ReadFileSingle(head& L, char* FileAdress, int& digits) {
FILE* fp;
char bowl = '\0';
int replace = 0;
DigandVal* needle = 0x00;
fopen_s(&fp, FileAdress, "rt");
if (!fp)
return ERROR;
int i = 0;
while (!feof(fp)) {
++i;
fscanf_s(fp, "%1d", &replace);
bowl = replace + 48;
InitialList_L(needle);
needle->Position = i;
needle->Value = bowl;
needle->next = L->next;
L->next = needle;
bowl = '\0';
}
L->next = needle->next;
delete needle;
needle = L->next;
i--;
int flag;
for (flag = 1; flag <= i; flag++) {
needle->Position = flag;
needle = needle->next;
}
digits = i;
fclose(fp);
return OK;
}
//将数据补成相同的位数:位数最大的数的最大位前面填一个0,其他的通过加零来达到相同的位数
Status CompleteFormat(int originaldigit,int expandeddigits, head &L) {
head p;
p = L->next;//p指向第一个结点
//找到最后一个结点
for (int i = 1; i <= originaldigit; i++) {
if(i!= originaldigit)
p = p->next;
}
//开始创建新添结点
head s;
for (int howmanyleft = expandeddigits - originaldigit; howmanyleft > 0; howmanyleft--) {
InitialList_L(s);
s->Position = originaldigit + 1;
s->Value = 0+48;
p->next = s;
p = p->next;
}
return OK;
}
//将两个数从个位相加,并在每一次循环中删除其中一列的某一位,最后结果用位数大的那一个链表表示出来
Status AddUp(head &HeadNo1,head &HeadNo2, int expandeddigits) {
head p1pre,p1, p2;
p1 = HeadNo1->next;
p2 = HeadNo2->next;
int a1=0,a2=0,ultimate=0,carry=0;
for (int flag = expandeddigits; flag > 0; flag--) {
a1 = p1->Value - 48;
a2 = p2->Value - 48;
ultimate = (a1 + a2 + carry) % 10;
carry = (a1 + a2 + carry - ultimate) / 10;
p2->Value = ultimate + 48;
p1 = p1->next;
p2 = p2->next;
}
//到此为止没有问题
//删除第一个链表的结点
p1 = HeadNo1->next;
p1pre = HeadNo1;
for (int flag2 = expandeddigits; flag2 > 0; flag2--) {
p1pre = p1->next;
delete p1;
p1 = p1pre;
}
return OK;
}
void main() {
head L1, L2, writeafile,LReverse,checkforzero,checkzeropartner;
int n1, n2;
int n3;
InitialList_L(L1);
InitialList_L(L2);
InitialList_L(LReverse);
char strfile1[] = "C:\\Users\\Nighfearti\\Desktop\\input1.txt";
char strfile2[] = "C:\\Users\\Nighfearti\\Desktop\\Reverse1.txt";
//读文件
ReadFile(L1, L2,strfile1, n1,n2);
if (n1 == n2)
n3 = n1 + 1;
else if (n1 > n2)
n3 = n1 + 1;
else
n3 = n2 + 1;
CompleteFormat(n1, n3, L1);
CompleteFormat(n2, n3, L2);
AddUp(L1, L2, n3);
//检查2号链表的最后有没有0
checkforzero = L2->next;
for (int flag3 = n3; flag3 > 2; flag3--) {
checkforzero = checkforzero->next;
}
checkzeropartner = checkforzero->next;
if (checkzeropartner->Value == 48) {
checkforzero->next = NULL;
delete checkzeropartner;
n3--;
}
head print;
print = L2->next;
for (int baga = 1; baga <= n3; baga++) {
if (baga != n3) {
print = print->next;
}
}
//写文件
//先把这个数从个位开始输入到文件里再扫描文件,把这个数构建一个链表然后再输出就是从高位开始写了
//这个操作需要再来一个链表
FILE* fp2;
fopen_s(&fp2, strfile2, "wt");
writeafile = L2->next;
for (int flag = n3; flag > 0; flag--) {
fprintf(fp2, "%c", writeafile->Value);
writeafile = writeafile->next;
}
fclose(fp2);
ReadFileSingle(LReverse, strfile2, n3);
head writeafile2;
FILE* fp3;
fopen_s(&fp3, strfile1, "at");
writeafile2 = LReverse->next;
fprintf(fp3, "\n");
for (int flag = n3; flag > 0; flag--) {
fprintf(fp3, "%c", writeafile2->Value);
writeafile2 = writeafile2->next;
}
fclose(fp3);
//删掉第二个链表的节点
head p1pre, p1;
p1 =L2->next;
p1pre = L2;
for (int flag2 = n3; flag2 > 0; flag2--) {
p1pre = p1->next;
delete p1;
p1 = p1pre;
}
//删掉两个头结点以及其他辅助指针
delete L1;
delete L2;
delete LReverse;
}