• 《编程珠玑》阅读小记(1)— 开篇


    1. 前言

    久闻《编程珠玑》一书的大名,一直没有找到合适的机会深入学习阅读,最近终于得以入手,便决心投入细细的研究,提升一下自己的编程思想与技术。阅读之后才发现,这本书确实一本不可多得的好书。它以计算机领域应用与编程算法相结合,让读者面对实际问题时,不单单局限于考虑该问题的解决方案,而是在入手实践之前能够驻足于考虑,该方案是否符合当前的实际环境,它的时间与空间的消耗是否达到了一个比较好的指标。
    通过阅读这本书,很大程度上拓宽了我这样一个菜鸟程序员的视野。对于我来讲,发现要想真正的对书中内容有所理解、领悟,每一个小节我都不得不反复阅读两遍以上,唉,或许是我太笨了,正所谓“书读百遍,其义自见”,笨人有笨办法,就这样慢慢学习吧~

    2. 开篇导读

    本书第一章“开篇”,当我按照往日阅读速度完成第一章节时,却发现几乎不知所云。于是,只好回头重读!

    2.1 问题描述

    本章以一个常见磁盘文件排序开始介绍,这并不仅仅是一个简单的排序问题。我们要考虑的包括时间和空间的消耗。整个问题可以描述如下:
    输入:一个最多包含n个正整数的文件,每个数都小于10^7,没有重复数据。n<=10000000;
    输出:按升序排列的输入整数的列表;
    约束条件:最多有1MB的内存空间可用,有充足的磁盘空间,运行时间最多几分钟,性能到达10秒钟之内无需进一步优化。


    2.2 问题分析

    对以上问题进行分析,引出归并排序与快速排序的对比:
    (1)以一种基于磁盘的归并排序为起点,进行相应的调整,改变为针对当下问题的1000万条整数记录排序,代码量可由约200h缩减为几十行,但是归并排序的性能却不尽如人意,时间复杂度为T(n)=O(nlogn),完全执行该程序仍然需要几天时间。
    也就是说,合并排序从输入中一次性读取文件,多次调用工作文件,之后对输出文件一次性写入完成整个过程。
    归并排序执行图
    (2)另一种解决方案,则考虑在1MB的内存空间中,多趟快排来实现。使用32位存储每个整数,那么1MB的内存可存储1MB/4B = 250000个整数,这样对于10000000个整数需要40趟排序来完成,此时改程序不需要借用磁盘空间,于是整个流程可如下图所示:
    快速排序执行图
    然而该程序具有的代价则是读取输入文件40次!
    (3)那么,除了以上两种解决方式,还有没更好的举措呢?既不需要利用磁盘空间又不需要多读取输入文件。这就引入了一种位图解决方案,我们输入数据是1000万个7位十进制整数,我们可以使用一个1000万个位的字符串来表示这个文件,其中当且仅当整数i在文件中存在时,该位为1。感觉有点不对哈?确实,1MB只有800万个位,存储空间不足呀,我们需要1.25MB的存储空间呢。对于这个问题,一种方式是寻找出200万的稀疏位,或者采取两趟排序,就可以得以解决了,纵观性能,还是符合当下条件的。位图解决方案的流程如下图所示:
    位图排序执行图
    整个程序分三个步骤完成:

    • 初始化所有位为0
    • 读入文件,遍历记录,对应位设置为1
    • 检验每一位,存在则打印,输出序列即为升序序列

    程序执行伪代码如下:

    /*phase 1: initialize set to empty*/
    for i = [0 , n)
        bit[i] = 0
    /*phase 2: insert present elements into the set*/
    for each i in the input file
        bit[i] = 1
    /*phase 3: write sorted output*/
    for i = [0 , n)
        if bit[i] == 1
            write i on the output file

    3. 动手之前驻足思考

    从以上案例,我们可以看出一个道理“磨刀不误砍柴工”,对面临的实际问题仔细斟酌分析,精巧的编程可以得到更加稳固,性能更好的代码。
    所以,在任何一个程序员面临实际问题的解决时,一定要注意:

    • 明确正确的问题
    • 牢记位图数据结构,该数据结构不仅可以应用在排序问题,它在其它应用也经常可作为高效率解决方案
    • 内存不足时,可以考虑多趟算法
    • 时间-空间的折中与双赢
    • 简单的设计,简单程序相比繁杂程序通常更加安全、可靠,更健壮、高效且易于维护

    4. 习题分析解答

    问题1. 本题要求利用具有库的语言实现排序,在我熟悉的语言即是C和C++了,C语言库“stdlib.h”中有qsort函数,而C++中标准容器库set本身就是一个无重复数据且升序排列元素的容器,具体实现如下:

    
    /*
    *   《编程珠玑》习题1.6
    *   习题1:如果不缺内存,使用一个具有库的语言实现一种排序算法以表示和排序集合
    *   在C语言中,使用qsort函数
    *   函数定义于“stdlib.h”库中,定义为:void qsort(void *base,size_t nelem,size_t width,int (*Comp)(const void *,const void *));
    */
    
    #include <iostream>
    #include <cstdlib>
    #include <set>
    
    using namespace std;
    
    //定义比较函数,确定排序模式
    int comp(const void *x, const void *y)
    {
        return *(int *)x - *(int *)y;
    }
    
    int arr[10000];
    int main()
    {
        int n = 0, i;
        while (cin >> arr[n])
        {
            n++;
        }
    
        qsort(arr, n, sizeof(int), comp);
    
        for (i = 0; i < n; i++)
        {
            cout << arr[i] << "	";
        }
        cout << endl;
        system("pause");
        return 0;
    }
    /*
    *   《编程珠玑》习题1.6
    *   习题1:如果不缺内存,使用一个具有库的语言实现一种排序算法以表示和排序集合
    *   在C++语言中,使用标准程序库容器set 自动存储不重复升序数据
    */
    
    int main()
    {
        set<int> s;
        int temp;
        while (cin >> temp)
            s.insert(temp);
    
        set<int>::iterator siter;
    
        for (siter = s.begin(); siter != s.end(); siter++)
            cout << *siter << "	";
    
        cout << endl;
        system("pause");
    
        return 0;
    }

    问题2/3/4. 此三个问题相结合,实现位图排序解决方案,位向量采用位逻辑运算实现:

    /*
    *   《编程珠玑》习题1.6
    *   习题2 3 4 关联性习题
    *
    */
    
    #include <iostream>
    #include <cstdlib>
    
    using namespace std;
    
    //2. 位逻辑运算实现位向量
    #define BITSPERWORD 32
    #define SHIFT 5
    #define MASK 0x1F
    #define N 10000000
    int a[1 + N / BITSPERWORD];
    
    void _set(int i)
    {
        a[i >> SHIFT] |= (1 << (i & MASK));
    }
    
    void clr(int i)
    {
        a[i >> SHIFT] &= ~(1 << (i & MASK));
    }
    
    int test(int i)
    {
        return a[i >> SHIFT] & (1 << (i & MASK));
    }
    
    //4. 得到[0 ,n)内k个非重复随机数
    /*等学习到12章节,补充此处*/
    
    //3. 主程序
    
    int main(void)
    {
        int i;
        for (i = 0; i < N; i++)
            clr(i);
        //随机数现采取用户输入方式,待12章学习完毕,补充随机数生成算法
        while (cin >> i)
        {
            _set(i);
        }
    
        for (i = 0; i < N; i++)
        if (test(i))
            cout << i << "	";
    
        system("pause");
        return 0;
    }
    

    问题5. 采取两趟排序。

    问题6. 我们之前的案例是无重复数据出现,当数据重复时,我们依然可以采用位图解决方案,只不过不能采用1bit来记录出现次数了,当2bit时可记录< 4次 , 3bit 可记录<8次,那么当最多不出现10次时,就可以采用4bit来记录每一个数据的出现次数,此时需要增大内存使用量,若内存空间有限制,只能针对数据量采用多趟位图解决问题。

    习题7-12. 每人有不同的见解,我也只是参考了下答案,才有所思考,此处就不重复答案书上的解法。

  • 相关阅读:
    C#导出EXCEL方法总结
    C#程序——多条件查询
    C# checklistbox控件用法总结(怎样得到多选的值,以及动态加载数据)
    C#获取当前日期时间(转)
    C# winform 中MessageBox用法大全(附效果图)
    多条件查询
    vs2013 c#连接mysql数据库并显示查询结果到DataGridView上
    C# 登录界面从数据库取用户名密码匹配结束后进入登录界面
    Chart控件X轴显示不全的解决方法
    bootstrap + vue 简易留言板(todolist)
  • 原文地址:https://www.cnblogs.com/shine-yr/p/5214953.html
Copyright © 2020-2023  润新知