NO.3:求循环小数
题目要求
对于任意的真分数 N/M ( 0 < N < M ),均可以求出对应的小数。如果采用链表表示各个小数,对于循环节采用循环链表表示,则所有分数均可以表示为如下链表形式。
输入: N M k
输出: 转换后的小数(不超过 k )
要求: 仅编写将分数转换为小数的函数 change( int n, int m, NODE * head ) 。
下面给出输入输出样例:
测试输入 | 期待的输出 | |
---|---|---|
测试用例 1 | 以文本方式显示
| 以文本方式显示
|
测试用例 2 | 以文本方式显示
| 以文本方式显示
|
测试用例 3 | 以文本方式显示
| 以文本方式显示
|
测试用例 4 | 以文本方式显示
| 以文本方式显示
|
预设代码
#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;
}
信息梳理
我们先来看一下预设代码中的信息:
先是结构体NODE,包含两个元素int data以及NODE *next:
typedef struct node
{ int data;
struct node * next;
} NODE;
然后两个基本操作,output(NODE *, int)已经给出,需要完成change(int, int, NODE * ):
void output( NODE *, int );
void change( int, int, NODE * );
我们想想该怎么做,根据题目意思我需要将一个真分数转化成小数,而这个小数可以是有限的,也可以是循环小数(这里注意,不用担心没有循环的情况,因为真分数都是有理数,而有理数都能转化成有限或者循环小数)。并且要求我们用链表表示小数部分,循环小数要求使用循环链表。
我们先来举个例子模拟一下(1/8):
不难发现第一位小数是,1借了一位变成10,然后10/8=1(商),第二位小数是10%8(余数),
后面以此类推。这里面涉及到三个关键步骤,乘10、取商、取余数。
代码实现
其实题目并不难,我们只要按照逻辑一步一步写就行了:为了方便直接先开两个数组来存放商和余数:
int shang[10010], yu[10010];
memset(shang, 0, sizeof(shang));memset(yu, 0, sizeof(yu));
// memset初始化数组的操作(全部赋0)
然后就是不断地取商,取余数。那么循环什么时候停止呢?
前面已经说了,真分数是一定可以转化成一个有限小数或者循环小数的,所以能够停止循环的条件有两个:
- 有一次的商为0,也就是被整除了
- 有一次的商和余数,与之前某一次的商和余数相等,那么将会循环
那么这个循环体,我们可以这样写:
int p1 = 0, p2 = 0; //p1、p2用来记录出现商、余数相同时的两个位置
int flag = 0; //flag是一个标志,用来标记是不是存在循环部分
int num = n * 10; //因为是个真分数,所以第一步就要*10操作for (int i = 0; ; i++) //这是默认循环条件一直为真的意思,因为循环体中有终止条件
{
shang[i] = num / m;
yu[i] = num % m;for (int j = 0; j < i; j++)
{
/* 当商和余数与之前相同时,即标志着出现循环
用p1、p2记录位置,flag=1表示存在循环 */
if(shang[j] == shang[i] && yu[j] == yu[i])
{
p1 = j;
p2 = i;flag = 1;
break;
}
}
num = yu[i] * 10;if(!num) //如果num被除尽,变为0,则没有循环
{
p1 = i + 1;
break;
}
if(flag == 1)
break;
}
然后跳出循环后,我们就应该将之前存好的商(也就是小数部分)依次存入链表:
NODE *r = head;
for (int i = 0; i < p1; i++) //p1一定的是循环跳出时的位置
{
//尾插法建立链表
NODE *q = (NODE*)malloc(sizeof(NODE));
q->data = shang[i];
q->next = NULL;
r->next = q;
r = q;
}
注意:这里一定要使用尾插法建立链表,有人问为什么。因为预设代码中output时就是从head一直往后的!!!如果用头插的话,就全反了!!!
最后我们是不是要通过flag标志来判断是不是要加上循环部分啊:
//补上循环
if (flag == 1)
{
NODE *r_save = r;
for (int i = p1; i < p2; i++) //因为p1、p2是两次商、余数相同的两个位置
{ //所以p2-p1就是循环部分的长度
NODE *q = (NODE*)malloc(sizeof(NODE));
q->data = shang[i];
q->next = NULL;
r->next = q;
r = q;
}
r->next = r_save->next; //注意题目要求是循环部分用循环链表
}
}
运行一下:
AC!!!过啦!!!
NO.4:求循环节
题目要求
这道题实在与上一道的题目背景一样:
输入: N M
输出: 整个循环节
要求:编写一个尽可能高效的查找循环节起始点的函数: NODE * find( NODE * head, int * n ) 。函数的返回值为循环节的起点(即图中的指针p),n为循环节的长度。
说明:提交程序时请同时提交将分数转换为小数的函数 change( int n, int m, NODE * head ) (前面题目中已经编写)。
下面是部分测试样例:
测试输入 | 期待的输出 | 时间限制 | 内存限制 | |
---|---|---|---|---|
测试用例 1 | 以文本方式显示
| 以文本方式显示
| 1秒 | 64M |
测试用例 2 | 以文本方式显示
| 以文本方式显示
| 1秒 | 64M |
测试用例 3 | 以文本方式显示
| 以文本方式显示
| 1秒 | 64M |
测试用例 4 | 以文本方式显示
| 以文本方式显示
| 1秒 | 64M |
预设代码
#include <stdio.h>
#include <stdlib.h>
typedef struct node
{ int data;
struct node * next;
} NODE;
NODE * find( NODE * , int * );
void outputring( NODE * );
void change( int , int , NODE * );
void outputring( NODE * pring )
{ NODE * p;
p = pring;
if ( p == NULL )
printf("NULL");
else
do { printf("%d", p->data);
p = p->next;
} while ( p != pring );
printf("\n");
return;
}
int main()
{ int n, m;
NODE * head, * pring;
scanf("%d%d", &n, &m);
head = (NODE *)malloc( sizeof(NODE) );
head->next = NULL;
head->data = -1;
change( n, m, head );
pring = find( head, &n );
printf("ring=%d\n", n);
outputring( pring );
return 0;
}
/* Here is waiting for you.
void change( int n, int m, NODE * head )
{
}
NODE * find( NODE * head, int * n )
{
}
*/
信息梳理
预设代码与上一题几乎完全一样,就不再赘述了,我们来想一想怎么去找这个循环节。其实这道题可以钻一个空子,因为在上一道题写的change()函数中,已经存过遇见商、余数相同的两个位置p1、p2(又因为当时从前往后遍历的),所以p1就是第一个循环节,而p2-p1就是循环节长度ring。但是因为之前的的p1、p2变量是局部变量,跳出函数change()就失效了,但是这根本难不倒想偷懒的我!!!我直接将p1、p2用全局变量定义,不就能保留下来了吗,我真是个小天才!!!
代码实现
#include <string.h>
int p1 = 0, p2 = 0;
void change(int n, int m, NODE *head )
{
//初始化
int shang[10010], yushu[10010];
memset(shang, 0, sizeof(shang));
memset(yushu, 0, sizeof(yushu));
p1 = 0, p2 = 0;
int flag = 0;
int num = n * 10;
for (int i = 0; ; i++)
{
shang[i] = num / m;
yushu[i] = num % m;
for (int j = 0; j < i; j++)
{
/* 当商和余数与之前相同时,即标志着出现循环
用p1、p2记录位置,flag=1表示存在循环 */
if(shang[j] == shang[i] && yushu[j] == yushu[i])
{
p1 = j;
p2 = i;
flag = 1;
break;
}
}
num = yushu[i] * 10;
if(!num)//如果num被除尽,变为0,则没有循环
{
p1 = i + 1;
break;
}
if(flag == 1)
{
break;
}
}
/*根据 p1 的位置,如果没有循环,则建立完整链表;
如果有循环,则建立的是前缀。 */
NODE *r = head;
for (int i = 0; i < p1; i++)
{
//尾插法建立链表
NODE *q = (NODE*)malloc(sizeof(NODE));
q->data = shang[i];
q->next = NULL;
r->next = q;
r = q;
}
//补上循环
if (flag == 1)
{
NODE *r_save = r;
for (int i = p1; i < p2; i++)
{
NODE *q = (NODE*)malloc(sizeof(NODE));
q->data = shang[i];
q->next = NULL;
r->next = q;
r = q;
}
r->next = r_save->next;
}
}
NODE * find( NODE *head, int *n )
{
if(p2 > p1)//存在循环
{
NODE *p = head->next;//初始化
//计算长度
*n = p2 - p1;
//越过前缀
for (int i = 0; i < p1; i++)
{
p = p->next;
}
return p;
}
else//不存在循环
{
*n = 0;
return NULL;
}
}
运行一下:
哈哈哈哈!!!没有问题!!!
源码
求循环小数
求循环节
加油!!!国庆不会断更的!!!(^///^)