算法这东西,一直是令很多人头疼的东西(当然我也是),但是想做出高效的程序,还真离不开。其实很多算法已经集成进了各种编程语言的库,系统调用库,似乎足够用了。比如Java中的TreeSet使用红黑树存储元素,c++中的map用到了红黑树,linux内核在调度进程的时候也用到了红黑树。由此可见,高效的算法会很快推广,从系统到应用都可以派上用场。
回想一下,工作中用得多吗,这个看情况,对于安卓客户端程序,用户有一个延迟体验的晃动值100-200ms(书上说的,哈哈),当然这不适用于游戏场景,如果超过100ms,体验就比较糟糕,按钮点击响应时间或许可以慷慨一点。据我以往的经验看,就安卓客户端,有几个场景会遇到问题,首先是屏幕的显示刷新,还有就是音频相关的应用,还有文件的加载编辑。显示这一块儿是做过最多的,按理说目前客户端最重要的还是显示(但是安卓系统中声音线程的优先级高于UI线程,可能手机通话的重要性更高),也是最吸引人的地方,比如酷炫的动画,然而仅靠安卓自带的动画接口,似乎有些限制,每次玩狂野飙车(gameloft出品)的时候,就被动画特效吸引,这种科技感十足的动画是怎么搞的,就像变形金刚,而且给按键加上了音效,这个确实很棒,目前很多日常使用的app还没有这样做,未来可能会比较普遍。毫无疑问,想做出不一样的特效,还真得研究算法,毕竟你没有接口方法可调用。而且算法这东西,一般比较耗时,而且效果往往还不够理想。算法确实需要长期的积累,狂野飙车的游戏已经有15年之久了,好的东西,确实不是一蹴而就。这又让我想起了以前的工作经历,由于工作需要,我得在本地实现一个绘图库,因为安卓上层提供的方法不够高效。于是就自己上网查资料,去实现画线,抗锯齿的特效,因为开始是用C开发的,用筚路蓝缕一点不为过,C嘛,连vector都没有,还得用malloc,free,realloc这些去定义各种结构体,各种函数,宏,位运算,结果搞了几个月,效果也不理想。无奈只好向库低头,skia真香,哈哈。可是在研究skia的时候,我发现有些代码文件是源自2003年的,不得不说,好东西真是可以传承的。这不禁又让我想起最近的一件事,为了打发时间,研究一下linux内核吧,毕竟是最大的开源项目,全球1000多位精英程序员历经近三十年的杰作。Linus Benedict Torvalds确实是教父级的人物,一个linux,一个git,记得刚毕业那会儿,git命令真是让人头疼,可是再到后来,每次敲击git,都有一种莫名的快感,难道这就是开源精神的召唤吗。学习内核,绝不是好高骛远,而是觉得一知半解,危害匪浅。最近安卓11发布了,安卓的内核也更新到了5.8.8。上层的变动如何,不太清楚,应该会比较大吧,内核中很多东西变化不是很大,比如进程相关的结构体task_struct(很复杂有700多行)中,进程优先级的一些变量,依然好好的躺在那里,和10年前一模一样。这些不变的东西,我觉得学习的价值更大。不得不佩服搞内核的这些人,在进程调度的时候,嫌弃红黑树时间复杂度logN,不够高效,可以做到log(1),简直太疯狂了。
扯得有点远了,今天就练习了一道比较简单的算法体,也是费了半天劲,可算编译过了,捡起了我心爱的c++。有时我觉得,算法题的意义主要是锻炼思维。话不多说,上代码。
题目描述如下:
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
输入:"23" 输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
实现代码如下:
class Solution {
public:
char getMaskChar(int num, int mask){
char numChar;
if(num <= 6){
if(mask == 3){
return ' ';
}
numChar = mask + 97 + (num - 2)*3;
}else if(num == 7){
numChar = mask + 112;
}else if(num == 8){
if(mask == 3)
return ' ';
numChar = mask + 116;
}else{
numChar = mask + 119;
}
return numChar;
}
vector<string> letterCombinations(string digits) {
vector<string> result;
int size = digits.size();
if(size == 0) return result;
for(int mask = 0; mask < (1 << size * 2); mask++){
string tempStr;
for(int i = 0; i < size; i++){
int move = (size - i - 1)*2;
char tempChar = getMaskChar(digits[i] - 48, (mask & (3 << move)) >> move);
if(tempChar == ' ')
break;
tempStr.push_back(tempChar);
}
if(tempStr.size() == size)
result.push_back(tempStr);
}
return result;
}
};
可能写得不够简洁,但我觉得简洁的东西不一定高效,c++只要稍微使用一下位运算,效果就会出乎意料的好。这个题用从数学角度看不难,可是编码实现,还是有些棘手,解决的关键是如何表示所有的可能情况,每个数字可以用两位二进制数表示,有些数字可以是四个字母,大部分是三个,需要加一些判断。
其实,想要程序高效运行,除了选用合适的算法,如果掌握一些常用的技巧,也可以提升几十倍,比如减少函数调用,减少对象创建与引用,循环展开(利用多核处理器一次处理多条指令的特性),甚至结构体中也能压榨出字节。