求循环小数 | C++ | 单向循环链表

一、题目描述

输入: N M k

输出: 转换后的小数(不超过 k )

要求: 仅编写将分数转换为小数的函数 change( int n, int m, NODE * head ) 。

预设代码

/* PRESET CODE BEGIN - NEVER TOUCH CODE BELOW */  
 
#include <stdio.h>  
#include <stdlib.h>  
  
typedef struct node  
{   int         data;  
    struct node * next;  
} NODE;  
  
void output( NODE *, int );  
void change( int, int, NODE * );  
  
void output( NODE * head, int kk )  
{   int k=0;  
  
    printf("0.");  
    while ( head->next != NULL && k<kk )  
    {   printf("%d", head->next->data );  
        head = head->next;  
        k ++;  
    }  
    printf("\n");  
}  
  
int main()  
{   int n, m,k;  
    NODE * head;  
  
    scanf("%d%d%d", &n, &m, &k);  
    head = (NODE *)malloc( sizeof(NODE) );  
    head->next = NULL;  
    head->data = -1;  
    change( n, m, head );  
    output( head,k );  
    return 0;  
}  
  
/* PRESET CODE END - NEVER TOUCH CODE ABOVE */  
测试输入期待的输出时间限制内存限制额外进程
测试用例 1以文本方式显示
  1. 1 8 50↵
以文本方式显示
  1. 0.125↵
1秒64M0
测试用例 2以文本方式显示
  1. 29 33 10↵
以文本方式显示
  1. 0.8787878787↵
1秒64M0

二、 思路分析

        循环小数,对其非循环部分,采用单向的线性链表;对于其循环部分,采用循环链表。因此,最终的存储结构就很清晰了。

那么现在只剩两个问题

其一、怎么将小数部分分为非循环部分和循环部分?

其二、怎么确定循环的长度和内容?

为了解决第一个问题,我采用另一个视角去看一个循环小数。

        例如0.285714285714......,我可以说没有非循环部分,循环部分是285714; 我也可以说非循环部分是2,循环部分是857142; 我也可以说非循环部分是28,循环部分是571428......我的意思是循环部分可以分几个数字给非循环部分,反正不破坏最终的结果,对吧?

要解决第一个问题,我还需要再补充一项假设。

        假设一:n/m的非循环部分不超过10位(循环部分不分数字给非循环部分的情况下)。嗯,也许实际上是不超过5位,也许是不会超过20位,那有什么关系呢?这只是一个大胆的假设罢了。

        这样做,虽然我不能直接确定非循环部分的长度或者内容,但是我可以从循环部分“借”几个数字给非循环部分,让其扩充到10位,这也算是解决了第一个问题了。

具体的操作流程是这样的:

①小数部分的前十位组成一个单向的链式表

②第十一位之后的内容一定是循环的,我将其整成一个单向循环链表。

第二个问题相对于第一个问题就更好解决了。

        首先引入一个假设。

假设二:n是被除数,m是除数,n÷m的小数部分如果有循环部分,则循环部分长度一定小于m。

为了验证这个假设,我当时立刻列举了好几个例子,例如

除数为68,循环部分的长度是16.

除数为79,循环部分的长度是13.

极端的情况有

除数为97,循环部分的长度则是整整达到了96。但无论如何,96仍然小于97。

        

        这还没有结束。不难看出,这96个数字组成的序列中,如果任取相邻的两个数字,你无法在这个序列的其他任何地方找到同样的相邻两个数字。

于是我提出下面的假设。

        假设三:n是被除数,m是除数,d是m的位数,n÷m的小数部分如果有循环部分,而且它的循环部分的长度大于等于d,则循环部分的任意连续d个数字,在同一个循环内找不着第二个。

以上三个假设都是我的观察结论,我不会数论,如果有大佬知道这些怎么证明的话还望不吝赐教!

有了上面的一个视角和三个假设,思路已经了然。


三、代码示例

这道题目的话,我总共写了四版。

第一版是初代尝试时的一个大胆的想法:我直接建立一个长度为一百万的线性链表,干就完了!

void change( int n, int m, NODE * head ) {
	
	NODE *p; 
	p = (NODE*) malloc (sizeof(NODE));
	p->next = NULL;
	head->next = p;
	p->data = 10*n/m;
	n = n*10 - p->data *m;
	
	if (n!=0)
	for (int i=0;i<1000000;i++) {
		NODE *q;
		q = (NODE*) malloc (sizeof(NODE));
		
		q->next = NULL;
		q->data = 10*n/m;
		n = n*10 - q->data *m;
		
		p->next = q;
		p = q;
		if (n==0) break;
	}
		
	return;
	
}

        这种写法十分简单、无脑,甚至可以说是取巧。它对了前10个用例,但是对于第11个用例,一百万根本不够,盲目开内存也会直接爆。

第二版相对来说是一个比较新的思路,很长,也没什么太大的意义,建议不要感兴趣。

void change( int n, int m, NODE * head ) {
	
	int circle = 0; 
    //circle=0表示循环,否则表示不循环 
    //check用来核对,当出现与check一致的序列时,说明上个循环结束,新的循环开始,它的长度由m的位数digit决定,它的内容是loop的前digit位
    //fraction是小数部分,loop是循环部分
    //例如小数0.28571428571428...,fraction为28571428571428,loop是824175,check是8
	
	int fraction[m+11],loop[m];

	int digit=0;
	{
		int M = m;
		while (M>0) {
			M/=10; digit++;
		} 
	}
	
	for (int i=0;i<m+10;i++) {
		fraction[i] = 10*n/m;
		n = n*10 - fraction[i]*m;
		if (n==0&&circle==0) {
			circle = i+1;
		}
	}
	
	
	int j=0,check[digit];  
	for (int i=0;i<digit;i++) {
		check[i] = fraction[m+9-i];
	}
	
	int flag = 0;
	for (int i=m+9;i>=0;i--) {
		if (i==m+9) {
			loop[j] = fraction[i];
			j++;
			continue;
		}
		
		if (fraction[i]==check[0]) {
			flag = 1;
			for (int r=0;r<digit;r++) {
				if (fraction[i-r]!=check[r]) {
					flag = 0; break;
				}
			}
		}
		
		if (flag==1) {
			break;
		}
		else {
			loop[j] = fraction[i];
			j++;
		}
	}
	
	//建立闭环
	NODE *p,*phead;
	p = (NODE*) malloc (sizeof(NODE));
	p->data = loop[j-1];
	p->next = NULL;
	phead = (NODE*) malloc (sizeof(NODE));
	phead = p;
	
	for (int i=j-2;i>=0;i--) {
		NODE *q;
		q = (NODE*) malloc (sizeof(NODE));
		q->next = NULL;
		q->data = loop[i];
		p->next = q;
		p = p->next;
	}
	p->next = phead;
	
	if (circle>0) {
		p = head;
		for (int i=0;i<circle;i++) {
			NODE *q;
			q = (NODE*) malloc (sizeof(NODE));
			q->next = NULL;
			q->data = fraction[i];
			p->next = q;
			p = p->next;
		}
	}
	else {
		p = head;
		for (int i=0;i<m;i++) {
			NODE *q;
			q = (NODE*) malloc (sizeof(NODE));
			q->next = NULL;
			q->data = fraction[i];
			p->next = q;
			p = p->next;
		}
		
		NODE *q;
		q = phead;
		while (q->data!=fraction[m-1]&&q->next->data!=fraction[m]) {
			q = q->next;
		}
		p->next = q->next;
	}
	
	return;
	
}

第三版则是完全依照我们在思路分析中的内容。


void change( int n, int m, NODE * head ) {

	NODE *p;
	p = (NODE*) malloc (sizeof(NODE));
	p = head;
    
    //计算出小数部分的前十位,并建立线性表
	for (int i=0;i<10;i++) {
		NODE *q;
		q = (NODE*) malloc (sizeof(NODE));
		q->next = NULL;
		q->data = n*10/m;
		n = n*10 - q->data*m;
		p->next = q;
		p = p->next;
		if (n==0) return;
	}
	
    //计算m的位数
	int digit=0;
	{
		int M = m;
		while (M>0) {
			M/=10; digit++;
		}
	}
	
    //loop是循环的内容,j用来计数
	int loop[m+digit],j=0;
	NODE *phead;
	phead = p;
	{
		int flag = 0; //flag=1时表示循环结束,这时已经找到了需要的循环内容,退出while
		while (flag==0) {

			loop[j] = n*10/m;
			n = n*10 - loop[j]*m;
			if (j>=digit)

				for (int i=digit-1;i>=0;i--) {
					flag = 1;
					if (loop[j-digit+1+i]!=loop[i]) {
						flag = 0; break;
				}

			}
			j++;
		}	
	}
	j-=digit;	//只有loop[0]到loop[j-digit]才是有效的循环部分
	
    //建立闭环
	for (int i=0;i<j;i++) {
		NODE *q; 
		q = (NODE*) malloc (sizeof(NODE));
		q->next = NULL;
		q->data = loop[i];
		p->next = q;
		p = p->next;        
	}
	p->next = phead->next;
	
	return;

}

        即便是这种方法,第十个样例依然过不去,错误原因是内存引用错误。究其原因,应该是m非常大,导致我开的loop数组太大,爆内存了。

        第四版也就是最终AC的版本适当地结合了第一版和第三版的内容。前面分析地足够清楚,故第四版就不放了,自己写吧。以上。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值