title: 编程珠玑:位排序(Python实现)
date: 2019-09-09 20:49:48
tags: 数据结构
categories: 计算机理论
问题描述
- 输入
给出至多10,00,000个正整数的序列
- 特征:
–每个数都小于10,000,00
–数据不重复且 数据之间不存在关联关系
-
输出:增序输出序列
-
约束:
–内存容量1MB
–磁盘空间充足
–运行时间至多几分钟—最好线性时间
代码:https://github.com/XiaoZhong233/DataStructure_Python/blob/master/sort/vectorsort.py
采用位排序可满足上面的要求。位排序的优点就是特别特别省内存空间~100万个不同的整数排序只需要32Byte的内存空间。
什么是位排序
位排序就是将n个整数转为二进制位,然后通过按顺序读出二进制达到排序的作用。
例如[3,7,5,2] = 00110101 然后从左往右读出该二进制串,可以获得最后的有序序列[2,3,5,7],如果你想变成降序,那就从右往左读。
python实现位集合
借鉴了某个大佬的代码
# coding='utf-8'
import array
class BitSet(object):
def __init__(self, capacity):
#"B"类型相当于 C 语言的 unsigned char, 即占用1byte(8位),所以size大小设置为8,一个数占一个字节
self.unit_size = 8
self.unit_count = int((capacity + self.unit_size - 1) / self.unit_size)
self.capacity = capacity
self.arr = array.array("B", [0] * self.unit_count)
pass
# 是否存在为1的位
def any(self):
for a in self.arr:
if a != 0:
return True
return False
def all(self):
#是否所有位都为 1, 即是否存在置为 0 的位
t = (1 << self.unit_size) - 1
for a in self.arr:
if (a & t) != t:
return False
return True
def none(self):
#是否所有位都为 0,即是否不存在置为 1 的位
for a in self.arr:
if a != 0:
return False
return True
def count(self):
# 1 的位的个数
c = 0
for a in self.arr:
while a > 0:
if a & 1:
c+=1
a = a>>1
pass
return c
def size(self):
#所有位的个数
return self.unit_count * self.unit_size
def get(self, pos):
#获取第 pos 位的值
index = int(pos / self.unit_size)
offset = (self.unit_size - (pos - index * self.unit_size) - 1) % self.unit_size
return (self.arr[index] >> offset) & 1
def test(self, pos):
#判断第 pos 位的值是否为 1
if self.get(pos):
return True
return False
def set(self, pos = -1):
#设置第 pos 位的值为 1,若 pos 为 -1, 则所有位都置为 1
if pos >= 0:
index = int(pos / self.unit_size)
offset = (self.unit_size - (pos - index * self.unit_size) - 1) % self.unit_size
self.arr[index] = (self.arr[index]) | (1 << offset)
else:
t = (1 << self.unit_size) - 1
for i in range(self.unit_count):
self.arr[i] = self.arr[i] | t
def reset(self, pos = -1):
#设置第 pos 位的值为 0,若 pos 为 -1, 则所有位都置为 0
if pos >= 0:
index = int(pos / self.unit_size)
offset = (self.unit_size - (pos - index * self.unit_size) - 1) % self.unit_size
x = (1 << offset)
self.arr[index] = (self.arr[index]) & (~x)
else:
for i in range(self.unit_count):
self.arr[i] = 0
def flip(self, pos = -1):
#把第 pos 位的值取反,若 pos 为 -1, 则所有位都取反
if pos >= 0:
if self.get(pos):
self.reset(pos)
else:
self.set(pos)
else:
for i in range(self.unit_count):
self.arr[i] = ~self.arr[i] + (1 << self.unit_size)
def binstr(self):
b = ''
for a in self.arr:
t = bin(a)
b += "0" * (self.unit_size - len(t) + 2) + t + ","
return "[" + b.replace("0b", "").strip(",") + "]"
def show(self):
return self.arr
if __name__ == '__main__':
bitSet = BitSet(32)
bitSet.set()
print(bitSet.binstr())
print(bitSet.count())
位排序(Python实现)
# coding = "utf-8"
import sys
sys.path.append('../tool')
import TimeCalculator
import BitSet
import numpy as np
def generate_data(n):
return np.random.randint(low=0, high=n, size=n)
# 写成txt文件
def wirte_data(n):
data = generate_data(n)
np.savetxt('./data.txt', X=data, fmt="%d",delimiter=" ", newline = " ")
# 写成二进制文件
def wirte_data2(n):
data = generate_data(n)
np.save(file ='./data', arr = data, )
def load_data2(file = './data.npy'):
return np.load(file)
# 对n(n<=1000000)个小于n的正整数序列进行排序
# 时间在10s以内
# 空间占用小于1MB
@TimeCalculator.display_time
def vertorsort(data, n=1000000):
bitset = BitSet.BitSet(n)
# 初始化位图,模拟从I/O读入数据
for i in data:
bitset.set(i)
result = []
# 输出位图排序结果,模拟向I/0写入数据
print("空间大小占用:")
print(str(sys.getsizeof(bitset)) + "Byte")
for i in range(bitset.size()):
if bitset.test(i):
result.append(i)
return result
pass
if __name__ == '__main__':
# 模拟100万个100万内的数据
wirte_data2(10**6)
# 模拟从磁盘中读取数据
data = load_data2()
# 模拟向I/O输出排序结果
result = vertorsort(data, 10**6)
np.savetxt('./datasort.txt', X=result, fmt="%d",delimiter=" ", newline = " ")
# print(result)
运行结果
时间与空间花费
输出结果
优点与缺点
- 优点:
位排序非常非常非常节省内存空间,100万个不同的整数排序,只需要32B的辅助空间。
- 缺点
数据需要从I/O读取是硬伤,实际上正是由于了I/O拖累了整体的速度,100万个整数排序,需要4600ms(4.6秒)左右(如果采用C语言应该会更快),时间成本上比快速排序差远了。
如果使用快速排序,100万个整数差不多需要4~5MB的内存空间,非常大了,是位排序的15万倍。但快排的时间成本也是非常可观的,只需要400ms(0.4秒)作用。
下图是非递归快排的100万不同随机整数的排序时间花费:
快速排序果然是内部排序之王
适用场景
位排序的适用场景是内存空间十分有限,但速度没那么严格的情况,典型的时间换空间。