编程珠玑:位排序(Python实现)


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)

运行结果

时间与空间花费

20190910143008

输出结果

20190910143135

优点与缺点

  • 优点:

位排序非常非常非常节省内存空间,100万个不同的整数排序,只需要32B的辅助空间。

  • 缺点

数据需要从I/O读取是硬伤,实际上正是由于了I/O拖累了整体的速度,100万个整数排序,需要4600ms(4.6秒)左右(如果采用C语言应该会更快),时间成本上比快速排序差远了。

如果使用快速排序,100万个整数差不多需要4~5MB的内存空间,非常大了,是位排序的15万倍。但快排的时间成本也是非常可观的,只需要400ms(0.4秒)作用。

下图是非递归快排的100万不同随机整数的排序时间花费:

20190910144020

快速排序果然是内部排序之王

适用场景

位排序的适用场景是内存空间十分有限,但速度没那么严格的情况,典型的时间换空间。

猜你喜欢

转载自blog.csdn.net/weixin_41154636/article/details/100753437