Javascript算法练习(七)
这两天碰到个小问题,跟斐波那契数列有关,顺便也回顾下回调函数的使用。
sumOddFibonacciNumber: 得到小于number的所有斐波那契数的和
递归的三种方式
- 直接递归法:容易引起内存泄漏,效率底下,慎用
- 闭包: 能很好的练习js闭包用法,虽然效率不是最高,但是推荐使用
- for循环 效率最高,简单粗暴直观
var count = 0; // 测试三种方式的计算次数
直接递归法
// 基础递归方法,没什么好说的,很简单的操作 var fibonacciBasic = function (number) { if (number == 0) { return 0; } else if (number == 1) { return 1; } else { count++; return fibonacciBasic(number - 1) + fibonacciBasic(number - 2); } };
闭包,即通过函数内部定义闭包,并且使用相应的数组或对象去缓存已经计算出的值,方便下次直接提取,能大大减少计算的次数
// 闭包实现 var fibonacciClosure = (function () { // 这个用来缓存计算出的值,方便下次直接提取使用 var res = [0, 1]; return function (number) { if (isNaN(number) || number < 0) return; // 测试用,计算回调次数 count++; // 判断该值是否曾经计算且保存过,有则直接取出来使用,没有则进行下一步, // 并且将新值缓存起来 if (res[number] || res[number] === 0)) { console.log("exist, res[" + number + "] = " + res[number]); return res[number]; } else { res[number] = fibonacciClosure(number - 1) + fibonacciClosure(number - 2); return res[number]; } }; })();
通过for循环方式去处理,效率最高,且不存在内存泄漏的问题,如果不喜欢用闭包,可以使用这种方式,并且此方式用来处理相加比较容易,后面有通过这个扩展来处理所有斐波那契数值的和
// 循环 var fibonacciCircle = function (number) { if (number == 0) { return 0; } else if (number == 1) { return 1; } else { var x = 0; // 保存第一个值 var y = 1; // 保存第二个值 var z = 2; // 保存第一个和第二个值的和 for (var i = 2; i <= number; i++) { count++; // 进行值替换 z = y + x; y = x; x = z; } return z; } }
也顺便看看上面三种执行的次数吧,记忆会更深点以下是分别计算:第10,20,30个的数据的count值;通过下面的对比可以看出三种方式,第一种是最糟糕的,必须严厉禁止使用,后面两个差距并不是非常明显,闭包的实现,是在牺牲了一定的内存上去换的运行时间,所以可以根据个人喜好选择。
- 第10个fibonacci:
- [Basic]value == 55, count == 88;
- [Closure]value == 55, count == 19;
- [Circle]value == 55, count == 10;
- 第20个fibonacci:
- [Basic]value == 6765, count == 10945;
- [Closure]value == 6765, count == 39;
- [Circle]value == 6765, count == 20;
- 第30个fibonacci:
- [Basic]value == 832040, count == 1346268;
- [Closure]value == 832040, count == 59;
- [Circle]value == 832040, count == 30;
- 第10个fibonacci:
通过for循环扩展,处理所有斐波那契数值的奇数值的和,最后需要用到reduce方法去处理最后得到的数组,将它们的元素统统加起来
// 得到小于number的所有斐波那契数的和 function sumOddFibonacciNumber(number) { // 要求传入的参数为合法的数值类型 if (isNaN(number) || number < 0) return; var total = [0, 1]; // 保存计算出的fibonacci数 if (number == 0 || number == 1) { return 1; } else { var x = 0; // 保存第一个值 var y = 1; // 保存第二个值 var z = 1; // 保存第一个和第二个值的和 for (var i = 1; i <= number; i++) { if (z > number) break; console.log("total = " + total + ", z = " + z); // 过滤调第一个z的值,防止把它的初始值保存进去了 if (i > 1 && (z % 2 != 0)) { total.push(z); } // 值替换和累加操作 z = y + x; y = x; x = z; } // 上述操作结束后,total就保存了小于number的所有fibonacci数值 // 然后将他们全部相加,得到我们想要的值 return total.reduce(function(preV, currV, currIndex, array){ return preV + currV; }); } };
数值进制之间的转换
- function decimalToOther(number, type); number:要转换的数字,type:转成的目标进制;
- function otherToDecimal(numberStr, type); numberStr:转化成10进制的字符串或者数字也可以,如果是数字直接返回,是字符串则需要进一步转换,type:传入的第一个参数的进制类型(后续考虑优化去掉这个参数,直接去方法里面判断);
function prefixHandler(bitArr, type, method); 辅助方法,前两个方法的前缀处理,bitArr:每个数值位或进制用split(“”);字符串分割后的数组;type:进制类型,数值型;method:处理方式,0-不处理,1-添加前缀,2-删除前缀。
进制之间的转换原理还是挺简单的,就是用数字去除要转换的进制,取得余数作为转换后的相应的位数,除数作为下一次循环的数字。
方法实现的步骤或注意点
- 各种进制的前缀:如果是二进制则需要考虑前面是否需要补0,8进制前缀:’0’,16进制’0x’;
- 16进制的10-15的转换,用数组处理[‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’];
- 在其他进制转成10进制时,需要用到reduce处理。
function decimalToOther(number, type);代码实现
NumberHandler.prototype.decimalToOther = function (number, type) { // 保证参数都是数字 // type一般取值:2, 8, 10, 16 if (isNaN(number) || isNaN(type) || number < 0) return; if (type === 10) return number; var hexAlpha = ['A', 'B', 'C', 'D', 'E', 'F']; var bitArr = []; // 存储计算得到的每一位上的数 var rema = 0; // 保存余数 var prefix = ""; // 每种进制的前缀,如:16进制的"0x", 8进制的'0'等 while (number !== 0) { // 取余数保存,用来组合成最后进制字符串 rema = number % type; // 当type == 16时,需要处理10-15到ABCDEF的转换 if (type === 16 && rema > 9 && rema < 16) { rema = hexAlpha[rema - 10]; } bitArr.unshift(rema); // 取除数,进行下一个循环 number = Math.floor(number / type); } // 添加前缀 this.prefixHandler(bitArr, type, 1); return bitArr.join(""); }
function otherToDecimal(numberStr, type);代码实现
NumberHandler.prototype.otherToDecimal = function (numberStr, type) { // 如果是十进制就直接返回 if (type === 10 && !isNaN(numberStr)) return numberStr; // 保证除十进制外传进来的都是其他进制的字符串形式 if (typeof numberStr != "string") return; // 将numberStr变成数组 var bitArr = numberStr.split(""); var hexAlpha = ['A', 'B', 'C', 'D', 'E', 'F']; var alphaIdx = 0; // 去掉前缀 this.prefixHandler(bitArr, type, 2); // 16进制转换成数字 if (type === 16) { bitArr.map(function (value, index) { alphaIdx = hexAlpha.indexOf(value); if (alphaIdx != -1) { // 用数字去替换字母 bitArr.splice(index, 1, 10 + alphaIdx); } }); } var len = bitArr.length; return bitArr.reduce(function (preV, currV, currIndex, array) { return preV + parseInt(currV) * Math.pow(type, len - currIndex - 1); }, 0); }
function prefixHandler(bitArr, type, method); 前缀处理
NumberHandler.prototype.prefixHandler = function (bitArr, type, method) { // method: 0 - 什么都不做,1 - 添加,2 - 删除 if (!bitArr || isNaN(type)) return; // 如果method == 0, 什么都不做 if (method === 0) return; var prefix = ""; var count = 0; switch (type) { case 2: if (method === 1) { prefix = '0'; // 2进制前位补0 while (bitArr.length < 8) { bitArr.unshift(prefix); } } break; case 10: break; case 8: // 八进制则删除最左边的'0' prefix = '0'; count = 1; // 删除一位 break; case 16: // 十六进制删除'0x' prefix = '0x'; count = 2; // 删除两位 break; default: break; } // 2进制单独在switch里处理 if (type === 2) return; // 1 - 添加,2 - 删除,0 - 什么都不做 method === 1 ? bitArr.unshift(prefix) : bitArr.splice(0, count); return bitArr; }
结束语
妮妲太凶悍了,只怪暴风雨来的太凶猛,却还是不够凶猛啊,公司原定上午“在家办公的”,下午回公司上班,结果下午还在持续爆发之中,弄的周六去补下午的班,在屋里听朋友说公司直接“在家办公一整天,哈哈!!”,额……,别人家的公司,好熟悉的词!!!不过公司还是很不错的,至少没扣薪水是吧,^_^!! 要努力成为勇于,敢于,善于,爱好加班的好童鞋嘛,才是未来公司的好班长不是,嘎嘎嘎,Come on, come on, baby!!!