首页 > 技术文章 > 菜鸟谈算法和数学对写程序的影响

kmsfan 2014-11-30 21:08 原文

  首先声明,本人在数学和算法上没什么造诣,只是发表一下自己的观点吧,最近一段时间在学习算法。引用一下我大学的时候的一位老师(刘伟)的一篇文章,这是他在CSDN上的一篇文章,他也一直是我人生中的偶像。http://blog.csdn.net/lovelion/article/details/1350127

  因为自己进公司的以来,一直从事的是C#和JAVA方面的开发,而且框架都是调用公司的框架,所以一直感觉数学在编程当中不起到什么作用。PS:不是数学的作用不大,而是自己确实对这方便很菜,所以才会有这么短浅的认识。直到自己看到了某位大神写的破解游戏源文件的代码居然是用C#写的,才意识到无论是什么语言,数学,算法,逻辑,都是非常重要的。先贴一段让我觉得头晕的代码吧,当然我现在看好一些了,没那么头晕了,不过C#代码居然可以这么写,当时我是震惊了。

  具体的思路我给大家介绍一下,下面的方法其实就是重新建立文件的一个方法,也就是说,这个程序想要实现一个给游戏文件打补丁的功能,比如游戏更新了,你不可能重新下载客户端,肯定是打补丁,而下面方法,建立临时的文件,把临时文件里的内容填充。

   private string RebuildFile(BinaryReader reader, string fileName, string tempDir, string msDir)
        {
            string tempFileName = Path.Combine(tempDir, fileName);
            EnsureDirExists(tempFileName);
            uint oldCheckSum0 = reader.ReadUInt32(); //旧版本文件hash
            uint newCheckSum0 = reader.ReadUInt32(); //新版本文件hash

            FileStream oldWzFile = new FileStream(Path.Combine(msDir, fileName), FileMode.Open, FileAccess.Read);
            uint oldCheckSum1 = CheckSum.ComputeHash(oldWzFile, (int)oldWzFile.Length); //旧版本文件实际hash

            PrintMsg("正在检查旧文件版本 " + oldCheckSum1);

            long curPos = reader.BaseStream.Position;
            int newFileLength = CalcNewFileLength(reader);

            PrintMsg("检测新文件长度 " + newFileLength + " bytes");

            try
            {
                VerifyCheckSum(oldCheckSum0, oldCheckSum1, fileName + "[old]");
            }
            catch
            {
                if (oldWzFile.Length == newFileLength && oldCheckSum1 == newCheckSum0) //文件已更新的场合
                {
                    oldWzFile.Close();
                    PrintMsg("文件已经为最新 补丁跳过");
                    return null;
                }
                throw;
            }

            int cmd;
            int blockLength;
            FileStream tempFileStream = new FileStream(tempFileName, FileMode.Create, FileAccess.ReadWrite);
            reader.BaseStream.Seek(curPos, SeekOrigin.Begin);

            PrintMsg("正在重构文件");

            while ((cmd = reader.ReadInt32()) != 0)
            {
                switch ((uint)cmd >> 0x1C)
                {
                    case 0x08:
                        blockLength = cmd & 0x0fffffff;
                        MoveStream(reader.BaseStream, tempFileStream, blockLength);
                        break;
                    case 0x0c:
                        blockLength = (cmd & 0x0fffff00) >> 8;
                        byte byteData = (byte)(cmd & 0xff);
                        FillStream(tempFileStream, blockLength, byteData);
                        break;
                    default:
                        blockLength = cmd;
                        int startPos = reader.ReadInt32();
                        oldWzFile.Seek(startPos, SeekOrigin.Begin);
                        MoveStream(oldWzFile, tempFileStream, blockLength);
                        break;
                }
            }

            PrintMsg("正在检查新文件版本");

            tempFileStream.Seek(0, SeekOrigin.Begin);
            uint newCheckSum1 = CheckSum.ComputeHash(tempFileStream, (int)tempFileStream.Length); //新生成文件的hash
            VerifyCheckSum(newCheckSum0, newCheckSum1, fileName + "[new]");

            oldWzFile.Close();
            tempFileStream.Flush();
            tempFileStream.Close();

            PrintMsg("创建临时文件 " + tempFileName + ",文件长度 " + newFileLength + " bytes");
            return tempFileName;
        }

  

其中上面的方法有一部分我是醉了。就是上的那个WHILE循环里面的东西,反正我是很难看懂。下面的是上面WHILE循环中要用到的方法。或许看到这里,我说的话还不具有说服力,不过以我的理解也就只能到这里了,大家还请见谅哈。

        private void MoveStream(Stream src, Stream dest, int length)
        {
            byte[] buffer = new byte[0x8000];
            while (length > 0)
            {
                int count = src.Read(buffer, 0, Math.Min(buffer.Length, length));
                if (count == 0)
                    break;
                dest.Write(buffer, 0, count);
                length -= count;
            }
        }


        private void FillStream(Stream stream, int length, byte data)
        {
            byte[] buffer = new byte[length];
            for (int i = 0; i < length; i++)
            {
                buffer[i] = data;
            }
            stream.Write(buffer, 0, length);
        }

  于是在这之后。。。我买了大量的数学书,比如微积分,离散数学,还有2本没到的(路上),分别是线性代数和概率论与数理统计。当然了,我自己个人也挺喜欢学数学的,只是看的时间不长而已,总共才3个月吧,满打满算,还是在工作之余才看的。

  不过对我触动最大的,还是那本《算法导论》,如果没有算法导论,估计我对编程的认识只会停留在调用别人(公司的大神)写的方法那么简单了吧?!其实很想写一些算法的总结出来,但是心里面一想,觉得自己肚子里没什么货,还是算了。不过打个比方吧,以前在写C#代码的时候,不是经常要用到递归算法吗?相信大家对递归算法应该不陌生吧,其实我以前对递归的认识只有一句话:自己调用自己。

  好吧,自己调用自己确实是对的,但是怎么才能写好递归算法呢?其实递归属于一种“分治策略”,分解,解决,合并,递归算法必须要有一个递推公式,不说复杂的,大家中学应该都学过等比数列或者等差数列吧,这当然是一种最简单的数列了,而递归其实就是应用了一个类似通项公式的东西,只是每次给出的形参不同而已,而我们就是要寻找这个通项公式,说白了,就是数学,哈哈。书中还提到了一个很特殊的东西 - 最坏运行时间或者最好运行时间,我们其实从名字可以听出一点端倪,不同的算法决定了这个程序,这段代码的执行效率,可见算法的重要性显露无疑。当然,这次我不太想去解释某些算法的重要性(原因是自己也才看这本书,只是个人觉得很重要而已)。

  上面举了递归算法,那么下面我们再来说一下排序,不拿指针说事,就拿最简单的线性存储结构:数组来说吧,如果我们想要明白各种排序的优缺点,我们需要算法的支持,需要对数学的了解,比如插入排序,冒泡排序,堆排序,归并排序,当然这里具体的方法大家如果有兴趣的话可以上网查,我自己也学得半拉子水吧,不过我觉得这些东西想当的重要,有些人说这是些很简单的,我不这么认为。因为自己大学的时候,老师没教过,也没有重视过这些东西。

  下面是一个讨论的话题,也是我自己想到的,就是:计算机培训学校是不是应该重视数学修养的培养?可能大家看到我这篇文章有点疑惑,这些东西你们大学没学过吗,老师没要求吗?我可以告诉你,我真的没学过这些东西,原因就是我大学上的是一个类似培训学校(北大青鸟)之类的学习出来的,我感觉这些学校和正规的本科院校有一点差别,就是培训学校比较注重人的动手能力,所以很多人,只要认真学了,都可以把一样东西做得很好,但是我们没有(或者说是不列入毕业考核)数学这门课,甚至连算法这门课都没有,我现在已经工作了,好多算法方面的东西还是很差的,虽然暂时也看不出来有什么不好,在我住的旁边,新开了一个IT培训学校,我有时候在想(勿喷),有时候计算机行业在20年前,基本上都是用C,汇编,那时候的程序员也很少,计算机技术也不发达,而现在计算机的从业人员已经突飞猛进了,但是门槛却越来越低,很多人可以不用学,甚至一辈子可以不用学习那些20年前程序员们的必修课(数学,算法)等等,这究竟是一种进步,还是退步呢?

  好吧,本文只是一个工作一年的小菜鸟,在一个不如流的软件公司的一个边缘化的部门里工作的人写出的一篇不怎么好的文章,如果有错,还请批评指正。

推荐阅读