第二章 啊哈!算法
2.1 三个问题
A 一个最多40亿个随机排列的32位整数文件,找出一个不在文件中的32位整数。足够内存下如何解决?有几个外部临时文件可用,仅几百字节内存,如何解决?
B 将一个n元一维向量x向左旋转i个位置,abcdefgh旋转为defghabc,仅使用数十个额外字节的存储空间。
C 一个英语词典中,找出所有变位词组合,stop tops pots
2.2二分搜索
初始条件:已知一个对象存在于一个给定的范围内,一次探测可得到对象是否高于,等于,低于当前的位置。
过程:重复探测中点,直到找到或范围为空。
问题A解法:
对每个整数的32位进行二分搜索:
<1>第一趟读取40亿个数,并将第一位以0开头的写入一个文件,以1开头的写入另一个文件。有一个文件包含20亿个整数,一个文件包含整数数量小于20亿。
<2>对上一步处理得到的总数小于20亿的文件进行处理,探测第二位,为0的保存在一个文件,为1的保存在另一个文件。
第一趟读取n个整数,第二趟读取n/2个整数,以此类推,总运行时间正比于nlogn。
2.3基本操作的威力
对于问题B,有以下几种解法:
解1:将一维向量x前i位存入临时数组,剩下n-i位向前移动i个位置,然后将前i位放入剩余位置。
问题:需要大量额外空间
解2:定义一个函数,将一个一维数组x向左旋转一位,重复调用i次。
问题:巨大时间消耗
解3:x[0]保存在临时变量t中,x[i]移动到x[0],x[2i]移动到x[i],以此类推。然后将x[0]从临时变量取出放入。如果所有的元素都被移动则结束,否则对x[1]重复。
解4:将x分为a b两段,x左移即相当于交换ab两段位置,考虑到a长度为i,b长度为n- i,a b长度可能不等,将b分为bl br,br长度为i。
将问题看作把数组ab转为ba,通过reverse实现,先对a reverse到a'b,然后对b reverse a'b',然后再对整体reverse即可得到ba。
按照以上reverse的思路,代码实现如下:
def lefti(x,i): x[0:i] = list(reversed(x[0:i])) x[i:] = list(reversed(x[i:])) x = list(reversed(x)) return x x = ['a','b','c','d','e','f','g','h'] print(lefti(x,3))
2.4排序
问题C的解法:
1.考虑单词中所有字母的所有排列方法(使用完全相同的字母组合,但位置顺序不同,如果一个单词长为i,将有i!中方法)
2.为每个单词创造一个标识,使得变位词具有相同的标识:将单词中字母按照字母表顺序排列(deposit dopiest的标识均为deposit)然后将所有单词按照其标识进行排序,将具有相同标识的单词即变位词集中在一起。
2.5原理
排序:
<1>产生有序的输出。
<2>另一个程序(如二分搜索)的前期准备工作。
<3>把字母按照字母表顺序排列作为单词的标识。
二分搜索:
<1>在有序表查找元素。
<2>内存排序或磁盘排序。
缺陷:整个表必须已知并且事先排好序。
标识:
标识使得一类中的每一项都具有相同的标识。
比如问题C中的单词标识,以及用来识别读音相同但拼写不同的名字的soundex方法。
2.6习题
1.考虑查找给定输入单词的所有变位词问题。仅给定单词和字典的情况下,如何解决?
仅给定单词和字典,无法进行预处理,只能遍历词典中的单词,计算每个单词的标识(按字母字典顺序排列),然后与给定单词的标识比较。
5.向量旋转函数将向量ab变为ba。如何将向量abc变为cba?(交换非相邻内存块问题进行了建模)
题解关键:(a’b’c’)’=cba 分别对abc每个部分求逆,然后对整个字符串求逆,得到的结果就是cba
代码:
def abc_to_cba(a,b,c): reversed_a = "".join(reversed(a)) reversed_b = "".join(reversed(b)) reversed_c = "".join(reversed(c)) print(reversed_a) print(reversed_b) print(reversed_c) print(type(reversed_a)) mystr = reversed_a+reversed_b+reversed_c newstr = "".join(reversed(mystr)) print(newstr) return newstr
结果:
注意⚠️:
python中字符串翻转方式:
<1>str[::-1]
<2>"".join(reversed(str))
6.一个“电话号码簿”辅助程序,可以使用按键在号码薄中查找电话。查找Mike Lesk,可以按"LESK*M*"即5375*6*实现。但不同的名字可能具有相同的按键编码。实现一个以名字的按键编码为参数,返回所有可能的匹配名字的函数。
将按键编码作为名字的标识,对号码簿中的所有名字按照按键编码进行标识,并按照按键编码进行排序。