P随动算法
‘壹’ rsa算法中p,q,n,e,d一般大小都为多少啊
RSA遭受攻击的很多情况是因为算法实现的一些细节上的漏洞所导致的,所以在使用RSA算法构造密码系统时,为保证安全,在生成大素数的基础上,还必须认真仔细选择参数,防止漏洞的形成。根据RSA加解密过程,其主要参数有三个:模数N,加密密钥e,解密密钥d。
3.4.1 模数N的确定
虽然迄今人们无法证明,破解RSA系统等于对N因子分解,但一般相信RSA系统的安全性等同于因子分解,即:若能分解因子N,即能攻破RSA系统,若能攻破RSA系统,即能分解因子Ⅳ。因此,在使用RSA系统时,对于模数N的选择非常重要。在RSA算法中,通过产生的两个大素数p和q相乘得到模数N,而后分别通过对它们的数学运算得到密钥对。由此,分解模数N得到p和q是最显然的攻击方法,当然也是最困难的方法,如果模数N被分解,攻击者利用得到的P和q便可计算出,进而通过公开密钥e由解密密钥d,则RSA体制立刻被攻破。相当一部分的对RSA的攻击就是试图分解模数N,选择合适的N是实现RSA算法并防止漏洞的重要环节。一般地,模数N的确定可以遵循以下几个原则:
①p和q之差要大。
当p和q相差很小时,在已知n的情况下,可假定二者的平均值为,然后利用,若等式右边可开方,则得到及,即N被分解。
②p-1和q-1的最大公因子应很小。
③p和q必须为强素数。
一素数p如果满足:
条件一:存在两个大素数,,使得|p-1且|p+1;
条件二:存在四个大素数,,,使得。则此素数为强素数。其中,,,称为3级的素数,,称为2级的素数,p则称为1级的素数,很明显地,任何素数均为3级的素数。只有两个强素数的积所构成的N,其因子分解才是较难的数学问题。
④p和q应大到使得因子分解N为计算上不可能。
RSA的安全性依赖于大数的因子分解,若能因子分解模数N,则RSA即被攻破,因此模数N必须足够大直至因子分解N在计算上不可行。因子分解问题为密码学最基本的难题之一,如今,因子分解的算法已有长足的进步,但仍不足以说明RSA可破解。为保证安全性,实际应用中所选择的素数P和拿至少应该为300位以上的二进制数,相应的模数N将是600位以上的二进制数。
目前,SET(Secure Electronic Transaction)协议中要求CA采用2048比特长的密钥,其他实体使用1024比特的密钥。随着计算能力的提高和分布式运算的发展,安全密钥的长度将是动态增长的。
Jadith Moore给出了使用RSA时有关模数的一些限制:
①若给定模数的一个加/解密密钥指数对已知,攻击者就能分解这个模数。
②若给定模数的一个加/解密密钥指数对已知,攻击者无需分解模数Ⅳ就可以计算出别的加/解密密钥指数对。
③在通信网络中,利用RSA的协议不应该使用公共模数。
④消息应该用随机数填充以避免对加密指数的攻击。
3.4.2 e的选取原则
在RSA算法中,e和互质的条件容易满足,如果选择较小的e,则加、解密的速度加快,也便于存储,但会导致安全问题。
一般地,e的选取有如下原则:
①e不能够太小。在RSA系统中,每人的公开密钥P只要满足即可,也即e可以任意选择,为了减少加密运算时间,很多人采用尽可能小的e值,如3。但是已经证明低指数将会导致安全问题,故此,一般选择e为16位的素数,可以有效防止攻击,又有较快速度。
②e应选择使其在的阶为最大。即存在i,使得,
可以有效抗击攻击。
3.4.3 d的选取原则
一般地,私密密钥d要大于。在许多应用场合,常希望使用位数较短的密钥以降低解密或签名的时间。例如IC卡应用中,IC卡CPU的计算能力远低于计算机主机。长度较短的d可以减少IC卡的解密或签名时间,而让较复杂的加密或验证预算(e长度较长)由快速的计算机主机运行。一个直接的问题就是:解密密钥d的长度减少是否会造成安全性的降低?很明显地,若d的长度太
小,则可以利用已知明文M加密后得,再直接猜测d,求出是否等于M。若是,则猜测J下确,否则继续猜测。若d的长度过小,则猜测的空间变小,猜中的可能性加大,已有证明当时,可以由连分式算法在多项式时间内求出d值。因此其长度不能过小。
‘贰’ 什么是PID调节器,并举例说明P、I、D的调节作用。
PID 调节器是一个在工业控制应用中常见的反馈回路部件,PID是以它的三种纠正算法而命名的。这三种算法都是用加法调整被控制的数值。而实际上这些加法运算大部分变成了减法运算因为被加数总是负值。以下是PID的调节作用举例:
1.比例- 来控制当前,误差值和一个负常数P(表示比例)相乘,然后和预定的值相加。P只是在控制器的输出和系统的误差成比例的时候成立。这种控制器输出的变化与输入控制器的偏差成比例关系。比如说,一个电热器的控制器的比例尺范围是10°C,它的预定值是20°C。那么它在10°C的时候会输出100%,在15°C的时候会输出50%,在19°C的时候输出10%,注意在误差是0的时候,控制器的输出也是0。
2.积分 - 来控制过去,误差值是过去一段时间的误差和,然后乘以一个负常数I,然后和预定值相加。I从过去的平均误差值来找到系统的输出结果和预定值的平均误差。一个简单的比例系统会振荡,会在预定值的附近来回变化,因为系统无法消除多余的纠正。通过加上一个负的平均误差比例值,平均的系统误差值就会总是减少。所以,最终这个PID回路系统会在预定值定下来。
3.微分- 来控制将来,计算误差的一阶导,并和一个负常数D相乘,最后和预定值相加。这个导数的控制会对系统的改变作出反应。导数的结果越大,那么控制系统就对输出结果作出更快速的反应。这个D参数也是PID被称为可预测的控制器的原因。D参数对减少控制器短期的改变很有帮助。一些实际中的速度缓慢的系统可以不需要D参数。
(2)P随动算法扩展阅读:
用更专业的话来讲,一个PID控制器可以被称作一个在频域系统的滤波器。这一点在计算它是否会最终达到稳定结果时很有用。如果数值挑选不当,控制系统的输入值会反复振荡,这导致系统可能永远无法达到预设值。
‘叁’ 如何计算统计学中的P值(200分)
P值即为拒绝域的面积或概率。
P值的计算公式是
=2[1-Φ(z0)] 当被测假设H1为 p不等于p0时;
=1-Φ(z0) 当被测假设H1为 p大于p0时;
=Φ(z0) 当被测假设H1为 p小于p0时;
总之,P值越小,表明结果越显着。但是检验的结果究竟是“显着的”、“中度显着的”还是“高度显着的”需要我们自己根据P值的大小和实际问题来解决。
p值是指在一个概率模型中,统计摘要(如两组样本均值差)与实际观测数据相同,或甚至更大这一事件发生的概率。换言之,是检验假设零假设成立或表现更严重的可能性。
p值若与选定显着性水平(0.05或0.01)相比更小,则零假设会被否定而不可接受。然而这并不直接表明原假设正确。p值是一个服从正态分布的随机变量,在实际使用中因样本等各种因素存在不确定性。产生的结果可能会带来争议。
‘肆’ 双向链表p点后插入节点的算法
双向链表节点:\r\ntypedef struct LinkNode\r\n{\r\n struct LinkNode * pre;\r\n\r\n struct LinkNode *next;\r\n\r\n int data;\r\n\r\n}node;\r\nsrand()\r\nrand()\r\n产生随机数插入到链表\r\n然后你可以网络一下直接插入排序算法,修改指针指向完成排序\r\n\r\n单链表:\r\ntypedef struct LinkNode\r\n{\r\n struct LinkNode *next;\r\n\r\n int data;\r\n\r\n}\r\n方法同上,\r\n自己动手做一下,有助于提高。其实动起手来很简单的,不要想的太复杂
‘伍’ 简述算法的各种表示形式
一、什么是算法
算法是一系列解决问题的清晰指令,也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。算法常常含有重复的步骤和一些比较或逻辑判断。如果一个算法有缺陷,或不适合于某个问题,执行这个算法将不会解决这个问题。不同的算法可能用不同的时间、空间或效率来完成同样的任务。一个算法的优劣可以用空间复杂度与时间复杂度来衡量。
算法的时间复杂度是指算法需要消耗的时间资源。一般来说,计算机算法是问题规模n 的函数f(n),算法执行的时间的增长率与f(n) 的增长率正相关,称作渐进时间复杂度(Asymptotic Time Complexity)。时间复杂度用“O(数量级)”来表示,称为“阶”。常见的时间复杂度有: O(1)常数阶;O(log2n)对数阶;O(n)线性阶;O(n2)平方阶。
算法的空间复杂度是指算法需要消耗的空间资源。其计算和表示方法与时间复杂度类似,一般都用复杂度的渐近性来表示。同时间复杂度相比,空间复杂度的分析要简单得多。
二、算法设计的方法
1.递推法
递推法是利用问题本身所具有的一种递推关系求问题解的一种方法。设要求问题规模为N的解,当N=1时,解或为已知,或能非常方便地得到解。能采用递推法构造算法的问题有重要的递推性质,即当得到问题规模为i-1的解后,由问题的递推性质,能从已求得的规模为1,2,…,i-1的一系列解,构造出问题规模为I的解。这样,程序可从i=0或i=1出发,重复地,由已知至i-1规模的解,通过递推,获得规模为i的解,直至得到规模为N的解。
【问题】 阶乘计算
问题描述:编写程序,对给定的n(n≤100),计算并输出k的阶乘k!(k=1,2,…,n)的全部有效数字。
由于要求的整数可能大大超出一般整数的位数,程序用一维数组存储长整数,存储长整数数组的每个元素只存储长整数的一位数字。如有m位成整数N用数组a[ ]存储:
N=a[m]×10m-1+a[m-1]×10m-2+ … +a[2]×101+a[1]×100
并用a[0]存储长整数N的位数m,即a[0]=m。按上述约定,数组的每个元素存储k的阶乘k!的一位数字,并从低位到高位依次存于数组的第二个元素、第三个元素……。例如,5!=120,在数组中的存储形式为:
3 0 2 1 ……
首元素3表示长整数是一个3位数,接着是低位到高位依次是0、2、1,表示成整数120。
计算阶乘k!可采用对已求得的阶乘(k-1)!连续累加k-1次后求得。例如,已知4!=24,计算5!,可对原来的24累加4次24后得到120。细节见以下程序。
# include <stdio.h>
# include <malloc.h>
# define MAXN 1000
void pnext(int a[ ],int k)
{ int *b,m=a[0],i,j,r,carry;
b=(int * ) malloc(sizeof(int)* (m+1));
for ( i=1;i<=m;i++) b[i]=a[i];
for ( j=1;j<=k;j++)
{ for ( carry=0,i=1;i<=m;i++)
{ r=(i<a[0]?a[i]+b[i]:a[i])+carry;
a[i]=r%10;
carry=r/10;
}
if (carry) a[++m]=carry;
}
free(b);
a[0]=m;
}
void write(int *a,int k)
{ int i;
printf(“%4d!=”,k);
for (i=a[0];i>0;i--)
printf(“%d”,a[i]);
printf(“\n\n”);
}
void main()
{ int a[MAXN],n,k;
printf(“Enter the number n: “);
scanf(“%d”,&n);
a[0]=1;
a[1]=1;
write(a,1);
for (k=2;k<=n;k++)
{ pnext(a,k);
write(a,k);
getchar();
}
}
2.递归
递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。
能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。
【问题】 编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。
斐波那契数列为:0、1、1、2、3、……,即:
fib(0)=0;
fib(1)=1;
fib(n)=fib(n-1)+fib(n-2) (当n>1时)。
写成递归函数有:
int fib(int n)
{ if (n==0) return 0;
if (n==1) return 1;
if (n>1) return fib(n-1)+fib(n-2);
}
递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算fib(n-1)和fib(n-2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib(1)和fib(0),分别能立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。
在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。
在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。
由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。
【问题】 组合问题
问题描述:找出从自然数1、2、……、n中任取r个数的所有组合。例如n=5,r=3的所有组合为: (1)5、4、3 (2)5、4、2 (3)5、4、1
(4)5、3、2 (5)5、3、1 (6)5、2、1
(7)4、3、2 (8)4、3、1 (9)4、2、1
(10)3、2、1
分析所列的10个组合,可以采用这样的递归思想来考虑求组合函数的算法。设函数为void comb(int m,int k)为找出从自然数1、2、……、m中任取k个数的所有组合。当组合的第一个数字选定时,其后的数字是从余下的m-1个数中取k-1数的组合。这就将求m个数中取k个数的组合问题转化成求m-1个数中取k-1个数的组合问题。设函数引入工作数组a[ ]存放求出的组合的数字,约定函数将确定的k个数字组合的第一个数字放在a[k]中,当一个组合求出后,才将a[ ]中的一个组合输出。第一个数可以是m、m-1、……、k,函数将确定组合的第一个数字放入数组后,有两种可能的选择,因还未去顶组合的其余元素,继续递归去确定;或因已确定了组合的全部元素,输出这个组合。细节见以下程序中的函数comb。
【程序】
# include <stdio.h>
# define MAXN 100
int a[MAXN];
void comb(int m,int k)
{ int i,j;
for (i=m;i>=k;i--)
{ a[k]=i;
if (k>1)
comb(i-1,k-1);
else
{ for (j=a[0];j>0;j--)
printf(“%4d”,a[j]);
printf(“\n”);
}
}
}
void main()
{ a[0]=3;
comb(5,3);
}
3.回溯法
回溯法也称为试探法,该方法首先暂时放弃关于问题规模大小的限制,并将问题的候选解按某种顺序逐一枚举和检验。当发现当前候选解不可能是解时,就选择下一个候选解;倘若当前候选解除了还不满足问题规模要求外,满足所有其他要求时,继续扩大当前候选解的规模,并继续试探。如果当前候选解满足包括问题规模在内的所有要求时,该候选解就是问题的一个解。在回溯法中,放弃当前候选解,寻找下一个候选解的过程称为回溯。扩大当前候选解的规模,以继续试探的过程称为向前试探。
【问题】 组合问题
问题描述:找出从自然数1,2,…,n中任取r个数的所有组合。
采用回溯法找问题的解,将找到的组合以从小到大顺序存于a[0],a[1],…,a[r-1]中,组合的元素满足以下性质:
(1) a[i+1]>a[i],后一个数字比前一个大;
(2) a[i]-i<=n-r+1。
按回溯法的思想,找解过程可以叙述如下:
首先放弃组合数个数为r的条件,候选组合从只有一个数字1开始。因该候选解满足除问题规模之外的全部条件,扩大其规模,并使其满足上述条件(1),候选组合改为1,2。继续这一过程,得到候选组合1,2,3。该候选解满足包括问题规模在内的全部条件,因而是一个解。在该解的基础上,选下一个候选解,因a[2]上的3调整为4,以及以后调整为5都满足问题的全部要求,得到解1,2,4和1,2,5。由于对5不能再作调整,就要从a[2]回溯到a[1],这时,a[1]=2,可以调整为3,并向前试探,得到解1,3,4。重复上述向前试探和向后回溯,直至要从a[0]再回溯时,说明已经找完问题的全部解。按上述思想写成程序如下:
【程序】
# define MAXN 100
int a[MAXN];
void comb(int m,int r)
{ int i,j;
i=0;
a[i]=1;
do {
if (a[i]-i<=m-r+1
{ if (i==r-1)
{ for (j=0;j<r;j++)
printf(“%4d”,a[j]);
printf(“\n”);
}
a[i]++;
continue;
}
else
{ if (i==0)
return;
a[--i]++;
}
} while (1)
}
main()
{ comb(5,3);
}
4.贪婪法
贪婪法是一种不追求最优解,只希望得到较为满意解的方法。贪婪法一般可以快速得到满意的解,因为它省去了为找最优解要穷尽所有可能而必须耗费的大量时间。贪婪法常以当前情况为基础作最优选择,而不考虑各种可能的整体情况,所以贪婪法不要回溯。
例如平时购物找钱时,为使找回的零钱的硬币数最少,不考虑找零钱的所有各种发表方案,而是从最大面值的币种开始,按递减的顺序考虑各币种,先尽量用大面值的币种,当不足大面值币种的金额时才去考虑下一种较小面值的币种。这就是在使用贪婪法。这种方法在这里总是最优,是因为银行对其发行的硬币种类和硬币面值的巧妙安排。如只有面值分别为1、5和11单位的硬币,而希望找回总额为15单位的硬币。按贪婪算法,应找1个11单位面值的硬币和4个1单位面值的硬币,共找回5个硬币。但最优的解应是3个5单位面值的硬币。
【问题】 装箱问题
问题描述:装箱问题可简述如下:设有编号为0、1、…、n-1的n种物品,体积分别为v0、v1、…、vn-1。将这n种物品装到容量都为V的若干箱子里。约定这n种物品的体积均不超过V,即对于0≤i<n,有0<vi≤V。不同的装箱方案所需要的箱子数目可能不同。装箱问题要求使装尽这n种物品的箱子数要少。
若考察将n种物品的集合分划成n个或小于n个物品的所有子集,最优解就可以找到。但所有可能划分的总数太大。对适当大的n,找出所有可能的划分要花费的时间是无法承受的。为此,对装箱问题采用非常简单的近似算法,即贪婪法。该算法依次将物品放到它第一个能放进去的箱子中,该算法虽不能保证找到最优解,但还是能找到非常好的解。不失一般性,设n件物品的体积是按从大到小排好序的,即有v0≥v1≥…≥vn-1。如不满足上述要求,只要先对这n件物品按它们的体积从大到小排序,然后按排序结果对物品重新编号即可。装箱算法简单描述如下:
{ 输入箱子的容积;
输入物品种数n;
按体积从大到小顺序,输入各物品的体积;
预置已用箱子链为空;
预置已用箱子计数器box_count为0;
for (i=0;i<n;i++)
{ 从已用的第一只箱子开始顺序寻找能放入物品i 的箱子j;
if (已用箱子都不能再放物品i)
{ 另用一个箱子,并将物品i放入该箱子;
box_count++;
}
else
将物品i放入箱子j;
}
}
上述算法能求出需要的箱子数box_count,并能求出各箱子所装物品。下面的例子说明该算法不一定能找到最优解,设有6种物品,它们的体积分别为:60、45、35、20、20和20单位体积,箱子的容积为100个单位体积。按上述算法计算,需三只箱子,各箱子所装物品分别为:第一只箱子装物品1、3;第二只箱子装物品2、4、5;第三只箱子装物品6。而最优解为两只箱子,分别装物品1、4、5和2、3、6。
若每只箱子所装物品用链表来表示,链表首结点指针存于一个结构中,结构记录尚剩余的空间量和该箱子所装物品链表的首指针。另将全部箱子的信息也构成链表。以下是按以上算法编写的程序。
【程序】
# include <stdio.h>
# include <stdlib.h>
typedef struct ele
{ int vno;
struct ele *link;
} ELE;
typedef struct hnode
{ int remainder;
ELE *head;
Struct hnode *next;
} HNODE;
void main()
{ int n, i, box_count, box_volume, *a;
HNODE *box_h, *box_t, *j;
ELE *p, *q;
Printf(“输入箱子容积\n”);
Scanf(“%d”,&box_volume);
Printf(“输入物品种数\n”);
Scanf(“%d”,&n);
A=(int *)malloc(sizeof(int)*n);
Printf(“请按体积从大到小顺序输入各物品的体积:”);
For (i=0;i<n;i++) scanf(“%d”,a+i);
Box_h=box_t=NULL;
Box_count=0;
For (i=0;i<n;i++)
{ p=(ELE *)malloc(sizeof(ELE));
p->vno=i;
for (j=box_h;j!=NULL;j=j->next)
if (j->remainder>=a[i]) break;
if (j==NULL)
{ j=(HNODE *)malloc(sizeof(HNODE));
j->remainder=box_volume-a[i];
j->head=NULL;
if (box_h==NULL) box_h=box_t=j;
else box_t=boix_t->next=j;
j->next=NULL;
box_count++;
}
else j->remainder-=a[i];
for (q=j->next;q!=NULL&&q->link!=NULL;q=q->link);
if (q==NULL)
{ p->link=j->head;
j->head=p;
}
else
{ p->link=NULL;
q->link=p;
}
}
printf(“共使用了%d只箱子”,box_count);
printf(“各箱子装物品情况如下:”);
for (j=box_h,i=1;j!=NULL;j=j->next,i++)
{ printf(“第%2d只箱子,还剩余容积%4d,所装物品有;\n”,I,j->remainder);
for (p=j->head;p!=NULL;p=p->link)
printf(“%4d”,p->vno+1);
printf(“\n”);
}
}
5.分治法
任何一个可以用计算机求解的问题所需的计算时间都与其规模N有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算;n=2时,只要作一次比较即可排好序;n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。
分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
如果原问题可分割成k个子问题(1<k≤n),且这些子问题都可解,并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。
分治法所能解决的问题一般具有以下几个特征:
(1)该问题的规模缩小到一定的程度就可以容易地解决;
(2)该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
(3)利用该问题分解出的子问题的解可以合并为该问题的解;
(4)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。
上述的第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;第二条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用;第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑贪心法或动态规划法。第四条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。
分治法在每一层递归上都有三个步骤:
(1)分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题;
(2)解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题;
(3)合并:将各个子问题的解合并为原问题的解。
6.动态规划法
经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题。简单地采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂级数增加。
为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法。以下先用实例说明动态规划方法的使用。
【问题】 求两字符序列的最长公共字符子序列
问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。
考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质:
(1) 如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列;
(2) 如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列;
(3) 如果am-1!=bn-1,则若zk-1!=bn-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列。
这样,在找A和B的公共子序列时,如有am-1=bn-1,则进一步解决一个子问题,找“a0,a1,…,am-2”和“b0,b1,…,bm-2”的一个最长公共子序列;如果am-1!=bn-1,则要解决两个子问题,找出“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列和找出“a0,a1,…,am-1”和“b0,b1,…,bn-2”的一个最长公共子序列,再取两者中较长者作为A和B的最长公共子序列。
代码如下:
# include <stdio.h>
# include <string.h>
# define N 100
char a[N],b[N],str[N];
int lcs_len(char *a, char *b, int c[ ][ N])
{ int m=strlen(a), n=strlen(b), i,j;
for (i=0;i<=m;i++) c[i][0]=0;
for (i=0;i<=n;i++) c[0][i]=0;
for (i=1;i<=m;i++)
for (j=1;j<=m;j++)
if (a[i-1]==b[j-1])
c[i][j]=c[i-1][j-1]+1;
else if (c[i-1][j]>=c[i][j-1])
c[i][j]=c[i-1][j];
else
c[i][j]=c[i][j-1];
return c[m][n];
}
char *buile_lcs(char s[ ],char *a, char *b)
{ int k, i=strlen(a), j=strlen(b);
k=lcs_len(a,b,c);
s[k]=’\0’;
while (k>0)
if (c[i][j]==c[i-1][j]) i--;
else if (c[i][j]==c[i][j-1]) j--;
else { s[--k]=a[i-1];
i--; j--;
}
return s;
}
void main()
{ printf (“Enter two string(<%d)!\n”,N);
scanf(“%s%s”,a,b);
printf(“LCS=%s\n”,build_lcs(str,a,b));
}
7.迭代法
迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行:
(1) 选一个方程的近似根,赋给变量x0;
(2) 将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0;
(3) 当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。
若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为:
程序如下:
【算法】迭代法求方程组的根
{ for (i=0;i<n;i++)
x[i]=初始近似根;
do {
for (i=0;i<n;i++)
y[i] = x[i];
for (i=0;i<n;i++)
x[i] = gi(X);
for (delta=0.0,i=0;i<n;i++)
if (fabs(y[i]-x[i])>delta) delta=fabs(y[i]-x[i]); } while (delta>Epsilon);
for (i=0;i<n;i++)
printf(“变量x[%d]的近似根是 %f”,I,x[i]);
printf(“\n”);
} 具体使用迭代法求根时应注意以下两种可能发生的情况:
(1)如果方程无解,算法求出的近似根序列就不会收敛,迭代过程会变成死循环,因此在使用迭代算法前应先考察方程是否有解,并在程序中对迭代的次数给予限制;
(2)方程虽然有解,但迭代公式选择不当,或迭代的初始近似根选择不合理,也会导致迭代失败。
8.穷举搜索法
穷举搜索法是对可能是解的众多候选解按某种顺序进行逐一枚举和检验,并从众找出那些符合要求的候选解作为问题的解。
【问题】 将A、B、C、D、E、F这六个变量排成如图所示的三角形,这六个变量分别取[1,6]上的整数,且均不相同。求使三角形三条边上的变量之和相等的全部解。如图就是一个解。
程序引入变量a、b、c、d、e、f,并让它们分别顺序取1至6的整数,在它们互不相同的条件下,测试由它们排成的如图所示的三角形三条边上的变量之和是否相等,如相等即为一种满足要求的排列,把它们输出。当这些变量取尽所有的组合后,程序就可得到全部可能的解。程序如下:
# include <stdio.h>
void main()
{ int a,b,c,d,e,f;
for (a=1;a<=6;a++) {
for (b=1;b<=6;b++) {
if (b==a) continue;
for (c=1;c<=6;c++) {
if (c==a)||(c==b) continue;
for (d=1;d<=6;d++) {
if (d==a)||(d==b)||(d==c) continue;
for (e=1;e<=6;e++) {
if (e==a)||(e==b)||(e==c)||(e==d) continue;
f = 21-(a+b+c+d+e);
if ((a+b+c==c+d+e))&&(a+b+c==e+f+a)) {
printf(“%6d,a);
printf(“%4d%4d”,b,f);
printf(“%2d%4d%4d”,c,d,e);
scanf(“%*c”);
}
}
}
}
}
}}
按穷举法编写的程序通常不能适应变化的情况。如问题改成有9个变量排成三角形,每条边有4个变量的情况,程序的循环重数就要相应改变。
‘陆’ noip要用到哪些算法
前言
离NOIP还有一个星期,匆忙的把寒假整理的算法补充完善,看着当时的整理觉得那时还年少。第二页贴了几张从贴吧里找来的图片,看着就很热血
的。旁边的同学都劝我不要再放PASCAL啊什么的了,毕竟我们的下一级直接学C++。即便我本人对C++也是赞赏有加,不过PASCAL作为梦的开始终
究不能忘记。不像机房中其余的OIERS,我以后并不想学计算机类的专业。当年来学这个竞赛就是为了兴趣,感受计算机之美的。经过时迁,计划赶不上变化,
现在尚处于迷茫之中,也很难说当时做的决定是对是错。然而我一直坚信迷茫的时候选择难走的路会看见更好的风景。
这篇文章简单的说了一下NOIP考试中会常用的算法,可能难度掌握的不是太好,有一部分内容不是NOIP考查范围,然而随着难度的增加,看一些更高级的算法也没有坏处。还有一些非常非常基础的比如链表啊什么的就直接没有写上(别问我为什么整理了那么多的排序算法)。
最后祝大家在NOIP中取得理想的成绩!
搜索
DFS
框架
procere dfs(x);
var
begin
if 达到目标状态 then 输出结果并退出过程;
if 满足剪枝条件 then exit;
for i:=1 to 搜索宽度 do
begin
备份现场;(注意如果现场使用了全局变量,则需要使用局部变量备份)
dfs(参数+增量);
恢复现场;
end;
优化
(1) 最优化剪枝:求最优值时,当前的状态无论如何不可能比最优值更优,则退出,可与展望结合剪枝
(2) 可行性剪枝:提前判断该状态是否能得到可行解,如不能则退出
(3) 记忆化搜索:对于已经搜索过的状态直接退出
(4) 改变搜索顺序:对于看起来希望更大的决策先进行搜索
(5) 优化搜索策略
(6) 预处理找到大体搜索翻译
(7) 改写成IDA*算法
(8) 卡时(注意现在联赛中禁止使用meml掐时)
BFS
框架
初始化;把初始布局存入
设首指针head=0; 尾指针tail:=1;
repeat
inc(head),取出队列首记录为当前被扩展结点;
for i:=1 to 规则数 do {r是规则编号}
begin
if 新空格位置合法 then
begin
if 新布局与队列中原有记录不重复
tail增1,并把新布局存入队尾;
if 达到目标 then 输出并退出;
end;
end;
until head>=tail; {队列空}
优化
判重的优化:hash,二叉排序树
双向广搜或启发式搜索
改写成A*算法
二分优化
排序
冒泡排序
var a:array[1..100] of longint;t,n,i,j:longint;
procere sort;
begin
for i:=1 to n-1 do{与每个数都进行比较}
for j:=1 to n-i do
if a[j]>a[j+1] then
begin
t:=a[j];
a[j]:=a[j+1];
a[j+1]:=t;
end;
end;
选择排序
var a:array[1..100] of longint;t,n,i,j:longint;
procere sort;
begin
for i:=1 to n-1 do
for j:=1+i to n do{大数沉小数浮}
if a[j]>a[i] then
begin
t:=a[j];
a[j]:=a[i];
a[i]:=t;
end;
end;
插入排序
var a:array[0..100] of longint;n,i,j,t:longint;
procere sort;
begin
for i:=2 to n do
for j:=1 to (i-1) do
begin
if (a[i]<a[j]) then
begin
t:=a[j];
a[j]:=a[i];
a[i]:=t;
end;
end;
end;
桶排序
var a,b:array[0..100] of longint;r,i,j,t,k,n:longint;
procere sort;
begin
for i:=0 to 100 do b[i]:=0;{为B数组清零,小桶内容清零}
for i:=1 to n do b[a[i]]:=b[a[i]]+1;
{桶的序号就是那个要排序的东西;出现一次,桶里得旗数加一}
for i:=0 to 100 do{扫描所有的桶}
begin
if b[i]<>0 then{桶里有旗}
for j:=1 to b[i] do write(i,' ');{桶的序号就是那个数}
end;
end;
快速排序
var a:array[1..100] of longint;
n,i,h,g:longint;
procere kp(l,r:longint);{变量不能与全局变量相同,否则会被抹去}
var b,m,i,j,t:longint;
begin
i:=l;
j:=r;
m:=a[(l+r) div 2];{基准数最好从中间取}
repeat
while a[j]>m do dec(j);
while a[i]<m do inc(i);{两侧的哨兵移动}
if i<=j then
{哨兵未碰面}{“=”利用repeat循环的性质,使repeat循环得以结束}
begin
t:=a[j];
a[j]:=a[i
a[i]:=t;{交换两个哨兵的值}
inc(j);
dec(j);{哨兵继续运动}
end;
until i>j;
if j>l then kp(l,j);
if i<r then kp(i,r);{都是循环不结束后进行的动作}
end;
begin
read(n);
for i:=1 to n do read(a[i]);
kp(1,n); {“一”位置与“N”位置}
for i:=1 to n-1 do write(a[i],' ');
write(a[n]);{防止多输出空格使程序结果出错}
end.
堆排序
var a:array[1..100] of longint;
n,i,b:longint;
procere jianshu(i:longint);
begin
while ((a[i]>a[i*2])or(a[i]>a[i*2+1]))and(i<=n div 2) do
{当父亲数大于子女数时并且他有孩子时进行}
begin
if a[i*2]<=a[i*2+1]{左儿子小于右儿子}
then
begin
b:=a[i*2]; a[i*2]:=a[i];a[i]:=b;{左右儿子的值互换}
jianshu(i*2);{继续为左儿子建树}
end
else
begin
b:=a[i*2+1];a[i*2+1]:=a[i];a[i]:=b;
jianshu(i*2+1);{上同,不过是为右儿子建树}
end;
end;
end;
procere tiao;
begin
while n<>0 do
begin
write(a[1]);
a[1]:=a[n];
n:=n-1;
for i:=(n div 2) downto 1 do
jianshu(i);
end;
end;
begin
read(n);
for i:=1 to n do
read(a[i]);
for i:=(n div 2) downto 1 do
jianshu(i);
tiao;
end.
数学定理
中国剩余定理
若有一些两两互质的整数m1, m2,… mn,则对任意的整数: a
1, a
2,... an
,以下联立同余方程组对模数m1, m2,… mn 有公解:
康托展开
a[i]为当前未出现的元素中是排在第几个(从0开始)
把一个整数X展开成如下形式:
X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[2]*1!+a[1]*0!
其中a[i]为当前未出现的元素中是排在第几个(从0开始),并且0<=a[i]<i(1<=i<=n)
错排通项
考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。
f[1]=0;f[2]=1;
f[n] =(n-1)(f[n-2) + f[n-1])
f[n]:=n![1-1/1!+1/2!-1/3!……+(-1)^n*1/n!]
f[n] = (n!/e+0.5),其中e是自然对数的底,[x]为x的整数部分。
费马大定理
费马大定理,又被称为“费马最后的定理”,由法国数学家费马提出。它断言当整数n >2时,关于x, y, z的方程 xn + yn = zn 没有正整数解。
被提出后,经历多人猜想辩证,历经三百多年的历史,最终在1995年被英国数学家安德鲁·怀尔斯证明。
费马小定理
假如a是一个整数,p是一个素数,那么 ap ≡a (mod p)。
如果a不是p的倍数,这个定理也可以写成ap-1 ≡1 (mod p)。----这个更加常用
逆元
由费马小定理:假如p是质数,且gcd(a,p)=1,那么ap-1≡1(mod p)
逆元:如果ab≡1(mod p),那么在模p意义下,a、b互为逆元。
所以,假如p是质数,且gcd(a,p)=1,那么a的逆元是ap-2
逆元的作用:在模意义下进行除法。乘a的逆元等同于除以a。
欧拉函数
在数论中,对正整数n,欧拉函数是小于或等于n的正整数中与n互质的数的数目。此函数以其首名研究者欧拉命名,它又称为φ函数、欧拉商数等。
若m,a为正整数,且m,a互素,(gcd(a,m) = 1),则aφ(m)≡1,其中为φ(m)欧拉函数,mod m为同余关系。
欧拉定理实际上是费马小定理的推广。
Stirling数
第一类s(p,k)的一个的组合学解释是:将p个物体排成k个非空循环排列的方法数。
s(p,k)的递推公式: s(p,k)=(p-1)*s(p-1,k)+s(p-1,k-1) ,1<=k<=p-1
边界条件:s(p,0)=0 ,p>=1 s(p,p)=1 ,p>=0
递推关系的说明:
考虑第p个物品,p可以单独构成一个非空循环排列,这样前p-1种物品构成k-1个非空循环排列,方法数为s(p-1,k-1);
也可以前p-1种物品构成k个非空循环排列,而第p个物品插入第i个物品的左边,这有(p-1)*s(p-1,k)种方法。
第二类S(p,k)的一个组合学解释是:将p个物体划分成k个非空的不可辨别的(可以理解为盒子没有编号)集合的方法数。
k!S(p,k)是把p个人分进k间有差别(如:被标有房号)的房间(无空房)的方法数。
S(p,k)的递推公式是:S(p,k)=k*S(p-1,k)+S(p-1,k-1) ,1<= k<=p-1
边界条件:S(p,p)=1 ,p>=0 S(p,0)=0 ,p>=1
递推关系的说明:
考虑第p个物品,p可以单独构成一个非空集合,此时前p-1个物品构成k-1个非空的不可辨别的集合,方法数为S(p-1,k-1);
也可以前p-1种物品构成k个非空的不可辨别的集合,第p个物品放入任意一个中,这样有k*S(p-1,k)种方法。
PS:第一类斯特林数和第二类斯特林数有相同的初始条件,但递推关系不同。
Stirling's approximation
快速求阶乘,不推荐用于竞赛。
数论
GCD&LCM
//GCD
function gcd(a,b:longint):longint;
begin
if b=0 then gcd:=a
else gcd:=gcd (b,a mod b);
end ;
//LCM
function lcm(a,b:longint):longint;
begin
if a<b then swap(a,b);
lcm:=a;
while lcm mod b>0 do inc(lcm,a);
end;
素数
//单个判断
function prime (n: longint): boolean;
var i longint;
begin
for i:=2 to trunc(sqrt(n)) do
if n mod i=0 then exit(false)
exit(true);
end;
//筛法打表
procere main;
var i,j:longint;
begin
fillchar(f,sizeof(f),true);
f[0]:=false;f[1]:=false;
for i:=2 to trunc(sqrt(maxn)) do
if f[i] then
begin
j:=2*i;
while j<= maxn do
begin
f[j]:=false;
inc(j,i);
end;
end;
end;
快速幂
{a^b mod n}
function f(a,b,n:int64):int64;
var t,y:int64;
begin
t:=1;
y:=a;
while b<>0 do
begin
if(b and 1)=1 then t:=t*y mod n;
y:=y*y mod n;
{这里用了一个很强大的技巧,y*y即求出了a^(2^(i-1))不知道这是什么的看原理}
b:=b shr 1;{去掉已经处理过的一位}
end;
exit(t);
end;
模运算法则
(A+B) mod C = (A mod C + B mod C) mod C
(A-B) mod C = (A mod C - B mod C) mod C
(A * B) mod C = (A mod C) * (B mod C) mod C
(A / B) mod C = ???
‘柒’ PID算法中的动态P是什么意思
1,PID增量式算法:是PID控制算法的一种,有滤波的选择,系统的动态过程加速的功能。(1)滤波的选择:可以对输入加一个前置滤波器,使得进入控制算法的给定值不突变,而是有一定惯性延迟的缓变量。(2)系统的动态过程加速:如果被控量继续偏离给定值,则这两项符号相同,而当被控量向给定值方向变化时,则这两项的符号相反。由于这一性质,当被控量接近给定值的时候,反号的比例作用阻碍了积分作用,因而避免了积分超调以及随之带来的振荡,这显然是有利于控制的。但如果被控量远未接近给定值,仅刚开始向给定值变化时,由于比例和积分反向,将会减慢控制过程。2,PID增量算法的饱和作用及其抑制:在PID增量算法中,由于执行元件本身是机械或物理的积分储存单元,如果给定值发生突变时,由算法的比例部分和微分部分计算出的控制增量可能比较大,如果该值超过了执行元件所允许的最大限度,那么实际上执行的控制增量将时受到限制时的值,多余的部分将丢失,将使系统的动态过程变长,因此,需要采取一定的措施改善这种情况。
‘捌’ 怎么理解 P 问题和 NP 问题
P的含义是polynomial(多项式的)。
要了解什么是P问题NP问题,首先要引入算法和时间复杂度的概念。
算法一般指的是一套用于解决问题的流程。算法要保障结果的正确性和运行效率。
对于时间复杂度,一般指的是运算步骤次数和数据量之间的关系。比如说对于一个算法,如果数据量为n,那么如果它的运算步骤次数为2*N^2+2次,我们取最高影响的那一项为N^2。这项是整个算法增长速度最快一项。于是我们把算法复杂度计成O(N^2)。
一般的算法关系如此O(logn)<O(n)<O(n*logn)<O(n^2)等等
然后若K和C是任意常数,如果C<1,则O(C^n)<O(1)<O(n^k)。反之,若C>1,O(C^n)>O(n^k)。就好比2的n次方比n的任意阶多项式都增长得快。
P问题指的是一些能够被多项式时间复杂度的算法准确解出的问题。而NP问题是目前为止,尚未发现多项式时间复杂度内算法能够解决的问题。NP问题目前最快也是指数级别的算法时间复杂度。
在当前,我们有时会用近似算法快速解决NP问题。对于近似算法(有时用贪心法,有时用放宽条件的线性规划),我们会得到接近最优解的可行解。比如求一个最大值,我如果用的是0.9倍近似算法,意味着我的算法产生的结果最小也是最优解的0.9倍。这样可以保障得到的结果足够好。
如果P=NP被证明了,也就是说NP问题都能被多项式时间复杂度解决了,那这将是人类历史的巨大变革。
在目前准确解决NP问题的技巧中,你可以使用决策树算法,算法核心以及动态规划结合图的树分解等技巧。然而,这些算法技巧还不足以把NP问题放在多项式时间复杂度下面解决。
‘玖’ 物理中,物质波的动量P怎样计算
物质波的有关计算
在光具有波粒二象性的启发下,法国物理学家德布罗意(1892~)在1924
年提出一个假说,指出波粒二象性不只是光子才有,一切微观粒子,包括电子和
质子、中子,都有波粒二象性.他把光子的动量与波长的关系式p=h/λ
推广到一切微观粒子上,指出:具有质量m 和速度v 的运动粒子也具有波动性,这种波
的波长等于普朗克恒量h 跟粒子动量mv 的比.即λ= h/(mv).这个关系式后来就叫做德布罗意公式.
‘拾’ 一道数据结构问题,请问,这道11题,这里算法中为什么引入p
首先,题主所说可以一直使用参数t而不引入p,在具体实现(c语言实现)中是完全正确的。甚至可以说不引入p可以节约一次赋值操作,效率更优。
如果回答地简单点,那就是“没有什么特殊含义,就是习惯如此而已”。
但是我们“过分”解读一下,这种习惯还是有其独到之处的。
这就不得不提到在思考问题中的递增的思考方式:
怎样健壮地实现功能
在以上基础上,怎样高效率地实现功能
在以上基础上,怎样优雅地实现功能
在以上基础上,怎样使得这种实现更具有一般性(划重点)
在以上基础上,怎样使得这种实现具有可扩充性、可复用性。
这也是在工程开发中要考虑的一些点。
所以:
1.这样写具有更高的通用性和一般性:我们知道,不是每个语言对参数的传递都是“传值”,我们可以把这段代码看成c语言,也可以看成伪代码。有些语言在函数内修改参数是可以影响到调用方的。根据普遍性来讲,这里复制一份比较具有通用性和一般性,因为这样写不管你用的是什么类型的语言都不会因为参数的传递方式不同而出现错误。
2.高扩扩性:在函数内部,在某很多些场合,我们尽量不去将参数直接拿来当变量,因为我们不知道什么时候会有改动需求,可能之后函数下面还需要用到原来参数的值,如果上面将参数改了,后面再进行扩充时就需要改变原有的、已经稳定了的代码——这无疑会带来风险和成本。