编程珠玑 Column 1

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

概述

Column 1由一个问题展开讨论:如何给一个包含0到9,999,999编号的记录文件排序并输出到另一个文件,其中编号不重复,只允许1M左右的内存空间。

最优的解法自然是采用位数组,1.25×106B大小的位数组含10,000,000位,可以保存规定范围内数是否存在。这种方法很好的利用了编号不重复的特性。


生成随机数文件

要对算法进行实现,面临的第一个问题就是,如何生成这么一个记录文件?这也练习4提出的问题:如何从范围0到n-1范围内生成k个不重复的随机数。以下是我的一个解法(Column 12对这个问题有详细的分析并提出了一些解法):

首先生成一个大小为n的整型数组,从第0位到第n-1位数组内容分别填对应的数组下标,这样数组就包含了不重复的从0到n-1的所有数字。那么接下来的工作就是每次从该数组未被选择的数中随机选择出一个数直到选出k个数。对此我们可以将每次选出的数和当前剩余的未选出的数的最后一个交换,这样选择结束后数组的最后k个数就是我们“生成”的随机数。以下是生成随机数文件的程序代码

 

使用这个程序后生成了一个随机数文件,发现生成的文件明显不随机!文件开始的一些数字很小,但是之后的数字几乎全部集中在9,000,000之上!这显然是存在问题的。试着减小了MAXN和SELECTK的规模到1000和100,就很正常了。查了一下rand()函数的说明,生成的伪随机数的范围是在0到RAND_MAX之间,我的第一感觉就是肯定RAND_MAX偏小!在程序中加入了打印RAND_MAX的语句后,可以看到RAND_MAX的值为32767。看来我的猜测是正确的。所以需要修改程序增大随机数生成范围,最简单的办法就是rand()生成的随机数再平方作为最终的随机数。所以上述代码进行以下修改

 

再次进行测试,发现这次生成的结果就比较正常了。这样,有了ranfile.txt文件,就可以接下来进行排序算法实现了。




排序算法

C++提供了bit vector的模板类bitset,我们可以很方便的像使用普通数组那用利用bitset而不必关心位操作的细节。当然也可以自己实现位数组,书中也进行了一些讨论并给出了示例。实现的代码如下

 

bitset模板的默认是全部被初始化为0的。注意的是我在这里给位赋值的时候并没有用true或者false,因为true的定义不一定是1,有可能是除0以外的某个整数,所以使用0和1可以保证最大的兼容性。

还有一个问题是大变量的定义,我们应该尽量不定义在栈上而是全局变量区或者堆上,这样可以避免栈溢出。虽然这种情况在用户空间的应用程序中几乎不可能发生因为应用程序的堆是可以动态增长的,但放在栈资源紧缺的内核空间下,这样的做法就显得危险了。所以没有必要我认为还是不要在栈上定义大变量。


问题讨论

问题5:在解决这个问题时我们使用的实际内存是1.25M。如果内存的限制不是粗略的1M而是精确的,那么怎么解决呢?

这个问题可以结合书中之前给出的解法综合一下解决。我们可以将给出的大文件先分成两份,一份只含有0-4,999,999而另一份只含5,000,000-9,999,999。这样按照之前的方法进行两次排序,最后将两个排序结果文件合并。这样的话我们和之前相比多出了若干次文件操作,文件操作相对内存操作是要费时很多的,所以我们应尽量减少文件操作,可以两次读取ranfile.txt文件,一次只处理0-4,999,999将结果写入输出文件,然后再次读取ranfile.txt并只处理5,000,000-9,999,999并将结果继续写入输出文件。这样我们就只多进行了一次文件读取操作,加上文件系统的缓存策略,这种方法的效率和1.25M内存的解法差不了太多。

问题6:如果每个数不是最多出现一次,而是最多10次,那么该怎么解决呢?

我们可以对这个bitset进行改进,没个单元不是1位,而是4位,每一单元存储这个数出现的次数,问题就得到解决了。当然,如果空间还是1M限制的话,呵呵,参见问题5~

编程珠玑 column 10 主要介绍的稀疏矩阵的存储方式。没必要存储矩阵的每个点。只需存储不为0的值的点。使用一个数组来代表所有的列,一个链式表来代表给定列中的不为0的元素 该链式表可以定义成 struct Node p{ int pointnum; int row; Node *next } 阅读详情

相关推荐

编程珠玑译-Column 1:开篇

这个程序的问题是简单的,“我该怎么样给你个磁盘文件做排序?”  在我告诉你我是怎么犯下我第一个错误之前,让我给你一个机会去做得比我还好。你会怎么回答? 1.1 一次友好的谈话     我的错误就是回答了他的这个问题。为给了他一个关于怎么在磁盘上实行归并排序的缩略图草图。我建议他深入研究一下这个会让他遇见比一时的热情更少错误的算法文本 ——他更关心如何解决这个问题而不是深入他的学习。接着我告诉了

Seere的博客 486

编程珠玑 column 11 sorting

快速排序算法的比较。 在排序一个所有元素均相同的数组的时候: qsort1每一次递归调用排序区间只能缩减1,只能排序好一个数字,可以从代码qsort1(m+1,u)中体现。因为所有元素相同时,qsort中m值执行的时候不会发生变化。qsort1排序区间的缩小只能体现在 qsort1(m+1,u)中。即要执行n-1次qsort1递归调用。每次qsort1递归调用,需进行 改进后快速排

kiwi's garden 733

编程珠玑column12

12.2 列举了从[0,5)中选2个数的情形,0被选中的概率是2/5 通常情况下,从r个数中选择s个数,某一个数(确切说是选择第1个数时)被选中的概率是s/r 可以这样思考:从r个数中任选一个数该数被选中的概率是1/r,但这是选择s个数,就相当于s次机会,相当于s/r,这样思考不严谨。 参考了http://blog.csdn.net/songzitea/article/detail

kiwi's garden 686

编程珠玑column14 heaps

堆的siftdown操作 假设x[1..n]的序列已经是一个堆,现在修改x[1]的值,然后调整这个序列使维持堆的性质 初始化所以i为1,直到i没有子节点或者子节点的值均大于i所在的节点的值为止。 把c设置成节点i的子节点中值较小的节点索引 c=2*i if(c+1 if(x[c+1] c++

kiwi's garden 1073

编程珠玑总结—column 11 Sorting

Technorati 标签: 算法,笔记 插入排序: 插入排序,快速排序

Jason技术笔记 620

编程珠玑Column14中堆排序的实现

最关键的函数: 1 inline void swap(int *arr,int i,int j) 2 { 3 if(i==j) 4 return; 5 arr[i]=arr[i]^arr[j]; 6 arr[j]=arr[i]^arr[j]; 7 arr[i]=arr[i]^arr[j]; 8 } 9 10...

weixin_30919429的博客 145

编程珠玑column15 strings of pearls

15.2 介绍了求字符串中最长的重复子串的方法。 其中一种方法是先申请一个指针数组a保存指向该字符串中每一个字符所在的地址。 这样a中的元素依次为第0个字符,第1个字符,第2个字符.的地址...。 然后对a进行一个快速排序。这样最长的重复子串只需从a中的相邻元素中搜索。 排序耗时o(nlgn),,相邻元素之间比较计算最长重复子串耗时o(n)

kiwi's garden 648

编程珠玑Column12中从n个数中产生随机的m个数

方法1: 1 void rand1(int n,int m) 2 { 3 for(int i=0;i<n;++i) 4 if(rand()%(n-i)<m)//用n-i比再定义一个num记录还剩几个更好 5 { 6 cout<<i<<" "; 7 ...

weixin_30917213的博客 133

编程珠玑Column9中二分查找的优化

二分查找的确是个很不错的算法,虽然简单,但是边界容易出错。 书中最初的二分查找l和u(我的代码中是r)对应数组中最小下标和最大下标,即l=0,u=n-1,数组为arr[l..u]。而后面优化的代码中l和u就变成了最小下标-1和最大下标+1了,即l=-1,u=n,数组为arr[l+1...u-1]。不太明白其中深刻含义,我试了一下l=0,u=n(Python中范围都是这么表示),发现第二段代码(即...

weixin_30432007的博客 174

编程珠玑Column13中5种实现整型集合(IntSet)的方式

首先构造公共基类,因为包含纯虚函数,所以是虚基类 View Code 1 #pragma once 2 3 class IntSet{ 4 public: 5 virtual int size()=0; 6 virtual void report(int *v)=0; 7 virtual void insert(int t)=0; 8 }; ...

weixin_30432007的博客 133

编程珠玑Column11中插入排序和快排序

插入排序最终版本: insertion sort 快排序最终版本: quick sort 1 int cutoff=10;//瞎定义的 2 3 void quick_sort1(int l,int u) 4 { 5 if(u-l<cutoff) 6 return ; 7 int p=l+rand()%(u-l+1);//...

weixin_30715523的博客 114

Reservoir Sampling - 蓄水池抽样问题

问题起源于编程珠玑Column 12中的题目10,其描述如下:   How could you select one of n objects at random, where you see the objects sequentially but you do not know the value of n beforehand? For concreteness, how would yo...

weixin_34056162的博客 131

Apr 4 2020 - What I learned

**今天在撸《编程珠玑》,更少了,明天补。 【数据结构】栈(Stack)和队列(Queue)都是操作受限的线性表。栈仅在表尾插入和删除元素;队列仅在表头删除元素、在表尾插入元素。总结:两头不能随意操作=操作受限。一个线性序列经过Queue后只能得到与原序列相同的元素序列,而经过一个Stack后则可以得到多种元素序列。 Tips:用两个Stack可以模拟出一个Queue的入队和出队操作。 Queu...

只会啃书的博客 192

Reservoir Sampling - 蓄水池抽样

问题起源于编程珠玑Column 12中的题目10,其描述如下:   How could you select one of n objects at random, where you see the objects sequentially but you do not

OOM 1225

排序算法解析:从基础到高级,面试必备技术

排序算法是计算机科学中的基础算法,通过比较和交换元素实现数据有序排列。其核心原理包括分治、递归等思想,时间复杂度从O(n²)到O(nlogn)不等。在工程实践中,排序算法直接影响系统性能,特别是在大数据处理和数据库查询等场景。快速排序、归并排序等高级算法因其高效性被广泛应用,而插入排序等基础算法在小数据量时仍具优势。技术面试中,排序算法常被用来考察候选人的编码能力和算法思维,如字节跳动等公司常结合实际问题考察排序优化。掌握各类排序算法的实现和适用场景,对提升编程能力和通过技术面试都至关重要。

weixin_30266885的博客 304

Linux IPC之共享内存

简介 共享内存(shared memory)是最简单的Linux进程间通信方式之一。使用共享内存,不同进程可以对同一块内存进行读写。由于所有进程对共享内存的访问就和访问自己的内存空间一样,而不需要进行额外系统调用或内核操作,同时还避免了多余的内存拷贝,所以,这种方式是效率最高、

Rui的专栏 1万+

Facade和Adapter

设计模式初探 设计模式有多么重要?身为在校学生,很少有人能有机会参与到真正的大型项目的设计中来,相信很多人在学习了C++和Java等面向对象的语言后,能够用面向对象语言提供的语法和特性设计一些程序并进行了一些实践后,就认为自己对面向对象有了一些体会。对于很多人来说,精通面向对象

Rui的专栏 3602

玩转二进制

前段时间和朋友讨论到一个小题目:如何判断一个正整数是2的整数次幂。 先举几个例子看一看: 2^0 = 1; 2^1 = 2 = 10b; 2^2= 4 = 100b; 2^3 = 8 = 1000b; 2^4 = 16 = 10000b; 将这些整数转换为二进制后,

Rui的专栏 1418
下一篇: Linux内核内存分配函数
ruizeng88
博客等级 码龄17年 19粉丝 15原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值