编程珠玑

本文源自《编程珠玑》一书,探讨如何通过深入思考和巧妙算法解决编程难题。基础篇中,通过位向量解决排序问题,揭示了在时间和空间约束下寻找最佳解法的重要性。性能篇强调了性能分析、算法设计和代码调优的技巧,如动态规划和性能度量。应用篇则讨论了实际编程中常见的算法应用,如排序和搜索。文章提倡程序员应深入思考、适时优化,追求代码的简洁和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文首发于个人博客之编程珠玑,之后会陆续将博客转移至个人博客,期待与各位的交流


这不是一本具体算法的讲解或者代码编写的教程,但是从书中的字里行间,我们可以学到的是更多的软知识:对编程新的认识、更加发散的思维方式、更严格的代码要求、堪比瑞士军刀的小技巧…… 编程也许入门并不难,但是要想真正成为一名优秀的软件工程师,还是需要很多锤炼。内外兼修,方成大器。


基础篇

  • 第一章 开篇

    首先作者提出一个实际问题:

    如何给磁盘的某个文件排序,更具体来说就是是对一个最多包含1千万条记录,每条记录都是7位整数的文件,而且只有1MB的内存可以使用

    从实际问题中提炼出更明确的数学定义:

    输入:一个最多包含n个整数的文件,每个数都小于n,其中n=10^7。可以保证输入文件中不存在重复整数
    输出:按升序排列的输入整数的列表
    约束:1MB左右的内存空间,充足的磁盘存储空间。运行时间最多几分钟,控制在10秒内不再需要进一步优化

    考虑一般的解法,直接读入所有的整数,然后进行快排堆排之类的排序,时间复杂度很明显是O(nlogn),但空间复杂度是O(n),即如果n=10^7时,用4个字节的int型存储每个整数,那么需要的空间是(4*107)/210210=38MB,很显然超出了内存限制,而考虑实际n的大小限制和每个整数只会出现一次的限制,而所谓的排序也只是把从文件中的整数按在1-n内出现的顺序输出而已,因此只要对n之内出现的整数做一下标记最后输出标记过的整数就可以了,考虑到这,位向量(也叫位图)就成了比较合适的数据结构的选择,每个位的0、1值表示一个数字是否出现过,实现的时间复杂度为O(n),空间复杂度为O(n),考虑n取最大值10^7时,需要的空间为107/8/210/210=1.2MB,代码实现如下:

    
    #include <iostream>
    
    
    #include <fstream>
    
    
    #include <bitset>
    
    
    #include <string.h>
    
    
    #define N 1000000
    
    using namespace std;
    
    void intSort(){
       bitset<N> numBits;
       ifstream = testFile("/Users/smy/temp/data.txt");
        string s;
        int count = 0;
        while(testFile >> s){
            numBits[atoi(s.c_str())-1] = 1;
            ++count;
        }
        testFile.close();
    
        for(int i=0;i<N;++i){
            if(numBits[i] == 1){
                cout<<i+1<<endl;
            }
        }
    }
    int main(int argc, const char * argv[]) {
        intSort();
        return 0;
    }

    那么进一步考虑:

    如果严格限制程序占用内存不能超过1M,应该怎么处理?
    如果每个数最多出现10次,又应该如何改动算法?所用存储空间是怎样变化的呢?

    在解决这个实际问题的方案中,我们看到了不同于比较排序的一种排序方式:位向量排序,而且从这个问题中也引出了作者的一些思考和对读者的启示:

    • 做一个”懒”工程师:处理问题时,即便是一看上去就知道解法的问题,不要马上动手编写代码,花些时间去明确问题,抽象问题模型,结合问题的特点去分析,等灵光一现时再动手。作者在解决上题时首先是对问题进行了数学定义,考虑到问题中数据的特点以向量的方式解决了问题并且时间复杂度和空间复杂度都较低。这样的方式不仅能收到更好的编码效果,考虑的情况会更加完善,调试起来的时间也会缩短,而且经过深度思考再动手的印象要深刻的多,等之后再遇到类似的问题时,马上就会涌现解决问题的思路,这样才能积累真正的经验。
    • 时间和空间可以是双赢的:确实很多时候也许我们会在时间和空间之间做折中的选择,但是这种折中一定应该是在我们仔细分析了某方面不能再有改善的情况下,而很多情况下因为自己算法能力的不足和思维能力的局限,也许自己的算法可以在时间和空间双重优化,这就要求一方面自己要深度思考当前算法的优化空间,另一方面可以向更优秀的人请教是否有完全不同但是可以达到更好效果的方法或者对自己算法的建议,如果确实确定要做折中处理的话,要明确问题对时间和空间要求的严格性和最大容忍限度,然后在合理牺牲某方面的前提下向预定目标靠拢。
    • 简单的设计:“设计者确定其设计已经达到了完美的标准不是不能再增加任何东西,而是不能再减少任何东西”,在满足要求的前提下尽量让设计简洁,有利于今后的扩展和排错,“大道至简“,设计需要的也许更多是简洁的美。
  • 第二章 啊哈!算法

    1. 给定一个最多包含m=40亿个随机排列的n=32位整数的顺序文件,找出一个不在文件中的32位整数。在内存足够的情况下如何解决该问题?如果有几个外部的“临时文件“可用,但是仅有几百字节的内存,又该如何处理?
    2. 将一个n元一维向量想做旋转i个位置(如对n=3元向量“abc“,当i=1时,结果为“bca“)
    3. 给定一个英文字典,找出其中的所有变位词集合。(变位词指的包含相同数量相同字母的单词,因为他们通过调整字母的顺序可以变为一个单词所以叫变位词,如”stop”和”tops”和”pots”)

    对于问题1,首先明确一下肯定是存在整数不在文件中的,因为32位整数最多可以表示的数字是2^32=4294967296>4000000000
    (1)在内存充足的情况下,可以用上述位向量的做法,初始化2^32个位为0,每读到一个数字就将对应下标的位数组的值置为1,最后统计值仍为0的元素就是没有出现过的整数,这样的做法时间复杂度是O(2^n),空间占用大约为O(2n/8)B,当n=32时占用空间大小约为2n/8/210/210=512MB
    (2)内存不够有临时文件可以使用时,又该如何做呢?既然有临时文件可以用,那可以将原来大文件中的数字分配到临时文件中,当临时文件的规模够小时再采用上面说的位向量的算法。分配可以采用散列的方法,比如通过简单的模上临时文件的个数将结果相同的聚合到一个文件,判断缺失元素在哪个文件的方法是在向临时文件插入元素的时候统计每个临时文件的数字个数,因为我们是可以知道根据我们选定的散列算法在没有元素缺失情况下的数字个数k的,那么这样就只需要比较散列完成后临时文件的个数与k比较,如果比k小的话那么这个临时文件中就存在缺失元素,接下来以这个临时文件为主文件,再次采用前面所说的散列方法(此时散列算法的参数可能需要调整)如此迭代下去直到限制的内存空间足以承载包含缺失元素的文件规模时,回到第一种情况处理。 可以看到这也体现了二分搜索的思想,只不过是以文件包含的一系列整数为范围,用包含这些整数的文件表示这个范围&

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值