您的位置:首页 > 其它

与数字相关算法

2015-08-12 14:17 134 查看
丑数

问题描述:

我们把只包含因子(因子都是质数,合数不叫做因子)2、3、5的数称为丑数,如6、8都是丑数,14不是丑数,因为14包含因子7,习惯上1也是丑数。求出1到1500之间的所有丑数。

思路:

解法一:逐个判断是不是丑数,时间复杂度比较大

从丑数的定义可知,如果一个数是丑数,那么该数一定能被2、3、5整除,故判断一个数m是不是丑数,可以通过执行下面操作来完成:

while(m%2==0){m /= 2;}

while(m%3==0){m /= 3;}

while(m%5==0){m /= 5;}

如果最后得出的m等于1,则原来的m是丑数,否则不是丑数。

怎么证明?假如最后的值为n,只要能证明n是一个质数(则n本身是m的一个因子,而n不等于2、3、5)或者n包含除2、3、5以外的因子:

假如n是一个质数,则m便是丑数

假如n是合数,那么n可以表示为若干个质数的积,如果这些质数包含了2、3、5,那么在之前的循环中已经被除去了,所以合数n必然包含了出2、3、5以外的因子,那么m自然也就包含除2、3、5以外的因子了。

知道怎么判断一个数是不是丑数,那么可以通过逐个判断来求1到1500之间的所有丑数。

解法二:通过已知的丑数,求后面剩余的丑数,避免对非丑数的计算,用空间换时间

假如,已知有序的丑数1、2、3、5、7,怎么求下一个丑数?

有规则如:

丑数乘以2、3或5得到的一定也是丑数,而任一个丑数也一定可以由另一个丑数乘以2、3或5得到。

所以,求下一个最小丑数,可以通过前面的丑数乘以2、3或5(注意不能是乘以多个,如m*2*3,那在其之前就有m*2和m*3了,所以就直接用(m*2)*3或(m*3)*2便可,不需要乘以多个)。

所有的有可能的下一最小丑数:

前面出现的所有丑数(已经保存在数组中)分别乘以2,然后取其中一个最小的且大于数组中最大的一个(数组的最后一个,因为数组是有序的),如,已出现的丑数为:1、2、3、5、7,那可以求的2、4、6、10、14,由于10是比7大的最小一个,所以取10。

同理求乘以3和乘以5的情况,如,已出现的丑数为1、2、3、5、7,乘以3对应的是3、6、9、15、21,取9,乘以5对应的是5、10、15、25、35,取10。

然后取三者中的最小作为下一个最小丑数,这里是9,。为什么取最小,因为较大可以通过重复以上操作来求得。

由于数组本身有序,故在求乘2(乘3或乘5)的候选丑数时,可以通过二分法,如上面的1、2、3、5、7,我们可以先求3*2,得到6,由于6<7,不满足,再进一步对5、7二分,求5*2,得到10,10>7,但可能还存在比10小且比7大的数,故还要进一步二分,直到无法二分为止,这里要对5的左边3的右边部分二分,由于已经没有数了,故到此为止,所以候选值为10。同理,也通过二分求乘3和乘5的候选值。

随机生成和为S的N个正整数——投影法

以生成和为20的4个数为例,可以先生成随机生成0到20之间的三个数字再排序,假设得到了4,7,18。然后在X-Y数轴上画出这三个数,如下图:



然后将这些数值投影到Y轴上,可得下图:



由图很容易看出AB,BC,CD,DE这四段的长度和肯定为20。因此AB,BC,CD,DE这四段的长度即和为20的4个数,这4个数分别为4,3,11,2。这种方法只要随机生成N
- 1个小于S的不同数字,排序后计算两两差值就可以得到和为S的N个正整数。

C++实现:

#include<cstdio>
#include<ctime>
#include<set>
#include<algorithm>
usingnamespace std;
//在[s,
e)区间上随机取n个不同的数并存放到a[]中
void GetRandomNum(int *a, intn, ints, inte)
{
set<int>
set_a;
srand(time(0));
for (int i
= 0; i < n; i++)
{
int num
= (rand() % (e-s))
+ s;
if (set_a.find(num)
== set_a.end())
set_a.insert(num);
else
i--;
}
int i
= 0;
set<int>::iterator pos;
for (pos
= set_a.begin(); pos != set_a.end(); pos++)
a[i++]
= *pos;
}
int main()
{
constint NSUM
= 20;
constint NCOUNT
= 4;
printf("生成和为%d的%d个数 \n",
NSUM, NCOUNT);
int a[NCOUNT];
GetRandomNum(a, NCOUNT - 1, 10, NSUM);
sort(a, a + NCOUNT - 1);
a[NCOUNT - 1] = NSUM;
printf("已经生成和为%d的%d个数:
\n", NSUM, NCOUNT);
printf("%d
", a[0]);
for (int i
= 1; i < NCOUNT; i++)
printf("%d
", a[i] - a[i - 1]);
putchar('\n');
return 0;
}

n个骰子的点数
问题:
n个骰子的点数之和s,求s的所有可能的值及其出现的概率
思路:
用一个数组A保存s出现的次数,A[s]表示和为s的值出现的次数,假如有n个骰子,那么数组的大小为6n,算上A[0],那么数组大小为6n+1,因为所有骰子都为6的时候和最大,最大的和便是6n。
数组A的初始时每个元素的值都为0,我们逐个骰子往里面加:
首先加入第一个骰子,则A[0]=0、A[1]=1、A[2]=1、A[3]=1、A[4]=1、A[5]=1、A[6]=1、A[7]=0、A[8]=0。。。
然后加入第二个骰子,我们从坐标为2*6=12的位置开始向前修改数组的值,因为坐标大于12的元素都不受影响,依旧是0,怎么求新的A[s]的值呢?因为新加进来的骰子的值只能从1到6,假设其为i,要想和为s,那么原来骰子的和要是s-i,原来骰子的和为s-i出现的次数为A[s-i],则有当新骰子为i时,总骰子和为s出现的次数为A[s-i],由于新骰子可以从1到6,所以:
A[s]=A[s-1]+A[s-2]+A[s-3]+A[s-4]+A[s-5]+A[s-6]
加入第二个骰子时,我们需要修改A[12]到A[1]的值
加入第三个骰子时,需要修改A[18]到A[2]的值
依次类推
C实现(windows):

#include<stdio.h>
double
* func(intn,
intmaxValue)
{

if (n<
= 0 || maxValue
<= 0)

returnNULL;

int
size = maxValue
* n
+ 1;

//用于存放和出现的次数

int
* sumArray = (int
*)malloc(size * sizeof(int));

//初始化数组

for
(int
i = 0; i < size; i++)
{
sumArray[i] = 0;
}

//加入第一个骰子

for
(int
i = 1; i <= maxValue;
i++)
{
sumArray[i] = 1;
}

//逐个加入骰子

for
(int
i = 2; i <= n;
i++)
{

for
(int
j = i * maxValue;
j >= i - 1; j--)
{
sumArray[j] = 0;

for
(int
k = 1; k <= maxValue;
k++)
{

if (j
- k <= i - 2)

break;
sumArray[j] += sumArray[j - k];
}
}
}

//用于存放和出现的概率

double
* probabilitiesArray = (double
*)malloc((size + 1) * sizeof(double));

//和出现的总次数,可以通过求sumArray数组的和得到,也可以通过排列数得到

int
sum = 1;

for
(int
i = 0; i < n;
i++)
sum *=
maxValue;

for
(int
i = 0; i < size; i++)
{

/*
double除以int得double,int除以double也得double,(double)sumArray[i]/sum,
C会先将sumArray[i]转换成double,然后再除以sum,得到的是double,故小数部分不会丢失,

如果改成(double)(sumArray[i]/sum)虽然结果被转换成了double,但转换之前的结果是int,

小数部分已经丢失了
*/
probabilitiesArray[i] = (double)sumArray[i]
/ sum;
}

free(sumArray);
probabilitiesArray[size] = -1;

return
probabilitiesArray;
}
int
main()
{

double
* result = func(3, 6);

for
(int
i = 1;; i++)
{

if (result[i]
== -1)

break;
printf("%d,%f\n",
i, result[i]);
}
free(result);
}

maxValue用于设置一个骰子的面数,当然,默认情况下一个骰子有6面,为了使函数更具通用性,这里允许设置骰子的面数

求1+2+3+4+....+n
问题:
求1+2+3+4+....+n,要求不能使用乘除法、for、while、,if、else、switch及(A?B:C)

解法一:利用函数指针实现递归
C实现:

#include<stdio.h>
//定义一个函数指针
typedefint(*fun)(int);
int
method1(intn)
{

return
0;
}
int
method2(intn)
{

fun
f[2] = { method1, method2 };

return
f[!!n](n-1)
+ n;
}
int
main()
{

int
result = method2(5);
printf("%d",
result);
}

说明:
!!n:当n非零时,对n连续做两次反运算得到true,C/C++里面true与1是等价的,当n为零时,对n连续做两次反运算得到false,C/C++里false与0等价。
method2实际上是一个递归,因为当n不为0时,都变成执行f[1](n-1)+n,即method2(n-1)+n,当n为0时,调用f[0](n),即method1(n)来结束递归。

解法二:利用构造函数实现循环
C++实现:因为C没有类

#include<iostream>
usingnamespace
std;
classTemp
{
public:
Temp(){ ++n; sum += n; }

staticvoid
Reset(){ n = 0; sum = 0; }

staticint
GetSum(){ return
sum; }
private:

staticint
n;

staticint
sum;
};
int
method(intn)
{

Temp::Reset();

Temp
*a = newTemp[n];

delete[]a;
a =
NULL;

returnTemp::GetSum();
}
void
main()
{

int
sum = method(5);
cout<< sum << endl;
}

说明:
method中创建一个大小为n的数组,会调用Temp构造函数n次,每调用一次Temp构造函数便执行一次相加,结果保存在静态变量中,因而每次调用都共享这个静态结果。
注意:
如果换成Java便不可行,因为Java创建数组的时候构造函数不会被调用:

class SuperClass{

public SuperClass(){
System.out.println("SuperClass构造函数被调用");
}
}
publicclass
Test {

publicstaticvoid
main(String[]args){
SuperClass[]sup =
new SuperClass[5];
}
}

以上代码不会有任何输出

解法三:利用虚函数实现递归
C++实现:

#include<iostream>
usingnamespace
std;
classA;
A
* Array[2];
classA
{
public:

virtualint
sum(intn){
return
0; }
};
classB
:publicA
{
public:

int
sum(intn)
{

return
Array[!!n]->sum(n
- 1) + n;
}
};
int
method(intn)
{

A
a;

B
b;
Array[0] =& a;
Array[1] =& b;

int
value = Array[1]->sum(n);

return
value;
}
void
main()
{

int
sum = method(5);
cout<< sum << endl;
}

求素数(质数)
问题:

给定正整数参数 N,求小于 N的质数,从小到大打印出来

思路:
解法一:
分别判断1到N,判断n是否为质数,可以分别用n除以2到(n-1)的每个数,都不能整除则n为质数,该解法时间复杂度为O(N^2)。
解法二:
除了2以外,所有的质数一定是奇数,所以不需要从1到N逐个判断,只要判断3到N的所有奇数便可,也就是循环时不再用for(int n= 1;n<=N;n++),而是用for(int n = 3;n<=N;n+=2)。解法二比解法一减少了一半的运算量。
解法三:
n如果是非质数,那么n一定有一个小于等于n/2的质因子,证明:
假设n=x*y,因为x>=2,所以y<=n/2,怎么证明"质因子"?假设y是质数,则可证,假设y不是质数,则y有两个因子,即y=a*b,如果a或b是质数,则可证,否则进一步对a或b求因子,这样直到找到一个质数因子为止,为什么算法一定会结束?因为每次分解后的因子都小于等于分解前的一半,所以算法总会结束。
这样的话,试除的时候就不必从2到(n-1),只要从2到(n/2)便可,所以时间复杂度又减少了一半。还可以进一步优化,只要除以2到(n/2)的质数便可,而我们在求n之前的质数时,已经把这些质数保存起来了,所以不必从2到(n/2),可以从保存起来的质数数组或链表中去取,取出来后与n/2比较一下,只比较小于等于n/2的便可。
解法四:
如果n是非质数,则n一定有小于等于根号n的质因子,证明:
假设n=x*y,假设x>=根号n,则y<=根号n,进一步对y分解,最终一定找到一个质因子。
所以,只要试除根号n以内的质数便可,这样一来时间复杂度进一步减少了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: