读《编程珠玑》 (一)

第一章 开篇

程序设计中需求的明确化非常重要,开篇两人的对话对此进行了说明

A:我该如何对磁盘文件进行排序?
B:需要排序的内容是什么?文件中有多少条记录?每个记录的格式是什么?
A:该文件包含至多10,000,000个记录,每条记录都是一个7位整数。
B:如果文件那么小,为什么要使用磁盘排序呢?为什么不在主存中对它排序?
A:该功能是某大型系统中的一部分,大概只能提供1MB主存给它。
B:你能将记录方面的内容说得更详细一些吗?
A:每个记录是一个7位正整数,没有其它的关联数据,每个整数至多只能出现一次。
..

之后我们获得了明晰的需求

输入:
所输入的是一个文件,至多包含n个正整数,每个正整数都要小于n,这里n=10^7。
如果输入时某一个整数出现了两次,就会产生一个致命的错误。
这些整数与其它任何数据都不关联。
输出:
以增序形式输出经过排序的整数列表。
约束:
大概有1MB的可用主存,但可用磁盘空间充足。运行时间至多允许几分钟,
10秒钟是最适宜的运行时间。

在有限的条件下,我们有多种的解决方案,最直接的,由于内存只能容纳一小部分数据,因此将数据分为40份,每次读取一份进行快速排序,写入中间文件中,最后进行统一的归并可以获得需要的结果。然而多次的读写文件操作将为大大降低程序的效率,需要更为优越的办法。

位图在实际运用中十分常见,我们可以用10000000位的字符串来表示这个数据集,对于有的数据就在相应的位上置1,反之则置0。从而我们的算法可以简化成以下3个步骤:第一,初始化所有的位为0;第二,读取文件中每个整数, 如果该整数对应的位已经为1,说明前面已经出现过这个整数,抛出异常,退出程序 (输入要求每个整数都只能出现一次)。否则,将相应的位置1;第三, 检查每个位,如果某个位是1,就写出相应的整数,从而创建已排序的输出文件。

实例中,问题的定义占据了非常重要的地位,明晰的定义将有助于针对性的解决问题,同时巧妙的数据结构表达将极大的节省代码量和时间成本。

第二章 啊哈 算法

第一章介绍了如何定义一个编程问题,第二章则介绍下一步,即如何利用原语来解决问题,其中主要就3个算法问题进行展开。

问题一:给定一个包含32位整数的顺序文件,它至多只能包含40亿个这样的整数, 并且整数的次序是随机的。请查找一个此文件中不存在的32位整数。 在有足够主存的情况下,你会如何解决这个问题? 如果你可以使用若干外部临时文件,但可用主存却只有上百字节, 你会如何解决这个问题?

如果在内存无限的情况下,我们可以使用第一章中的位图技术进行求解。同时问题还提出如果内存只有上百字节,在若干外部临时文件的帮助下的情况。如此可以利用二分法的思想,因为二进制中每一位要么是0要么是1,对这40亿数来说,进行如下操作:从最高位到最低位,对于最高位为1的分在一组,最高位为0的分在另一组,这样40亿数据就被分配成了两组了,若两组数有相同的个数,那么我们可以判断这两组数中都缺少了全体数(2^32个)中的某一个或者多个数,可以任选一组再次进行二分法。若两组数目不同,我们可以肯定是小的那部分数据肯定缺少了某一个或者多个数。因此对小的那组进行二分法,最终就能找到缺少的数。

问题二:请将一个具有n个元素的一维向量向左旋转i个位置。例如,假设n=8,i=3, 那么向量abcdefgh旋转之后得到向量defghabc。

该问题有多种的解法。一是借助中间量t,将x[0]的数放入t中,将x[0]置为x[i],x[i]置为x[2i]并直到x[0]的元素结束这次移动,之后开始移动x[1]并以此类推直到完全移动完成。

另一种方法是利用三次的反转达到目的,显得更为巧妙

reverse(0, i-1); // cbadefgh
reverse(i, n-1); // cbahgfed
reverse(0, n-1); // defghabc

问题三:给定一本英语单词词典,请找出所有的变位词集。例如,因为“pots”, “stop”,“tops”相互之间都是由另一个词的各个字母改变序列而构成的, 因此这些词相互之间就是变位词。

这里需要用到一种签名的概念,因为对某一个单词中字母打乱并一一匹配其它单词的程序需要的时间令人难以置信,因此我们需要从另一个角度来考虑问题。首先将每个单词按字母表顺序进行排序,比如“deposit”和“deiopst”的签名都是“deiopst”,接着将签名进行排序,可以将具有相同签名的单词排到一起,最终将这些具有相同签名的单词输出就可以得到需要的变位词集了。

时间: 2024-10-20 19:34:29

读《编程珠玑》 (一)的相关文章

读《Java并发编程的艺术》(一)

离开博客园很久了,自从找到工作,到现在基本没有再写过博客了.在大学培养起来的写博客的习惯在慢慢的消失殆尽,感觉汗颜.所以现在要开始重新培养起这个习惯,定期写博客不仅是对自己学习知识的一种沉淀,更是在督促自己要不断的学习,不断的进步. 最近在进一步学习Java并发编程,不言而喻,这部分内容是很重要的.现在就以<并发编程的艺术>一书为主导线,开始新一轮的学习. 进程和线程 进程是一个应用程序在处理机上的一次执行过程,线程是进程的最小基本单位(个人理解).一个进程可以包含多个线程. 上下文切换 我们

Java并发编程的艺术(六)——线程间的通信

多条线程之间有时需要数据交互,下面介绍五种线程间数据交互的方式,他们的使用场景各有不同. 1. volatile.synchronized关键字 PS:关于volatile的详细介绍请移步至:Java并发编程的艺术(三)--volatile 1.1 如何实现通信? 这两种方式都采用了同步机制实现多条线程间的数据通信.与其说是"通信",倒不如说是"共享变量"来的恰当.当一个共享变量被volatile修饰 或 被同步块包裹后,他们的读写操作都会直接操作共享内存,从而各个

《java并发编程的艺术》读书笔记-第三章Java内存模型(二)

一概述 本文属于<java并发编程的艺术>读书笔记系列,第三章java内存模型第二部分. 二final的内存语义 final在Java中是一个保留的关键字,可以声明成员变量.方法.类以及本地变量.可以参照之前整理的关键字final.这里作者主要介绍final域的内存语义. 对于final域,编译器和处理器要遵守两个重排序规则: 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序. 初次读一个包含final域的对象的引用,与随后初次读这

&lt;java并发编程的艺术&gt;读书笔记-第三章java内存模型(一)

一概述 本文属于<java并发编程的艺术>读书笔记系列,继续第三章java内存模型. 二重排序 2.1数据依赖性 如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性.数据依赖分下列三种类型: 名称 代码示例 说明 写后读 a = 1;b = a; 写一个变量之后,再读这个位置. 写后写 a = 1;a = 2; 写一个变量之后,再写这个变量. 读后写 a = b;b = 1; 读一个变量之后,再写这个变量. 上面三种情况,只要重排序两个操作的执行顺序,

Java并发编程的艺术下载 &#155949;

下载地址: http://www.gqylpy.com/di/11 <Java并发编程的艺术>PDF高清完整版-下载 内容简介 并发编程领域的扛鼎之作,作者是阿里和1号店的资深Java技术专家,对并发编程有非常深入的研究,<Java并发编程的艺术>是他们多年一线开发经验的结晶.本书的部分内容在出版早期发表在Java并发编程网和InfoQ等技术社区,得到了非常高的评价.它选取了Java并发编程中核心的技术进行讲解,从JDK源码.JVM.CPU等多角度全面剖析和讲解了Java并发编程的

基于CAS线程安全的计算方法 java并发编程的艺术上的一个案例

package thread; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /**  * @author  changxiangxiang  * @date 2014年8月6日 下午3:25:12  * @description  * @since  sprint2  */ public class Counter {     privat

Java并发编程的艺术——互动出版网

这篇是计算机类的优质预售推荐>>>><Java并发编程的艺术> 阿里系和1号店资深技术专家撰写,Java并发编程领域的扛鼎之作,内容在InfoQ等社群得到高度认可,从JDK源码.JVM.CPU等多角度全面剖析与讲解Java并发编程的框架.原理和核心技术 编辑推荐 阿里系和1号店资深技术专家撰写,Java并发编程领域的扛鼎之作 内容在InfoQ等社群得到高度认可,从JDK源码.JVM.CPU等多角度全面剖析与讲解Java并发编程的框架.原理和核心技术 前言 为什么要写这本

Java并发编程的艺术,解读并发编程的优缺点

并发编程的优缺点 使用并发的原因 多核的CPU的背景下,催生了并发编程的趋势,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升. 在特殊的业务场景下先天的就适合于并发编程. 比如在图像处理领域,一张1024X768像素的图片,包含达到78万6千多个像素.即时将所有的像素遍历一边都需要很长的时间, 面对如此复杂的计算量就需要充分利用多核的计算的能力.又比如当我们在网上购物时,为了提升响应速度,需要拆分,减库存, 生成订单等等这些操作,就可以进行拆分利用多线程的技术完成. 面对复

《Java并发编程的艺术》之ConcurrentLinkedQueue

队列这个数据结构已经很熟悉了,就不多介绍,主要还是根据代码理解Doug Lea大师的一些其他技巧. 入队 如图所示,很多人可能会很疑惑,为什么第一次入队后,TAIL没有指向Node2?答案是为了效率!Σ(っ °Д °;)っ 那这还能叫队列吗?当然,它依然符合先进先出(FIFO)的规则.只是TAIL变量不一定指向尾结点,那么来看看大师是怎么做的. public boolean offer(E e) { checkNotNull(e); final Node<E> newNode = new No

读Java并发编程实践中,向已有线程安全类添加功能--客户端加锁实现示例

在Java并发编程实践中4.4中提到向客户端加锁的方法.此为验证示例,写的不好,但可以看出结果来. package com.blackbread.test; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public