16种算法
㈠ 7172 5696 4948 9273 5247 6479 8359 怎么算24点
24点游戏题目【7,1,7,2】共有1种算法1. [﹙7×7﹚-1]÷2 24点游戏题目【5,6,9,6】共有1种算法1. ﹙9×6﹚-﹙5×6﹚ 24点游戏题目:用4,4,8,9算24点。24点游戏规则:每个数字只用一次,但可以调换顺序,只允许加、减、乘、除等四则运算,可以使用括号改变运算顺序。用4,4,8,9算24点,共有8种算法。[﹙9×4﹚-8]-4﹙9×4﹚-﹙8+4﹚﹙4×9﹚-﹙4+8﹚[﹙4×9﹚-8]-4﹙4×9﹚-﹙8+4﹚﹙9×4﹚-﹙4+8﹚[﹙9×4﹚-4]-8[﹙4×9﹚-4]-824点游戏题目:用2,3,7,9算24点。24点游戏规则:每个数字只用一次,但可以调换顺序,只允许加、减、乘、除等四则运算,可以使用括号改变运算顺序。用2,3,7,9算24点,共有26种算法。[﹙7×3﹚-9]×2﹙3÷2﹚×﹙9+7﹚9+[3×﹙7-2﹚]3×[﹙7+9﹚÷2][﹙9+7﹚÷2]×3﹙7+9﹚÷﹙2÷3﹚[﹙9+7﹚×3]÷2[﹙7+9﹚×3]÷29-[﹙2-7﹚×3][3×﹙9+7﹚]÷2﹙3÷2﹚×﹙7+9﹚﹙9+7﹚×﹙3÷2﹚[3×﹙7-2﹚]+93÷[2÷﹙7+9﹚]3×[﹙9+7﹚÷2][﹙7-2﹚×3]+9[3×﹙7+9﹚]÷22×[﹙7×3﹚-9]2×[﹙3×7﹚-9]﹙7+9﹚×﹙3÷2﹚﹙9+7﹚÷﹙2÷3﹚3÷[2÷﹙9+7﹚][﹙3×7﹚-9]×29-[3×﹙2-7﹚]9+[﹙7-2﹚×3][﹙7+9﹚÷2]×324点游戏题目:用2,4,5,7算24点。24点游戏规则:每个数字只用一次,但可以调换顺序,只允许加、减、乘、除等四则运算,可以使用括号改变运算顺序。用2,4,5,7算24点,共有20种算法。[﹙7+5﹚×4]÷24÷[2÷﹙5+7﹚]﹙5+7﹚×﹙4-2﹚[4×﹙7+5﹚]÷2﹙7+5﹚×﹙4-2﹚﹙4÷2﹚×﹙7+5﹚[4×﹙5+7﹚]÷2﹙5+7﹚÷﹙2÷4﹚﹙4÷2﹚×﹙5+7﹚﹙7+5﹚×﹙4÷2﹚﹙7+5﹚÷﹙2÷4﹚4÷[2÷﹙7+5﹚]4×[﹙5+7﹚÷2]4×[﹙7+5﹚÷2]﹙4-2﹚×﹙5+7﹚[﹙7+5﹚÷2]×4[﹙5+7﹚×4]÷2﹙5+7﹚×﹙4÷2﹚[﹙5+7﹚÷2]×4﹙4-2﹚×﹙7+5﹚24点游戏题目:用4,6,7,9算24点。24点游戏规则:每个数字只用一次,但可以调换顺序,只允许加、减、乘、除等四则运算,可以使用括号改变运算顺序。用4,6,7,9算24点,共有16种算法。[﹙9+7﹚×6]÷46×[﹙7+9﹚÷4]﹙7+9﹚÷﹙4÷6﹚[﹙9+7﹚÷4]×6﹙9+7﹚×﹙6÷4﹚﹙6÷4﹚×﹙9+7﹚[﹙7+9﹚÷4]×6﹙6÷4﹚×﹙7+9﹚6÷[4÷﹙7+9﹚]﹙7+9﹚×﹙6÷4﹚6÷[4÷﹙9+7﹚][6×﹙7+9﹚]÷46×[﹙9+7﹚÷4][﹙7+9﹚×6]÷4﹙9+7﹚÷﹙4÷6﹚[6×﹙9+7﹚]÷424点游戏题目:用3,5,8,9算24点。24点游戏规则:每个数字只用一次,但可以调换顺序,只允许加、减、乘、除等四则运算,可以使用括号改变运算顺序。用3,5,8,9算24点,共有18种算法。5+[﹙3×9﹚-8][9×﹙8-5﹚]-35+[﹙9×3﹚-8]﹙5-8﹚+﹙3×9﹚[﹙8-5﹚×9]-3﹙9×3﹚-﹙8-5﹚﹙9×3﹚+﹙5-8﹚﹙3×9﹚-﹙8-5﹚[﹙3×9﹚+5]-8﹙5-8﹚+﹙9×3﹚[5+﹙3×9﹚]-8[﹙9×3﹚-8]+5﹙3×9﹚+﹙5-8﹚[5+﹙9×3﹚]-8[﹙3×9﹚-8]+55-[8-﹙3×9﹚][﹙9×3﹚+5]-85-[8-﹙9×3﹚]㈡ 大数据最常用的算法有哪些
奥地利符号计算研究所(Research Institute for Symbolic Computation,简称RISC)的Christoph Koutschan博士在自己的页面上发布了一篇文章,提到他做了一个调查,参与者大多数是计算机科学家,他请这些科学家投票选出最重要的算法,以下是这次调查的结果,按照英文名称字母顺序排序。
大数据等最核心的关键技术:32个算法
1、A* 搜索算法——图形搜索算法,从给定起点到给定终点计算出路径。其中使用了一种启发式的估算,为每个节点估算通过该节点的最佳路径,并以之为各个地点排定次序。算法以得到的次序访问这些节点。因此,A*搜索算法是最佳优先搜索的范例。
2、集束搜索(又名定向搜索,Beam Search)——最佳优先搜索算法的优化。使用启发式函数评估它检查的每个节点的能力。不过,集束搜索只能在每个深度中发现最前面的m个最符合条件的节点,m是固定数字——集束的宽度。
3、二分查找(Binary Search)——在线性数组中找特定值的算法,每个步骤去掉一半不符合要求的数据。
4、分支界定算法(Branch and Bound)——在多种最优化问题中寻找特定最优化解决方案的算法,特别是针对离散、组合的最优化。
5、Buchberger算法——一种数学算法,可将其视为针对单变量最大公约数求解的欧几里得算法和线性系统中高斯消元法的泛化。
6、数据压缩——采取特定编码方案,使用更少的字节数(或是其他信息承载单元)对信息编码的过程,又叫来源编码。
7、Diffie-Hellman密钥交换算法——一种加密协议,允许双方在事先不了解对方的情况下,在不安全的通信信道中,共同建立共享密钥。该密钥以后可与一个对称密码一起,加密后续通讯。
8、Dijkstra算法——针对没有负值权重边的有向图,计算其中的单一起点最短算法。
9、离散微分算法(Discrete differentiation)。
10、动态规划算法(Dynamic Programming)——展示互相覆盖的子问题和最优子架构算法
11、欧几里得算法(Euclidean algorithm)——计算两个整数的最大公约数。最古老的算法之一,出现在公元前300前欧几里得的《几何原本》。
12、期望-最大算法(Expectation-maximization algorithm,又名EM-Training)——在统计计算中,期望-最大算法在概率模型中寻找可能性最大的参数估算值,其中模型依赖于未发现的潜在变量。EM在两个步骤中交替计算,第一步是计算期望,利用对隐藏变量的现有估计值,计算其最大可能估计值;第二步是最大化,最大化在第一步上求得的最大可能值来计算参数的值。
13、快速傅里叶变换(Fast Fourier transform,FFT)——计算离散的傅里叶变换(DFT)及其反转。该算法应用范围很广,从数字信号处理到解决偏微分方程,到快速计算大整数乘积。
14、梯度下降(Gradient descent)——一种数学上的最优化算法。
15、哈希算法(Hashing)。
16、堆排序(Heaps)。
17、Karatsuba乘法——需要完成上千位整数的乘法的系统中使用,比如计算机代数系统和大数程序库,如果使用长乘法,速度太慢。该算法发现于1962年。
18、LLL算法(Lenstra-Lenstra-Lovasz lattice rection)——以格规约(lattice)基数为输入,输出短正交向量基数。LLL算法在以下公共密钥加密方法中有大量使用:背包加密系统(knapsack)、有特定设置的RSA加密等等。
19、最大流量算法(Maximum flow)——该算法试图从一个流量网络中找到最大的流。它优势被定义为找到这样一个流的值。最大流问题可以看作更复杂的网络流问题的特定情况。最大流与网络中的界面有关,这就是最大流-最小截定理(Max-flow min-cut theorem)。Ford-Fulkerson 能找到一个流网络中的最大流。
20、合并排序(Merge Sort)。
21、牛顿法(Newton’s method)——求非线性方程(组)零点的一种重要的迭代法。
22、Q-learning学习算法——这是一种通过学习动作值函数(action-value function)完成的强化学习算法,函数采取在给定状态的给定动作,并计算出期望的效用价值,在此后遵循固定的策略。Q-leanring的优势是,在不需要环境模型的情况下,可以对比可采纳行动的期望效用。
23、两次筛法(Quadratic Sieve)——现代整数因子分解算法,在实践中,是目前已知第二快的此类算法(仅次于数域筛法Number Field Sieve)。对于110位以下的十位整数,它仍是最快的,而且都认为它比数域筛法更简单。
24、RANSAC——是“RANdom SAmple Consensus”的缩写。该算法根据一系列观察得到的数据,数据中包含异常值,估算一个数学模型的参数值。其基本假设是:数据包含非异化值,也就是能够通过某些模型参数解释的值,异化值就是那些不符合模型的数据点。
25、RSA——公钥加密算法。首个适用于以签名作为加密的算法。RSA在电商行业中仍大规模使用,大家也相信它有足够安全长度的公钥。
26、Sch?nhage-Strassen算法——在数学中,Sch?nhage-Strassen算法是用来完成大整数的乘法的快速渐近算法。其算法复杂度为:O(N log(N) log(log(N))),该算法使用了傅里叶变换。
27、单纯型算法(Simplex Algorithm)——在数学的优化理论中,单纯型算法是常用的技术,用来找到线性规划问题的数值解。线性规划问题包括在一组实变量上的一系列线性不等式组,以及一个等待最大化(或最小化)的固定线性函数。
28、奇异值分解(Singular value decomposition,简称SVD)——在线性代数中,SVD是重要的实数或复数矩阵的分解方法,在信号处理和统计中有多种应用,比如计算矩阵的伪逆矩阵(以求解最小二乘法问题)、解决超定线性系统(overdetermined linear systems)、矩阵逼近、数值天气预报等等。
29、求解线性方程组(Solving a system of linear equations)——线性方程组是数学中最古老的问题,它们有很多应用,比如在数字信号处理、线性规划中的估算和预测、数值分析中的非线性问题逼近等等。求解线性方程组,可以使用高斯—约当消去法(Gauss-Jordan elimination),或是柯列斯基分解( Cholesky decomposition)。
30、Strukturtensor算法——应用于模式识别领域,为所有像素找出一种计算方法,看看该像素是否处于同质区域( homogenous region),看看它是否属于边缘,还是是一个顶点。
31、合并查找算法(Union-find)——给定一组元素,该算法常常用来把这些元素分为多个分离的、彼此不重合的组。不相交集(disjoint-set)的数据结构可以跟踪这样的切分方法。合并查找算法可以在此种数据结构上完成两个有用的操作:
查找:判断某特定元素属于哪个组。
合并:联合或合并两个组为一个组。
32、维特比算法(Viterbi algorithm)——寻找隐藏状态最有可能序列的动态规划算法,这种序列被称为维特比路径,其结果是一系列可以观察到的事件,特别是在隐藏的Markov模型中。
以上就是Christoph博士对于最重要的算法的调查结果。你们熟悉哪些算法?又有哪些算法是你们经常使用的?
㈢ 请问多少除以加乘等于16有几种算法
32/4+8
=8+8
=16
结果为16
㈣ 二进制.十进制.八进制.十六进制四种算法之间的互相转换) .讲简洁.明白
几种进制的解释与转化说明
一)、数制
计算机中采用的是二进制,因为二进制具有运算简单,易实现且可靠,为逻辑设计提供了有利的途径、节省设备等优点,为了便于描述,又常用八、十六进制作为二进制的缩写.
一般计数都采用进位计数,其特点是:
(1)逢N进一,N是每种进位计数制表示一位数所需要的符号数目为基数.
(2)采用位置表示法,处在不同位置的数字所代表的值不同,而在固定位置上单位数字表示的值是确定的,这个固定位上的值称为权.
在计算机中:D7 D6 D5 D4 D3 D2 D1 D0 只有两种0和1
8 4 2 1
二)、数制转换
不同进位计数制之间的转换原则:不同进位计数制之间的转换是根据两个有理数如相等,则两数的整数和分数部分一定分别相等的原则进行的.也就是说,若转换前两数相等,转换后仍必须相等.
有四进制
十进制:有10个基数:0 9 ,逢十进一
二进制:有2 个基数:0 1 ,逢二进一
八进制:有8个基数:0 7 ,逢八进一
十六进制:有16个基数:0 9,A,B,C,D,E,F (A=10,B=11,C=12,D=13,E=14,F=15) ,逢十六进一
1、数的进位记数法
N=a n-1*p n-1+a n-2*p n-2+…+a2*p2+a1*p1+a0*p0
2、十进制数与P进制数之间的转换
①十进制转换成二进制:十进制整数转换成二进制整数通常采用除2取余法,小数部分乘2取整法.例如,将(30)10转换成二进制数.
将(30)10转换成二进制数
2| 30 ….0 ----最右位
2 15 ….1
2 7 ….1
2 3 ….1
1 ….1 ----最左位
∴ (30)10=(11110)2
将(30)10转换成八、十六进制数
8| 30 ……6 ------最右位
3 ------最左位
∴ (30)10 =(36)8
16| 30 …14(E)----最右位
1 ----最左位
∴ (30)10 =(1E)16
3、将P进制数转换为十进制数
把一个二进制转换成十进制采用方法:把这个二进制的最后一位乘上20,倒数第二位乘上21,……,一直到最高位乘上2n,然后将各项乘积相加的结果就它的十进制表达式.
把二进制11110转换为十进制
(11110)2=1*24+1*23+1*22+1*21+0*20=
=16+8+4+2+0
=(30)10
把一个八进制转换成十进制采用方法:把这个八进制的最后一位乘上80,倒数第二位乘上81,……,一直到最高位乘上8n,然后将各项乘积相加的结果就它的十进制表达式.
把八进制36转换为十进制
(36)8=3*81+6*80=24+6=(30)10
把一个十六进制转换成十进制采用方法:把这个十六进制的最后一位乘上160,倒数第二位乘上161,……,一直到最高位乘上16n,然后将各项乘积相加的结果就它的十进制表达式.
把十六制1E转换为十进制
(1E)16=1*161+14*160=16+14=(30)10
3、二进制转换成八进制数
(1)二进制数转换成八进制数:对于整数,从低位到高位将二进制数的每三位分为一组,若不够三位时,在高位左面添0,补足三位,然后将每三位二进制数用一位八进制数替换,小数部分从小数点开始,自左向右每三位一组进行转换即可完成.例如:
将二进制数1101001转换成八进制数,则
(001 101 001)2
| | |
( 1 5 1)8
( 1101001)2=(151)8
(2)八进制数转换成二进制数:只要将每位八进制数用三位二进制数替换,即可完成转换,例如,把八进制数(643.503)8,转换成二进制数,则
(6 4 3 .5 0 3)8
| | | | | |
(110 100 011 .101 000 011)2
(643.503)8=(110100011.101000011)2
4、二进制与十六进制之间的转换
(1)二进制数转换成十六进制数:由于2的4次方=16,所以依照二进制与八进制的转换方法,将二进制数的每四位用一个十六进制数码来表示,整数部分以小数点为界点从右往左每四位一组转换,小数部分从小数点开始自左向右每四位一组进行转换.
(2)十六进制转换成二进制数
如将十六进制数转换成二进制数,只要将每一位十六进制数用四位相应的二进制数表示,即可完成转换.
例如:将(163.5B)16转换成二进制数,则
( 1 6 3 .5 B )16
| | | | |
(0001 0110 0011.0101 1011 )2
(163.5B)16=(101100011.01011011)2
㈤ 几种CRC16算法
一. CRC16算法首先在源文件头文件加入表值:[c] view plain ////////////////////////////////////////////////////////////////////////// // CRC16码表 static WORD const wCRC16Table[256] = { 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040}; 然后在文件中加入下列函数:[c] view plain ////////////////////////////////////////////////////////////////////////// // 函数功能: CRC16效验 // 输入参数: pDataIn: 数据地址 // iLenIn: 数据长度 // 输出参数: pCRCOut: 2字节校验值 void CCRCDlg::CRC16(const CHAR* pDataIn, int iLenIn, WORD* pCRCOut) { WORD wResult = 0; WORD wTableNo = 0; for(int i = 0; i < iLenIn; i++) { wTableNo = ((wResult & 0xff) ^ (pDataIn[i] & 0xff)); wResult = ((wResult >> 8) & 0xff) ^ wCRC16Table[wTableNo]; } *pCRCOut = wResult; } 二.CRC16(MODBUS)[c] view plain ////////////////////////////////////////////////////////////////////////// // CRC MODBUS 效验 // 输入参数: pDataIn: 数据地址 // iLenIn: 数据长度 // 输出参数: pCRCOut: 2字节校验值 void CCRCDlg::CheckCRCModBus(const CHAR* pDataIn, int iLenIn, WORD* pCRCOut) { WORD wHi = 0; WORD wLo = 0; WORD wCRC; wCRC = 0xFFFF; for (int i = 0; i < iLenIn; i++) { wCRC = CalcCRCModBus(*pDataIn, wCRC); pDataIn++; } wHi = wCRC / 256; wLo = wCRC % 256; wCRC = (wHi > 1; wCRCIn = wCRCIn & 0x7fff; if(wCheck == 1) { wCRCIn = wCRCIn ^ 0xa001; } wCRCIn = wCRCIn & 0xffff; } return wCRCIn; } 三.CRC16(CCITT的0XFFFF)[c] view plain ////////////////////////////////////////////////////////////////////////// // 函数功能: CRC16效验(CCITT的0XFFFF效验) // 输入参数: pDataIn: 数据地址 // iLenIn: 数据长度 // 输出参数: pCRCOut: 2字节校验值 void CCRCDlg::CRCCCITT(const CHAR* pDataIn, int iLenIn, WORD* pCRCOut) { WORD wTemp = 0; WORD wCRC = 0xffff; for(int i = 0; i < iLenIn; i++) { for(int j = 0; j < 8; j++) { wTemp = ((pDataIn[i] > 8); wCRC
㈥ 11乘16有几种算法
16+16+16+16+16+16+16+16+16+16+16=* 11+11+11+11+11+11*11+11+11+11+11+11+11+11+11+11=* 11※16 16※11
㈦ 4,5,6,12的24点算法
可以这样做:
(6+4)*12/5
=10*12/5
=120/5
=24;
其实,这几个数字,只有这一种组合,其他的方式,都是有这种组合,演变而来的。
㈧ 16乘以24有几种不同的算法
16*24=(20-4)(20+4)=20²-4²=400-16=384
16*24=3*8*8*2=6*64=384
其他的算法都是类似的都要乘积
16*24=(10+6)*24=240+144=384
㈨ 求助大神还有哪些16位冗余校验计算方法,试过常见的几种CRC16都不对
CRC校验又称为循环冗余校验,是数据通讯中常用的一种校验算法。它可以有效的判别出数据在传输过程中是否发生了错误,从而保障了传输的数据可靠性。
CRC校验有多种方式,如:CRC8、CRC16、CRC32等等。在实际使用中,我们经常使用CRC16校验。CRC16校验也有多种,如:1005多项式、1021多项式(CRC-ITU)等。在这里我们不讨论CRC算法是怎样产生的,而是重点落在几种算法的C51程序的优化上。
计算CRC校验时,最常用的计算方式有三种:查表、计算、查表+计算。一般来说,查表法最快,但是需要较大的空间存放表格;计算法最慢,但是代码最简洁、占用空间最小;而在既要求速度,空间又比较紧张时常用查表+计算法。
下面我们分别就这三种方法进行讨论和比较。这里以使用广泛的51单片机为例,分别用查表、计算、查表+计算三种方法计算
1021多项式(CRC-ITU)校验。原始程序都是在网上或杂志上经常能见到的,相信大家也比较熟悉了,甚至就是正在使用或已经使用过的程序。
编译平台采用 Keil C51 7.0,使用小内存模式,编译器默认的优化方式。
常用的查表法程序如下,这是网上经常能够看到的程序范例。因为篇幅关系,省略了大部分表格的内容。
code unsigned int Crc1021Table[256] = {
0x0000, 0x1021, 0x2042, 0x3063,... 0x1ef0
};
unsigned int crc0(unsigned char *pData, unsigned char nLength)
{
unsigned int CRC16 = 0;
while(nLength>0)
{
CRC16 = (CRC16 << 8 ) ^ Crc1021Table[((CRC16>>8) ^ *pData) & 0xFF];
nLength--;
pData++;
}
return CRC16;
}
编译后,函数crc0的代码为68字节,加上表格占用的512字节,一共使用了580个字节的代码空间。
下面是常见的计算法的程序:
unsigned int crc2(unsigned char *ptr,unsigned char count)
{
unsigned int crc =0;
unsigned char i;
while(count-- >0)
{
crc = ( crc^(((unsigned int)*ptr)<<8));
for(i=0;i<8;i++)
{
if(crc&0x8000) crc= ((crc<<1)^0x1021);
else crc <<= 1;
}
ptr++;
}
return crc;
}
下面是常见的一种查表+计算的方法:
unsigned int crc4(unsigned char *ptr, unsigned char len) {
unsigned int crc;
unsigned char da;
code unsigned int crc_ta[16]={ /* CRC余式表 */
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
};
crc=0;
while(len-->0) {
da = ((crc/256))/16; /* 暂存CRC的高四位 */
crc <<=4;
/* CRC右移4位,相当于取CRC的低12位)*/
crc ^= crc_ta[da^(*ptr/16)];
/* CRC的高4位和本字节的前半字节相加后查表*/
/*计算CRC,然后加上上一次CRC的余数 */
da = ((crc/256))/16; /* 暂存CRC的高4位 */
crc <<=4;
/* CRC右移4位, 相当于CRC的低12位) */
crc ^= crc_ta[da^(*ptr&0x0f)];/* CRC的高4位和本字节的后半字节相加后查表*/
/*计算CRC,然后再加上上一次CRC的余数 */
ptr++;
}
return crc;
}
程序优化策略:上面程序都只是给出了通用算法,并没有考虑到51单片机的特点。我们知道,51单片机是8位单片机,使用的变量类型也是8位的。如果在程序中使用8位的变量速度是最快的,比使用16位的变量代码短、效率高。在上面的程序中都使用了大量整型数类型(16位)的表格和整型数类型的变量,特别是关键的变量。如果我们不使用整型类型的表格和变量,而使用字节类型的表格和变量,就能够使程序的性能得到优化。基于这种思路,我们将原来整型的表格拆分为两个字节型(8位)的表格,即将原来表格的高低字节分别拆开,每个表格还是256个单元,这样表格的大小和顺序都没有变;原来使用16位变量计算的地方,改用8位变量计算。
修改后的查表程序如下(省略了表格的内容):
code unsigned char crctableh[256]={
0x00,0x10,0x20,0x30,... 0x0E,0x1E,
};
code unsigned char crctablel[256]={
0x00,0x21,0x42,0x63,... 0xD1,0xF0,
};
unsigned int crc1(unsigned char *buf,unsigned char n)
{
unsigned char t;
union{
unsigned char c[2];
unsigned int x;
}data crc;
crc.x = 0;
while(n !=0)
{
t = crc.c[0]^*buf;
crc.c[0] = crc.c[1]^crctableh[t];
crc.c[1] = crctablel[t];
n--;
buf++;
}
return ( crc.x );
}
表面上看起来,函数crc1比crc0的源代码还长一些。但是编译后,函数crc1的目标代码实际为44个字节,加上表格占用的512个字节,一共使用了556个字节,比函数crc0反而节约了24个字节。这两个函数的运行对比情况见表一。
我们采用和上面相同的优化方法来优化计算法的程序,优化后的计算法程序为:
unsigned int crc3(unsigned char *ptr,unsigned char count)
{
data unsigned char i;
union{
unsigned char c[2];
unsigned int x;
}data crc;
crc.x=0;
while(count!=0)
{
crc.c[0] ^= *ptr;
for(i=8;i>0;i--)
{
if(crc.c[0]&0x70)crc.x=(crc.x<<1)&0x1021;
else crc.x=crc.x<<1;
}
ptr++;
count--;
}
return crc.x;
}
编译后函数crc2的代码长度为76,函数crc3的代码长度为68,变化不是太大,但是执行效率是很不一样的,具体差别见后面的表一。
优化后的查表+计算法的程序为:
unsigned int crc5(unsigned char *ptr,unsigned char len)
{
code unsigned char crch[16]={ /* CRC余式表 */
0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70,
0x81,0x91,0xa1,0xb1,0xc1,0xd1,0xe1,0xf1,
};
code unsigned char crcl[16]={ /* CRC余式表 */
0x00,0x21,0x42,0x63,0x84,0xa5,0xc6,0xe7,
0x08,0x29,0x4a,0x6b,0x8c,0xad,0xce,0xef,
};
union{
unsigned char c[2];
unsigned int x;
}data crc;
unsigned char t;
crc.x =0;
while(len!=0) {
t = (crc.c[0]>>4) ^ (*ptr >>4);
crc.x <<=4;
crc.c[0] ^=crch[t];
crc.c[1] ^=crcl[t];
t = (crc.c[0]>>4) ^ (*ptr & 0x0F);
crc.x <<=4;
crc.c[0] ^=crch[t];
crc.c[1] ^=crcl[t];
ptr++;
len--;
}
return crc.x;
}
优化前后的代码长度分别为175字节和146字节(包括了32字节的表格空间)。
代码测试:仅代码长度变短是不够的,衡量优化后的程序一个重要的标准是看优化前后两个函数执行相同的计算量使用的时间,或者说执行的效率是否提高了。如果优化后的函数需要时间少,就说明我们优化后的函数确实提高了效率,否则就是反而降低了程序的效率,优化失败。我在 Keil C51 下编写了一个测试程序,在程序中调用了上面的六个函数,共同计算一个长度为200字节的CRC校验,并记录下每个函数使用的时间。测试方法为:使用 Keil C51 的软件仿真功能(采用带计时功能的硬件仿真器也可以),在每个函数上加上断点,记录下执行到每个断点的时间,然后前后相减就得出每个函数的执行时间。仿真时使用的单片机型号为AT89C51,晶体频率为12MHz。
测试文件的源代码为:
xdata unsigned char buf[200];
unsigned char i;
unsigned int crc;
extern unsigned int crc0(unsigned char *,unsigned char);
extern unsigned int crc1(unsigned char *,unsigned char);
extern unsigned int crc2(unsigned char *,unsigned char);
extern unsigned int crc3(unsigned char *,unsigned char);
extern unsigned int crc4(unsigned char *,unsigned char);
extern unsigned int crc5(unsigned char *,unsigned char);
void main(void)
{
for(i=0;i<200;i++)
buf[i]=i+1;
crc=crc0(buf,200);
crc=crc1(buf,200);
crc=crc2(buf,200);
crc=crc3(buf,200);
crc=crc4(buf,200);
crc=crc5(buf,200);
i=0;
}
测试结果见表一:
函数
代码长度(字节)
执行时间(微秒)
提高效率
查表法
Crc0
68+512=580
10626
28%
Crc1
44+512=556
7622
计算法
Crc2
76
30309
13.6%
Crc3
68
26186
查表+计算
Crc4
175
24229
18.2%
Crc5
146
19822
表一:三种CRC16校验算法及其优化的比较
从表中可以看出,优化后的函数执行速度都有了明显的提高,这说明我们的优化是很有效的。其实优化前后的函数区别并不大,算法都是完全一样的,只是个别地方的写法调整了一下,但是取得的效果是非常明显的。现在很多人写单片机的程序,都带有写VC或C++的习惯,而没有考虑到51单片机的特点,从而造成了一些资源的浪费。
这三种计算方法之间的对比也很清楚,查表法使用的时间最短,查表+计算次之,计算法最长,它们的执行效率比为1:3.4:2.6,代码效率比为8.1:1:2.1(优化后的函数)。从代码长度看,查表法因为使用了一个相对较大的表格,所以代码最长,适合于代码空间比较充足的情况;计算法没有使用任何表格,所以代码长度最小,适合于ROM空间比较小的情况。而查表+计算法从执行速度和代码长度都是处于中流,是一种很好的折衷方案。
下面是我总结的一些C51编程优化的几个小技巧和方法:
1. 尽量使用无符号的变量;
2. 尽量使用data与idata寄存器变量;它们之间的效率顺序为data>idata>xdata ;
3. 使用最小的变量类型,能使用整型时就不要使用长整型,能使用字符型时就不要使用整型;
4. 多使用局部变量,少使用全局变量、静态变量,这样可以充分利用Keil提供的变量覆盖技术。同时局部变量也要尽量使用寄存器变量;
5. 少使用浮点数。使用浮点数需要连接浮点库,会增加大约1K的代码;而且浮点运算的速度是很慢的,没有特殊情况尽量使用定点数代替浮点数;
6. 没有必要就不要使用printf等标准输入输出函数,它会增加大约3K的代码。
7. 函数带有的参数不要太多,多了会影响效率;
8. 注意C51的特点,按照C51的特点编写程序;
9. 多用几种方法优化,比较后找出最佳方法;
10. 注意软件的写法,有时一个写法上很小的变化就会有意想不到的效果。因为Keil的优化是与上下文的程序相关的,对于不同的情况产生不同的优化。如果对比一下产生的汇编代码,就能够更加容易的找出不合理的代码,从而有针对性的优化了;
11. 不要过于依赖C51提供的优化功能,能够自己优化的地方就自己手工优化了,程序不可能完成所有的事情;
12. 不同版本的C51(特别是6.0和7.0的版本与低于6.0的版本相比)产生的优化效果可能会不同,产生的代码会不完全一样,需要特别注意;
13. 在循环中比较条件尽量与0作比较(这与51单片机的特点有关,对比一下编译产生的汇编代码就明白了),这样可以减少代码,加快速度。如:
将while( n > 10 )修改为
while( n != 0 );
14. 在循环中比较中不要直接使用 n-- 或 n++ ,而要分开写,这样产生的代码较小。
如:while( n-- != 0)修改为
while(n != 0)
{
n--;
...//其他代码
}
15. 尽量不要使用太复杂的表达式,虽然这样程序简洁,但是对程序的调试不方便。
只要能充分考虑到所使用的单片机的特点,在加上一些经验和技巧,就一定可以编写出高性能的程序来。