《编程珠玑》习题练习In Python——第一章 开篇

本文通过Python解决《编程珠玑》第一章的排序问题,涉及位图数据结构、排序算法和内存限制。讨论了如何在内存有限的情况下,使用位图对大量整数进行排序,并探讨了处理重复数、多个数据区段和输入合法性检查的策略。
摘要由CSDN通过智能技术生成

这里写图片描述

问题:如何给磁盘文件排序

输入:一个包含最多n个正整数的文件,每个数都小于n,其中n=10^7。如果在输入文件中有任何整数重复出现就是致命错误。没有其他数据与该数据相关联。
输出:按升序排列的输入整数的列表。
约束:最多有(大约)1MB内存空间可用,有充足的磁盘存储空间可用。运行时间最多几分钟,运行时间为10秒就不需要进一步优化了。

这个问题描述认真看其实是有问题的,正整数小于n最多是只有n-1个而不是n个的。方便起见我们可以把输入要求变为非负数。

在我写的程序中,a数组代表输入,b数组代表输出。文件的读写的代码就没有写了,因为每次从a,b数组读入数据其实等同于对文件的输入输出。另外python本来运行速度较慢,所以输入的数据规模缩小到了10^6。这些关系不大,主要的精力应该放在算法上面。

习题1

使用具有库的语言来实现一种排序算法以表示和排序集合。

使用python build-in 函数:sorted()

a = [3,4,5,2,6,7,5,6,7,89,4,5,6]
a = sorted(a)

习题2

使用位运算来实现位图数据结构。

位图数据结构的一般方法是使用一个数组。当需要置第i位时,判断该位属于数组中的哪个数,以及该数的哪一位

Python中的位运算有:
与 &
或 |
异或 ^
非 ~
左移 <<
右移 >>

Python中数组是不能定义数据类型的,可以使用numpy实现。我使用uint32作为数组中的元素,该类型有32比特位。对于一个需要置位的数 i,将i写成二进制形式,有
xxxx xxxx xxxx
末尾红色的五位,有32种取值,可以用一个uint32的32位表示。前端剩余的位,则表示为位图数组的下标。

BITS_PER_WORD = 32
MASK = 0x1f
SHIFT = 5
def set(i, bitMap):
    bitMap[i >> SHIFT] |= 1 << (i & MASK)
def clr(i, bitMap):
    bitMap[i >> SHIFT] &= ~(1 << (i & MASK))
def test(i, bitMap):
    return (bitMap[i >> SHIFT] & (1 << (i & MASK))) != 0
def create_bitmap(numberSize):
    return  np.zeros(math.ceil(numberSize/BITS_PER_WORD),dtype=np.uint32)
def test_2():
    numberSize = 10**6
    bitMap = create_bitmap(numberSize)
    set(1,bitMap)
    set(2,bitMap)
    print(test(1,bitMap))
    clr(1,bitMap)
    print(test(1,bitMap))
    print(test(2,bitMap))

代码输出:
True
False
True

参考答案中定义bitMap数组大小时候写的是:

int a[1 + N/BITSPERWORD]

这是不精确的,当需要的位数正好可以被BITSPERWORD整除时,将会多出一个数组元素。

习题3

使用位图结构排序。

依次将所有数写入位图,然后依次读取位图中的数。

def test_3():
    numberRange = 10**6
    a = list(range(0,numberRange))
    random.shuffle(a)
    bitMap = create_bitmap(numberRange)
    for i in a:
        set(i,bitMap)
    b = []
    for i in range(0,numberRange):
        if test(i,bitMap):
            b.append(i)
    print(b[:10])

输出为:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

习题4

生成小于n且不重复的k个整数的方法。

Python中使用random.shuffle()函数可以很方便完成:

def shuffle_rand(numberRange,numberCount):
    a = list(range(0, numberRange))
    random.shuffle(a)
    a = a[:numberCount]
    return a

这里有一个意外收获,range()函数实际上生成一个range对象而不是list对象,所以不能将range直接放入shuffle中。

习题5

题目要求最多10^7次方个数,这么多比特位最后需要多于1MB的内存,如果严格限制内存在1MB之内,就无法用一个位图统计所有数的出现情况。

这时候可以使用两趟算法,分为两步,首先用位图排序[0,5000000)的数,再用一次同样的算法排序[5000000,10000000)之间的数。推而广之,可以得到n趟算法的代码:

def test_5():
    # 推广到n趟算法
    runTime = 2
    numberCount = 8*10**5
    numberRange = 10**6
    a = shuffle_rand(numberRange,numberCount)
    b = []
    for n in range(0,runTime):
        r = [int(numberRange*n/runTime),int(numberRange*(n+1)/runTime)] # 左闭右开
        bitMap = create_bitmap(r[1]-r[0])
        for i in a:
            if i >=r[0] and i < r[1]:
                set(i-r[0],bitMap)
        for i in range(0,r[1]-r[0]):
            if test(i,bitMap):
                b.append(i+r[0])
    print(b[:10],b[-10:],len(b))

这里r表示当前处理的数的范围。注意这个范围是左闭右开的,应为第一个范围需要包括0,而最后一个范围不需要包括10^7。

习题6

如果每个整数不是最多出现一次而是最多出现10次,应该如何实现该算法?

一个比特位可以表示一个数出现或者不出现,同理只要有4个比特位就可以表示该数出现的次数。

这时候就需要重写bitMap的代码,方法类似。这个时候一个uint32元素可以存放8个数。bitMap代码如下:

NUM_PER_WORD = 8
MASK = 0x7
SHIFT = 3
def create_bitmap(numberRange):
    return np.zeros(math.ceil(numberRange/NUM_PER_WORD),dtype=np.uint32)
def set(i,bitMap):
    bitMap[i>>SHIFT] += 1<<((i&MASK)*4)
def test(i,bitMap):
    return ( bitMap[i>>SHIFT]>>(i&MASK)*4 )&0xf

生成输入数据仍然使用shuffle,只是我们可以首先连接10个range产生的数组,然后对该数组shuffle取部分元素。代码如下:

def multi_shuffle_rand(multi,numberRange,numberCount):
    a = []
    for i in range(0,multi):
        a += list(range(0,numberRange))
    random.shuffle(a)
    return a[:numberCount]

最后就是算法的代码了,除了输出部分,其他代码和单比特的算法是一样的。

numberRange = 10**6
numberCount = 8*10**5
runTime = 5
a = multi_shuffle_rand(10,numberRange,numberCount)
b = []
for n in range(0,runTime):
    r = [int(numberRange*n/runTime),int(numberRange*(n+1)/runTime)] # 左闭右开
    bitMap = create_bitmap(r[1]-r[0])
    for i in a:
        if i >=r[0] and i < r[1]:
            set(i-r[0],bitMap)
    for i in range(0,r[1]-r[0]):
        if test(i,bitMap) > 0:
            c = test(i,bitMap)
            for j in range(0,c):
                b.append(i + r[0])

习题7

检查输入合法性。那么可能的违法输入有什么呢?第一,范围错误:小于0,大于最大值n。第二,类型错误:输入浮点数,字符串等。第三,输入重复。

1、检查类型使用Python的isinstance()函数或者type()函数,如下:

a = 1
print( isinstance(a,int) )
print(type(a) == int)

返回:
True
False

isinstance函数第二个参数可以使用类型的列表,那么该变量只要属于列表中任意一种类型都会返回True。

2、检查重复调用位图对应函数查看该位是否被占用即可。

3、错误处理
题目中说输入重复是严重错误,那么就可以在出错时抛出异常同时终止程序。

使用raise 方法抛出异常:

raise Exception("input error")

Exception中参数是抛出错误时显示的说明。

习题8

1、增加了不同的区号。
假定区号也需要参与排序。那么按顺序依次排序不同区号号码。也就是多运行几趟算法而已了。

在习题5的基础上改进代码。三个区号,每个区号运行两次程序,一共六次。代码如下:

areaCode = [800,888,887]
runTime = 2
numberCount = 8 * 10 ** 5
numberRange = 10 ** 6
a = rand_with_area_code(numberRange,numberCount,areaCode)
b = []
sorted(areaCode)
for ac in areaCode:
    for n in range(0, runTime):
        r = [int(numberRange * n / runTime), int(numberRange * (n + 1) / runTime)]  # 左闭右开
        bitMap = create_bitmap(r[1] - r[0])
        for i in a:
            curNumber = int(i.split('-')[1])
            curAreaCode = int(i.split('-')[0])
            if curNumber >= r[0] and curNumber < r[1] \
                    and curAreaCode == ac:
                set(curNumber - r[0], bitMap)
        for i in range(0, r[1] - r[0]):
            if test(i, bitMap):
                b.append(str(ac) + "-"+str(i + r[0]))
print(b[:10], b[-10:], len(b))

rand_with_area_code()函数生成代区号的号码,返回的号码会是字符串类型,格式类似xxx-xxxxxxx。代码:

def rand_with_area_code(numberRange,numberCount,areaCode):
    a = list(range(0, numberRange))
    random.shuffle(a)
    a= a[:numberCount]
    acSize = len(areaCode)
    for i in range(0,len(a)):
        ac = str(areaCode[random.randint(0,acSize-1)])
        a[i] = ac + "-"+str(a[i])
    return a

2、快速查询
存储生成的每个位图,每次查询根据号码范围查找对应的位图。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值