洛基准备把一些python 3实现的算法题总结到这儿(python大法真香)。其实洛基觉得,不管是C++,java,python,各有各的妙处,只要玩的好了,都可以写出优雅、高效的代码,实现各种各样酷炫的功能。大家也没必要争吵到底谁才是第一语言。Anyway,语言只是工具,最重要的是我们能用工具完成什么样的任务,创造什么样的价值。
于是乎立下此博,把一些常见的算法存放于此,闲时看一看,以期巩固提高。书读百遍其意自现,算法看百遍内功up up up!
——各种排序算法。
1.冒泡。和名字一样的有趣且简洁的算法。复杂度 Time(n2) 、Space(1) ,是稳定的。
# py3
def sortBubble(nums):
n = len(nums)
# 1 to n-1
for i in range(1, n):
# range(0, n-1) to range(0, 1)
for j in range(0, n - i):
if nums[j] > nums[j + 1]:
nums[j], nums[j + 1] = nums[j + 1], nums[j]
return nums
2.选择排序,从第一个索引开始,每趟在后续的序列中找到最小的,与当前位置交换。复杂度Time(n2)、Space(1), 不稳定,比如 3 3 2 1,选择排序后第一个3到末尾去了,第二个3跑到倒数第二个位置了。
def sortSelect(nums):
length = len(nums)
for cur in range(length - 1):
min_idx = cur
for i in range(cur + 1, length):
if nums[i] < nums[min_idx]:
min_idx = i
nums[cur], nums[min_idx] = nums[min_idx], nums[cur]
return nums
3.希尔排序,又称为“缩小增量排序”。 第一趟选定step=length/2:从step开始,每次以一定步长和前面的元素进行比较,一直循环到数组的第一个元素;接着从step+1开始继续执行...此为一趟。第二趟则把step继续缩小一半,重复之前的步骤。 复杂度:Time(nlogn)、 Space(1)。不稳定。
def sortShell(nums):
step = len(nums) // 2
while step > 0:
for i in range(step, len(nums)):
# 因为前面的已经两两比较过了,所以当后面没有因新的更小元素而交换时,不需要再遍历i-step之前的元素
while i >= step and nums[i] < nums[i - step]:
nums[i], nums[i - step] = nums[i - step], nums[i]
i -= step
step = step // 2
return nums
4.归并排序,分治思想。首先将序列二分成两个子序列,然后对子序列再进行二分...直到所有子序列都只包含单个元素。然后将相邻两个序列进行归并操作,并保证归并后的序列内部有序;接着再对相邻序列进行归并,一直到只剩下一个序列为止。Time(nlogn) 、Space(1) , 稳定的。
def sortMerge(nums):
if len(nums) <= 1:
return nums
# 要拆分,必须要找到中点mid
mid = len(nums) // 2
# 递归拆分
left = sortMerge(nums[:mid])
right = sortMerge(nums[mid:])
# 归并子序列得到有序序列
return merge(left, right)
def merge(left, right):
result = []
while left and right:
if left[0] <= right[0]: # 为了排序的稳定性
result.append(left.pop(0))
else:
result.append(right.pop(0))
return result + left + right
5.快速排序(请叫它面试小常客)。T(nlogn) ,空间复杂度有两种情况,如果原地交换元素,那么空间复杂度是递归消耗的Space(logn),如果采用额外辅助数组(会很好理解),那么空间也可以达到Space(n)(夸我)。不稳定。
# 借助辅助数组的话,空间复杂度O(n)
def sortQuick(nums):
if len(nums) <= 1:
return nums
small, equal, big = [], [], []
p = nums[0]
for num in nums:
if num < p:
small.append(num)
elif num > p:
big.append(num)
else:
equal.append(num)
# 对剩余的small和big数组继续快排
small = sortQuick(small)
big = sortQuick(big)
return small + equal + big
# 快速排序方法二,就地排序,空间复杂度:S(logn),是递归消耗的复杂度
# 时间仍然是T(nlogn)
# 思路:定义一个区间内快排的函数,对一个区间进行一趟快排,取pivat为区间末尾元素,
# 先取index为头元素保存最终的pivat应去的位置;扫描区间内前面n-1个元素,交换...;函数返回中间idx。
# 定义一个递归函数,先对整个数组进行一趟快排,得到中间idx,再递归对划分出来的两个子区间进行快排。
def sortQuick(nums, start, end):
'''一趟快排'''
# index指示小的那个值应该放到当前哪个位置
index = start
pivot = nums[end]
# 在指定区间内进行一趟快排
for i in range(start, end): # 没有检查最后一个元素,因为最后一个元素是pivat,最终index会指引它应该去的位置
if nums[i] < pivot:
nums[index], nums[i] = nums[i], nums[index]
index += 1
# 最后,把mid值放到它应放的位置上,index之前的数都小于mid,index之后的数都大于mid
nums[index], nums[end] = nums[end], nums[index]
return index
def mysort(nums, start, end):
if start < end: # 如果start和end相等或者start大于end,那么不需要在这个区间快排了
m_index = sortQuick(nums, start, end)
mysort(nums, start, m_index - 1)
mysort(nums, m_index + 1, end)
6.计数排序。当输入的元素是n个0到k之间的整数时,运行时间是Time(n + k), 空间Space(k)。稳定。 当数据范围k比数组长度小很多时,此方法比较好。
def sortCount(nums):
small, big = nums[0], nums[0]
for x in nums:
if x < small:
small = x
elif x > big:
big = x
# 计数数组
count = [0] * (big - small + 1)
for x in nums:
count[x - small] += 1
# 填值
# index是当前填入nums的位置,每填一个值index就加一,如此遍历到nums的末尾
index = 0
for i, freq in enumerate(count):
# 原数组中出现的某个值=i+small, 出现次数=freq
x = i + small
while freq > 0:
nums[index] = x
index += 1
freq -= 1
return nums
7.堆排序。后面会补充。之所以暂时每做,是因为python自带的heapq包已经继承了小根堆的功能,堆排序仅仅几行代码不到就可以搞定,感觉很trick(trick是对于面试官来说的。自己用的话,heapq有多么舒适,谁用谁知道=。=)。之后等洛基手撸一个堆出来再把这里补上咯。
8.topK问题:一亿个整数,求topk(假设k=100),分析时间复杂度和空间复杂度。
第一种思路,直接做快速排序,然后取最大k个,时间复杂度Time(nlogn);个人认为,由于大数据下的快速排序不可能使用递归解决,必须用额外的辅助数组,故空间复杂度应该是Space(n)。
第二种思路,建立一个小根堆,并把这个堆的元素个数限制为k(此例中k=100),然后先从全部数据中拿出100个数装到堆里面,调整堆之后得到一个小根堆,此时堆顶元素就是整个堆中最小的了(小根堆的性质)。接着,每次从全部数据中读取一部分至内存,遍历这一小部分元素,每次都与堆顶元素进行比较,如果新的元素大于堆顶元素,那么新的元素替换掉堆顶元素,调整一下堆。如此循环,即可以在O(n)的时间内,以O(k)的额外空间(建堆的空间)完成任务了,很酷不是吗?What's more,用python的heapq包真的能很优雅的处理这个问题。朋友你觉得用python不香吗~
import heapq
class topkHeap:
def __init__(self, k):
self.heap = []
self.k = k
def push(self, num):
if len(self.heap) < self.k:
heapq.heappush(self.heap, num)
else:
heapq.heappushpop(self.heap, num)
def topk(self):
return sorted(self.heap, reverse=True)
# 最小的k个值,与topk异曲同工,只是会先把原数据的逐个元素取反。
class minkHeap:
def __init__(self, k):
self.heap = []
self.k = k
def push(self, num):
if len(self.heap) < self.k:
heapq.heappush(self.heap, -num) # 取反
else:
heapq.heappushpop(self.heap, -num)
def mink(self):
return sorted([-num for num in self.heap])