数据结构与算法课程设计报告分数变小数问题.docx
《数据结构与算法课程设计报告分数变小数问题.docx》由会员分享,可在线阅读,更多相关《数据结构与算法课程设计报告分数变小数问题.docx(22页珍藏版)》请在冰豆网上搜索。
![数据结构与算法课程设计报告分数变小数问题.docx](https://file1.bdocx.com/fileroot1/2023-2/5/64a545ae-a40c-4951-b241-3b71a0bd1f9f/64a545ae-a40c-4951-b241-3b71a0bd1f9f1.gif)
数据结构与算法课程设计报告分数变小数问题
数据结构与算法课程设计报告
1.分数变小树问题
2.问题分析:
对于分数变小数问题,其内容要求为写出一个程序,接受一个以N/M的形式输入的分数。
其中N为分子,M为分母,并能输出他的小数形式。
如果它的小数形式存在循环,要将启用括号括起来。
如1/3=.3333表示为(3)。
这个题目第一眼看去,会觉得并不难,就是分数转换为小数,只要进行简单的计算即可。
实际上,如果是一个有限小数,这的确很容易。
先让N除以M,再将余数乘以10再除以M,并将商保存起来,直到余数为0即可。
但是,这只是限于有限小数,即非循环小数,完全为达到题目要求。
对于循环小数,要实现转换并且将它的循环节用括号括起来,这就复杂得多了。
我们知道,循环小数又分为非纯循环小数和纯循环小数两种。
当然,处理他们的方法应该大同小异。
对于非纯循环小数,首先应该判断他的非循环节的位数,这样我们才能在输出正确的结果。
对于他的循环节,则可通过相应的数学分析方法得到。
纯循环小数的循环节位数的判断和他一样。
为了达到问题要求,它们只是在输出方式上的不同,这就需要对他们的输出程序进行区别对待,以保证输出结果的正确。
本问题中主要的就是循环节问题。
如果这个问题一旦解决,其他的问题将会迎刃而解。
对循环节位数判断后好,可通过相应的语句输出时将他们用括号括起来,这就是安排输出语句的问题了。
对于其他的要求,要实现起来很容易。
当然,在解决问题时,可能存在一些可能的缺点,如循环节位数无限大时,要解决起来会很麻烦,在循环节位数达到一定数量时,会自动退出程序。
但是解决了循环节位数就基本解决了主要问题,也就达到了题目所要求的内容。
3.数据结构选择和概要设计:
在本课程设计中,我应用了数据结构中的二叉排序树。
我们知道,二叉排序树中,右子树上的结点的值均大于根结点的值,左子树上的结点的值均小于根结点的值,另外,其左右子树也分别为二叉排序树。
在程序中,用到了二叉排序树的插入以及遍历。
它主要用于存放分数表达式中分母分解的质数因数结果。
如2,3,5等。
另外,二叉排序树的中序遍历有一特点,即它为一组递增序列。
通过遍历,将二叉排序树中各节点的值从小到大存放到向量容器中。
对于向量容器,它是C++中的一种顺序容器,它是一种线性结构。
我们可以直接访问它的元素。
每次添加元素时,添加在向量容器的尾部。
在C++模板库中,有一些向量容器的相关应用函数,我们可以直接进行调用,通过电用这些函数来实现向量容器的相关操作,从而达到应用他的目的。
通过它的应用,是程序简化了不少,因为他的一些操作函数已包含在头文件《vector>中,只要在程序开头是声明即可。
对于本程序的概要设计如下:
首先输入表达式N/M,通过while循环while(m!
=0)时,可以进行无限次的输入。
输入表达式后,会调用函数calculate(n,m)来对分数表达式进行转换。
在函数中,先判断余数是否为0,若为0,结束。
否则,n=yushu,移走向量容器中的元素即清空向量容器.R.erase(R.begin(),R.end());if(tr!
=NULL),调用函数distroy(tr),使二叉排序树变为一棵空树,即二叉链表为空链表。
此时,调用化简函数huajian(n,m),来对表达式中的N和M进行化简。
在此函数中调用了函数findgcd(intn,intm),即求他们的最大公约数函数,最后达到化简的目的。
之后,再调用分解函数fenjie(m),求分母的质树因数,然后调用便利函数inorder(tr)来对二叉排序树进行遍历。
最后调用check(n,m)函数,通过对小数的分类,来实现不同的计算转换和输出结果。
4.算法思想:
首先输入表达式,我们对其进行化简,使它们之间除1外,不再具有公约数,这样则易于之后的处理计算。
此时,则对分母进行分解,求出它的质数因数空二叉排序树的根节点,第二个则与他比较,若比他大,则作为右子树,否则,作为左子树。
按照生成二叉排序树原则进行构造。
此时,对此树进行中序遍历,使它成为一组递增有序序列并将它存放在向量容器中。
接下来是处理了,分为三种情况:
非循环小数,纯循环小数和非纯循环小数。
它们的整数部分都是一样的,主要是小数点之后的部分。
通过数学分析方法得到,对于非循环小数,即坟墓中质因数完全由和5构成,其小数的位数等于分数表达式中的分母含2,5因数数目中较大的一个数目。
而非纯循环小数,即分母分解成的质数因数中既含有2,5,又含有除2,5以外的因数。
此时,它的非循环节位数也是分母中2,5因数数目中较大的一个。
至于它的循环节位数,则是能被它的分母除2,5以外的因数的因数乘积整除的各位全部由9组成的数字中数大小最小的一个数的位数,即9的个数。
对于纯循环小数,它的小数点之后的位数全是循环节,球位数则等于能本木整除的各位全部由9组成的数字中最小的一个数的位数。
当然,我们要首先对分母的质数因数中2和5的个数进行比较,获得最大的因数个数。
之后即可分情况进行计算处理并分情况输出结果。
5、详细设计和编码字段:
本程序设计中共有8个自定义函数和一个主函数,其它一些函数则包含在头文件中。
在其中有一个头文件,它是向量容器的头文件。
在程序的开头,自定义了两个数据类型typedefstructnode{intdata;intcount;structnode*lchild;structnode*rchild;}*Stree,TreeNode;和typedefstructcnode{intdata;intcount;}Cnode;另外,vectorR是定义了一个Cnode类型的向量容器R。
在主函数中,首先是输入语句scanf(“%d%c%d”,&n,&ch,&m);通过该语句可输入分数表达式。
另外,又建立了一个while循环,while(m!
=0)使得可进行无数次的输入。
在while循环中,调用了分数进行转换计算的函数calculate(n,m),其中n为分子,m为分母。
Voidcalculate(intn,intm)为自定义函数,无返回值。
在该函数中,首先做除法并对余数进行判断if(yushu==0)printf(“%d\n”,zhengshu);否则,if(R.size()!
=0){R.erase(R.begin(),R.end());}其中R.size()为向量容器元素个数。
此函数包含在头文件中。
不需另作定义。
如果该容器不空,就移走其中的元素,即清空该容器。
为下一次添加元素做准备。
另外还有一个处理就是对二叉排序树的处理,if(tr!
=NULL)distory(tr);使二叉排序树变为空树,从而为下次建立二叉排序树作准备。
此时,要对输入的表达式中的分子分母进行处理了,首先是调用自定义函数huajian(n.m)。
在该函数voidhuajian(int&n,int&m)中,其中形参为引用调用,用引用作为形参的函数调用的目的是对形参的任何操作均直接作用于实参。
在他的函数体中的,一开始又调用了求最大公约数函数intfindgcd(intm,intn),此函数中,如果m=0){tem=m;m=n;n=temm%n;}首先将分母的值赋给temm,再将分子的值赋给m,并令n=temm求余n,如果此时m%n!
=0,继续循环,直到为0为止,最后返回m%n=0时的n作为输入表达式中分子分母的最大公约数。
求出公约数以后,在huajian函数中便可进行处理了。
如果最大公约数为0,便不作处理,保持原值。
否则m=m/gcd;n=n/gcd;,此时化简分数表达式完成。
在calculate函数中,化简以后是进行分解,此时,调用分解函数fenjie(m)。
在分解函数voidfenjie(intm)中,首先是while循环,for(i=2;i<=sqrt(m);i++)其中sqrt(m)为求平方根函数。
包含在头文件中,如果(m%i==0),则i肯定是质数,m肯定不是质数。
此时调用函数insertTree(tr,i),把i存入到二叉排序树中,对于函数voidinsertTree(Stree&root,intn),STree已经定义过,为结构指针。
在本函数中,root为二叉链表的根指针。
If(root==NULL){TreeNode*temp=newTreeNode;temp->data=n;temp->count=1;temp->rchild=NULL;temp->lchild=NULL;root=temp;}其中new的功能是动态分配内存,申请分配用于存放TreeNode数据类型的内存空间,同语句TreeNode*temp=(TreeNode*)malloc(sizeof(TreeNode));并将n的值赋给temp的数据域。
另外,temp->count则用来记录数字的个数。
如果root不空if(root->data==n)root->count++;如果root->datarchld,n),使该节点成为树root的右孩子。
如果root->data>n,则递归调用成为左孩子。
在分解函数中,直到分解m为质数为止,此时,原分数表达式中的的质数因数生成了一棵二叉排序树。
在voidcalculate(intn,intm)中,分解之后,则是遍历。
调用中序遍历函数inorder(tr),在函数voidinorder(Stree&root)中,if(root!
=NULL)递归调用遍历其左子树,inorder(root-lchild);直到将整个左子树中序遍历完毕,再遍历根结点root。
最后则是递归调用inorder(tr)遍历其右子树。
在遍历过程中,依次将根结点的相应值添加到向量容器的尾部。
此时,中序遍历的结构则是一组有序的递增序列,并与其相应的出现的次数一起存放在向量容器中。
接下来,则是调用处理过程中较重要和复杂的一个函数check(n,m)。
在voidcheck(intn.,intm)中,首先,vectorresult;定义了一个整型的向量容器result,用于存放商。
接下来是对分母中含2,5因数个数的比较,并取最大的一个作为max25的值。
通过以下语句完成。
For(inti=0;imax25)max25=R[i].count;}else{….}}接下来要分三种情况进行处理了。
首先是max25!
=0的情况下,if(!
other)即如果此时other==0,分数表达式中的分母的质因数除了2,5以外,不含其它质因数,经过数学分析,他必能化为有限小数,并且小数。
yushu=n;while(yushu!
=0){shang=(10*yushu)/m;result.push.back(shang);yushu=(10*yushu)%m;}其中result.push.back(shang)是将商添加到向量容器result的尾部。
接下来,则是输出处理结果了。
For(inti=0;i此时,如果other!
=0,则是混合小数。
小数点后面的尾数中既有不循环的部分,又有循环的部分。
不循环的部分并不需做处理,主要的则是循环部分。
根据数学分析可知,如果分数表达式中得分母既含有因数2,5,又含有2,5以外的质因数,它转换成的小数就是非纯循环小数,并且循环节的位数等于能被分母中除2,5以外的因数积整除的各位全部由9组成的数字中最小数字的位数即9的个数。
计算后面循环节长度语句如下:
intlen=1;intk=9;while(k%othervalue!
=0){len++;k=k*10+9;if(len>=1024)printf(“istoolong,break\n”);break;}}将循环界的各位数字求出来以后,便可进行输出处理了。
此时max25!
=0的情况下结束。
Max25==0的情况下,及表达式中的分母的质因数中不含2或5,全为2,5以外的质因数。
根据数学分析得到,此时,它是纯循环小数并且这个纯循环小数的循环节的位数等于能被分母整除的各位全部由9组成的数字中最小的数的位数即9的个数。
计算它的循环节长度同上。
再求出循环节各位数的数值后,printf(“(“);for(inti=0;i此时,有限小数,非纯循环小数,纯循环小数均处理完毕。
本次程序设计也就基本上完成了将分数转换为小数的功能。
6.上机调试情况记录:
在刚开始调试时,主要是语句的错误。
大多是由输入粗心造成的。
还有一部分是语句不搭配导致编译错误。
经过简单的调试后,改正了上述错误。
但是,并不能运行处正确结果。
对于函数voidinsertTree(STree&root,intn)以及遍历函数voidinorder(STree&root),其中STree为指针,形参中包含了ROOT和整型变量n。
对本函数进行了多次改变,主要是形参的改变。
初始,是TreeNode*root,并且其它函数中也以TreeNode作为形参,并且令TreeNode*tr=NULL;,虽然编译无错误,但是运行后结果并不正确。
输入1/3,输出0.(3),但对于1/6,输出为0.
(1),1/1010则为0.(0),不仅后面的位数无法输出,而且循环节判断错误。
此时,如果将root改为&root,则出现编译错误。
于是改为STreeroot,此时,输出会出现同样的错误。
但是改为STree&root后,编译正确,运行结果部分正确,即有限小数运行正确,且只能运行一次。
对于循环while则好像根本未起作用。
仔细检查程序后,发现应是由于存放质因数的向量容器R的问题。
对于R,它每次添加元素均是添加在向量容器的尾部由于它声明在程序开头,在主函数运行一次后,他之中已存放了数据元素。
再次远算时,又添加元素,出现了混乱。
于是在函数voidcalculate(intn,intm)中又添加了语句if(R.size()!
=0)R.erase(R.begin(),R.end());,即移走R中的元素,使它为空。
此时,运行依然一次,很容易想到了所建立的二叉排序树。
建立一个孔数也是在程序开头建立的。
于是又添加了将原二叉排序树置空的语句if(tr!
=NULL)distroy(tr);。
其中,distroy(STree&root)为置空二叉排序树函数。
通过处理tr==NULL;,再次运行后时,便可在此建立二叉排序树,且不会造成混乱。
这些错误改正后,依然存在问题,那就是对于循环小数的处理不正确,并不能输出正确结果。
经过多次检查后,发现是循环节计算出错。
其中while(k%othervalue!
=0)中,othervalue的值不正确主要原因是未对othervalue进行赋初值,使之经过处理后结果不正确。
改正后,输出依然错误,再次观察程序,则发现在输出时语句中出现了问题for(i=0;ii=0改为I,经过更正后,运行程序正确输入了数,单循环节并未被用括号正确的括起来。
检查程序,挪动输出括号语句,此次调试后,运行结果完全正确。
在这些错误全部更正,并且程序正确运行后,我再次改变voidinsertTree(STree&root,intn)中的形参root的定义,一旦改为其它形式,便会出现错误。
如果将去掉,依然是运行错误,只能输出小数点后面的第一位数,并且还用括号括起来。
如果改为TreeNode*root或其它形式,则可能导致编译错误。
7.测试用例,结果及算法性能分析:
输入表达式1/2,会输出0.5;输入表达式1/16,会输出0.625;输入表达式10/2,会输出5
输入表达式1/6,会输出0.1(6);输入表达式1/14,会输出0.0(714285);
输入表达式1/1010,会输出0.0(0099)
输入表达式1/3会输出0.(3);
输入表达式1/999,会输出0.(001);
输入表达式1/21,会输出0.(047619);
输入表达式10/3,会输出3.(3);
输入表达式100/91,会输出1.(098901);
输入表达式20/7,会输出2.(857142);
输入表达式50/27,会输出1.(851);
输入表达式29/12,会输出2.41(6);
输入表达式17/9,会输出1.(8);
算法时间性能分析:
在本程序设计中,从主函数开始,distroy(tr)算法的时间性能为O(n),其算法时间由二叉链表的长度决定。
遍历函数inorder(tr)运算的算法也是如此。
对于huajian(int&n,int&m),它的算法执行时间与问题的规模无关。
它的时间性能为O
(1)。
对于分解函数fenjie(m),其中有一个for循环,它的时间性能为O(n的1/2次方)。
对于函数check(n,m),其中虽有多个for循环,但它们的时间性能均为O
(1)。
本程序设计并不复杂,并未牵涉到查找,排序等问题,大多只是一些简单的for循环,又由于问题的简单,所以,本程序的时间性能还可以。
8.用户使用说明:
本程序实现的是分数变小数。
分数可以是真分数,假分数,其中分子可为0,但分母不可为0.如果一旦为0,则会退出本程序。
本程序在运行时,会在屏幕上显示出这样的提示:
请输入表达式,用户可根据要求输入表达式,如2315,输入之后,按下键,便可输出转换结果。
本程序可进行连续的多次输入。
用户如果想退出,直接关闭本程序即可。
另外,对于一般的分数,本程序都能解决,无论是有限小数,还是无限小数。
其中无限小数有包括了纯循环小数和非纯循环小数。
但是,对于特殊的一些分数,如198,194,组会输出一大窜的循环节。
一旦循环节的位数超过1024位后,便会自动退出。
显然,超大的循环节位数业务实际意义。
本程序基本上能满足用户的需求。
由于本人能力有限,本程序还存在不足之处。
敬请大家谅解。
谢谢大家使用。
9.参考文献:
1.王昆仑,李红.数据结构与算法.北京:
中国铁道出版社2006年5月
2.何钦铭颜晖C语言程序设计北京:
高等教育出版社2008年1月
3.郑莉董渊张瑞丰C++语言程序设计北京:
清华大学出版社2004年1月
4.严蔚敏等《数据结构(c语言版)》北京:
清华大学出版社,1997年4月
5.胡学钢等《数据结构算法设计指导》北京:
清华大学出版社,1999年第1版。
10.附录源程序
#include
#include
#include
#include
#include
usingnamespacestd;
intzhengshu,yushu;
typedefstructnode
{
intdata;
intcount;
structnode*lchild;
structnode*rchild;
}*STree,TreeNode;
typedefstructcnode
{
intdata;
intcount;
}Cnode;
vectorR;/*存储分解质因式的结果*/
STreetr=NULL;
voidinsertTree(STree&root,intn)
{
if(root==NULL)
{
TreeNode*temp=newTreeNode;
temp->data=n;
temp->count=1;
temp->lchild=NULL;
temp->rchild=NULL;
root=temp;
}
else
{
if(root->data==n)
{
root->count++;
}
elseif(root->data{
insertTree(root->rchild,n);
}
else
{
insertTree(root->lchild,n);
}
}
}
voidinorder(STreeconstroot)
{
if(root!
=NULL)
{
inorder(root->lchild);
Cnodet;
t.data=root->data;
t.count=root->count;
R.push_back(t);
inorder(root->rchild);
}
else
{
return;
}
}
intfindgcd(intm,intn)/*求最大公约数*/
{
if(m{
intt=m;
m=n;
n=t;
}
inttemm=0;
while(m%n!
=0)
{
temm=m;
m=n;
n=temm%n;
}
returnn;
}
voidhuajian(int&n,int&m)/*化简函数*/
{
intgcd=findgcd(m,n);
if(gcd==0)
{
printf("gcd=0\n");
}
else
{
m/=gcd;
n/=gcd;
}
}
voidfenjie(intm)/*分解函数*/
{
inti=2;
intfc=0;
intfound=0;
while
(1)/*永远循环*/
{
for(;i<=sqrt(m);i++)
if(m%i==0)/*i肯定是质数,m肯定不是质数*/
{
fc=1;
found=1;
insertTree(tr,i);/*把分解结果存入到二叉排序树树中*/
m=m/i;
break;
}
if(fc)/*如果fc=1执行*/
{
fc=0;
continue;
}
if(found)
{
insertTree(tr,m);
}
else
{
//cout<insertTree(tr,m);
}
break;
}
}
voidcheck(intn,intm)
{
vectorresult;
intother=0;
intothervalue=1;
intshang,yushu;
intmax25=0;
printf("%d.",zhengshu);
for(inti=0;i{
if(R[i].data==2||R[i].data