所以我试图用尽可能紧凑的函数写斐波那契数列中的第n个数字:
public uint fibn ( uint N )
{
return (N == 0 || N == 1) ? 1 : fibn(N-1) + fibn(N-2);
}
但是我想知道是否可以通过更改来使它更加紧凑和高效
(N == 0 || N == 1)
进行一次比较。是否有一些高级的移位操作可以做到这一点?
fibn(N-1) + fibn(N-2)
不是要说N * fibn(N-1)
?
所以我试图用尽可能紧凑的函数写斐波那契数列中的第n个数字:
public uint fibn ( uint N )
{
return (N == 0 || N == 1) ? 1 : fibn(N-1) + fibn(N-2);
}
但是我想知道是否可以通过更改来使它更加紧凑和高效
(N == 0 || N == 1)
进行一次比较。是否有一些高级的移位操作可以做到这一点?
fibn(N-1) + fibn(N-2)
不是要说N * fibn(N-1)
?
Answers:
这个也可以
Math.Sqrt(N) == N
0和1的平方根将分别返回0和1。
Math.Sqrt
是一个复杂的浮点函数。与仅整数替代方案相比,它运行缓慢!
有多种方法可以使用按位算术来实现算术测试。您的表情:
x == 0 || x == 1
在逻辑上等效于以下各项:
(x & 1) == x
(x & ~1) == 0
(x | 1) == 1
(~x | 1) == (uint)-1
x >> 1 == 0
奖金:
x * x == x
(证明需要一些努力)但是实际上,这些形式是最易读的,并且性能上的微小差异实际上不值得使用按位算术:
x == 0 || x == 1
x <= 1
(因为x
是无符号整数)x < 2
(因为x
是无符号整数)(x & ~1) == 0
x == 0 || x == 1
少于(x & ~1) == 0
或(x | 1) == 1
。对于第一个,它足够聪明,可以将其识别为等效x <= 1
并输出一个simple cmpl; setbe
。其他人将其混淆并使其生成更糟糕的代码。
由于参数是uint
(unsigned)您可以
return (N <= 1) ? 1 : N * fibn(N-1);
可读性差(IMHO),但如果您计算每个字符(Code Golf或类似字符)
return N < 2 ? 1 : N * fibn(N-1);
编辑:针对您编辑的问题:
return (N <= 1) ? 1 : fibn(N-1) + fibn(N-2);
要么
return N < 2 ? 1 : fibn(N-1) + fibn(N-2);
return N<2?1:f(N-1)+f(n-2)
。:P
您还可以像这样检查所有其他位是否为0:
return (N & ~1) == 0 ? 1 : N * fibn(N-1);
为了完整起见,感谢Matt提供了更好的解决方案:
return (N | 1) == 1 ? 1 : N * fibn(N-1);
在这两种情况下,您都需要注意括号,因为按位运算符的优先级低于==
。
(N|1)==1
如果要使函数更有效,请使用查找表。查找表只有47个条目,令人惊讶地小-下一个条目将溢出32位无符号整数。当然,这也使得该函数的编写很简单。
class Sequences
{
// Store the complete list of values that will fit in a 32-bit unsigned integer without overflow.
private static readonly uint[] FibonacciSequence = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,
233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418,
317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169,
63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, 2971215073
};
public uint fibn(uint N)
{
return FibonacciSequence[N];
}
}
您显然可以对阶乘做同样的事情。
如果要使用位移位并使代码有些晦涩(但简短),则可以执行以下操作:
public uint fibn ( uint N ) {
return N >> 1 != 0? fibn(N-1) + finb(N-2): 1;
}
对于N
语言c 的无符号整数,N>>1
丢弃低位。如果结果不为零,则表示N大于1。
注意:此算法效率极低,因为它不必要地重新计算已计算序列中的值。
一遍计算它,而不是隐式地构建一个fibonaci(N)大小的树:
uint faster_fibn(uint N) { //requires N > 1 to work
uint a = 1, b = 1, c = 1;
while(--N != 0) {
c = b + a;
a = b;
b = c;
}
return c;
}
正如某些人所提到的,即使是64位无符号整数也不会花费很长时间。根据要尝试的大小,您将需要使用任意精度的整数。
uint
它不能隐式转换为bool
,并且该问题专门标记为C#。
--N != 0
代替。关键是O(n)比O(fibn(n))更可取。
使用uint(不能取负)时,可以检查是否 n < 2
编辑
或者对于特殊功能情况,您可以编写如下:
public uint fibn(uint N)
return (N == 0) ? 1 : N * fibn(N-1);
}
当然,这将导致相同的结果,但要付出额外的递归步骤。
1 * fibn(0) = 1 * 1 = 1
fibn
再
只需检查是否N
<= 1,因为您知道N是无符号的,那么只能有2个条件N <= 1
导致TRUE
:0和1
public uint fibn ( uint N )
{
return (N <= 1) ? 1 : fibn(N-1) + finb(N-2);
}
(N == 0 || N == 1)
。您知道它不会小于0(因为它将被签名!),最大值可能是1。 N <= 1
简化了它。我想不能保证无符号类型,但是应该在其他地方处理。
int N
,并且您保留了原始条件,那么当N的原始条件为N时,它将无限递归。由于这是未定义的行为,因此我们实际上无需担心。因此,无论声明如何,我们都可以假定N为非负数。
免责声明:我不了解C#,也没有测试此代码:
但是我想知道是否可以通过将其更改为单个比较来使其更加紧凑和高效...
无需进行移位或类似操作,它仅使用一个比较,并且效率应该更高(我认为O(n)vs O(2 ^ n)?)。该函数的主体更加紧凑,尽管声明结束了它的长度。
(要消除递归的开销,有一个迭代版本,如Mathew Gunn的答案所示)
public uint fibn ( uint N, uint B=1, uint A=0 )
{
return N == 0 ? A : fibn( N--, A+B, B );
}
fibn( 5 ) =
fibn( 5, 1, 0 ) =
return 5 == 0 ? 0 : fibn( 5--, 0+1, 1 ) =
fibn( 4, 1, 1 ) =
return 4 == 0 ? 1 : fibn( 4--, 1+1, 1 ) =
fibn( 3, 2, 1 ) =
return 3 == 0 ? 1 : fibn( 3--, 1+2, 2 ) =
fibn( 2, 3, 2 ) =
return 2 == 0 ? 2 : fibn( 2--, 2+3, 3 ) =
fibn( 1, 5, 3 ) =
return 1 == 0 ? 3 : fibn( 1--, 3+5, 5 ) =
fibn( 0, 8, 5 ) =
return 0 == 0 ? 5 : fibn( 0--, 5+8, 8 ) =
5
fibn(5)=5
PS:这是累加器迭代的常见功能模式。如果您替换为N--
,则表示N-1
您实际上没有在使用任何突变,因此可以在纯功能方法中使用它。
这是我的解决方案,在优化此简单函数方面没有太多的事情,另一方面,我在这里提供的是作为递归函数的数学定义的可读性。
public uint fibn(uint N)
{
switch(N)
{
case 0: return 1;
case 1: return 1;
default: return fibn(N-1) + fibn(N-2);
}
}
斐波那契数的数学定义也是如此。
进一步迫使开关盒建立一个查找表。
public uint fibn(uint N)
{
switch(N)
{
case 0: return 1;
case 1: return 1;
case 2: return 2;
case 3: return 3;
case 4: return 5;
case 5: return 8;
case 6: return 13;
case 7: return 21;
case 8: return 34;
case 9: return 55;
case 10: return 89;
case 11: return 144;
case 12: return 233;
case 13: return 377;
case 14: return 610;
case 15: return 987;
case 16: return 1597;
case 17: return 2584;
case 18: return 4181;
case 19: return 6765;
case 20: return 10946;
case 21: return 17711;
case 22: return 28657;
case 23: return 46368;
case 24: return 75025;
case 25: return 121393;
case 26: return 196418;
case 27: return 317811;
case 28: return 514229;
case 29: return 832040;
case 30: return 1346269;
case 31: return 2178309;
case 32: return 3524578;
case 33: return 5702887;
case 34: return 9227465;
case 35: return 14930352;
case 36: return 24157817;
case 37: return 39088169;
case 38: return 63245986;
case 39: return 102334155;
case 40: return 165580141;
case 41: return 267914296;
case 42: return 433494437;
case 43: return 701408733;
case 44: return 1134903170;
case 45: return 1836311903;
case 46: return 2971215073;
default: return fibn(N-1) + fibn(N-2);
}
}
switch
当您有一系列答案时,为什么还要使用a ?
Dmitry的答案是最好的,但是如果它是Int32返回类型并且您有更大的整数集可供选择,则可以执行此操作。
return new List<int>() { -1, 0, 1, 2 }.Contains(N) ? 1 : N * fibn(N-1);
List.Contains
是O(n),3)简单地做两个比较而不是(N > -3 && N < 3
)将给出更短,更易读的代码。
斐波那契数列是一系列数字,其中一个数字是通过将两个数字相加而得到的。有两种类型的起点:(0,1,1,2,..)和(1,1,2,3)。
-----------------------------------------
Position(N)| Value type 1 | Value type 2
-----------------------------------------
1 | 0 | 1
2 | 1 | 1
3 | 1 | 2
4 | 2 | 3
5 | 3 | 5
6 | 5 | 8
7 | 8 | 13
-----------------------------------------
N
在这种情况下,位置从开始1
,而不是0-based
数组索引。
使用C#6 Expression-body功能和Dmitry关于三元运算符的建议,我们可以针对类型1编写具有正确计算的单行函数:
public uint fibn(uint N) => N<3? N-1: fibn(N-1)+fibn(N-2);
对于类型2:
public uint fibn(uint N) => N<3? 1: fibn(N-1)+fibn(N-2);
参加聚会有点晚,但您也可以这样做 (x==!!x)
!!x
将a值转换为1
not 0
,然后将其保留0
。
我在C模糊处理中经常使用这种东西。
注意:这是C,不确定是否可以在C#中使用
uint n = 1; if (n == !!n) { }
给Operator '!' cannot be applied to operand of type 'uint'
在!n
C#中。仅仅因为某些东西可以在C中工作并不意味着它可以在C#中工作;甚至#include <stdio.h>
在C#中也不起作用,因为C#没有“ include”预处理程序指令。这些语言与C和C ++的区别更大。
因此,我创建了一个List
这些特殊整数,并检查是否N
与之有关。
static List<uint> ints = new List<uint> { 0, 1 };
public uint fibn(uint N)
{
return ints.Contains(N) ? 1 : fibn(N-1) + fibn(N-2);
}
您也可以将扩展方法用于不同的用途,该方法Contains
仅被调用一次(例如,当您的应用程序正在启动和加载数据时)。这提供了更清晰的样式,并阐明了与值(N
)的主要关系:
static class ObjectHelper
{
public static bool PertainsTo<T>(this T obj, IEnumerable<T> enumerable)
{
return (enumerable is List<T> ? (List<T>) enumerable : enumerable.ToList()).Contains(obj);
}
}
应用它:
N.PertainsTo(ints)
这可能不是最快的方法,但是对我来说,这似乎是一种更好的风格。