洛基準備把一些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])