小根堆法:
定義中位數爲一個有序數組(len(array)+1)//2處的元素,“//”代表下取整。我知道中位數的定義分按照數組長度的奇偶性分兩種,但是面試官會告訴你這裏求的中位數就是有序數組(len(array)+1)//2處的元素。
如:
1 2 3 4 5 6 7 8 9 的中位數是(9+1)//2 = 5
1 2 3 4 5 6 7 8 9 10 的中位數是(10+1)//2=5
現在來求無序數組的中位數:
步驟 1 :如果數組長度爲奇數則取數組的前 (len(array)+1)//2 個元素建立 一個最小堆,如果爲偶數則取(len(array)+1)//2 +1個元素建立 一個最小堆。
步驟 2 :遍歷剩餘元素,如果該元素小於堆頂元素,則丟棄或不作處理;如果該元素大於堆頂元素,則將其取代堆頂元素,並且重新調整當前堆爲最小堆。
步驟 3 :遍歷結束後,返回堆頂元素,它就是所要尋找的中位數。
原理:
(建立大根堆也是行得通,這裏選小根堆來解釋)
這裏假設數組長度爲奇數,步驟1中說是從前往後取(len(array)+1)//2 個元素建立 一個最小堆,其實任意取(len(array)+1)//2 個元素都可以,所取的這(len(array)+1)//2 個元素有兩種可能的情況。
情況1:這(len(array)+1)//2 個元素中包含了中位數。
情況2:這(len(array)+1)//2 個元素沒有包含中位數。
對於情況1:
中位數可能位於堆頂或者堆內部。
(1)當中位數位於堆頂的時候,因爲是小根堆,所以數組內所有比中位數大的元素都在小根堆內,此時遍歷數組剩餘元素,根本不可能找到比堆頂大的元素,所以遍歷完數組剩餘元素堆頂保持不變。
如1 2 3 4 5 6 7 8 9 任取(9+1)//2=5個元素建立小根堆,如果5是堆頂,那堆內的其餘元素只能是6 7 8 9了。遍歷剩餘元素1 2 3 4 ,堆頂保持不變。
(2)當中位數位於堆內的時候,堆頂元素比中位數小,則數組剩餘元素中必然含有比中位數大的元素。如果用數組剩餘元素中比堆頂小的來替換堆頂,那小根堆根本不用調整,這誰頂得住啊,小根堆不調整那中位數什麼時候能到堆頂。所以要用數組剩餘元素中比堆頂大的來替換堆頂,上面說了數組剩餘元素中必然含有比中位數大的元素,現記它爲T,那T必然比堆頂要大,所以用T替換堆頂,當T成了堆頂,而且它比中位數要大,所以T必然下沉,中位數必然上升,只要經過若干次這樣的替換與調整,中位數肯定會到達堆頂。
對於情況2:
這(len(array)+1)//2 個元素沒有包含中位數,那這些元素中肯定包含比中位數大的和比中位數小的,並且比中位數小的必然在堆頂,此時中位數位於數組的剩餘元素中。與情況1描述的操作一樣,多次替換與調整後中位數肯定會替換掉比它小的堆頂元素,此時中位數又到了小根堆內,這就和情況1一模一樣了。
代碼如下:
# 調整自頂向下調整小根堆
def changed_top_bot(array):
flag = len(array)//2 # 最後一個分支節點的索引
for i in range(1, flag+1): # 從堆頂開始到最後一個分支節點
if i == flag:
if (2 * i + 1) > len(array): # 判斷最後一個分支節點是否有右孩子
if array[i-1] > array[2 * i-1]:
temp = array[i-1]
array[i-1] = array[2 * i-1]
array[2 * i-1] = temp
continue
if not (array[i-1] <= array[2*i-1] and array[i-1] <= array[2*i]):
temp = array[i-1]
if array[2*i-1] >= array[2*i]:
array[i-1] = array[2*i]
array[2*i] = temp
else:
array[i-1] = array[2*i-1]
array[2*i-1] = temp
return array
# 調整自底向上調整小根堆
def changed_bot_top(array):
flag_branch = len(array) // 2 # 數組長度除以2下取整表示最後一個分支節點
# print 'flag_branch: ', flag_branch
while flag_branch != 0: # 從最後一個分支結點開始自底向上調整該二叉樹
# print '執行了'
if flag_branch == len(array)//2:
if (2 * flag_branch + 1) > len(array): # 因爲是完全二叉樹,最後一個分支結點可能沒有右孩子
# print '執行了'
if array[flag_branch - 1] >= array[2 * flag_branch - 1]: # 數組中索引要減1
temp = array[flag_branch - 1]
array[flag_branch - 1] = array[2 * flag_branch - 1]
array[2 * flag_branch - 1] = temp
flag_branch -= 1
continue
# print array[flag_branch-1] <= array[2 * flag_branch-1] and array[flag_branch-1] <= array[2 * flag_branch]
if not (array[flag_branch-1] <= array[2 * flag_branch-1] and array[flag_branch-1] <= array[2 * flag_branch]):
# 該分支結點的孩子節點中有比它小的,所以選擇小的替換該分支節點
temp = array[flag_branch-1]
if array[2 * flag_branch-1] >= array[2 * flag_branch]:
array[flag_branch-1] = array[2 * flag_branch]
array[2 * flag_branch] = temp
else:
array[flag_branch-1] = array[2 * flag_branch-1]
array[2 * flag_branch-1] = temp
flag_branch -= 1
# print 'flag_branch: ', flag_branch
# print array
return array
# 實現小根堆找無序數組中位數
def find_mid_num(array):
# 根據數組長度奇偶性取部分元素
extract_array = []
remain_array = []
if not len(array) % 2:
extract_array += array[:(len(array)+1)//2+1]
remain_array += array[(len(array)+1)//2+1:]
else:
extract_array += array[:(len(array)+1)//2]
remain_array += array[(len(array)+1)//2:]
# print 'extract_array: \n', extract_array
# print remain_array
# 利用所取的部分元素建立完全二叉樹
# 根據完全二叉樹在數組的中的存儲,現在調整該完全二叉樹使其成爲小根堆
# 將完全二叉樹調整爲小根堆需要先自低向上調整再自頂向下調整
extract_array = changed_bot_top(extract_array)
# 自底向上調整完,必須在自頂向下調整一次纔可以完成初始小根堆
extract_array = changed_top_bot(extract_array)
# print extract_array
small_root_array = extract_array # 得到了初始小根堆
for remain_item in remain_array: # 遍歷剩餘元素數組
if remain_item < small_root_array[0]: # 剩餘元素小於堆頂元素
continue
else:
small_root_array[0] = remain_item # 替換
small_root_array = changed_top_bot(small_root_array) # 只要建立了初始小根堆,後面的調整隻需自定向下)
return small_root_array[0] # 堆頂元素就是中位數
if __name__ == '__main__':
array = [5, 2, 4, 1, 3, 6, 7, 8, 9]
# array = [5, 2, 4, 1, 3, 6, 7, 8, 9, 10]
mid_num = find_mid_num(array)
print mid_num