《编程珠玑》第二章的三个问题

本文详细探讨了《编程珠玑》第二章的三个经典问题:如何找出不在文件中的32位整数,如何实现循环左移,以及如何寻找变位词。通过深入分析和比较不同解法,阐述了在资源有限情况下优化算法的重要性,同时分析了各方法的时间复杂度和效率。
摘要由CSDN通过智能技术生成

2.1 三个问题

A. 找出不在文件中的32位整数

初始想法

当内存足够时可以直接在内存中维护位向量,有对应位是否为1表示是否出现了对应整数。当内存只有几百字节时可以考虑将位向量分段,每次读一遍顺序文件正确设置对应位,位向量的每个段都存入外部文件。

经典解法

提出的第一种解法实际上超出了题目要求的范围,因为仔细读题目会发现只是要找出一个不在给定文件中的32位整数,而不需要找出所有,可以用二分查找的思想快速解决这个问题,每次给定一个缺少一个整数的范围都寻找合适的分割方法将包含一个缺失整数的范围缩小,再在缩小的范围内继续查找,直到范围足够小使得这个范围内的整数都可以放入内存中,然后就是用位向量方法找出缺失整数。

// 以下方法针对32位无符号整数,如果是一般整数就需要先用0分割
mask = 1 << 31
while cnt > 5
	for each num in inputfile // 根据输入文件中数字的最高位为0还是1放入不同的输出文件
		if num & mask
			cnt1++
			write num to outfile1
		else
			cnt2++
			write num to outfile2
	mask >> 1 //逻辑右移一位 
	inputfile = cnt1 < cnt2 ? outfile1: outfile2
	cnt = cnt1 < cnt2 ? cnt1: cnt2
// 根据位向量找出缺失整数,难在确定当前范围,之后得到当前范围的位向量即可
mask = mask-1
if any num in inputfile bigger than mask
	 range = [mask+1, mask+1 | mask]
else
	range = [0, mask]

B. 循环左移

在只有几十字节内存的情况下,实现循环左移i位的经典方法有三种

a. 链式移位
1. 初始想法

假设原向量为a,循环左移前后元素的索引具有以下对应关系:

01i-1ii+1n-1
n-in-i+1n-101n-i-1

上面的对应关系可以翻译成:原向量的a[k]将变为左移后向量的a[(k+n-i)%n],这样写不够优美,可以借助模的性质改写为a[(k-i)%n]。以n=10,i=3为例,转移关系为x[0]<-x[3]<-x[6]<-x[9]<-x[2]<-x[5]<-x[8]<-x[1]<-x[4]<-x[7]<-x[0],这可以看成是转移链,也就是这个方法被我称为链式移位的原因。
伪码如下:

prev = a[i]
k = 0
do
	temp = a[k]
	a[k] = prev
	k = (k+i) % n
	prev = temp
while k > 0
2. 完善后

但是无法确定是不是所有元素都在一条转移链上,实际上所有向量项可以分为d条转移链,其中dn,i的最大公约数,这些链的代表元素为a[0],a[1],...,a[d-1],对上面伪码修改如下:

for t from 0 to d-1
	k = t
	prev = a[k+i]
	do
		temp = a[k]
		a[k] = prev
		k = (k+i) % n
		prev = temp
	while k != t

为什么链的代表元素为a[0],a[1],...,a[d-1]

b. 递归

待循环左移的数组ab,根据a,b的大小分为以下两种情况

  1. b的大小比a大,将b分为bl,br两部分,也就是ablbr,将a与br交换得到brbla,这时a已经放到了合适的位置上,考虑子数组brbl,只需要将该子数组循环左移i位,末尾拼接上a就可以得到原数组循环左移i位的结果。
  2. b的大小比a小,将a写为alar,其中 ∣ a l ∣ = ∣ b ∣ , 长 度 相 等 |a_l|=|b|,长度相等 al=b,将b与al交换,得到baral,只要把aral循环左移 i − ∣ b ∣ i-|b| ib位就可以得到balar,也就是原数组循环左移i位的结果。

伪码如下:

shift(v, bg, i, n) //将数组v[bg...bg+n-1]循环左移i位
	if i == 0 || n == 0
		return
	if i < n-i
		swap(v[bg...bg+i-1], v[bg+n-i...bg+n-1])
		shift(v, bg, i, n-i)
	else
		swap(v[bg...bg+i-1], v[bg+n-i...bg+n-1])
		shift(v,bg+i, i-(n-i), n-(n-i))
	return
简单分析

分析递归的时间复杂度关注的是递归层数与每层开销,也可以使用聚合分析,分析每个元素到达数组中合适位置需要多少次移动。
考虑情况1,交换后得到brbla,a部分的每个元素到达这个位置涉及3次移动(交换需要3次移动),且这部分的元素之后不会再改动位置。同样的分析可以用于情况2,交换后得到baral,b部分每个元素到达这个位置涉及3次移动(交换需要3次移动),且这部分的元素之后不会再改动位置。因此每个元素要达到最终的位置都只需要3次移动的开销,整个递归的复杂度为 O ( n ) O(n) O(n)

c. 逐段转置

待循环左移的数组ab,整个转置得到b'a',其中b'b的转置,a'a的转置,因此只要再分别对a',b'转置就可以得到数组ba,也就是循环左移的结果。

d. 三种程序用时比较

C. 寻找变位词

给定字典找出某个单词的所有变位词,只要两个单词的字母种类和个数相等就认为两个单词是变位词

1. 初始想法

构造一棵深度为26的树,一个非叶子节点表示一个字母,该非叶子节点出发的一条边表示单词中有多少个该字母,叶子节点表示变位词等价类,变位词用链表连接,先遍历一遍字典构建这棵树,然后给定一个单词就沿着这棵树直到达到某个叶子节点找到所有变位词。

2. 经典解法

给每个单词一个标识(单词种类和个数),以此为键排序可以将同一个标识的单词集合在一起,也就将所有变位词集中到了一起。

3. 简单分析

方法1的时间复杂度为 O ( n ) O(n) O(n),方法2的时间复杂度为 O ( n l g n ) O(nlgn) O(nlgn)。尽管方法1时间复杂度低,但是使用快排时可以很好地利用缓存,而方法1中每次加载树上不同的节点用于查找可能不能很好地利用缓存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值